Customization guide
Config file
clientsmith generate --config clientsmith.toml. Every key is optional.
| Key | Meaning |
|---|---|
package |
Package name (CLI --package wins) |
templates |
Directory of template overrides (CLI --templates wins) |
[names.operations] |
operationId = "method_name"; the name is used as-is (still escaped if it is a keyword) |
[names.models] |
SchemaKey = "ClassName" |
[retry] |
Keyword arguments of the generated RetryConfig(...) default: max_retries, backoff_factor, max_backoff, retry_unsafe |
[pagination.<operationId>] |
Pagination hints, below |
Pagination
Detection runs on every GET whose success response is a model with an array field. It is deliberately conservative.
| Style | Detected when | Generated iterator |
|---|---|---|
cursor |
a query parameter named cursor, page_token, after, starting_after, next_token or page_cursor and a response field named next_cursor, next_page_token, next_token, cursor, after or next |
sends the field back as the parameter until it is empty |
next_link |
no cursor parameter and a response field named next, next_url, next_link, next_page or next_page_url |
follows the URL as-is (query parameters of the first call are not re-sent) |
offset |
a query parameter named offset or skip |
increases the offset by the number of items received; stops on an empty page or when a total/total_count/count field says it is done |
The item array is the field named items, data, results, records or entries, else the only array field.
Override any of this per operation (wire names or Python names both work):
[pagination.listEvents]
style = "cursor" # cursor | next_link | offset | none (none disables detection)
items_field = "events"
cursor_param = "page" # request parameter that carries the cursor
next_field = "nextToken" # response field holding the next cursor / link
offset_param = "skip" # offset style
total_field = "totalCount" # offset style, optional
For an operation list the generator adds list_iter(...) (a generator in the sync resource, an async generator in the
async one). It takes the same arguments as list minus the pagination parameter and yields individual items.
Retry and timeouts
Defaults are set in [retry] and can be overridden per client:
from petstore_client import Client, RetryConfig
client = Client(
"https://api.example.com",
timeout=10.0,
retry=RetryConfig(max_retries=5, backoff_factor=1.0, max_backoff=20, retry_unsafe=True),
)
Delay before attempt n (0-based) is min(backoff_factor * 2**n, max_backoff), or the server's Retry-After
(seconds or HTTP date) when present. 429 is retried for every method; 500/502/503/504 and transport errors only for
idempotent methods (GET HEAD PUT DELETE OPTIONS) unless retry_unsafe=True. A 401 triggers one token refresh for
OAuth2 before the request is retried. Pass http_client=httpx.Client(...) to control proxies, mTLS or transports.
Authentication arguments
Client(...) accepts one group of keyword arguments per security scheme in the spec. The first scheme of each kind uses
plain names, later ones are prefixed with the scheme name.
| Scheme | Arguments |
|---|---|
http bearer (and OAuth2 flows other than client credentials, OpenID Connect) |
token |
http basic |
username, password |
apiKey (header, query or cookie) |
api_key |
oauth2 clientCredentials |
client_id, client_secret, scopes, token_url (defaults to the spec's tokenUrl) |
Each operation lists the scheme alternatives it accepts; the first alternative whose schemes are all configured is used,
and security: [] operations are sent without credentials.
Template overrides
--templates DIR puts DIR in front of the built-in template search path, so you only provide the files you want to change
(same file names as in src/clientsmith/templates/):
| Template | Output |
|---|---|
core.py.j2 |
_core.py runtime (auth, retries, serialization) |
models.py.j2 |
models.py |
errors.py.j2 |
errors.py |
resource.py.j2 |
resources/<tag>.py |
resources_init.py.j2 |
resources/__init__.py |
client.py.j2 |
client.py |
init.py.j2 |
__init__.py |
pyproject.toml.j2 |
pyproject.toml |
README.md.j2 |
README.md |
Copy the built-in template and edit it. Python outputs are passed through ruff afterwards (unused imports removed,
imports sorted, formatted), so templates can import liberally. Template variables:
models.py.j2:api(ApiIR),title,aliases_sorted,all_nameserrors.py.j2:error_classes((status, {name, base, body})),model_namesresource.py.j2:resource,class_name,ops,method(op, is_async, compat),model_namesclient.py.j2:schemes,client_args,auth_params,auth_call,retry_args,resources,resource_names,base_url,titlepyproject.toml.j2:package,title,version;README.md.j2:package,title,version,description,examples, ...
Method bodies are produced by render.py (they are too branchy for a template); to change their shape, override
resource.py.j2 and call your own macro instead of method(...).
Regeneration and CI
Output is deterministic (sorted models and operations, fixed formatting), so regenerating into a committed directory yields a minimal diff. A typical CI guard:
clientsmith generate openapi.yaml -o sdk/ --config clientsmith.toml
git diff --exit-code sdk/
clientsmith diff openapi.old.yaml openapi.yaml --fail-on-breaking