mirror of
https://github.com/kennethreitz/responder.git
synced 2026-07-21 18:39:29 +00:00
236 lines
9.6 KiB
ReStructuredText
236 lines
9.6 KiB
ReStructuredText
Runtime Contracts
|
|
=================
|
|
|
|
Responder keeps a small set of runtime contracts stable across the 9.x series.
|
|
This page collects the behaviors that application code, tests, generated
|
|
clients, and OpenAPI consumers can rely on.
|
|
|
|
|
|
Error Responses
|
|
---------------
|
|
|
|
Framework-generated errors use ``application/problem+json`` by default. The
|
|
payload always includes:
|
|
|
|
- ``type``: ``"about:blank"`` unless an application ``problem_handler`` changes
|
|
it or the error was raised as a ``responder.Problem`` with an explicit
|
|
``type``.
|
|
- ``title``: the HTTP status title.
|
|
- ``status``: the numeric HTTP status code.
|
|
|
|
When extra context exists, the payload may also include:
|
|
|
|
- ``detail`` for human-readable context.
|
|
- ``errors`` for structured validation failures.
|
|
- ``request_id`` when request ID middleware or structured logging has attached
|
|
one to the request scope.
|
|
- ``instance`` (defaulting to the request path) plus any extension members when
|
|
the error was raised as a ``responder.Problem``.
|
|
|
|
The standard framework error statuses are:
|
|
|
|
.. list-table::
|
|
:header-rows: 1
|
|
|
|
* - Status
|
|
- Default title
|
|
- Notes
|
|
* - ``400``
|
|
- ``Bad Request``
|
|
- Malformed request bodies, such as invalid JSON.
|
|
* - ``401``
|
|
- ``Unauthorized``
|
|
- Authentication failed or credentials were missing.
|
|
* - ``403``
|
|
- ``Forbidden``
|
|
- Authentication succeeded, but the principal lacks required scope.
|
|
* - ``404``
|
|
- ``Not Found``
|
|
- No route matched the request path.
|
|
* - ``405``
|
|
- ``Method Not Allowed``
|
|
- The path exists but does not accept the request method.
|
|
* - ``413``
|
|
- ``Content Too Large``
|
|
- ``max_request_size`` rejected the body.
|
|
* - ``422``
|
|
- ``Validation Error``
|
|
- Path, query, header, cookie, form, file, or body validation failed.
|
|
* - ``500``
|
|
- ``Internal Server Error``
|
|
- Unhandled application errors and response-model validation failures.
|
|
* - ``504``
|
|
- ``Gateway Timeout``
|
|
- ``request_timeout`` expired before the handler finished.
|
|
|
|
Pass ``API(problem_details=False)`` to keep the legacy behavior. In that mode,
|
|
requests that ask for JSON receive ``{"error": "..."}``, while validation
|
|
failures receive ``{"errors": [...]}``. Requests that do not ask for JSON may
|
|
receive plain text, matching older Responder releases.
|
|
|
|
``API(problem_handler=...)`` can enrich or replace problem-details payloads. The
|
|
handler receives ``(payload, request, exc)``; returning ``None`` means the
|
|
payload was mutated in place. If the handler raises or returns a non-dict value,
|
|
Responder logs the failure and uses the original framework payload.
|
|
|
|
|
|
Authentication
|
|
--------------
|
|
|
|
Application-level auth configured with ``API(auth=...)`` applies to every route
|
|
by default. A route can opt out with ``auth=None`` or replace it with
|
|
``auth=...`` on the route decorator.
|
|
|
|
Auth helpers inject the authenticated principal as ``user``, ``principal``, and
|
|
``auth`` when the route declares those keyword-only parameters. They also set
|
|
``req.state.user`` and ``req.state.auth``.
|
|
|
|
``auth.optional()`` only treats missing credentials as anonymous. Malformed
|
|
credentials, wrong schemes, bad API keys, and failed verification still fail
|
|
through the wrapped auth scheme. For scoped auth, anonymous requests are allowed
|
|
when credentials are missing, but supplied credentials must still satisfy the
|
|
required scopes.
|
|
|
|
Scoped auth returns ``403`` when a principal is authenticated but does not hold
|
|
every required scope or role. If the wrapped scheme provides a challenge,
|
|
Responder adds an ``insufficient_scope`` ``WWW-Authenticate`` challenge with the
|
|
missing scopes.
|
|
|
|
``api.policy(name, auth)`` creates a named wrapper around an auth helper. The
|
|
policy name is an application label only: authentication, principal injection,
|
|
OpenAPI security schemes, optional auth, and scoped requirements all continue to
|
|
come from the wrapped helper.
|
|
|
|
|
|
Dependency Injection
|
|
--------------------
|
|
|
|
Responder resolves route dependencies after before-request hooks and auth, and
|
|
before the route handler. For HTTP requests, the order is:
|
|
|
|
1. Before-request hooks.
|
|
2. Route or application auth.
|
|
3. Route dependency guards.
|
|
4. Handler parameter dependencies.
|
|
5. Handler.
|
|
6. After-request hooks.
|
|
7. Request-scoped dependency teardown.
|
|
|
|
Each dependency is resolved at most once per request. Shared sub-dependencies
|
|
reuse the same value within that request.
|
|
|
|
Generator dependencies tear down in reverse dependency order. Teardown failures
|
|
are logged, and remaining teardowns still run.
|
|
|
|
App-scoped dependencies are resolved once for the application lifetime and are
|
|
protected by a lock during first resolution, so concurrent first users share the
|
|
same initialized value. If first initialization raises, the failed value is not
|
|
cached and a later request may retry. App-scoped teardowns run during lifespan
|
|
shutdown and clear the app-scope cache.
|
|
|
|
App-scoped dependencies may depend on other app-scoped dependencies, but they
|
|
cannot receive the request object and cannot depend on request-scoped
|
|
dependencies.
|
|
|
|
|
|
Response Contracts
|
|
------------------
|
|
|
|
A supported handler return annotation is the response contract. Pydantic
|
|
models, dataclasses, typed dictionaries, collections, unions, pagination
|
|
models, and JSON scalars are inferred through Pydantic ``TypeAdapter``. The
|
|
same contract drives runtime validation and serialization, OpenAPI, and
|
|
generated-client types. ``response_model=...`` declares the same contract
|
|
explicitly; ``response_model=False`` disables both inference and validation.
|
|
|
|
Validation runs after the handler and after-request hooks. Valid responses are
|
|
coerced and filtered through the contract. Invalid responses fail closed:
|
|
|
|
- In normal mode, the client receives a ``500`` framework error.
|
|
- In debug mode, the validation exception is raised for the developer.
|
|
|
|
Response-contract validation failures are passed to ``problem_handler`` as the
|
|
``exc`` argument when problem details are enabled.
|
|
|
|
String and bytes return annotations default to ``text/plain`` and
|
|
``application/octet-stream``. Other inferred contracts default to
|
|
``application/json``. Error responses (``4xx``/``5xx``) are not validated
|
|
against the success contract. Declare a status-specific contract when the
|
|
handler owns that response shape::
|
|
|
|
@api.get("/items/{item_id}", responses={404: NotFound})
|
|
def get_item(req, resp, *, item_id: str) -> ItemOut:
|
|
if item := find_item(item_id):
|
|
return item
|
|
return NotFound(detail="Item not found"), 404
|
|
|
|
For additional OpenAPI metadata, use ``responses={404: {"model": NotFound,
|
|
"description": "Item not found"}}``. Status-specific contracts validate the
|
|
final body just like the success contract. Informational, ``204``, ``205``,
|
|
and ``304`` responses cannot declare a body contract and never send content,
|
|
even if a handler assigned or returned a body; ``HEAD`` validates the
|
|
corresponding ``GET`` contract but sends headers only.
|
|
|
|
|
|
Typed Event Streams
|
|
-------------------
|
|
|
|
``@api.sse`` and ``@api.ndjson`` extend response contracts to incremental
|
|
responses. Annotate a sync or async iterator and Responder infers its item
|
|
type through the same Pydantic ``TypeAdapter`` path used for buffered return
|
|
annotations::
|
|
|
|
from collections.abc import AsyncIterator
|
|
|
|
@api.ndjson("/items")
|
|
async def items(req, resp) -> AsyncIterator[ItemOut]:
|
|
async for item in store.iter_items():
|
|
yield item
|
|
|
|
Use ``event_model=`` or ``item_model=`` when the return annotation cannot
|
|
express the contract. SSE routes may annotate ``AsyncIterator[SSE[ItemOut]]``;
|
|
the envelope's ``data`` is validated as ``ItemOut`` while ``event``, ``id``,
|
|
``retry``, and comment metadata remain protocol fields.
|
|
|
|
OpenAPI describes the wire media type and includes the item schema in the
|
|
``x-responder-item-schema`` media extension. Responder's generated Python,
|
|
JavaScript, TypeScript, Ruby, and PHP clients recognize that extension and
|
|
produce lazy streaming iterators rather than buffering the response.
|
|
|
|
An immediately available first item is validated before headers. Later
|
|
validation or producer failures are logged and terminate the stream without
|
|
emitting the broken item. Debug mode re-raises the failure. Idle SSE routes
|
|
with ``heartbeat=`` begin the response without waiting indefinitely for their
|
|
first event, allowing keepalives to flow; their first eventual item is still
|
|
validated before it is written.
|
|
|
|
|
|
OpenAPI Defaults
|
|
----------------
|
|
|
|
When OpenAPI is enabled, Responder generates operation IDs, summaries, tags,
|
|
request bodies, response schemas, validation responses, auth security
|
|
requirements, and common framework error responses from the route contract.
|
|
Route decorators can add or override operation metadata with ``responses=``,
|
|
``examples=``, ``response_examples=``, and ``openapi_extra=``; nested response
|
|
metadata is deep-merged with the generated contract.
|
|
|
|
``status_code=`` declares the route's default success status: the generated
|
|
operation keys its success response under it instead of ``200`` (informational,
|
|
``204``, ``205``, and ``304`` statuses document no response body), and
|
|
``resp.status_code`` is pre-seeded with it before the handler runs. An explicit
|
|
assignment in the handler — or a before-request hook short-circuiting the route
|
|
— still wins.
|
|
|
|
The generated document is cached: registering a route, schema, or security
|
|
scheme invalidates the cache, and the next request to the schema route (or
|
|
docs page) rebuilds it.
|
|
|
|
With problem details enabled, generated operations document
|
|
``application/problem+json`` responses and the reusable ``ProblemDetails``
|
|
component schema. With ``problem_details=False``, generated operations document
|
|
the legacy JSON error shape and do not register ``ProblemDetails``.
|
|
|
|
Generated OpenAPI documents are validated in the test suite for both OpenAPI
|
|
3.0.x and 3.1.x.
|