Oven logo

Oven

Published

Dynamic OpenAPI, Discovery, and GraphQL spec client for Python — turn any API spec into a fully-typed async client with attribute chaining, streaming, and file uploads

pip install fastspec

Package Downloads

Weekly DownloadsMonthly Downloads

Requires Python

>=3.10

Dependencies

fastspec

fastspec builds async Python clients from API schemas. It supports OpenAPI, Google Discovery, and GraphQL, with operations accessed through attribute chaining. Schemas supply operation signatures and parameter documentation; the clients handle requests, streaming responses, and file uploads.

Client packages can ship compact, pre-parsed schemas instead of the original specification. This supports broad API coverage without maintaining a handwritten method for each endpoint. ghapi uses this approach for GitHub’s REST and GraphQL APIs.

Install

pip install fastspec

Quick Start

Load a specification, construct a client with authentication headers, and call an operation. The examples use the specifications in this repository’s specs/ directory.

Loading Specs

fastspec supports both OpenAPI (JSON/YAML) and Google Discovery specs:

from fastcore.utils import *
from fastspec.oapi import *
from fastspec.spec import *
import json, yaml
specs_path = Path('../specs/')

# OpenAPI specs (Anthropic, OpenAI, GitHub, Stripe)
ant_spec  = SpecParser.from_openapi(dict2obj(yaml.safe_load((specs_path/'anthropic.yml').read_text())))
oai_spec  = SpecParser.from_openapi(dict2obj(yaml.safe_load((specs_path/'openai.with-code-samples.yml').read_text())))
gh_spec   = SpecParser.from_openapi(dict2obj(json.loads((specs_path/'github.json').read_text())))

# Google Discovery spec (Gemini)
gem_spec  = SpecParser.from_discovery(dict2obj(json.loads((specs_path/'gemini.json').read_text())))

ant_spec, oai_spec, gh_spec, gem_spec
(SpecParser(base_url='https://api.anthropic.com', ops=47),
 SpecParser(base_url='https://api.openai.com/v1', ops=241),
 SpecParser(base_url='https://api.github.com', ops=1112),
 SpecParser(base_url='https://generativelanguage.googleapis.com/', ops=81))

Creating Clients

Pass the parsed specification and authentication headers to OpenAPIClient. The examples read ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY, and GITHUB_TOKEN from the environment. Set the variables for the providers you use:

ant_cli = OpenAPIClient(ant_spec, headers={"x-api-key": os.environ["ANTHROPIC_API_KEY"], "anthropic-version": "2023-06-01"})
oai_cli = OpenAPIClient(oai_spec, headers={"Authorization": f"Bearer {os.environ['OPENAI_API_KEY']}"})
gh_cli  = OpenAPIClient(gh_spec,  headers={"Authorization": f"token {os.environ['GITHUB_TOKEN']}"})

Exploring Operations

Each client organizes operations into groups. Display a group to browse its operations:

ant_cli.messages

Read the actual operation with doc(ant_cli.models.models_get) or its bare display for parameter descriptions, bound defaults, request controls, and async usage. Inspecting the operation’s class cannot show its generated signature. xdir(group, pattern) filters large groups by name. Documentation and discovery send no requests:

ant_cli.models.models_get

Get a Model

Parameters:

  • model_id (str, required): Model identifier or alias.

Anthropic

A simple message request:

resp = await ant_cli.messages.messages_post(
    model="claude-sonnet-5",
    messages=[{"role": "user", "content": "Translate hello into French. Return only the translation."}],
    max_tokens=64,)
resp['content'][0]['text']
'Bonjour'

Pass stream=True and iterate over the response events:

resp = await ant_cli.messages.messages_post(
    model="claude-sonnet-5",
    messages=[{"role": "user", "content": "Translate hello into French, Spanish, and Japanese. Return only the translations."}],
    max_tokens=128, stream=True)
async for ev in resp: 
    if ct:= nested_idx(ev,'delta','text'): print(ct, end='')
Bonjour
Hola
こんにちは

OpenAI

Chat Completion

resp = await oai_cli.chat.create_chat_completion(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": "Translate hello into French. Return only the translation."}],
    max_tokens=64)
resp['choices'][0]['message']['content']
'Bonjour'

Text-to-Speech (file output)

resp = await oai_cli.audio.create_speech(model="tts-1", input="Hello from fastspec!", voice="alloy")
Path("hello.mp3").write_bytes(resp)
print(f"Saved {len(resp)} bytes to hello.mp3")
Saved 26400 bytes to hello.mp3

Transcription (file upload + streaming)

resp = await oai_cli.audio.create_transcription(
    file=open("hello.mp3", "rb"), model="gpt-4o-transcribe", stream=True)
async for ev in resp: print(ev.get('delta', ''), end='')
Hello from Fastbec.

Gemini

Google Discovery specs use nested resource groups with attribute chaining:

