Python Client Reference¶
Experimental
HTTPX2 client generation is experimental. Its options, generated package, and template values may change. See Experimental Features.
This page is the reference behind Python Client Generation: how the generated methods are
declared, how values are converted, and the full rules of timeouts, retries, request bodies, redirects, templates,
and errors. The guide covers the common cases with tested examples; come here for the precise rules of one setting.
The protocol helpers have a page of their own. The examples use the layout of the guide's
quick start: the generated package app.client and its models app.models, inside the application package app. The
text calls the generated package client, as in client.errors.
Signature style¶
--client-signature-style chooses how every
operation method of the generated package declares its keyword arguments.
| Option | Values | Default | Where |
|---|---|---|---|
--client-signature-style |
"explicit", "unpack" |
"explicit" |
--client-signature-style unpack; pyproject.toml: client-signature-style = "unpack"; generate(): client_signature_style="unpack" |
It is one choice for the whole package: there is no per-operation setting and no call-time switch. It changes only
how methods are declared. The calls a method accepts, the requests it sends, its results, and its errors are the same
in both styles, and the model backend is unaffected: unpack does not turn your models into TypedDicts.
explicit¶
Every keyword is a real keyword-only parameter of the method and of each of its overloads:
@overload
def get_pet(
self,
*,
petId: int,
response_media_type: None = None,
options: RequestOptions | None = None,
) -> models.Pet: ...
@overload
def get_pet(
self,
*,
petId: int,
response_media_type: Literal['application/json'],
options: RequestOptions | None = None,
) -> models.Pet: ...
@overload
def get_pet(
self,
*,
petId: int,
response_media_type: Literal['text/plain'],
options: RequestOptions | None = None,
) -> models.PetsPetIdGetResponse: ...
def get_pet(
self,
*,
petId: int,
response_media_type: Literal['application/json', 'text/plain'] | None = None,
options: RequestOptions | None = None,
) -> GetPetResponse:
"""
Call GET /pets/{petId}.
"""
return self._core.execute(
_operations.OPERATION_2,
(petId,),
options=options,
response_media_type=response_media_type,
).data
unpack¶
Every overload and the implementation take **kwargs typed by a TypedDict of that signature's keywords:
@overload
def get_pet(
self,
**kwargs: Unpack[Operation2Arguments],
) -> models.Pet: ...
@overload
def get_pet(
self,
**kwargs: Unpack[Operation2Arguments1],
) -> models.Pet: ...
@overload
def get_pet(
self,
**kwargs: Unpack[Operation2Arguments2],
) -> models.PetsPetIdGetResponse: ...
def get_pet(
self,
**kwargs: Unpack[Operation2Arguments3],
) -> GetPetResponse:
"""
Call GET /pets/{petId}.
"""
KEYWORDS_2.check(kwargs)
return self._core.execute(
_operations.OPERATION_2,
(kwargs['petId'],),
options=kwargs.get('options'),
response_media_type=kwargs.get('response_media_type'),
).data
The TypedDicts live in the private module client._generated.client_arguments, one for each distinct signature of an
operation, shared by its normal, with_response, with_raw_response, and with_streaming_response views. Their
names follow the operation's position and may change between generations, so do not import them: declare your own
TypedDict with the keys you forward. The module also holds the list of keywords each method takes:
class Operation2Arguments(TypedDict):
"""The keyword arguments of one signature of get_pet."""
petId: int
response_media_type: NotRequired[None]
options: NotRequired[RequestOptions | None]
class Operation2Arguments1(TypedDict):
"""The keyword arguments of one signature of get_pet."""
petId: int
response_media_type: Literal['application/json']
options: NotRequired[RequestOptions | None]
class Operation2Arguments2(TypedDict):
"""The keyword arguments of one signature of get_pet."""
petId: int
response_media_type: Literal['text/plain']
options: NotRequired[RequestOptions | None]
class Operation2Arguments3(TypedDict):
"""The keyword arguments of one signature of get_pet."""
petId: int
response_media_type: NotRequired[Literal['application/json', 'text/plain'] | None]
options: NotRequired[RequestOptions | None]
KEYWORDS_2: Final = Keywords(
'get_pet',
('petId', 'response_media_type', 'options'),
('petId',),
)
What differs between the styles¶
Both styles accept the same direct calls, and forwarding a mapping whose type declares known keys:
from typing_extensions import NotRequired, TypedDict
from app.client import Client
from app.client.options import RequestOptions
class GetArguments(TypedDict):
petId: int
options: NotRequired[RequestOptions | None]
def forward(client: Client, arguments: GetArguments) -> None:
print(client.pets.get_pet(**arguments))
- Introspection.
inspect.signatureshows the keyword-only parameters of anexplicitmethod, and a single**kwargsparameter for anunpackmethod.typing.get_type_hints(method, include_extras=True)resolves the TypedDict of anunpackmethod from any module, whose keys are the same keywords, optional ones markedNotRequired. - Binding errors. An unknown keyword or a missing required one raises
TypeErrorin both styles, before anything is sent. Python binds anexplicitasync method when it is called, while anunpackasync method checks its keywords when it is awaited; async streaming methods, which are ordinary functions, check them when called. The wording of the error message is not the same. - Type checkers. Both styles give the same keywords the same types. Checkers treat a TypedDict with extra keys
differently: forwarding one to an
explicitmethod is an error for mypy, Pyright, and ty, while for anunpackmethod only mypy reports it. The method still refuses the extra key at runtime.
Body field arguments¶
--client-body-arguments chooses whether a call
may give the declared fields of an object body as keyword arguments instead of the body. "body", the default, keeps the body argument alone; "both" adds the fields of every body that can
take them next to the body argument, on every view of the method.
| Option | Values | Default | Where |
|---|---|---|---|
--client-body-arguments |
"body", "both" |
"body" |
--client-body-arguments both; pyproject.toml: client-body-arguments = "both"; generate(): client_body_arguments="both"; one operation: its body_arguments member of --client-operations, where null inherits the package's |
body_field_names of --client-operations |
Property names by media type | none | One operation: its body_field_names member of --client-operations, such as {"application/json": {"tag": "pet_tag"}} |
With "both", a JSON body whose model is NewPet takes either call:
from app.client import Client
from app.models import NewPet
with Client() as client:
client.pets.create_pet(body=NewPet(name="Mimi", tag="cat"), media_type="application/json")
client.pets.create_pet(name="Mimi", tag="cat", media_type="application/json")
In pyproject.toml, the same settings are:
[tool.datamodel-codegen]
input = "fields.yaml"
input-file-type = "openapi"
output = "app/models.py"
output-model-type = "pydantic_v2.BaseModel"
openapi-scopes = ["schemas", "api"]
target-python-version = "3.11"
formatters = ["builtin"]
disable-timestamp = true
openapi-include-paths = ["/pets*", "/owners"]
generate-client = "httpx2"
client-output = "app/client"
client-package = "app.client"
client-model-package = "app.models"
client-body-arguments = "both"
[tool.datamodel-codegen.client-operations."/paths/~1pets/post".body_field_names]
"application/json" = { tag = "pet_tag" }
"application/x-www-form-urlencoded" = { tag = "pet_tag" }
[tool.datamodel-codegen.client-operations."/paths/~1pets~1{petId}~1visits/post"]
body_field_names = { "application/json" = { options = "visit_options" } }
[tool.datamodel-codegen.client-operations."/paths/~1pets~1{petId}~1owner/put"]
body_arguments = "body"
Which bodies take fields¶
A JSON or form body takes fields when its schema is one object whose model its backend constructs from them, an
object that may be null included: Pydantic models and dataclasses, stdlib dataclasses, TypedDicts, and msgspec
Structs alike. Other bodies keep only body: unions of objects, arrays and scalars, bodies without a schema, binary
bodies and form-data sent as parts, bodies whose model cannot hold a request, such as one with a required read-only
member. Extra keys, a null body, and an empty object also need body, and a nested object is given as its own model.
Each field argument takes its model field's name, as the model naming options give it, or for a key that is no
identifier the name such a field would take, and a read-only member has none. A field whose name a parameter, a
call option such as body or options, or another field takes is suffixed, such as name_1.
body_field_names names one field of one media type instead, without changing the model or the wire property. Fields
of different media types may share a name. Generation fails when a body field name or an --aliases entry takes the
name of a parameter, a call option, or another field of the same media type, and when a body field name names no
field argument or its operation keeps only body:
--client-operations: The body field name of the application/json property 'id' of POST /pets names no field argument
--client-operations: The body field name of the application/xml property 'tag' of POST /pets names no field argument
--client-operations: The body field name of the application/json property 'absent' of POST /pets names no field argument
/paths/~1pets/post: The application/json body fields of POST /pets cannot take the argument names 'media_type', 'tag', which other arguments take
--client-operations: The body field name of the text/plain property 'q' of POST /search names no field argument
Signatures¶
The body and each media type's fields are separate overloads. The body's takes every field as UNSET; a field
overload takes the body as UNSET, the fields its model's constructor requires without a default, and its other
fields with UNSET, and a field takes None only when its property is nullable. A field is required exactly when the
generated model requires it, so --force-optional and defaults make it optional. An optional body whose fields are all optional has one
overload for each field, which that overload requires, and one that takes nothing and sends nothing:
@overload
def update_pet(
self,
*,
petId: int,
body: models.PetPatch,
name: UNSET = UNSET,
tag: UNSET = UNSET,
media_type: Literal['application/json'] | None = None,
options: RequestOptions | None = None,
) -> UpdatePetResponse: ...
@overload
def update_pet(
self,
*,
petId: int,
body: UNSET = UNSET,
name: str,
tag: str | None | UNSET = UNSET,
media_type: Literal['application/json'] | None = None,
options: RequestOptions | None = None,
) -> UpdatePetResponse: ...
@overload
def update_pet(
self,
*,
petId: int,
body: UNSET = UNSET,
name: str | UNSET = UNSET,
tag: str | None,
media_type: Literal['application/json'] | None = None,
options: RequestOptions | None = None,
) -> UpdatePetResponse: ...
@overload
def update_pet(
self,
*,
petId: int,
body: UNSET = UNSET,
name: UNSET = UNSET,
tag: UNSET = UNSET,
media_type: None = None,
options: RequestOptions | None = None,
) -> UpdatePetResponse: ...
def update_pet(
self,
*,
petId: int,
body: models.PetPatch | UNSET = UNSET,
name: str | UNSET = UNSET,
tag: str | None | UNSET = UNSET,
media_type: Literal['application/json'] | None = None,
options: RequestOptions | None = None,
) -> UpdatePetResponse:
"""
Call PATCH /pets/{petId}.
"""
return self._core.execute(
_operations.OPERATION_1,
(petId,),
body=body,
fields=(name, tag),
media_type=media_type,
options=options,
).data
The implementation takes every field with an UNSET default, and with signature_style = "unpack" each overload
takes its own TypedDict.
Calls¶
A call gives the body or the fields of the media type it sends, never both, and an explicit UNSET counts as
omitted. The method refuses any other binding before any request or stream: with TypeError for a body with
fields, a missing required field, a field of another media type, or fields for a media type that takes none, and with
ConfigurationError for a missing or undeclared media type, as it does for bodies.
- Only the fields a call gives are sent: the model's defaults are not, and
Nonesendsnull. - A required body whose fields are all optional is sent as
{}when a call gives none of them. An optional body is omitted, and naming only its media type still raisesConfigurationError. - The model is built from the fields by its constructor, which validates Pydantic models and dataclasses, while stdlib dataclasses, TypedDicts, and msgspec Structs take the values as given.
Model codecs¶
The generated models determine validation and conversion. Text in parameters, response headers, forms and text parts
is decoded before the selected backend handles it: a declared integer, number or boolean becomes that builtin when the
text is its canonical JSON literal. Pydantic and msgspec receive any other text unchanged and decide by their native
conversion rules, as they do for unknown object members. Dataclass and TypedDict codecs validate nothing, so for them
such text is a decoding error, like text that cannot become a Decimal, date or UUID leaf; their JSON bodies are
still constructed without added schema validation. Repeated undeclared members keep their last value.
A package holds the codec of its own model backend only. A Pydantic package imports no msgspec, a msgspec package no Pydantic, and a dataclass or TypedDict package neither, so the package imports, and mypy, Pyright, and ty check it, with only the dependencies that generation prints installed.
Generated clients send and read model values through their model backend, with no validation layer of their own:
| Backend | Sends | Receives |
|---|---|---|
Pydantic v2 BaseModel |
model_dump_json(by_alias=True, exclude_unset=True); other types through a cached TypeAdapter |
TypeAdapter(T).validate_json |
| Pydantic v2 dataclass | TypeAdapter(T).dump_json(by_alias=True), which leaves out the fields that hold MISSING |
TypeAdapter(T).validate_json |
msgspec.Struct |
msgspec.json.encode |
msgspec.json.Decoder(T).decode |
dataclasses, TypedDict |
json.dumps of the renamed fields |
json.loads, then the renamed fields |
Adapters and decoders are built on a type's first use and cached for the process. dataclasses and TypedDict have no
validation of their own: the generated converter renames wire names to fields and builds nested models, lists, sets,
tuples, dicts, and optionals, and converts only datetime, date, and time (with fromisoformat), UUID,
Decimal (sent as a string), an Enum by value (an unknown value fails), and bytes as base64. A TypedDict keeps
unknown keys and leaves out absent ones; a dataclass drops unknown keys and leaves out a field whose value is its
None default.
- Requests. Arguments and bodies are serialized from the model values as given. Aliases, presence, and the
conversion of dates, decimals, enums, and media follow the models. A value is checked only as far as its model's
constructor already checked it, and what the serializer cannot represent fails with a request
DecodeErrorwith the reasonunencodablebefore anything is sent; itslocationnames the argument, never the value. An empty array or object in a style-encoded parameter or a form member is left out as an omitted value is: it writes no query pair, header, cookie, or part. A style or a form cannot write null, so aNonemember of an object parameter or of a URL-encoded or multipart form, and aFieldPartwhose value isNone, are left out the same way. A multipart form that is left with no part is still sent, as its closing boundary alone. A path segment cannot be left out, so an empty array or object in a path parameter fails with a requestDecodeErrorbefore anything is sent. - Parameter arguments. A parameter whose model type is a type alias or a root model takes the type it stands for,
such as
limit: intortags: list[str], never the alias or the root model. An alias's argument is sent as given, without validation. With Pydantic models, an argument whose parameter is a root model is built into it before it is sent, which checks it as the root model's constructor does: a value it refuses fails with a requestDecodeErrorwith the reasonunencodable. - Parameter defaults. An optional parameter whose schema declares a boolean, number, or string default of its
builtin type, alone or with null, defaults to it, such as
limit: int = 20; any other optional parameter defaults toUNSET. A parameter with such a default is always sent, with the default when the call omits it: the client pins the default it was generated with, so a later change of the server's default does not apply until you regenerate, and a default that breaks its own schema's constraints is sent as it is. A path parameter that names a placeholder of its path is a required argument without a default, as OpenAPI requires, even when the document leaves outrequiredor declares itfalse; a document with a path parameter that names no placeholder of its path is refused. - Responses. Bodies, parts, stream events, and socket messages are converted by the model backend into the declared
types, which check what those types declare and coerce as the backend does; a value the type refuses raises
DecodeErrorwith the reasoninvalid_value. Read-only and write-only behavior follows the generated model options. - Binding. A missing required argument, an unknown keyword, or a media type the operation does not declare fails
before anything is sent. A parameter value with text that UTF-8 cannot encode, such as a lone surrogate, fails with
a request
DecodeErrorof the reasonunencodable, with aParameterEncodingErrorcause, before anything is sent. - Path segments. Path arguments that make their segment
.or..once encoded in their parameters' styles and put into the path template,%2Ein either case counting as., such as..in the simple style, the empty string in the label style, or%2ewithallowReserved, fail with a requestDecodeErrorat the segment's first parameter whose encoded text is non-empty, with aParameterEncodingErrorcause, before anything is sent: URL normalization in clients, proxies, and servers would remove the segment and send the call to another resource, and encoding the dots does not prevent it....and.aare sent as they are, and a dot segment the path template spells is not refused. - Path text. The literal text of a path template is sent as RFC 3986 allows it in a path, as path arguments are:
any other character, such as
\,|,^,[,],?,#, a space, or non-ASCII text, is percent-encoded as UTF-8,\as%5C, so no client, proxy, or server reads\as/or ends the path early. A%and two hex digits is an octet the document already encodes and is sent as it is, so/files/a%20bsendsa%20b, which the generated server routes by its decoded value; any other%is sent as%25. The client holds the encoded path from generation, so calls pay nothing for it. - Header names. A header parameter, response header, or part header whose name is not an RFC 9110 token, such as
X SporX-Näme, stops generation with an error, as it stops the generated server's, since no HTTP client or server can send it. A declared media type whose text is not ASCII header text, such astext/plain; label="café", stops generation too, since its Content-Type could not be sent; a call'smedia_typeof such text raisesConfigurationErrorwith the reasoninvalid_valueand thefield_path("headers", "Content-Type"). - Header values. A header carries header text only: HTAB, SP, and visible ASCII, without whitespace at either end,
the text every HTTP client and server reads alike. RFC 9110 leaves any other byte opaque, and servers read it as
Latin-1 or as UTF-8; the generated FastAPI server answers
400for it in a header parameter of the operation. A header parameter of any style, or of text content, whose value, array item, or object member is any other text, such ascaféor one with a control character, fails with a requestDecodeErrorof the reasonunencodable, with aParameterEncodingErrorcause, before anything is sent, instead of arriving as other text. A header parameter of JSON content is written with its non-ASCII text and DEL escaped, so it carries any text; declare the header that way, or apply an encoding the API defines yourself, such as percent-encoding, Base64, or RFC 8187, and decode it in the service. The headers ofdefault_headersandextra_headersand anidempotency_keyfollow the same rule; see timeouts and cancellation. A value a server gave that a protocol helper writes to a header parameter, such as an event ID, a cursor, or a binding's value, follows it too. A declared response header is read by the same rule: a value with a byte outside ASCII fails itsdecode_..._headeraccessor with a responseDecodeErrorof the reasoninvalid_header, with aMalformedErrorcause, so its text does not depend on how HTTPX2 decodes the response's headers, as a whole set: ASCII, else UTF-8 when every header of the response is UTF-8, else Latin-1.ResponseInfo.headerskeeps the text HTTPX2 decodes. The generated server sends ASCII header text only. - Cookies. A cookie parameter with a single value is sent as it is, without percent-encoding, which is how HTTP
clients send cookies and how the generated server reads them. A cookie cannot carry
;,,,",\, whitespace, control characters, or non-ASCII text (RFC 6265), so such a value fails with a requestDecodeErrorof the reasonunencodable, with aParameterEncodingErrorcause, before anything is sent, instead of arriving cut short. Encode a value that needs those characters yourself, for example as Base64, and decode it in the service. Such a parameter whose name is not a token stops generation with an error. Array and object cookie parameters send one cookie for each item or property, percent-encoded in theformstyle. - Headers. Header values are converted through their native types. Read a body without any model with
with_raw_responseorwith_streaming_responseinstead.
A union of object models read by a stdlib dataclass or TypedDict converter needs a declared discriminator. Without
one, generation raises Error naming the use:
/paths/~1animals~1any/put/requestBody: The union of Cat and Dog needs a declared discriminator for the dataclasses.dataclass converter
/paths/~1kin/put/requestBody: The union of Animal and Cat needs a declared discriminator for the dataclasses.dataclass converter
Timeouts and cancellation¶
The guide's timeouts cover the settings; the details below cover the budget across attempts, cancellation, streams, and the clock the client reads.
An optional budget across attempts¶
The runtime supplies no whole-call budget by default. Set Client(total_timeout=60) to bound every call to a minute.
Native phase timeouts bound each I/O wait; they do not limit the total duration of a call that keeps making progress.
import httpx2
from app.client import Client
from app.client.options import RequestOptions
def read_with_budget(client: Client, url: str) -> bytes:
options = RequestOptions(total_timeout=10, timeout=httpx2.Timeout(3, connect=2))
return client.request_raw("GET", url, options=options).read()
The budget is checked before each attempt and retry wait and caps each native I/O timeout. A native timeout raises
APITimeoutError with the reason phase_timeout, or deadline_exceeded once the budget has expired, with the native
failure as cause. A fully received response stays available when decoding or cleanup finishes after expiry.
Native cancellation¶
Async calls run in their caller's task. Cancel that task through the native backend; its cancellation exception
propagates unchanged. The SDK never uncancels the task, selects a competing outcome, or wraps a BaseException.
Responses and bodies are released in finally, and callbacks such as the HTTP client's event_hooks run in the
caller's cancellation scope. Synchronous preparation and callbacks remain cooperative.
Streaming after handoff¶
Streaming uses the native read timeout for idle I/O. An optional acquisition budget caps the request's native phase timeouts before sending; it does not add a separate stream lifetime timer.
from typing import BinaryIO
import httpx2
from app.client import Client
from app.client.options import RequestOptions
def download(client: Client, url: str, destination: BinaryIO) -> None:
options = RequestOptions(timeout=httpx2.Timeout(600, read=60))
with client.with_streaming_response.request_raw("GET", url, options=options) as response:
response.stream_to(destination)
Always leave the response's context manager, including when abandoning a download early. A configured helper session may additionally stop before its next step when its total budget has expired.
stream_to(path) writes the decoded body to a temporary file beside the target and moves it there once the body is
complete; an existing target raises FileExistsError unless overwrite=True, and a consumed handle raises
ConfigurationError with the reason response_consumed. A failure removes the temporary file and leaves the target
unchanged. In async calls the file calls run one at a time in a worker thread, and a cancellation during the final move
lets the move finish. stream_to(file_object) writes to a borrowed file and never closes, seeks, or truncates it.
Clocks and retry jitter¶
Client(clock=Clock(...)) replaces the time, jitter, and wait sources of every call a client makes. Views and requests
cannot change it. Each source is a function:
| Source | Default | Read for |
|---|---|---|
monotonic |
time.monotonic |
Deadlines, elapsed times, retry targets, token expiry, and protocol helper sessions, poll intervals, and stream deadlines |
time |
time.time |
Placing a wall-clock instant on the monotonic scale once: an HTTP-date Retry-After or polling delay header at receipt and an access token's expires_at; a polling, stream, or upload helper's resume check of its state's expires_at; and a cache fetch's request, response, and age times |
random |
A secure uniform draw | The fraction in [0, 1) of a full-jitter backoff, drawn only when a retry needs one |
sleep |
time.sleep |
Each wait of a synchronous client, given its seconds: retry waits, polling intervals, and stream reconnection waits |
asleep |
anyio.sleep |
Each wait of an asyncio client, given its seconds, returning the awaitable the call waits on, which task cancellation interrupts |
A source that cannot be called raises ConfigurationError with the field_path ("clock", name). OAuth providers
keep their own time through their clock= argument and must agree with their clients on wall time. Retry,
polling, and reconnection waits pass the remaining duration to the clock's sleep or asleep, and I/O timeouts
use the duration the monotonic source measured. A test that records the waits and advances its own monotonic
source by each one runs every wait at once and can check its length:
from app.client import Client
from app.client.options import Clock
class FakeTime:
def __init__(self) -> None:
self.now = 0.0
self.waits: list[float] = []
def monotonic(self) -> float:
return self.now
def sleep(self, seconds: float) -> None:
self.waits.append(seconds)
self.now += seconds
def instant_retries(url: str, fake: FakeTime) -> Client:
return Client(base_url=url, clock=Clock(monotonic=fake.monotonic, sleep=fake.sleep))
Each retry of this client starts at once, server delays included, and fake.waits holds the waits it chose.
Instrumentation¶
The SDK adds no hook or limiter layer of its own. Instrument calls on the HTTP client: pass an httpx2.Client or
httpx2.AsyncClient with event_hooks, or a wrapping transport, as http_client. Request hooks see every attempt and
followed redirect, credentials included, and response hooks every response. An ordinary exception a hook raises ends
the call as APIConnectionError with that exception as cause, and the call is not sent again. Limit concurrency
with the HTTP client's pool limits or an application semaphore.
import httpx2
from app.client import Client
def log_request(request: httpx2.Request) -> None:
print(request.method, request.url.path)
def log_response(response: httpx2.Response) -> None:
print(response.status_code, response.request.url.path)
def instrumented(url: str, http: httpx2.Client) -> Client:
http.event_hooks = {"request": [log_request], "response": [log_response]}
return Client(http_client=http, base_url=url)
An injected client stays the caller's: close it after the SDK client.
Counters and cleanup¶
ResponseInfo and every SDKError expose attempt_count, counted at send time, elapsed, and request_id, also
when no response arrived. Closing a root refuses new calls from the root and its views and closes its created native
client once; borrowed clients and providers keep the caller's lifetime. Responses and files opened from paths are
released in finally; a release failure is named in the notes of the primary error, or propagates without one.
Retries and operation contracts¶
max_retries and retry=RetryOptions(...) apply on clients, views, and calls. The fields of RetryOptions merge
independently; omitted fields inherit, while a status set replaces the inherited set, and retry=None inherits the
whole record. Automatic retries require a candidate failure or status, operation safety, replayable input, and enough
time. JSON/model decoding, arbitrary callbacks,
cancellation, and logical deadlines never restart a request.
| Setting | Effective default | Meaning |
|---|---|---|
max_retries |
2 |
Retries after the initial attempt; 0 disables retries; a setting of its own, not a RetryOptions field |
initial_delay, max_delay |
0.5, 8 seconds |
Exponential backoff, with maximum at least the initial delay |
jitter |
"full" |
Uniform delay below the exponential cap; "none" uses the cap |
statuses |
{408, 429, 500, 502, 503, 504} |
Replace with an integer set in 400–599; 401/403/407 are forbidden |
max_retry_after |
60 seconds |
Positive cap on accepted server delay; None removes this cap |
respect_retry_after |
True |
Explicit False ignores server delay hints |
retry_after_ms_header, should_retry_header |
Operation declaration, otherwise None |
Vendor controls require generated metadata; explicit None disables one |
retry_on_pool_timeout |
False |
Avoid amplifying pool contention unless explicitly enabled |
GET, HEAD, OPTIONS, PUT, and DELETE are eligible for retries by default. POST, PATCH, and other methods require an explicit
retry_safety="idempotent" declaration or a valid server key contract. A native ConnectError (TLS and DNS failures
included), ConnectTimeout, or PoolTimeout proves the request unsent and can permit otherwise unsafe methods,
unless an earlier attempt of the call, or a response HTTPX2 followed a redirect from, reached the server; every other
failure after the send started may have been sent and is never sent again. retry_safety="never" prohibits every
resend, including an unsent request.
A server delay is a minimum: the client never shortens it to fit max_retry_after or the remaining deadline, and a
valid server veto prevents a retry. Retry-After gives seconds or an HTTP date, read by the standard library's
email.utils.parsedate_to_datetime: a date without a zone, with -0000, or with an unknown zone is UTC, and an
impossible date, such as one with a leap second, is ignored. The two-digit year of an RFC 850 date is the latest that
is at most 50 years after receipt, as RFC 9110 reads it; a year below 100 in another form is read as 1969 to 2068.
Without a valid hint, bounded exponential backoff applies. Retry waits consume the same logical budget as sending and
decoding. RetryOptions(respect_retry_after=False) is an explicit application policy override, disclosed in every
generated README, app/client/README.md; it is never silently embedded in generated defaults.
from app.client import Client
from app.client.options import RequestOptions, RetryOptions
def fetch_with_retries(client: Client, url: str) -> bytes:
options = RequestOptions(
max_retries=2,
retry=RetryOptions(max_retry_after=20),
follow_redirects=True,
total_timeout=30,
)
return client.request_raw("GET", url, options=options).read()
Buffered and streaming raw APIs return the final HTTP response when status retries end, including non-2xx statuses.
Transport, policy, deadline, and cancellation failures still raise. A response release failure
stops a planned retry after its response was discarded: the call then raises that response's APIStatusError with the
failure named in its notes.
Typed operations retain final response metadata in their APIStatusError. A streaming response can retry during
acquisition; after handoff, body failures terminate that stream and never issue another request.
Declare API guarantees during generation¶
Operation guarantees are set per operation in --client-operations, or client-operations of pyproject.toml.
This example assumes api.yaml declares POST /orders:
[tool.datamodel-codegen.client-operations."/paths/~1orders/post".runtime]
retry_safety = "method_default"
retry_after_ms_header = "X-Retry-In-Ms"
should_retry_header = "X-Retry-Permitted"
idempotency = { header_name = "Idempotency-Key" }
With the Python API, the same operation setting is a mapping of client_operations:
client_operations = {
"/paths/~1orders/post": {
"runtime": {
"retry_safety": "method_default",
"retry_after_ms_header": "X-Retry-In-Ms",
"should_retry_header": "X-Retry-Permitted",
"idempotency": {"header_name": "Idempotency-Key"},
},
},
}
Only header_name is required for idempotency. The key header must be an HTTP token and cannot share
an outgoing position with an effective parameter or authentication header. The two response control headers must
have distinct names. Header comparisons ignore ASCII case; outgoing and incoming positions are independent.
Unknown keys, malformed values, and ownership conflicts fail generation with an error naming the
setting. The generated README lists only the finalized selected operations and their contracts.
Supply and retain an idempotency key¶
The idempotency setting's header_name declares the API's idempotency key header. A key belongs to one call:
supply the caller's own as RequestOptions(idempotency_key="saved-value").
Unset, the client creates one UUID4 key per logical call for an operation with a declared header.
idempotency_key=None disables that automatic key. A caller key on an undeclared operation or request_raw fails
before sending with ConfigurationError and the reason not_declared. A header of the declared name among the call's
extra_headers or the client's or a view's default_headers is sent as it is and is the call's key instead, the same
on every call it reaches; a protocol helper, which needs a key per request, refuses it. The same key is sent unchanged on every eligible
retry. A declaration without an active key does not authorize unsafe retries. The contract does not guarantee
exactly-once business execution. A key is header text, HTAB, SP, and visible ASCII without
whitespace at either end: for any other value, such as clé, a line break, a space at its end, or a number,
RequestOptions(idempotency_key=...) raises ConfigurationError with the reason invalid_value and the
field_path ("idempotency_key",).
from app.client import Client
from app.client.options import RequestOptions
def keyed_options(value: str) -> RequestOptions:
return RequestOptions(idempotency_key=value)
Pass the returned options to an operation with the matching declaration. The client reuses the supplied value across eligible retries.
Request compression¶
Declare accepted codings¶
The accepted_content_encodings runtime setting, empty by default, lists the request content codings an operation
accepts. Values are HTTP tokens compared in lowercase, without duplicates; gzip is the only coding with a
builtin encoder, so another value, including identity, fails generation, and so does a repeated
one. As in Declare API guarantees during generation:
[tool.datamodel-codegen.client-operations]
"/paths/~1items/post" = { runtime = { accepted_content_encodings = ["gzip"] } }
The generated README lists the operations that accept a coding.
Select a coding¶
Client(compression="gzip") is the default. The SDK gzips only bodies of operations that declare gzip;
Client(compression=None) disables it. Views and calls inherit the client setting; a package without such an
operation has no compression setting. Undeclared operations,
bodyless requests, raw requests, and token requests stay uncompressed. A Content-Encoding header conflicts only
when the SDK compresses the body. The gzip encoder uses level 6 and a zero modification time.
Bytes and encoded bodies are compressed once and every retry resends the same bytes. Files, paths, iterables, and multipart bodies are compressed as each attempt streams, without a Content-Length, and replay exactly as they would uncompressed; a one-shot body stays one-shot.
Each protocol helper request follows its own operation's declaration and the client setting. Bodyless polls and followed URLs stay uncompressed. Token requests are never compressed.
Body replay and resource ownership¶
A package accepts the request bodies its operations declare. A schema-less form body exposes FormData in bodies.py
and uses the core operation runtime, without the binary or multipart body runtime. Without a binary or multipart
request body, request_raw takes bytes; with a binary body request_raw also takes the binary inputs below, and
with a multipart/form-data body a MultipartBody (AsyncMultipartBody for an asyncio client) as well. Encoded
bytes, JSON included, are kept once per logical call and resent unchanged; a multipart body keeps its boundary.
A binary body, and the content of a multipart FilePart, is one of these inputs. Nothing is buffered or spooled to
make a one-shot input replayable.
| Input | Calls | How it is read | Framing | Sent again |
|---|---|---|---|---|
bytes |
sync, async | Sent as given. | Content-Length |
Yes |
Binary file object with a synchronous read |
sync, async | read in chunks of at most 64 KiB from its position at call entry, up to the length measured there when it can seek, else until it returns no bytes. |
Content-Length when it can tell and seek, else chunked |
Yes after seeking back; no when it cannot seek |
os.PathLike path such as Path |
sync, async | Opened in binary mode when the body is first sent, read like a file, closed when the call ends. | Content-Length; chunked when its file cannot seek, such as a FIFO |
Yes; no when its file cannot seek |
Iterable of bytes |
sync, async | Iterated once; each item is sent as it is yielded. | Chunked | No |
Async file object whose read is a coroutine function, such as an anyio or aiofiles file; not as a FilePart |
async | await read(65536) from its current position until it returns no bytes, never line by line. |
Chunked | No |
Async iterable of bytes; not as a FilePart |
async | Iterated once; each item is sent as it is yielded. | Chunked | No |
A str is not read as a path. str, bytearray, memoryview, synchronous text-mode or closed files, a path that
cannot be opened, and an async file or async iterable as a FilePart (HTTPX2 reads multipart files synchronously)
raise a request DecodeError with the reason unencodable before anything is sent, with the underlying failure as
cause. An async file is not inspected first: a text-mode or closed one fails while sending as APIConnectionError.
Caller files stay open, wherever the last read left them. A path the call opened is closed when the call ends; a close
failure is attached to an error already propagating, and otherwise raises SDKError with the reason
cleanup_failed. Sync calls do all file I/O on the calling thread. In async calls a path the call opens is opened,
read, and closed in a worker thread, one chunk at a time; a caller's synchronous file is read on the event loop, and
an async file where it decides. A cancelled or timed-out call waits for a running file call before it closes the file.
A retry, or a 307 or 308 redirect HTTPX2 follows, resends bytes and seeks a seekable file back to its entry position; a
failing seek raises a request DecodeError with the reason body_not_replayable. A consumed one-shot input is not
retried: the failure that would have been retried is raised. A multipart body replays when every file part can. A
failure while a file or iterable is read during sending raises APIConnectionError with that failure as cause.
Multipart bodies¶
A MultipartBody with parts is sent through HTTPX2's files= encoding, its parts in their order: each FieldPart and
FilePart keeps its name, filename, media type and extra headers, and parts may repeat a name. A field's value goes
through its member's model codec, a repeated member sends a part for each item, a styled member the parts its style
gives, and a media type the member's encoding declares must cover the one a part names. A member's style, explode,
and allowReserved apply in OpenAPI 3.1 and 3.2 documents, whose parts carry the style's text without percent-encoding,
so a delimited value that holds its style's delimiter (,, a space, or |) raises a request DecodeError with the
reason unencodable before sending; OpenAPI 3.0 ignores them for multipart/form-data, and the member is written as
one without them, in its contentType. Generation stops for a form whose exploded object that allows other properties
shares the form with another such member, a deepObject member, or other properties of its own that its schema allows
with additionalProperties as true or a schema, or that its Pydantic model keeps by its configuration, such as
--extra-fields allow, which would write the same names, as it does for query parameters.
The SDK does not check parts against the schema. A part header HTTPX2 refuses, and a part that is not
a FieldPart or a FilePart with a string name, raises a request DecodeError with the reason unencodable before
sending. A body whose parts are all bytes is encoded once, with Content-Length; a body without parts, which HTTPX2
would not send, is written by the SDK as its closing boundary alone, with the multipart/form-data Content-Type that
names the boundary, which a server reads as a form without members. Each body gets a boundary of its own.
The type of a member decides its parts, in a MultipartBody and in a form-data body sent as its model alike. A
member repeats, one part for each item, when its type is an array. A value whose type has one scalar kind is text: a
string as it is, a number or a boolean as JSON writes it. A value whose type has none is one application/json part:
an object, a mapping, a union of a string and an object, an enum of mixed kinds, or a union of an array and another
type such as string | string[], whose array is then one part rather than one for each item. A member without a
type, whose schema is {}, takes any value, and that value is one application/json part as well, a string or an
array included; an array whose items have no type has one such part for each item. A media type that the member's
encoding declares takes precedence. A multipart response is read by the same rule, and a part that names a JSON
media type is read as JSON; a part of a member without a type that names none is read as text.
The client sends a JSON part as a field, without a filename, and Starlette, which the generated server and FastAPI
use, answers 400 for a field part of more than 1 MiB. Send such a value as a FilePart with a filename, which
Starlette does not limit by size, or read the form in your own server code with a larger max_part_size.
The properties a schema types without declaring them, with additionalProperties: {...}, follow the same rule under
whatever names a body gives them, by the type of their values: an object is one application/json part, an array of
objects one for each item, and a union of an array and another type one part. The type is the one the model keeps them
as, in a field of its own or one it inherits from a schema combined with allOf, and for a model without such a field,
the type of their schema, the form's own or that of a schema it combines with allOf, also when --reuse-model makes
the form's model inherit an equal one whose schema types them otherwise. Other properties the schema allows without a
type, with additionalProperties: {}, true, or no additionalProperties, are each one application/json part, as a
member without a type is, a string or an array included. A multipart response reads typed ones by their type too. A
URL-encoded form writes them as text of that type's kind, one pair for each item of an array, and generation stops with
A parameter value needs one unambiguous scalar kind at the additionalProperties that types them when the type has
none. A URL-encoded form writes a declared member by the same rule, and stops with that message for a member without one
scalar kind, unless the member's encoding gives it a style or a contentType (see URL-encoded members with an
encoding). The generated FastAPI server reads the members of form bodies, these
other properties included, by this rule too, and stops for a member or other properties without one scalar kind with the
same message at the same place. Both also refuse a URL-encoded body whose schema has patternProperties
(Pattern properties have no builtin parameter encoding) or is not an object, such as an array, a string, or
properties without type: object (A URL-encoded value must be an object); the server reads such a body only in the
request body mode. A form body whose schema is a union of one schema and null alone is written as the form of that
schema, by its members and other properties; a multipart body whose schema is a union of several schemas has no members
to write parts by, and generation stops with needs an object schema to be sent as parts, as the server's generation
stops for it.
A multipart response is read only when the operation declares one, with the standard library's MIME parser: a part's
bytes are kept as they arrived unless it names a transfer encoding, and a broken body, or a part that is itself a
multipart body or a message, raises DecodeError with the reason invalid_framing.
from pathlib import Path
from app.client import Client
from app.client.options import RequestOptions
def upload_file(client: Client, url: str, path: Path) -> bytes:
response = client.request_raw("PUT", url, body=path, options=RequestOptions(max_retries=2))
return response.read()
URL-encoded members with an encoding¶
An application/x-www-form-urlencoded request body's encoding can give a member a style, explode, or
allowReserved, in every OpenAPI version, and the member is then written as the query parameter that encoding
describes, as percent-encoded pairs: an object with style: form, the style of an encoding that names none, and
explode: true, its default, as the pairs of its own members, a deepObject as name[key] pairs, and form without
explode, spaceDelimited, and pipeDelimited as one delimited pair. These fields take precedence over a contentType.
A contentType alone writes the member as one pair of that text, or of JSON for a JSON media type; any other media
type, such as application/xml, stops generation with The member content has no builtin encoding. A style the
member's type has no form for, such as deepObject for an array, stops generation too, as it does for a query
parameter. Generation also stops for a form whose exploded object that allows other properties shares the form with
another such member, a deepObject member, or other properties of its own that its schema allows with
additionalProperties as true or a schema, or that its Pydantic model keeps by its configuration, such as
--extra-fields allow, which would write the same names. A form that leaves its other properties unsaid, with a model
that drops them, is written, and its exploded object takes the names no other member writes. The other members are
written as in a form without such members. The generated FastAPI server reads these members as the client writes them.
Redirects and transport construction¶
Calls are sent through the native client with send(request, stream=True): its auth, event hooks, redirect
setting, framing, and content decoding are effective. follow_redirects of the client, a view, or a call's
RequestOptions is the native boolean for that call; unset, an SDK-created client follows no redirect and an injected
one keeps its own. HTTPX2 follows redirects itself, with its method rewriting, its max_redirects limit
(TooManyRedirects raises APIConnectionError), and its header rules: on every redirect it replaces the Cookie
header of the request, a cookie parameter included, with the cookies of the client's cookie jar that match the hop, such
as one a Set-Cookie response header set earlier, and it drops Authorization across origins. The request of the
first hop carries no jar cookie, because the SDK builds it itself. Every hop consumes the same call deadline, and a
failure after a redirect was answered is never sent again.
In a package that declares security schemes, a request carrying a credential at a scheme's position other than
Authorization (an API key in a header, query field, or cookie, however the request came to carry it) is never
redirected: its 3xx is the final response, which the typed call raises as APIStatusError and with_raw_response
returns with its Location. To follow it anyway, place the credential with an httpx2.Auth of your own on the
injected client, which HTTPX2 runs on every request it sends:
import httpx2
from app.client import Client
class ApiKey(httpx2.Auth):
def __init__(self, key: str) -> None:
self.key = key
def auth_flow(self, request: httpx2.Request):
request.headers["X-API-Key"] = self.key
yield request
def following_client(key: str) -> Client:
native = httpx2.Client(auth=ApiKey(key), follow_redirects=True)
return Client(http_client=native)
Request event hooks and an httpx2.Auth of your own run after the follow decision, so they can still add a header such
as X-API-Key to a request sent to another origin.
The native client's Accept-Encoding is sent and response content codings are removed by HTTPX2; a body that does
not decode raises DecodeError with the reason malformed_coding. A streaming raw response's iter_raw_bytes()
yields the body as it arrived; a buffered one keeps only its decoded body. A HEAD operation's typed result stays
bodyless after a redirect.
A client the SDK creates is httpx2.Client(timeout=httpx2.Timeout(600, connect=5)), or its asyncio twin, with
every other HTTPX2 default. Configure TLS, proxies, pools, HTTP/2, and environment handling on an HTTPX2 client of
your own passed as http_client; the SDK keeps its settings, its timeout unless timeout is given, and its
ownership. Typed errors and exception bodies keep at most a 64 KiB prefix of an error body; buffered raw responses
and streams are not capped.
from ssl import create_default_context
import httpx2
from app.client import Client
def configured_client(ca_file: str) -> tuple[Client, httpx2.Client]:
context = create_default_context(cafile=ca_file)
native = httpx2.Client(verify=context, limits=httpx2.Limits(max_connections=50, max_keepalive_connections=10))
return Client(http_client=native), native
An injected native client keeps its own pool, proxy, and TLS settings. The root closes only the native client it
created, once; views share their root. A borrowed client's cookies, headers, and query defaults are not merged
into SDK requests; its Accept-Encoding is.
Templates¶
The client templates come from --custom-template-dir,
as model templates do: the files of its client directory replace the builtin roles of the same name:
facade.jinja2 (the package initializers and the public modules options, errors, responses, auth, bodies,
and protocols, and the webhook packages' initializers and webhooks/keys.py), client.jinja2 (_client.py and _async_client.py), resource.jinja2 (each
resource's _sync.py and _async.py), types.jinja2 (each resource's types/<resource>/_operations.py),
arguments.jinja2 (_generated/client_arguments.py), operations.jinja2 (_operations.py), security.jinja2
(_generated/security.py), model_bindings.jinja2 (_generated/model_bindings.py), helper_plans.jinja2
(protocols/_plans.py), helpers.jinja2 (protocols/_helpers.py and protocols/_async_helpers.py),
webhook.jinja2 (each webhook helper's module), readme.jinja2, and runtime.jinja2 (the runtime reference). A
role the directory does not hold, or a custom template directory without a client directory, keeps its builtin
template. The builtin templates are in the _client/templates directory of the installed package; copy
one to start from it. Each role receives the values its builtin template uses and, as custom model templates do, the
#all# entry of --extra-template-data; the role's
own values take precedence over the extra data.
Overrides are not checked against the builtin roles
A file of the client directory that is not one of these role names is ignored without a warning, so a
misspelled or renamed role keeps its builtin template. A value that an override names and the builtin no longer
provides, such as one that an older copy of a builtin template uses, prints as an empty string and iterates as
nothing, as Jinja's default Undefined does; the values a role receives can change while client generation is
experimental. Generation can then succeed and write a package that fails when it is imported. Reading an attribute
of such a value, or calling it, fails the render instead, which stops the run with an Error that names the
template. After each upgrade, compare your overrides with the
current builtin templates, and in CI import the generated package, or run datamodel-codegen --check and your
tests.
The templates write every module, class, function, signature, and call; they receive records of names, type
annotations, and the values of the runtime plans. Each role receives these top-level values, where imports is the
module's import block and the facade's module, which the builtin template does not use, is the module's dotted path
in the package, empty for the package itself:
| Role | Values |
|---|---|
arguments.jinja2 |
docstring, imports, records, keywords |
client.jinja2 |
asynchronous, docstring, imports, type_checking, lazy_imports, defaults, name, class_docstring, core, view_keywords, client_keywords, credentials, create, scheme_credentials, unset, request_options, cached_property, raw, binary, manager, self_type, traceback, add_secondary, resources, protocols, protocols_module, helper_settings |
facade.jinja2 |
module, docstring, future, imports, checking, classes, exports, lazy, loader, listing |
helper_plans.jinja2 |
docstring, imports, accessors, plans |
helpers.jinja2 |
asynchronous, docstring, imports, core, cached_property, namespaces, helpers |
model_bindings.jinja2 |
imports, codec_imports, models_annotation, models, codecs |
operations.jinja2 |
docstring, imports, servers, operations |
readme.jinja2 |
package, public_modules, reference, signature_style, body_arguments, backend, model_package, operations, contracts, helpers, stream_label, resumes, sockets, kinds, compression, dependencies, security, capabilities, runtime |
resource.jinja2 |
docstring, imports, core, cached_property, overload_decorator, views |
runtime.jinja2 |
package, oauth, schemes, security, raw_bytes, multipart_requests, credentials, example, flows, pagination, polling, cache, uploads, streams, sockets, compression |
security.jinja2 |
docstring, imports, schemes, root_annotation, root, operations |
types.jinja2 |
docstring, imports, type_alias, overload_decorator, aliases |
webhook.jinja2 |
kind, name, algorithm, timestamp, imports, exports, typevar, plan, functions |
resource.jinja2 receives one view for each class of a resource module, with its cached attributes and its
methods. A method has its name, view (plain, metadata, raw, or streaming), whether it is a
coroutine, its docstring, its overloads and implementation signatures, each with its parameters (name,
annotation, default) or the TypedDict it unpacked, and its returns, the keywords record it checks an
unpacked call with, and its call of the client core: the operation, the parameters tuple, and the keyword
arguments. The plan constants of operations.jinja2, security.jinja2, and arguments.jinja2 have a name, an
annotation, a constructor, and arguments, each with a keyword, empty for a positional one, and a value; so
do the plans of helper_plans.jinja2 and the plan of webhook.jinja2. helper_plans.jinja2 also receives the
typed accessors the plans read items and results with: each has a name, the data type it reads, the type it
returns, its docstring, the checks that end its read early (the value local, the read it keeps, whether it
ends at none, the unset type it ends at, when there is one, and the missing sentinel it ends at, when there is
one), and its result. helpers.jinja2 receives the helper namespaces, each with its name, docstring, the
core it takes, the core it wraps that in at the root, and its children, and the helpers, each with its name, docstring, methods, and the handle class a polling
helper with remote cancellation returns. A helper method, like a function of webhook.jinja2, has its name, whether
it is a coroutine, its positional parameters and the keywords after *, what it returns, its docstring
(empty for a webhook function, whose builtin template writes its own), and the runtime function it returns the call
of with its arguments. The keywords may be empty, as an unsigned webhook function's are, and a template writes
* only when they are not. webhook.jinja2 receives the signature kind
(none, adapter, or builtin), the helper's name and its signature's algorithm, and whether deliveries carry a
timestamp; model_bindings.jinja2 receives the stdlib field maps as models, each with its type and value, and
the codecs, each with its name, annotation, value, and description. A builtin template
writes each parameter, argument, export, and plan keyword on its own line, and leaves the type annotations and plan
values on one line for the formatter.
An operation method's docstring is the description of its --client-operations entry, or, with --use-schema-description, the
operation's summary and description, or else its route, such as Call GET /pets/{petId}., formatted as model
docstrings are, on one line with --use-single-line-docstring. A template that does not parse or render stops the
run with an Error that names the file in the custom template directory and, when Jinja knows it, the line.
Errors¶
Every exception the client defines derives from SDKError and is imported from client.errors. Python's own
exceptions pass through unchanged: TypeError for keywords a method cannot bind, FileExistsError for an existing
download target without overwrite=True, and the native cancellation exception of a cancelled task. Each SDKError
constructor is keyword-only and does not validate field values. Each carries reason, a stable snake_case symbol or
None, operation_id, info, the response metadata when one arrived, cause, and the measurements attempt_count,
elapsed, and request_id. Messages name safe call metadata (the class, operation_id, reason, and a few
class-specific details), never headers or credentials. The one payload a message carries is the start of the response
body in an APIStatusError message, described below; treat that message as response data before you log it. A
secondary failure beside the primary error, such as a cleanup or invalidation failure, is named in the primary error's
__notes__, never in an attribute or in its place.
| Exception | Raised for | Fields beside the shared ones |
|---|---|---|
ConfigurationError |
A setting, argument, or call the client refused, including the reasons client_closed, response_consumed, and invalid_state |
field_path, reason (invalid_value by default), helper_id |
APIConnectionError |
A classified I/O failure of the transport, with the native failure as cause; the reason is None for a transport failure, delivery_unknown when a WebSocket send or an upload append or completion may have reached the server and is never sent again, and negotiation_failed for a WebSocket 101 that selected no offered subprotocol |
None |
APITimeoutError |
A subclass of APIConnectionError: a phase cap that expired, with the reason phase_timeout, or the call's or stream's total timeout, with the reason deadline_exceeded |
None |
APIStatusError |
A final status the operation does not declare as a success | status_code, headers, request_id, body, body_bytes, truncated |
DecodeError |
A request argument its wire form cannot carry, or a response, stream event, record, or message that cannot become its declared value | direction (request or response), location, body_bytes, truncated, media_type |
AuthError |
Credential acquisition or a token exchange failed; exported only by a package whose operations take credentials | reason, status_code, oauth_error |
APIStatusError has a subclass for each common status: BadRequestError (400), AuthenticationError (401),
PermissionDeniedError (403), NotFoundError (404), ConflictError (409), UnprocessableEntityError (422),
RateLimitError (429), and InternalServerError (500 and above); any other error status raises APIStatusError
itself. Its message is Error code: <status> - <start of the body>, with at most 500 characters of the body and ...
when it is cut. Its body is the payload decoded with the operation's declared error schema for that status, or the raw
bytes, at most a 64 KiB prefix, when none is declared; a body that does not decode stays raw, and the decode
failure is the cause. A final status declared neither as a success nor as an error, such as an undeclared 3xx or 2xx,
raises APIStatusError with the reason unexpected_status.
A request DecodeError has the reason unencodable, with the argument's path as location and never its value, or
body_not_replayable at the location ("body",). A response DecodeError has the reason invalid_syntax,
invalid_value when the model or schema refuses the value, unexpected_media_type with the response's media_type,
forbidden_body, missing_body, invalid_framing, invalid_header with the location ("header", name),
or malformed_coding for a body whose content coding does not decode. A stream event, NDJSON record, or WebSocket
message that does not decode has the reason missing, null, type, value, or malformed, the location ("event", sequence) or ("message", sequence) with a pointer
where a member is at fault, and at most 64 KiB of it as body_bytes. AuthError has the reason provider_failed, invalid_expiry, oauth_error, timeout, or reauthorization_required.
It keeps no credential material or provider description itself; the cause of provider_failed is the application's
own exception from its credential callable or token callback.
Helpers raise ProtocolDataError, SessionLimitError, StreamInterruptedError, and
WebSocketClosedError, described with each helper, and SDKError with the reason store_failed for a cache store that
fails.