API surface

Turn operation metadata into documented, introspectable callables: real signatures, informative docstrings, and browsable grouped namespaces

A client generated from a machine-readable spec (an OpenAPI document, a Google Discovery document, an SDK symbol graph) is only pleasant to use if its runtime-created callables behave like hand-written ones: tab completion shows real parameters, ? and doc() show real docs, and related operations sit together in namespaces you can browse. This module builds those pieces from plain operation records, ducks with these attributes: name, group (a nesting path: string or list), summary, docs_url, params (ordered parameter names), required_params, param_types, param_defaults, and param_docs. mk_sig turns a record into an inspect.Signature (spec names sanitized to Python identifiers via sanitized_params), mk_doc renders a docstring, and OpGroup/mk_groups/full_docs assemble named callables into an attribute-chained tree with markdown summaries at every level.

fastspec builds its HTTP clients on this layer.

Names


source

snake

def snake(
    s:str
):

Convert an identifier-ish string to snake_case.

Spec names arrive in every convention at once: OpenAPI operation ids like tunedModels.generateContent, header-ish parameter names like max-tokens, Swift argument labels like networkAccessAllowed. snake normalizes any of them to one Python style. It differs from camel2snake by also folding runs of non-alphanumeric characters into underscores, so dotted and hyphenated names come out as identifiers too.

test_eq(snake('tunedModels.generateContent'), 'tuned_models_generate_content')
test_eq(snake('max-tokens'), 'max_tokens')
test_eq(snake('networkAccessAllowed'), 'network_access_allowed')
snake('HTMLParser v2')
'html_parser_v2'

sanitize_param_name is the stricter form for parameter names, which must be assignable: anything non-identifier becomes _ before snaking.


source

sanitize_param_name

def sanitize_param_name(
    p
):

Call self as a function.

Signatures

The running example throughout: one operation record for a chat-message API, with a hyphenated name and a Python keyword among its parameters. Any object with these attributes works.

send = SimpleNamespace(
    group='messages', name='send', summary='Send a chat message',
    docs_url='https://api.example.com/docs#send',
    params=['model', 'input', 'max-tokens', 'for', 'stream'],
    required_params=['model', 'input'],
    param_types=dict(model=str, input=str, stream=bool, **{'max-tokens':int, 'for':str}),
    param_defaults=dict(stream=False),
    param_docs={'model':'Model id', 'input':'Prompt text', 'max-tokens':'Cap on generated tokens', 'for':'End-user id'})

source

sanitized_params

def sanitized_params(
    ps
):

Mapping from spec param names ps to valid Python identifiers; exact names win collisions.

Two of those parameter names can’t be Python parameters as-is: max-tokens isn’t an identifier, and for is a keyword. sanitized_params maps every spec name to a usable identifier, leaving good names alone; a name that’s already exact always keeps its spelling, and invented names grow underscores until they’re unique.

sparams = sanitized_params(send.params)
test_eq(sparams['max-tokens'], 'max_tokens')
test_eq(sparams['for'], 'for_')
sparams
{'model': 'model',
 'input': 'input',
 'max-tokens': 'max_tokens',
 'for': 'for_',
 'stream': 'stream'}

source

mk_sig

def mk_sig(
    op, sparams:NoneType=None, defaults:NoneType=None
):

An inspect.Signature for operation record op; defaults values make their params optional.

mk_sig builds the real inspect.Signature: required parameters first, then optional ones. An optional parameter with no spec default gets the UNSET sentinel, so a wrapper can distinguish “caller omitted this” from any real value.

sig = mk_sig(send)
sig
<Signature (model: str, input: str, max_tokens: int = UNSET, for_: str = UNSET, stream: bool = False)>

Passing defaults makes those parameters optional with the given values — useful for client-level binding (an API version, a repo name) without touching the spec.

mk_sig(send, defaults={'model':'sonnet-4'})
<Signature (input: str, max_tokens: int = UNSET, for_: str = UNSET, model: str = 'sonnet-4', stream: bool = False)>

Docstrings

Summaries come from spec prose, so _op_summary flattens whitespace and rewrites relative markdown links against docs_url — a summary that reads correctly in isolation is what group listings and docstrings both need.

TestOp = namedtuple('TestOp', 'summary name docs_url')

