Skip to main content

aiodrf-asgi-lifespan

ASGI lifespan contexts and typed resource access for Django. It runs an async context manager for each server lifespan and gives requests its resource. It requires Python 3.12+ and Django 5.2+, and depends on neither Django REST framework nor django-aiodrf.

pip install aiodrf-asgi-lifespan

Usage

Write the resources as an async context manager:

# project/lifecycle.py
from contextlib import asynccontextmanager
from dataclasses import dataclass


@dataclass
class Resources:
    label: str


@asynccontextmanager
async def resources():
    # Open async clients here, and close them after the yield, with their
    # own context managers or a contextlib.AsyncExitStack.
    yield Resources(label="worker")

Name it in the DJANGO_LIFESPAN setting:

# settings.py
DJANGO_LIFESPAN = "project.lifecycle.resources"

and serve the application this package builds:

# project/asgi.py
import os

from aiodrf_asgi_lifespan.asgi import get_asgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings")
application = get_asgi_application()

The ASGI server must support the lifespan protocol and scope["state"] (see Servers). Each server process, and each event loop, enters its own context. Requests receive a shallow copy of the server's state, so the resources themselves are shared within the lifespan.

A view reads the resource with its type:

from django.http import JsonResponse

from aiodrf_asgi_lifespan.asgi import get_lifespan_state
from project.lifecycle import Resources


async def status(request):
    resources = get_lifespan_state(request, Resources)
    return JsonResponse({"label": resources.label})

get_lifespan_state() checks the resource's type and that its context is still open: a request kept beyond shutdown cannot reach a closed resource. It accepts Django and DRF requests.

Other entry points

  • get_asgi_application(lifespan=factory) uses factory instead of the setting; lifespan=None runs no resource context.
  • LifespanApplication(application, lifespan=factory) wraps any other ASGI application without building Django's handler.
  • aiodrf_asgi_lifespan.signals.asgi_startup and asgi_shutdown are sent with Django's robust async dispatch: startup receivers run after the context is entered, shutdown receivers before it is closed. A failure in either is reported to the server. The signals work without a resource context too.

Order of signal receivers

Receivers of asgi_startup and asgi_shutdown must not depend on each other's order. Django runs async receivers concurrently. Django 5.2 and 6.0 also run the sync receivers, one after another, concurrently with them, while Django 6.1 runs the sync receivers first. Every failing receiver is logged by the django.dispatch logger; the first failure is raised and reported to the server.

Resources that depend on each other belong in the lifespan context, opened in order with contextlib.AsyncExitStack and closed in reverse order:

# project/lifecycle.py
from contextlib import AsyncExitStack, asynccontextmanager
from dataclasses import dataclass


@dataclass
class Resources:
    database: Database
    service: Service


@asynccontextmanager
async def resources():
    # connect_database() and Service stand for the project's async clients.
    async with AsyncExitStack() as stack:
        database = await stack.enter_async_context(connect_database())
        service = await stack.enter_async_context(Service(database))
        yield Resources(database=database, service=service)

Servers

The package's CI runs a Django project under these servers, each as a process, and checks startup and shutdown order, the shared resource, a failed startup and, for Uvicorn, one context per worker process:

  • Uvicorn 0.54.0 or later, with --lifespan on;
  • Granian 2.8.3 or later, with --interface asgi;
  • Hypercorn 0.18.0 or later.

Daphne 4.2.3 does not implement the lifespan protocol. Under Daphne the context is never entered, asgi_startup and asgi_shutdown are never sent, and get_lifespan_state() raises ImproperlyConfigured ("No active lifespan state. ...").

Testing

aiodrf_asgi_lifespan.testing.lifespan() runs startup and shutdown around a block and yields the lifespan state:

from aiodrf_asgi_lifespan.testing import lifespan


async def test_status():
    async with lifespan() as state:
        ...  # give `state` to an ASGI test client

With django-aiodrf, pass it to aiodrf.test.AsyncAPIClient(lifespan=state).

With django-aiodrf

Install django-aiodrf's lifespan extra. When AIODRF["REQUEST_THREADS"] is set, keep aiodrf.asgi.get_asgi_application(): it adds request thread reuse around this package's lifespan.

Contributing

See CONTRIBUTING.md.

Metadata

Release files for aiodrf-asgi-lifespan 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for aiodrf-asgi-lifespan 0.1.1
File Size Uploaded
aiodrf_asgi_lifespan-0.1.1.tar.gz 19.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for aiodrf-asgi-lifespan 0.1.1
File Interpreter ABI Platform
aiodrf_asgi_lifespan-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 30.1 kB

Release files / aiodrf_asgi_lifespan-0.1.1.tar.gz

Download URL aiodrf_asgi_lifespan-0.1.1.tar.gz
Size 19.9 kB
Tags Source
SHA-256 checksum
How to use checksums
aba864170fc14678b188bd69a85ad730f0cc14044822e15cf11d24ba1b4e3a5c
BLAKE2b-256 checksum
How to use checksums
ea17d8a55b277fa9819a314cdf41bf366f0a718fe54edbc84a414d5e4fcae33f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 3, 2026.

Transparency log

Release files / aiodrf_asgi_lifespan-0.1.1-py3-none-any.whl

Download URL aiodrf_asgi_lifespan-0.1.1-py3-none-any.whl
Size 10.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
16e856d0000067df6bd913d74201069c2244fd0ca0586f5366cedd0b40d5e2e6
BLAKE2b-256 checksum
How to use checksums
1b49ce1d9aeecea6ab5e53ad4ec71e30482b5a9801a9db21e3c570722e8fe779
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 3, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page