snake
def snake(
s:str
):Convert an identifier-ish string to snake_case.
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.
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.
'html_parser_v2'
sanitize_param_name is the stricter form for parameter names, which must be assignable: anything non-identifier becomes _ before snaking.
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'})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.
{'model': 'model',
'input': 'input',
'max-tokens': 'max_tokens',
'for': 'for_',
'stream': 'stream'}
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.
<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.
<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)>
_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)")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 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 exposes each operation as an attribute. Its overview lists operations with signatures; each operation retains its own parameter documentation.
Overview only. Read doc(group.operation) for parameter details and doc(group.subgroup) to descend.
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.
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:
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.
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.
## 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*