Deployment¶
An aiodrf project is deployed like any Django project. It needs no patches, no replacement application registry and no special middleware.
ASGI¶
Serve the project with an ASGI server; choosing an ASGI server compares Uvicorn, Gunicorn with Uvicorn workers, Daphne and Granian. Use an ASGI server for streaming responses (NDJSON, JSON arrays, Server-Sent Events): under WSGI, an async iterator can be consumed entirely before the response is sent.
Django's own get_asgi_application() is enough for most projects. Use
aiodrf.asgi.get_asgi_application() in asgi.py when you need a
lifespan context manager for resources, or startup and shutdown
signals. It wraps Django's application and does not modify Django's handler.
With AIODRF["REQUEST_THREADS"] set, it also keeps the threads that run
requests' synchronous code for later requests, instead of starting two threads
per request (setting).
Run Django's deployment checks with the production settings:
python manage.py check --deploy
python manage.py check --deploy --tag compatibility
The system checks reference explains every check aiodrf adds and how to resolve it.
Under ASGI, keep CONN_MAX_AGE = 0 and use your database backend's connection
pool if you need one. Deployment behaviour describes
how aiodrf behaves with several Uvicorn workers behind Nginx and PostgreSQL:
worker reloads and crashes, connection budgets and streaming through the proxy.
Resources and cancellation¶
Create clients that belong to an event loop during the lifespan, not at module
import. Give the keys you store in the ASGI state a unique prefix, and handle
servers that do not provide scope["state"].
Prefer the lifespan context manager for resources: it runs around
both startup and shutdown signals and releases what it opened if startup fails.
Signal receivers, by contrast, only notify: each owns its resources, and one
receiver's failure does not undo another receiver's work. Exceptions from
receivers are collected before startup is reported as failed, and cancellation
is never swallowed. Open dependent resources inside the context manager with
async with or AsyncExitStack, and do not open and close the same client in
both the context manager and a receiver.
A streaming response closes its producer, and the iterator of the middleware
around it, when it completes or the client disconnects. Middleware that wraps a
response iterator must close it as well. Do not catch CancelledError to keep
producing. Once part of a JSON array has been sent, the response can no longer
be replaced by a DRF error response.
Cancelling a request does not stop a database operation or task already running
in a worker thread. An on_commit callback avoids publishing a task when the
transaction rolls back, but it does not make the database write and the
message delivery atomic.
Checking serializers before tuning¶
aiodrf_inspect_serializers reports which serializers the msgspec, Pydantic or
python backend can compile, and why the others cannot:
python manage.py aiodrf_inspect_serializers --backend msgspec --format json
python manage.py aiodrf_inspect_serializers --backend pydantic --format json
python manage.py aiodrf_inspect_serializers --serializer project.api.BookSerializer --backend msgspec
python manage.py aiodrf_inspect_serializers --serializer project.api.BookSerializer --serializer project.api.AuthorSerializer --format json
Without --serializer, the command finds the serializers your views declare,
including @action(serializer_class=...) and as_view(serializer_class=...).
Each JSON record lists the paths, HTTP methods and viewset actions that use the
serializer under usages. The command does not call get_serializer_class(),
so serializers chosen at request time are not found. --serializer can be
repeated; it inspects the named classes only, and their usages list is empty.
Each serializer is instantiated without a request, so its constructor runs:
inspect only code you trust. A path that cannot be imported or does not name a
DRF serializer raises CommandError. To inspect an instance built for a
specific request, call aiodrf.contrib.compiler.report_details(serializer) or
aiodrf.contrib.inputs.report_input_details(serializer). A serializer whose
input can be compiled may still pass a particular payload to DRF for
validation.
To see where a request runs synchronous code in worker threads, wrap it in
aiodrf.test.count_hops(): it records the aiodrf function behind each switch,
without logging request data. It does not count the thread switches Django
makes itself (synchronous middleware, signals). Enable it in tests and
diagnostics only, not while measuring throughput.