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
):

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 operation signature with parameter descriptions in Annotated; defaults makes those params optional

mk_sig builds the real inspect.Signature: required parameters first, then optional ones. An optional parameter without a declared default uses UNSET (omit it from the request), not None (which sends JSON null). Descriptions travel with parameter types as Annotated metadata. docments can read them without source code, including after delegates copies the parameters to a wrapper.

sig = mk_sig(send)
test_eq(typing.get_args(sig.parameters['model'].annotation), (str, 'Model id'))
sig
<Signature (model: Annotated[str, 'Model id'], input: Annotated[str, 'Prompt text'], max_tokens: Annotated[int, 'Cap on generated tokens'] = UNSET, for_: Annotated[str, 'End-user id'] = UNSET, stream: bool = False)>

Passing defaults makes those parameters optional with the given values. This supports client-level binding without changing the spec. An explicit None remains distinct from UNSET.

bound = mk_sig(send, defaults={'model':'sonnet-4', 'for':None})
test_eq(bound.parameters['model'].default, 'sonnet-4')
test_is(bound.parameters['for_'].default, None)
test_is(mk_sig(send, defaults={'model':None}).parameters['model'].default, None)
bound
<Signature (input: Annotated[str, 'Prompt text'], max_tokens: Annotated[int, 'Cap on generated tokens'] = UNSET, model: Annotated[str, 'Model id'] = 'sonnet-4', for_: Annotated[str, 'End-user id'] = None, stream: bool = False)>

Docstrings

_op_summary uses the operation name when a summary is absent. Whitespace in spec summaries collapses to one space.

TestOp = namedtuple('TestOp', 'summary name docs_url')
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")

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

Root-relative links resolve against docs_url. Anchor-only links and absolute URLs remain unchanged.

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)")

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.

send.__signature__ = sig
send.__doc__ = mk_doc(send, sig, sparams)
PrettyString(send.__doc__)
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
):

OpGroup exposes each operation as an attribute. Its overview lists operations with signatures; each operation retains its own parameter documentation.

messages = OpGroup('messages', [send])
assert 'messages.send' in messages.__doc__
messages
  • messages.send(model, input, max_tokens, for_, stream): Send a chat message

Overview only. Read doc(group.operation) for parameter details and doc(group.subgroup) to descend.


source

mk_groups

def mk_groups(
    ops
):

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

mk_groups builds a tree from each operation’s group: a string for a flat namespace, a list for nesting. Here a second copy of send belongs to the messages.batches subgroup.

batch_send = SimpleNamespace(**vars(send))
batch_send.group = ['messages', 'batches']
groups = mk_groups([send, batch_send])
test_is(groups['messages'].batches.send, batch_send)
groups['messages']
  • messages.send(model, input, max_tokens, for_, stream): Send a chat message
  • batches/

Overview only. Read doc(group.operation) for parameter details and doc(group.subgroup) to descend.

Attribute access descends through subgroups to the operation, which retains its parameter documentation:

PrettyString(groups['messages'].batches.send.__doc__)
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)

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
):

Markdown overview of every group and operation in a mk_groups tree.

full_docs combines the group overviews into one Markdown document, including nested groups. It lists operation names, parameter names, and summaries. Parameter types, defaults, and descriptions remain in each operation’s documentation.

PrettyString(full_docs(groups))
## messages

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

### messages.batches

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