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'
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.
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.
'html_parser_v2'
sanitize_param_name is the stricter form for parameter names, which must be assignable: anything non-identifier becomes _ before snaking.
Call self as a function.
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 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.
<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.
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.
'[messages.send](https://api.example.com/docs#send)(model, input, max_tokens, for_, stream): *Send a chat message*'
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.
Namespace for grouped operations: each op is an attribute, and the repr lists them all
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.
Nested groups chain as attributes, and each level is itself an OpGroup:
(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=''))
(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:
OpGroup.__allow__ supports hosts whose allow mechanism registers callable surfaces recursively: allowing a group allows its ops and every nested subgroup in one call.
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.