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[server]`` when you want the optional Granian server too:: uv pip install 'responder[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.