Bookshop: an example project on aiodrf¶
A Django project that combines the integrations below. Its tests call the full ASGI application, with the lifespan enabled, through an in-process HTTP transport.
Run with Docker¶
From the repository root:
docker compose -f examples/compose.yaml up --build --wait bookshop
docker compose -f examples/compose.yaml exec bookshop python manage.py check
The API is available on http://127.0.0.1:8118. See
container setup for port overrides, required services,
optional profiles, tests and data retention. The commands below run the same
application directly with uv.
Features¶
| Feature | Implementation | Guide |
|---|---|---|
A ModelViewSet with token authentication, django-filter, pagination and Meta.auto_prefetch |
catalog/views.py, catalog/serializers.py |
migration from DRF |
One request to the stock service per page, not per book (PrefetchListSerializer.aprefetch) |
catalog/serializers.py |
prefetch |
| The stock service's HTTP client, opened and closed with the server | bookshop/lifecycle.py, AIODRF["LIFESPAN"] |
lifespan |
304 Not Modified and 412 Precondition Failed from aget_etag |
BookViewSet.aget_etag |
extension hooks |
A validated query string (query_serializer_class), documented as OpenAPI parameters |
BookSearch |
implementation section 11 |
Newline-delimited JSON export from QuerySet.aiterator() |
BookViewSet.export |
streaming |
| Throttles that count atomically in the cache | REST_FRAMEWORK["DEFAULT_THROTTLE_CLASSES"] |
implementation section 11 |
Run locally¶
cd examples/bookshop
uv venv
uv pip install --python .venv/bin/python -r pyproject.toml --group test -e ../..
uv run --no-sync python manage.py migrate
uv run --no-sync uvicorn bookshop.asgi:application --host 127.0.0.1 --port 8118
curl http://127.0.0.1:8118/books/
uv run --no-sync pytest -q
The default inventory service is explicitly simulated with httpx.MockTransport;
stock values are SKU lengths, not real inventory. Set STOCK_SERVICE_URL to
use a remote service. Change --host/--port as needed and add a non-local
hostname through EXAMPLE_ALLOWED_HOSTS. Do not deploy the development secret.
The tests replace the stock service with their own httpx.MockTransport. They pass
their own lifespan factory to get_asgi_application(); the application code
does not change.
The test database is a file, not SQLite's in-memory default. Each ASGI request has its own thread and database connection, and Django closes connections only to a file database.