Skip to main content

Flexible ASGI application lifecycle management for Python web frameworks.

Project description

asgi-lifecycle

Flexible ASGI application lifecycle management for Python web frameworks (Django, FastAPI, Starlette, Celery, and more).

Features

  • Hook-based startup/shutdown: Register async or sync functions to run on app startup/shutdown.
  • Context-aware: Hooks receive a rich context object (service type, environment, config, metadata).
  • Service filtering: Only run hooks for relevant service types (main app, worker, API, scheduler, etc).
  • Singleton manager: Application-wide lifecycle state and hook registry.
  • Decorator API: Clean, Pythonic @lifespan.on_start and @lifespan.on_shutdown decorators.
  • Async and sync support: Works with both async and sync hooks.
  • Timeouts and error handling: Robust shutdown with timeouts and logging.
  • Framework-agnostic: Integrates with any ASGI app, including Django, FastAPI, Starlette, Celery, etc.

Why asgi-lifespan?

vs Django's built-in ASGI

  • Startup/shutdown hooks vs no lifecycle management
  • Service type filtering vs no service differentiation
  • Priority ordering vs no execution control
  • Rich context object vs no context passing
  • Error handling & timeouts vs no error management
  • Multiple hooks per phase vs no hook system at all

vs FastAPI's lifespan

  • ✅ Framework-agnostic
  • ✅ Multiple hooks per phase
  • ✅ Service type filtering
  • ✅ Singleton management

Performance

  • Minimal overhead: Hooks only run during startup/shutdown
  • Async-first: Built for modern async Python
  • Timeout protection: Prevents hanging shutdowns
  • Error isolation: Failed hooks don't stop others

Installation

pip install asgi-lifecycle

Quickstart

from asgi_lifecycle import lifespan, LifespanContext, ServiceType

@lifespan.on_start(priority=1, service_types=["app", "api"])
async def setup_database(context: LifespanContext):
    await database.connect()
    context.set_metadata("database_connected", True)

@lifespan.on_shutdown(priority=1, service_types=["app", "api"])
async def close_database(context: LifespanContext):
    if context.get_metadata("database_connected", False):
        await database.disconnect()

API Reference

Lifespan

  • on_start(priority=0, name=None, service_types=None): Decorator to register a startup hook.
  • on_shutdown(priority=0, name=None, service_types=None): Decorator to register a shutdown hook.
  • startup(context: LifespanContext): Run all startup hooks for the given context.
  • shutdown(context: LifespanContext): Run all shutdown hooks for the given context.
  • is_initialized(): Check if the manager is initialized.
  • reset_instance(): Reset the singleton (for testing).

LifespanContext

  • service_type: The type of service ("app", "worker", "api", "scheduler", "test").
  • environment: Environment name ("development", "production", etc).
  • config: Arbitrary config dict.
  • metadata: Arbitrary metadata dict.
  • get_config(key, default=None): Get config value.
  • set_metadata(key, value): Set metadata value.
  • get_metadata(key, default=None): Get metadata value.

Example Integrations

Django ASGI

1. Register Lifecycle Hooks

Add startup and shutdown hooks to your Django settings module. These will run when the ASGI app starts and stops.

# Basic startup hook
@lifespan.on_start()
async def setup_logging(context: LifespanContext):
    """Configure logging when the ASGI app starts."""
    logging.basicConfig(level=logging.INFO)
    logger.info(f"Starting {context.service_type} in {context.environment}")

# Database setup (only for main app)
@lifespan.on_start(priority=1, service_types=["app"])
async def setup_database(context: LifespanContext):
    """Setup database connections."""
    if context.environment == "production":
        await database.connect_with_ssl()
    else:
        await database.connect()
    
    context.set_metadata("database_connected", True)
    logger.info("Database connected")

# Shutdown hook
@lifespan.on_shutdown(priority=1, service_types=["app"])
async def close_database(context: LifespanContext):
    """Clean up database connections."""
    if context.get_metadata("database_connected", False):
        await database.disconnect()
        logger.info("Database disconnected")

