mirror of
https://github.com/kennethreitz/responder.git
synced 2026-07-21 18:39:29 +00:00
172 lines
5.5 KiB
ReStructuredText
172 lines
5.5 KiB
ReStructuredText
Migrating to v7
|
||
===============
|
||
|
||
Responder 7.0 focuses on explicit runtime contracts: framework errors use
|
||
problem details by default, auth can be declared at the app level, dependencies
|
||
can be attached to routes as graph-aware guards, and optional production server
|
||
tooling lives behind the ``server`` extra.
|
||
|
||
|
||
Problem Details by Default
|
||
--------------------------
|
||
|
||
Framework-generated errors now use RFC 9457-style
|
||
``application/problem+json`` responses by default::
|
||
|
||
{
|
||
"type": "about:blank",
|
||
"title": "Not Found",
|
||
"status": 404,
|
||
"detail": "Not Found"
|
||
}
|
||
|
||
This applies to framework errors such as 404, 405, request parsing failures,
|
||
validation failures, request timeouts, auth failures, and production
|
||
response-model validation failures. When validation details exist, the framework
|
||
still includes them as the extension member ``errors``::
|
||
|
||
{
|
||
"type": "about:blank",
|
||
"title": "Validation Error",
|
||
"status": 422,
|
||
"detail": "Validation failed",
|
||
"errors": [{...}],
|
||
}
|
||
|
||
If you need the previous content-negotiated behavior while migrating, pass
|
||
``problem_details=False``::
|
||
|
||
api = responder.API(problem_details=False)
|
||
|
||
|
||
App-Level Auth
|
||
--------------
|
||
|
||
Routes can still declare auth directly::
|
||
|
||
@api.get("/me", auth=bearer)
|
||
def me(req, resp, *, user):
|
||
resp.media = {"user": user}
|
||
|
||
In v7, apps can also define a default auth scheme. Routes inherit it unless
|
||
they explicitly opt out with ``auth=None``::
|
||
|
||
api = responder.API(auth=bearer)
|
||
|
||
@api.post("/login", auth=None)
|
||
def login(req, resp):
|
||
resp.media = {"token": issue_token()}
|
||
|
||
@api.get("/me")
|
||
def me(req, resp, *, user):
|
||
resp.media = {"user": user}
|
||
|
||
When OpenAPI is enabled, inherited auth is documented on protected operations
|
||
and ``auth=None`` marks public operations with an empty security requirement.
|
||
|
||
Auth helpers can also be wrapped with lightweight scope or role checks::
|
||
|
||
admin = bearer.requires("items:write")
|
||
|
||
@api.post("/items", auth=admin)
|
||
def create_item(req, resp, *, user):
|
||
resp.media = {"user": user}
|
||
|
||
Responder reads scopes from a principal's ``scopes`` or ``roles`` attribute/key.
|
||
Missing scopes produce ``403`` and scoped OpenAPI security requirements are
|
||
documented on the operation.
|
||
|
||
|
||
Route Dependency Guards
|
||
-----------------------
|
||
|
||
``Depends(...)`` still injects local dependency values into handler parameters.
|
||
Use route-level ``dependencies=[Depends(...)]`` when the dependency is a guard
|
||
or setup step and the handler does not need its return value. This keeps the
|
||
call in the dependency graph with caching/teardown behavior, and still works
|
||
for side effects::
|
||
|
||
def require_user(req):
|
||
if "Authorization" not in req.headers:
|
||
responder.abort(401, detail="Not authenticated")
|
||
|
||
@api.get("/private", dependencies=[Depends(require_user)])
|
||
def private(req, resp):
|
||
resp.media = {"ok": True}
|
||
|
||
Route dependencies follow the same lifecycle rules as parameter dependencies,
|
||
including sync/async providers, sub-dependencies, and generator teardown.
|
||
|
||
For raw before/after hooks, use ``before=``/``after=`` to keep intent explicit.
|
||
Hooks are not dependency providers: they run around the handler for
|
||
request/response mutation or short-circuiting, while ``Depends(...)`` remains
|
||
the path for dependency caching, sub-dependencies, and generator teardown.
|
||
|
||
The three route-local mechanisms are distinct:
|
||
|
||
- Handler parameters with ``Depends(...)`` resolve a value and inject it into
|
||
the handler.
|
||
- ``dependencies=[Depends(...)]`` resolves graph-aware providers for side
|
||
effects and ignores their return value.
|
||
- ``before=``/``after=`` runs raw hooks with no dependency graph participation.
|
||
|
||
Route execution order is now explicit: global ``before_request`` hooks, route
|
||
``before`` hooks, auth, validation, route ``dependencies=...``, handler,
|
||
response-model checks, and finally ``after`` hooks.
|
||
|
||
This ordering matters if a route has both hooks and dependency-guards.
|
||
|
||
|
||
Request Model Removal
|
||
---------------------
|
||
|
||
``request_model=`` and ``req.state.validated`` have been removed. Use a required
|
||
Pydantic-typed handler parameter for request-body validation instead::
|
||
|
||
class ItemIn(BaseModel):
|
||
name: str
|
||
|
||
@api.post("/items")
|
||
async def create_item(req, resp, *, item: ItemIn):
|
||
resp.media = item.model_dump()
|
||
|
||
This is the same validation path used by OpenAPI generation and returns ``422``
|
||
before the handler runs when the body is missing fields, has wrong types, or is
|
||
not a JSON object.
|
||
|
||
|
||
Server Extra
|
||
------------
|
||
|
||
The default install still includes the uvicorn runner used by ``api.run()``.
|
||
Install ``responder[orjson,server]`` when you want the recommended JSON backend
|
||
and optional Granian server too::
|
||
|
||
uv pip install 'responder[orjson,server]'
|
||
|
||
Then run the current app with Granian's embedded ASGI server::
|
||
|
||
api.run(server="granian")
|
||
|
||
If Granian is not installed, ``server="granian"`` raises a runtime error with
|
||
the install command. For multi-worker production deployments, point Granian's
|
||
CLI at your ASGI app::
|
||
|
||
granian --interface asgi --host 0.0.0.0 --port 8000 api:api
|
||
|
||
|
||
Request Method Type
|
||
-------------------
|
||
|
||
``req.method`` now returns an exact ``str``. It is still uppercase
|
||
(``"GET"``, ``"POST"``, etc.) and comparisons remain case-sensitive. The old
|
||
exported ``HTTPMethod`` subclass has been removed.
|
||
|
||
|
||
Python Support
|
||
--------------
|
||
|
||
Responder 7.0 requires **Python 3.11 or newer**; Python 3.10 is no longer
|
||
supported. CPython 3.11–3.15 (including free-threaded builds) and PyPy 3.11 are
|
||
tested in CI.
|