feat(story-2.2): phase-shortcut decorators @before_request, @after_response, @on_error#9
Merged
Merged
Conversation
Three decorators (@before_request, @after_response, @on_error) wrap async user functions into Middleware-conforming instances so consumers can write lifecycle hooks without authoring a full Middleware class. Decisions: archive-spec naming (before/after for phases around success; on_error for the event); colocated with the Protocol in middleware/__init__.py; per-decorator private class with f.__qualname__ in __repr__; @on_error catches Exception only (CancelledError propagates), returns Response to recover or None to re-raise. Bundled in: fix docs/engineering.md §8 line 142 stale decorator names (@on_request/@on_response → @before_request/@after_response). Strict epic boundary — AsyncClient wiring (2-5), Request helpers (2-3), auth coercion (2-4) land separately. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wraps an async f(Request) -> Request into a Middleware that applies f then forwards the (possibly transformed) request down the chain via await next(...). Returns a private _BeforeRequestMiddleware instance with a __repr__ that surfaces the original function name. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wraps an async f(Request, Response) -> Response into a Middleware that awaits next(...) then applies f to the result. Returns a private _AfterResponseMiddleware instance with the standard __repr__. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wraps an async f(Request, Exception) -> Response | None into a Middleware. Catches Exception (not BaseException, so CancelledError propagates). If the handler returns a Response, that becomes the caller's response; if it returns None, the original exception is re-raised with traceback intact via bare `raise`. Four tests pin the contract: recovery via Response, re-raise via None, CancelledError flows past untouched, handler receives the original exception instance. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three tests verify the three decorators interoperate: - isinstance() recognizes each as Middleware - a mixed chain of @before_request + class middleware + @after_response applies each phase in the correct position - repr() surfaces the phase name and the original user function's qualname for debug-friendly chain inspection No production code changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…NGELOG Adds before_request, after_response, on_error to httpware/__init__.py imports and __all__ so consumers can `from httpware import …` in addition to the subpackage path. Fixes docs/engineering.md §8 line 145 to reflect the canonical @before_request / @after_response / @on_error names (it had stale @on_request / @on_response from the distillation). CHANGELOG records the Story 2.2 surface. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… protocol ty rejected `def stream(...) -> None:` on _FailingTransport because the Transport protocol requires the method to return AbstractAsyncContextManager[StreamResponse]. Aligns with the _OkTransport.stream stub shape used since Story 2-1. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes the 95% → 100% coverage gap on src/httpware/middleware/__init__.py flagged by the final code review. The three __repr__ bodies are structurally identical, but the spec called for 100% line coverage. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drops two `# noqa: PLC0415` suppressions on in-function imports of `httpware` in the reexport tests. PLC0415 (import-outside-top-level) was flagged as a code smell; hoisting the import keeps both reexport assertions working and removes the suppression. 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
src/httpware/middleware/__init__.pyfor writing lifecycle hooks without authoring a fullMiddlewareclass:@before_requestwrapsasync f(Request) -> Requestand forwards the transformed request to the chain.@after_responsewrapsasync f(Request, Response) -> Responseand appliesfto the response.@on_errorwrapsasync f(Request, Exception) -> Response | None, catchesExceptiononly (CancelledErrorpropagates), returns the handler'sResponseor re-raises if the handler returnsNone.fcaptured via closure and a__repr__of the form<before_request(f.__qualname__)>for clean chain inspection.httpware.middleware.*andhttpware.*.tests/test_middleware.py(24 total): request and response transformations, on_error swallow/re-raise paths,CancelledErrornon-capture, exception identity, Protocol satisfaction, mixed-chain composition,repr()content, package-root re-export.Bundled-in doc fix:
docs/engineering.md§8 line 145 had stale@on_request/@on_responsenames from the distillation — corrected to the canonical@before_request/@after_response.Out of scope (subsequent stories):
Request.with_*helper expansion (2-3), auth coercion (2-4), AsyncClient wiring (2-5).Spec + plan:
docs/superpowers/specs/2026-05-31-phase-shortcut-decorators-design.md,docs/superpowers/plans/2026-05-31-phase-shortcut-decorators-plan.md.Test plan
just test— 184 passed, 1 deselected, 100% line coverage on the new factories.just lint-ciclean (`eof-fixer`, `ruff format --check`, `ruff check --no-fix`, `ty check`).tests/test_no_httpx2_leakage.pystill passes.🤖 Generated with Claude Code