Tags: modern-python/httpware
Tags
feat!: status-agnostic response-body cap (max_response_body_bytes) (#78) * feat: add reason discriminator to ResponseTooLargeError Status-agnostic semantics + explicit declared/streamed trip mode, ahead of the max_response_body_bytes cap rework. Existing stream() Content-Length checks pass reason="declared"; restructured in a later task. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat: add pure _accumulate_capped core with property test Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat: add shared _read_capped streaming accumulator _read_capped / _read_capped_async wrap the pure core with the Content-Length early-reject and the buffered Response rebuild; _safe_extensions drops the stale network_stream. Caller owns the stream lifecycle. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat!: replace max_error_body_bytes with max_response_body_bytes Status-agnostic, decoded-byte cap enforced at the non-streaming terminal via a streaming capped-accumulator (send(stream=True) + _read_capped). Branches on cap is None so the default path keeps plain send() and .elapsed. Construction validates >= 1. The old error-only param is removed (pre-1.0, no shim). BREAKING CHANGE: max_error_body_bytes is replaced by max_response_body_bytes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat: bound stream() error pre-read via _read_capped Both stream() error branches route the 4xx/5xx pre-read through the shared accumulator when a cap is set, so chunked/compression-bombed error bodies are caught instead of read unbounded; exc.response.content stays populated. User-driven success streaming is never capped. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test: lock ResponseTooLargeError resilience semantics Cap trips are not retried, do not trip the circuit breaker, and an over-cap retryable 5xx surfaces as ResponseTooLargeError (cap-wins). No prod change — these assert the behavior that falls out of the ClientError hierarchy. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: promote response-body cap into architecture; retire deferred item Rewrite architecture/client.md 'Bounded response bodies' and the errors.md ResponseTooLargeError entry for the status-agnostic decoded-byte cap; remove the actioned deferred item; add the 0.15.0 release note; check in the change bundle (design.md + plan.md). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(planning): mark response-body-cap bundle shipped (#78) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix: correct capped-rebuild header handling + skip bodiless responses Two bugs in the capped read path found in review of #78: - The rebuilt Response kept content-encoding while holding already-decoded content, so httpx2 re-decompressed and crashed on every compressed body under the cap. Strip content-encoding / transfer-encoding / (compressed) content-length via _buffered_headers; httpx2 recomputes content-length. - A bodiless response (HEAD / 204 / 304) with a large declared Content-Length was falsely rejected. _response_has_body short-circuits these: read the empty body and return the original response unchanged, preserving its headers (HEAD legitimately echoes the entity length). Adds within-cap compressed-body and bodiless regression tests at the _read_capped, send(), and stream() levels. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
chore(planning): archive circuit-breaker-state bundle, ship 0.14.0 (#70) Post-merge bookkeeping for PR #70: fill the 0.14.0 release-notes PR number, mark the bundle shipped (pr: 70), move it active -> archive, flip its Index line to Archived, and trim the deferred CircuitBreaker entry to just manual control now that read-only state has shipped. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(circuit-breaker): time-based failure-rate trip mode (0.13.0) (#69) * feat(circuit-breaker): add time-bucketed _RollingWindow recorder Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(circuit-breaker): thread rate-mode config + validation Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(circuit-breaker): rate-over-window trip mode Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(circuit-breaker): assert rate-mode circuit.opened attributes Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(circuit-breaker): document rate mode; 0.13.0 release notes Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(planning): add the circuit-breaker-rate-mode change bundle Design + plan for the opt-in time-based failure-rate trip mode, and the Active Index entry. Bundle stays active/draft until merge. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(circuit-breaker): document rate mode in the module docstring Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(planning): archive the circuit-breaker-rate-mode bundle (#69) Ship bookkeeping for PR #69: fill the 0.13.0 release-notes PR number, mark the bundle shipped (pr: 69), move it from changes/active/ to changes/archive/, flip its Index line to Archived, and trim the deferred "CircuitBreaker v2" item to the still-open axes (count-based window, manual control + state). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(circuit-breaker): clarify mode-switch precedence and cross-mode validation Address PR #69 review: note that failure_rate_threshold is the sole mode switch (both-set → rate wins, not an error), and that window_seconds / minimum_calls are validated in both modes even when inert in classic. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(client): per-verb *_with_response siblings (0.12.0) (#68) * refactor(client): extract _prepare_request from _request_with_body Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(client): add async per-verb *_with_response siblings Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(client): add sync per-verb *_with_response siblings Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(client): document per-verb *_with_response siblings Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(release): 0.12.0 — per-verb *_with_response siblings Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(planning): add the per-verb-with-response change bundle Design + plan for the get_with_response … request_with_response siblings, and the Active Index entry. Bundle stays active/draft until merge. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(release): cite PR #68 in 0.12.0 shipped-via Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(planning): archive the per-verb-with-response bundle (#68) Ship bookkeeping: mark design.md + plan.md shipped (pr: 68, outcome filled), move the bundle from changes/active/ to changes/archive/, and flip its Index line from Active to Archived. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
chore(release): 0.11.0 — deep-audit hardening Add 0.11.0 release notes (security redaction, bounded error bodies + ResponseTooLargeError, correctness fixes, middleware __all__, test/doc quality — the full 2026-06-14 deep-audit remediation, #62–#66). Reset the static pyproject version to the placeholder "0": releases are tag-driven (publish.yml runs `uv version $GITHUB_REF_NAME` from the tag), so the field was stale at 0.3.0 against the 0.10.1 release history. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PreviousNext