Skip to content

Commit bc2a3f0

Browse files
committed
fix(errors): correct failure-mode wording in _KeywordReduceMixin docs
Final review caught that the docstring and architecture/errors.md both described the wrong failure mode: an extra attribute beyond __init__'s keyword params raises TypeError on unpickle (loud), not a silent drop. The silent-drop case is the opposite: a keyword param __init__ forgets to assign to self, which reverts to its default on unpickle if it has one.
1 parent 700b6be commit bc2a3f0

2 files changed

Lines changed: 7 additions & 6 deletions

File tree

architecture/errors.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ The error-mapping table (what `httpx2` exception maps to which `httpware` except
1818

1919
The "no `__init__` override" rule scopes only to `StatusError` subclasses. Non-status `ClientError` subclasses — `DecodeError`, `MissingDecoderError`, `BulkheadFullError`, `RetryBudgetExhaustedError`, `CircuitOpenError`, `ResponseTooLargeError` — deliberately define `__init__` with keyword-only fields.
2020

21-
These six non-status `ClientError` subclasses inherit `__reduce__` from `_KeywordReduceMixin`, which pickles via `self.__dict__`. This requires `self.__dict__` to exactly mirror the `__init__` keyword parameters — do not store any additional derived attribute on these classes without also updating `__init__`'s parameters, or pickling will silently drop it.
21+
These six non-status `ClientError` subclasses inherit `__reduce__` from `_KeywordReduceMixin`, which pickles via `self.__dict__` and reconstructs via `cls(**kwargs)`. This requires `self.__dict__` to exactly mirror the `__init__` keyword parameters: an attribute stored beyond those parameters raises `TypeError` on unpickle (unexpected keyword argument), and a keyword parameter `__init__` doesn't assign to `self` is silently dropped if it has a default (unpickle reverts to it) or raises `TypeError` if it doesn't.
2222

2323
`ResponseTooLargeError` is raised when `max_response_body_bytes` is set and a response body would exceed the cap — status-agnostic (a `200` can trip it), counting **decoded** bytes. It fires from the non-streaming terminal (`send()`) and from `stream()`'s internal error pre-read; user-driven `stream()` iteration is never capped. The `reason` field discriminates the two trip modes: `"declared"` (the declared `Content-Length` already exceeds the cap, rejected before any byte is read — `content_length` holds it) and `"streamed"` (the decoded body crossed the cap mid-read, the chunked or compression-bomb case, where the true size is unknown by design). It is a non-status `ClientError`; it does not carry a `StatusError`-style positional `response` and is not in `STATUS_TO_EXCEPTION`. Because it is neither a `StatusError`, `NetworkError`, nor `TimeoutError`, it is not retried and does not count toward the circuit breaker.
2424

src/httpware/errors.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -144,11 +144,12 @@ class _KeywordReduceMixin:
144144
"""Shared __reduce__ for keyword-only ClientError subclasses.
145145
146146
For subclasses whose __init__ is keyword-only and whose instance
147-
__dict__ exactly mirrors it. Do not add this mixin to a class that
148-
stores any attribute beyond its __init__'s keyword parameters —
149-
reconstruction replays self.__dict__ as keyword arguments, so an
150-
extra/derived attribute would either be silently dropped or raise a
151-
TypeError on unpickle.
147+
__dict__ exactly mirrors it. Reconstruction replays self.__dict__ as
148+
keyword arguments (cls(**kwargs)): an attribute stored beyond
149+
__init__'s keyword parameters raises TypeError on unpickle (unexpected
150+
keyword argument); a keyword parameter __init__ doesn't assign to self
151+
is silently dropped if it has a default (unpickle reverts to it) or
152+
raises TypeError if it doesn't (missing required argument).
152153
"""
153154

154155
def __reduce__(self) -> tuple[Any, ...]:

0 commit comments

Comments
 (0)