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.
Initialize self. See help(type(self)) for accurate signature.
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:
Since mk_groups attaches subgroups after each OpGroup is constructed, __doc__ is a property computed on access: a group always lists its current direct ops followed by its subgroups, marked with a trailing /. These test ops have no __signature__, so tuned_models shows only its subgroup:
(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.