# Summary passthrough, and fallback to the op name
test_eq(_op_summary(TestOp("List models", "list", "")), "List models")
test_eq(_op_summary(TestOp("", "list_models", "")), "list_models")
test_eq(_op_summary(TestOp(None, "list_models", "")), "list_models")

# Whitespace cleanup
test_eq(_op_summary(TestOp("List  all\n models", "list", "")), "List all models")

# Relative link rewriting against docs_url; anchor-only and absolute links untouched
test_eq(_op_summary(TestOp("[details](/docs#rate)", "x", "https://api.example.com/docs")),
    "[details](https://api.example.com/docs#rate)")
test_eq(_op_summary(TestOp("[see](#limits)", "x", "https://api.example.com/docs")), "[see](#limits)")
test_eq(_op_summary(TestOp("[info](https://other.com/x)", "x", "https://api.example.com/docs")),
    "[info](https://other.com/x)")

_op_line renders one op as a markdown bullet for group listings: dotted group path, linked to its docs page, with the signature’s parameters inline.

_op_line(send, sig)
'[messages.send](https://api.example.com/docs#send)(model, input, max_tokens, for_, stream): *Send a chat message*'

source

mk_doc

def mk_doc(
    op, sig, sparams
):

Render operation docstring with summary, docs URL, and parameter hints.

mk_doc renders the docstring a generated callable should carry: summary, docs link, and one line per parameter with its type, requiredness or default, and spec description — under the sanitized name the caller will actually type.

print(mk_doc(send, sig, sparams))
Send a chat message

Docs: https://api.example.com/docs#send

Parameters:
- model (str, required): Model id
- input (str, required): Prompt text
- max_tokens (int, optional): Cap on generated tokens
- for_ (str, optional): End-user id
- stream (bool, default: False)

Groups


source

OpGroup

def OpGroup(
    name:str, ops
):

Namespace for grouped operations: each op is an attribute, and the repr lists them all


source

mk_groups

def mk_groups(
    ops
):

Nested tree of OpGroups from ops, following each op’s group path.

Ops declare where they live via group: a string for a flat namespace, a list for nesting. mk_groups builds the tree, OpGroup holds one level of it, and attribute chaining walks it. Group names are snaked, so tunedModels arrives as tuned_models.

TestOp = namedtuple('TestOp', 'group name docs_url')

ops = [TestOp("repos", "get", ""), TestOp("repos", "list", ""), TestOp(["tuned_models"], "get", ""),
    TestOp(["tuned_models", "permissions"], "list", ""), TestOp(["tuned_models", "permissions"], "delete", "")]
op_groups = mk_groups(ops)
op_groups
{'repos': , 'tuned_models': }

Nested groups chain as attributes, and each level is itself an OpGroup:

test_eq(isinstance(op_groups['tuned_models'].permissions, OpGroup), True)
op_groups['tuned_models'].get, op_groups['tuned_models'].permissions.delete, op_groups['tuned_models'].permissions.list
(TestOp(group=['tuned_models'], name='get', docs_url=''),
 TestOp(group=['tuned_models', 'permissions'], name='delete', docs_url=''),
 TestOp(group=['tuned_models', 'permissions'], name='list', docs_url=''))
op_groups['repos'].get, op_groups['repos'].list
(TestOp(group='repos', name='get', docs_url=''),
 TestOp(group='repos', name='list', docs_url=''))

An op that carries a __signature__ (anything dressed by mk_sig, or a real callable) contributes an _op_line bullet to its group’s __doc__, so the group’s repr is its documentation:

sendop = SimpleNamespace(**vars(send), __signature__=sig)
groups = mk_groups([sendop])
assert 'messages.send' in groups['messages'].__doc__
groups['messages']
  • messages.send(model, input, max_tokens, for_, stream): Send a chat message

OpGroup.__allow__ supports hosts whose allow mechanism registers callable surfaces recursively: allowing a group allows its ops and every nested subgroup in one call.

Reference docs


source

full_docs

def full_docs(
    groups
):

Complete markdown API reference for a mk_groups tree: every group and operation.

full_docs walks a mk_groups tree and emits one markdown document covering everything — an always-current API reference for feeding to a docs page or an LLM.

print(full_docs(groups))
## messages

- [messages.send](https://api.example.com/docs#send)(model, input, max_tokens, for_, stream): *Send a chat message*