feat(story-2.3): Request/Response immutability helper expansion#10
Merged
Conversation
Adds 5 helpers to Request (with_headers, with_cookie, with_cookies, with_extension, with_extensions) and 2 to Response (with_headers, with_status) so middleware can rewrite requests and responses ergonomically without falling back to dataclasses.replace. Pragmatic scope per engineering.md roadmap: archive's list plus cookies and extensions. Existing helpers (with_header, with_url, with_body, with_query) untouched — including with_query's REPLACE semantics, which the spec justifies vs with_headers' MERGE on three grounds (usage patterns, HTTP semantics, escape-hatch availability). Strict epic boundary — auth coercion (2-4), AsyncClient wiring (2-5), StreamResponse helpers (4-1) deferred. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds Request.with_headers(headers: Mapping[str, str]) -> Self that merges the incoming mapping into the existing headers; incoming keys override existing. Four tests cover add, override, preserve-others, and empty-input semantics. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds Request.with_cookie(name, value) -> Self and Request.with_cookies(cookies: Mapping) -> Self. Singular adds/replaces one cookie; plural merges a mapping with incoming keys overriding. Three tests cover the add, replace, and merge cases. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds Request.with_extension(name, value: Any) -> Self and Request.with_extensions(extensions: Mapping[str, Any]) -> Self. Extensions hold opaque user payloads (transport hints, debug attachments) — the Any value type is intentional and noqa'd. Three tests cover add-single, merge-plural, and Any-value-type behavior. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds Response.with_headers(headers: Mapping[str, str]) -> Self and Response.with_status(status: int) -> Self for ergonomic Response rewriting from middleware. Both use the existing dataclasses.replace pattern. with_status applies no validation by design — value objects don't enforce protocol semantics. Adds `import dataclasses` and `Self` to response.py's typing imports. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Request:with_headers(merge),with_cookie/with_cookies(singular add/replace + plural merge),with_extension/with_extensions(same pattern, value typeAny).Response:with_headers(merge) andwith_status(replace).with_X(name, value)adds or replaces one entry; pluralwith_Xs(items)merges with incoming keys overriding.with_query's REPLACE semantics. The asymmetry vswith_headersMERGE is justified by usage patterns, HTTP semantics, and the singular-helper escape hatch (full rationale in the spec).just testshows 198 passed.Out of scope (subsequent stories): auth coercion (2-4), AsyncClient wiring (2-5),
StreamResponse.with_*(Story 4-1), case-insensitive header keys (existing deferred-work entry).Spec + plan:
docs/superpowers/specs/2026-05-31-request-immutability-helpers-design.md,docs/superpowers/plans/2026-05-31-request-immutability-helpers-plan.md.Test plan
just test— 198 passed, 1 deselected, 100% line coverage.just lint-ciclean.tests/test_no_httpx2_leakage.pystill passes.🤖 Generated with Claude Code