2. Integrate LifespanMiddleware

Wrap your Django ASGI application with LifespanMiddleware to enable lifecycle hooks.

# my_django_project/my_django_app/asgi.py

import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myproject.settings")

import django
django.setup()  # Initialize Django before any other imports

from django.core.asgi import get_asgi_application
from asgi_lifecycle import LifespanMiddleware, LifespanContext

# Create the base ASGI application
django_app = get_asgi_application()

# Wrap with lifespan middleware
application = LifespanMiddleware(django_app)

4. Import Your Lifecycle Modules

Make sure to import any modules containing lifecycle hooks:

# In your settings.py
import myapp.lifecycle  # This registers the hooks
import myapp.database.lifecycle  # This too

FastAPI

from contextlib import asynccontextmanager
from fastapi import FastAPI
from asgi_lifecycle import lifespan, LifespanContext

@asynccontextmanager
async def lifespan_context(app: FastAPI):
    # Startup
    context = LifespanContext(service_type="api")
    await lifespan.startup(context)
    
    yield
    
    # Shutdown
    await lifespan.shutdown(context)

app = FastAPI(lifespan=lifespan_context)

Celery Worker

from celery.signals import worker_init, worker_shutdown
from asgiref.sync import async_to_sync
from asgi_lifecycle import lifespan, LifespanContext

@worker_init.connect
def init_worker_process(sender=None, conf=None, **kwargs):
    context = LifespanContext(service_type="worker", environment="production", config={"celery_conf": conf})
    async_to_sync(lifespan.startup)(context)

@worker_shutdown.connect
def shutdown_worker_process(sender=None, **kwargs):
    context = LifespanContext(service_type="worker", environment="production")
    async_to_sync(lifespan.shutdown)(context)

Service Types

# Only runs in main app
@lifespan.on_start(service_types=["app"])
async def setup_web_server():
    pass

# Only runs in worker
@lifespan.on_start(service_types=["worker"])
async def setup_worker_pool():
    pass

# Runs in both
@lifespan.on_start(service_types=["app", "worker"])
async def setup_shared_resources():
    pass

Error Handling

@lifespan.on_start(priority=1)
async def setup_database():
    try:
        await database.connect()
        logger.info("Database connected")
    except Exception as e:
        logger.error(f"Database connection failed: {e}")
        # Don't re-raise - let other hooks continue

License

MIT

Author

Tarek Sanger

Contributing

Pull requests and issues welcome! See CONTRIBUTING.md.

Roadmap

  • Auto-discovery of lifecycle modules
  • Built-in health checks
  • Metrics integration
  • Configuration file support

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

asgi_lifecycle-0.0.2.tar.gz (9.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

asgi_lifecycle-0.0.2-py3-none-any.whl (7.0 kB view details)

Uploaded Python 3

File details

Details for the file asgi_lifecycle-0.0.2.tar.gz.

File metadata

  • Download URL: asgi_lifecycle-0.0.2.tar.gz
  • Upload date:
  • Size: 9.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.8.20

File hashes

Hashes for asgi_lifecycle-0.0.2.tar.gz
Algorithm Hash digest
SHA256 f00420095e86b2bb4683c919fa4194bca1ac9d6a16e0a4dc1504a8b83faef70f
MD5 b4d7b0b603e513500c8b6faa17431721
BLAKE2b-256 b8a9034fb9ffce8130cb72961afeda2af717fae85b94c27871dd2d3135e4a9e1

See more details on using hashes here.

File details

Details for the file asgi_lifecycle-0.0.2-py3-none-any.whl.

File metadata

File hashes

Hashes for asgi_lifecycle-0.0.2-py3-none-any.whl
Algorithm Hash digest
SHA256 145a3bd80e15fd691ebf315bc497a01b0836d3488b2f36ddaae12f58d06efc7a
MD5 92350f73977c6edc7d13e464b886fcc7
BLAKE2b-256 ca40701b302775a5dba85fe160974da63116c17684bce9e32c4038ba962524e7

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page