Files

172 lines
5.5 KiB
ReStructuredText
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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.113.15 (including free-threaded builds) and PyPy 3.11 are
tested in CI.