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
ResponseTooLargeError's behavior was spelled out near-verbatim in
docs/errors.md, architecture/errors.md, and architecture/client.md;
architecture/errors.md now cross-references the other two instead of
restating them.
The "why not respx" paragraph was duplicated between docs/testing.md
and architecture/testing.md, and the prior audit-fix pass (#103) had
mechanically changed a "breaks across httpx major versions" claim to
say httpx2 instead. Verified against respx's own README (requires
httpx 0.25+, no stated httpx2 support) and GitHub history: the
breakage claim is real but about httpx, not httpx2. Corrected the
claim and deduped, keeping the full rationale in docs/testing.md.
Copy file name to clipboardExpand all lines: architecture/errors.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -20,7 +20,7 @@ The "no `__init__` override" rule scopes only to `StatusError` subclasses. Non-s
20
20
21
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.
22
22
23
-
`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.
23
+
`ResponseTooLargeError` 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. The cap mechanism itself — when it fires, the two enforcement sites, and the `declared`/`streamed``reason` split — is documented in [`client.md`](client.md); the full field-by-field API is in `docs/errors.md`.
24
24
25
25
## Security: request headers are reachable via `exc.response.request`
-**`pytest-asyncio` auto mode.** Async test functions do not require `@pytest.mark.asyncio`. The setting lives in `pyproject.toml` under `[tool.pytest.ini_options]`.
4
-
-**`httpx2.MockTransport` for transport mocking, not `respx`.** Tests construct `httpx2.AsyncClient(transport=httpx2.MockTransport(handler))` and pass it as `httpx2_client=` to `AsyncClient` (or the sync equivalent `httpx2.Client(transport=httpx2.MockTransport(handler))` to `Client`). `MockTransport` is the public test seam in `httpx2`; `respx` patches private internals and breaks across `httpx` major versions.
4
+
-**`httpx2.MockTransport` for transport mocking, not `respx`.** Tests construct `httpx2.AsyncClient(transport=httpx2.MockTransport(handler))` and pass it as `httpx2_client=` to `AsyncClient` (or the sync equivalent `httpx2.Client(transport=httpx2.MockTransport(handler))` to `Client`). `MockTransport` is a first-party `httpx2` API; `respx`targets `httpx` (not `httpx2`) and patches its internals directly — see `docs/testing.md` for why.
5
5
-**Hypothesis property-based tests** for concurrency-sensitive code: `RetryBudget`, `Bulkhead`, retry interleaving. Files are named `test_*_props.py` so they are easy to grep and treat separately in CI.
6
6
-**Performance tests are opt-in.** The `perf` pytest marker is registered in `pyproject.toml`; the default `addopts` line includes `-m 'not perf'`. Run benchmarks explicitly with `pytest -m perf`.
7
7
-**Coverage is 100% line coverage.** New code is expected to maintain this.
Copy file name to clipboardExpand all lines: docs/testing.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -107,7 +107,7 @@ For middleware with state-keeping (counters, circuit-breaker state), assert on i
107
107
108
108
## Why not `respx`?
109
109
110
-
`httpware` deliberately uses `httpx2.MockTransport` instead of `respx` for its own tests. `MockTransport` is the public test seam in `httpx2` — supported by the maintainers, stable across versions, lives in the public API surface. `respx`patches private internals and has historically broken across `httpx2` major versions. Stick with `MockTransport` unless you have a specific reason not to.
110
+
`httpware` deliberately uses `httpx2.MockTransport` instead of `respx` for its own tests. `MockTransport` is a first-party `httpx2`API — supported by the maintainers, stable across versions, part of the public API surface. `respx`targets the original `httpx` package (its README requires `httpx 0.25+`, with no stated `httpx2` support) and has a documented history of breaking across `httpx` major-version bumps, since it patches `httpx`/`httpcore` internals directly. Stick with `MockTransport` unless you have a specific reason not to.
keep). The dedup opportunity was entirely on the docs side, and concentrated:
28
+
`ResponseTooLargeError`'s behavior was spelled out near-verbatim in **three**
29
+
places (D1, resolved `2026-07-13.09`) and the "why not respx" paragraph in two
30
+
(D2, resolved `2026-07-13.09`) — both fixed as "full account in one place,
31
+
cross-reference from the rest," the same pattern `architecture/` already uses
32
+
elsewhere. Working D2 also surfaced and fixed a factual regression in I4's
33
+
original mechanical fix (see I4's correction note). The `CircuitBreaker`
34
+
overlap (D3) remains a lower-priority, mostly-intentional
35
+
tutorial-vs-reference depth split, deferred.
34
36
35
37
## Findings
36
38
@@ -76,16 +78,18 @@ This is a fresh drift, not a re-flag of the 2026-06-13 audit's I1 (that finding
76
78
`"MockTransport is the public test seam in httpx — supported by the maintainers, stable across versions ... respx patches private internals and has historically broken across httpx major versions."`
77
79
Every other reference in the same file (line 3) and in `architecture/testing.md:4` correctly says `httpx2` — a real, separate PyPI package (`httpx2>=2.0.0,<3.0`, maintained by Pydantic Services Inc.), not an alias for the original `httpx`. This line reads as a leftover phrase from before the `httpx2` rename, and as written it inaccurately implies `respx`'s breakage history is about `httpx2` specifically.
78
80
*Fix:* change both `httpx` occurrences on that line to `httpx2` (verify against `respx`'s actual `httpx2` support status if this section is touched — it may be that `respx` doesn't support `httpx2` at all, which is a stronger reason to use `MockTransport` than "it breaks across versions").
79
-
**Resolved** (`2026-07-13.07`) — both occurrences corrected to `httpx2`; the underlying "does respx support httpx2 at all" question is left as-is, not part of this mechanical fix.
81
+
**Resolved** (`2026-07-13.07`) — both occurrences corrected to `httpx2`; the underlying "does respx support httpx2 at all" question is left as-is, not part of this mechanical fix.**Correction** (`2026-07-13.09`) — that fix itself was a factual regression: it applied the "breaks across major versions" claim to `httpx2`, but the claim (verified against `respx`'s own README and GitHub history) is actually about the original `httpx` package, which `respx` targets and `httpx2` is not. Reverted the breakage clause to `httpx`; see D2.
80
82
81
83
### Duplication / compaction candidates
82
84
83
85
**D1 — `ResponseTooLargeError` behavior is spelled out near-verbatim in three files.**
84
86
`docs/errors.md:193-204`, `architecture/errors.md:23`, and `architecture/client.md:36` all restate the same handful of facts (status-agnostic, counts decoded bytes, fires from the non-streaming terminal and `stream()`'s error pre-read but not user-driven iteration, the `"declared"`/`"streamed"` reason split, "neither StatusError, NetworkError, nor TimeoutError — not retried, doesn't count toward the circuit breaker") in matching or near-matching phrasing. `docs/errors.md` has the fullest account.
85
87
*Suggest:* keep the full account in `docs/errors.md` (or `architecture/errors.md`, whichever is meant to be canonical for this fact), compress the other two to a one-line cross-reference.
88
+
**Resolved** (`2026-07-13.09`) — `docs/errors.md` (fields) and `architecture/client.md` (mechanism) keep their full accounts; `architecture/errors.md` trimmed to the errors-tree-specific facts plus a cross-reference to both.
86
89
87
90
**D2 — "why not respx" is duplicated between `docs/testing.md:108-110` and `architecture/testing.md:4`.**
88
91
Same argument, ~3 near-identical sentences in each. Bundle this cleanup with the I4 fix (same lines) — reconcile the `httpx`/`httpx2` wording and de-duplicate the reasoning in the same edit, keeping the fuller version in one file.
92
+
**Resolved** (`2026-07-13.09`) — while researching which side to keep, found `2026-07-13.07`'s I4 fix had regressed the underlying claim (see I4's correction note above). Researched `respx` against its own README and GitHub history: it requires `httpx 0.25+`, states no `httpx2` support, and has a documented history of breaking on `httpx` major-version bumps (patches `httpx`/`httpcore` internals directly) — `httpx2`'s own docs mentioning `respx` reads as inherited copy from its stewardship transfer, not a verified compatibility claim. `docs/testing.md` now carries the corrected full rationale (`MockTransport` is first-party `httpx2`; `respx` targets `httpx`, not `httpx2`, with no stated support); `architecture/testing.md` compresses to a cross-reference.
89
93
90
94
**D3 — `CircuitBreaker` states/failure-classification/rate-mode overlap between `docs/resilience.md:158-212` and `architecture/resilience.md:17,21`.** Lower priority: this is mostly a legitimate tutorial-vs-compressed-reference depth split, not verbatim duplication, but several exact clauses ("4xx including 429 count as successes," the `window_seconds=30.0`/`minimum_calls=20` defaults) are copied rather than merely covering the same ground. Worth a light pass if `resilience.md` is next revised for another reason — not worth a dedicated change on its own.
91
95
@@ -103,10 +107,15 @@ Same argument, ~3 near-identical sentences in each. Bundle this cleanup with the
summary: Deduped ResponseTooLargeError's triplicated behavior description down to one canonical account each, and corrected + deduped the "why not respx" rationale which had regressed to an unverifiable httpx2-specific claim.
0 commit comments