gem_cli = OpenAPIClient(gem_spec, headers={"x-goog-api-key": os.environ["GEMINI_API_KEY"]})
str(gem_cli.models)[:500]
'- models.generate_content(model, contents, access_token, alt, callback, fields, key, oauth_token, pretty_print, quota_user, upload_protocol, upload_type, xgafv, system_instruction, tools, tool_config, safety_settings, generation_config, cached_content, service_tier, store): *Generates a model response given an input `GenerateContentRequest`. Refer to the [text generation guide](https://ai.google.dev/gemini-api/docs/text-generation) for detailed usage information. Input capabilities differ betwee'
resp = await gem_cli.models.generate_content(
    model="models/gemini-3-flash-preview",
    contents=[{"parts": [{"text": "Translate hello into French. Return only the translation."}]}])
resp['candidates'][0]['content']['parts'][0]['text']
'Bonjour'

Nested resource groups are accessed with attribute chaining:

gem_cli.tuned_models.permissions.create

Create a permission to a specific resource.

Parameters:

  • parent (str, required): Required. The parent resource of the Permission. Formats: tunedModels/{tuned_model} corpora/{corpus}
  • role (str, required): Required. The role granted by this permission.
  • access_token (str, optional): OAuth access token.
  • alt (str, optional): Data format for response.
  • callback (str, optional): JSONP
  • fields (str, optional): Selector specifying which fields to include in a partial response.
  • key (str, optional): API key. Your API key identifies your project and provides you with API access, quota, and reports. Required unless you provide an OAuth 2.0 token.
  • oauth_token (str, optional): OAuth 2.0 token for the current user.
  • pretty_print (bool, optional): Returns response with indentations and line breaks.
  • quota_user (str, optional): Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters.
  • upload_protocol (str, optional): Upload protocol for media (e.g. “raw”, “multipart”).
  • upload_type (str, optional): Legacy upload protocol for media (e.g. “media”, “multipart”).
  • xgafv (str, optional): V1 error format.
  • name (str, optional): Output only. Identifier. The permission name. A unique name will be generated on create. Examples: tunedModels/{tuned_model}/permissions/{permission} corpora/{corpus}/permissions/{permission} Output only.
  • grantee_type (str, optional): Optional. Immutable. The type of the grantee.
  • email_address (str, optional): Optional. Immutable. The email address of the user of group which this permission refers. Field is not set when permission’s grantee type is EVERYONE.

GitHub

Route parameters (like {owner} and {repo}) are passed as regular function arguments:

resp = await gh_cli.repos.get(owner="AnswerDotAI", repo="fastcore")
resp['full_name'], resp['description'], resp['stargazers_count']
('AnswerDotAI/fastcore', 'Python supercharged for the fastai library', 1100)
gh_cli.repos.get

Get a repository

Docs: https://docs.github.com/rest/repos/repos#get-a-repository

Parameters:

  • owner (str, required): The account owner of the repository. The name is not case sensitive.
  • repo (str, required): The name of the repository without the .git extension. The name is not case sensitive.

GraphQL

GqlSpec stores a parsed introspection response. GqlClient uses it to check queries against the schema. Supply arguments as keyword arguments and select fields through attribute chaining. batch combines queries into one request.

This example reads the head commit of three repositories in one request. See the GraphQL documentation for discovery, raw queries, and error handling:

from fastspec.gql import GqlSpec, GqlClient, INTROSPECT
from fasttransport.core import AsyncTransport
gh_hdrs = {"Authorization": f"bearer {os.environ['GITHUB_TOKEN']}"}
raw = await AsyncTransport(base_headers=gh_hdrs).request('POST', 'https://api.github.com/graphql', json=dict(query=INTROSPECT))
gql = GqlClient(GqlSpec.from_introspection(raw), 'https://api.github.com/graphql', headers=gh_hdrs)
await gql.batch(*[gql.repository(owner='AnswerDotAI', name=n).defaultBranchRef.target.oid
    for n in ('fastcore', 'fasthtml', 'ghapi')])
['25c4f3228ccac3c5a63da71b5eaa4be3c428f602',
 'e5d967ae627c63035e296e6f319f220e278327e7',
 '4ca8469d7c2ccc42cb71e30a576c719f306b5cf7']

AI Tool Integration (python)

In solveit’s python sandbox, allow() registers fastspec operations that the assistant can call, including their network access. Choose the required scope:

Allow one operation:

allow(oai_cli.images.create_image)

Allow one group:

allow(oai_cli.chat)

Allow one client:

allow(oai_cli)

Allow OpFunc calls on all clients:

allow({OpFunc: ['__call__']})

Compact schemas and transports

Use SpecParser.to_dict, save, and from_dict to serialize and load compact specifications. A package can include the parsed form and avoid loading the multi-megabyte original at runtime. GqlSpec provides compact storage for GraphQL schemas.

Requests use fasttransport. Provider failures pass through the error layer.