fastspec0.2.5
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
Authors
Project URLs
Requires Python
>=3.10
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
- messages.messages_post(model, messages, max_tokens, cache_control, container, inference_geo, metadata, output_config, service_tier, stop_sequences, stream, system, temperature, thinking, tool_choice, tools, top_k, top_p): Create a Message
- messages.message_batches_post(requests): Create a Message Batch
- messages.message_batches_list(before_id, after_id, limit): List Message Batches
- messages.message_batches_retrieve(message_batch_id): Retrieve a Message Batch
- messages.message_batches_delete(message_batch_id): Delete a Message Batch
- messages.message_batches_cancel(message_batch_id): Cancel a Message Batch
- messages.message_batches_results(message_batch_id): Retrieve Message Batch results
- messages.messages_count_tokens_post(messages, model, cache_control, output_config, system, thinking, tool_choice, tools): Count tokens in a Message
- messages.beta_message_batches_post(requests): Create a Message Batch
- messages.beta_message_batches_list(before_id, after_id, limit): List Message Batches
- messages.beta_message_batches_retrieve(message_batch_id): Retrieve a Message Batch
- messages.beta_message_batches_delete(message_batch_id): Delete a Message Batch
- messages.beta_message_batches_cancel(message_batch_id): Cancel a Message Batch
- messages.beta_message_batches_results(message_batch_id): Retrieve Message Batch results
- messages.beta_messages_count_tokens_post(messages, model, cache_control, context_management, mcp_servers, output_config, output_format, speed, system, thinking, tool_choice, tools): Count tokens in a Message
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
.gitextension. 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.