You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 75f8544
Browse filesBrowse the repository at this point in the historyBrowse files
* fix: order-aware decoder cache, msgspec field claims, unread responses
- Key non-class models by themselves plus their __args__ in order, so
`A | B` and `B | A` (and `list[A | B]` vs `list[B | A]`) no longer share
a TypeAdapter; plain classes keep the identity key.
- Replace the verdict cache and adapter cache with one dict per decoder
(key -> adapter or None) read by both can_decode and decode.
- Key unhashable models by repr with an equality check, so their adapter
is built once instead of on every request.
- MsgspecDecoder.can_decode walks field types of Structs, dataclasses,
TypedDicts and NamedTuples (cycle-safe) and rejects models that would
need a dec_hook, so MissingDecoderError fires before the request.
- _BoundDecoder reads an unread response body before decoding, inside the
DecodeError wrap.
* fix: type the TypeAdapter builder argument as Any for pydantic 2.14 stubs
* test: build repr-collision models in a form pydantic 2.0 accepts
Copy file name to clipboardExpand all lines: docs/decoders.md
+3-2Lines changed: 3 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -33,10 +33,11 @@ Both clients take `decoders: Sequence[ResponseDecoder] | None = None`. The list
33
33
- Order is preference. With `decoders=[CsvDecoder(), PydanticDecoder()]`, pydantic only sees the types the CSV decoder declined. When two decoders could both handle a type, the earlier one wins.
34
34
-`decoders=None` uses the installed extras: pydantic then msgspec when both are installed, whichever one is installed, or no decoders at all. Passing a list replaces the defaults, so include the built-ins you still want: `decoders=[CsvDecoder(), PydanticDecoder()]`.
35
35
- If `response_model=` is set and no decoder accepts it, the client raises `MissingDecoderError` before sending the request. That means nothing handles the type, and the fix is to install an extra or pass `decoders=[...]`. A `DecodeError` instead means a decoder ran and the body didn't fit the type, which points at the server or the model. See [Errors](errors.md).
36
+
-`MsgspecDecoder` accepts a `Struct`, dataclass, `TypedDict` or `NamedTuple` only if msgspec can decode every field type itself, nested fields included. A field typed as a pydantic model or some other class would need a `dec_hook`, which `MsgspecDecoder` doesn't take, so it declines the model and you get `MissingDecoderError` before the request goes out.
36
37
37
38
## One sync protocol for both clients
38
39
39
-
Middleware comes in sync and async versions, but there is only one `ResponseDecoder` protocol, used by both clients. `decode` is synchronous because the body has already been read when it runs, so there is nothing to await. The same decoder works with either client.
40
+
Middleware comes in sync and async versions, but there is only one `ResponseDecoder` protocol, used by both clients. `decode` is synchronous because the client has the whole body by the time it runs, so there is nothing to await. If a middleware returns a streamed response it never read, the client reads it before decoding. In `AsyncClient` that stream also has to support sync iteration, as `httpx2.ByteStream` does; otherwise the middleware should `await response.aread()` before returning it. The same decoder works with either client.
40
41
41
42
## Writing your own
42
43
@@ -131,5 +132,5 @@ This decoder makes two passes: `json.loads`, then `structure`. The built-ins dec
131
132
132
133
## See also
133
134
134
-
-[`src/httpware/decoders/`](https://github.com/modern-python/httpware/tree/main/src/httpware/decoders): the built-in decoders, including how they cache `can_decode` results and parsers per type.
135
+
-[`src/httpware/decoders/`](https://github.com/modern-python/httpware/tree/main/src/httpware/decoders): the built-in decoders, including how each caches one parser, or one rejection, per type.
135
136
-[Quickstart: typed responses](index.md#typed-responses): `response_model=` with the default decoders.
0 commit comments