Skip to main content

Managing Application Lifecycle

Managing the application lifecycle in this codebase is centered around the lifespan context manager. This mechanism allows you to define logic that runs before the application starts receiving requests and logic that runs after the application has finished handling requests.

This approach replaces the deprecated on_event handlers (startup and shutdown) and provides a more robust way to manage resources like database connection pools, machine learning models, or shared HTTP clients.

The Lifespan Context Manager

The core of lifecycle management is a single async context manager passed to the FastAPI or APIRouter instance. This context manager uses the @asynccontextmanager decorator from the standard contextlib library.

Defining a Lifespan

A lifespan function typically follows this pattern:

  1. Startup: Code before the yield statement runs when the application starts.
  2. State Sharing: The yield statement can optionally return a dictionary. The keys in this dictionary are merged into the application state and become accessible in request handlers.
  3. Shutdown: Code after the yield statement runs when the application is shutting down.
from contextlib import asynccontextmanager
from fastapi import FastAPI

@asynccontextmanager
async def lifespan(app: FastAPI):
# Startup: Load resources
print("Application is starting up")
db_connection = {"status": "connected"}

yield {"db": db_connection}

# Shutdown: Clean up resources
print("Application is shutting down")
db_connection["status"] = "disconnected"

app = FastAPI(lifespan=lifespan)

Modular Lifespans with APIRouter

This codebase supports modular lifecycle management by allowing APIRouter instances to define their own lifespans. This is particularly useful for large applications where different modules have independent resource requirements.

When a router with a lifespan is included in a FastAPI application, the lifespans are merged using the _merge_lifespan_context function found in fastapi/routing.py.

State Merging Logic

When multiple lifespans are merged, their yielded states are combined into a single dictionary. In fastapi/routing.py, the merging logic ensures that the parent application's state takes precedence over the router's state in case of key collisions:

# From fastapi/routing.py
def _merge_lifespan_context(
original_context: Lifespan[Any], nested_context: Lifespan[Any]
) -> Lifespan[Any]:
@asynccontextmanager
async def merged_lifespan(
app: AppType,
) -> AsyncIterator[Mapping[str, Any] | None]:
async with original_context(app) as maybe_original_state:
async with nested_context(app) as maybe_nested_state:
if maybe_nested_state is None and maybe_original_state is None:
yield None
else:
# Original (parent) state overrides nested (router) state
yield {**(maybe_nested_state or {}), **(maybe_original_state or {})}
return merged_lifespan

Accessing Application State

The state yielded by the lifespan context manager is stored in the application's state object. Within path operations, you can access this state through the request.state attribute.

@app.get("/status")
async def get_status(request: Request):
# Access the 'db' key yielded by the lifespan
db_info = request.state.db
return {"database_status": db_info["status"]}

Legacy Event Handlers

While the lifespan parameter is the preferred method, the codebase maintains backward compatibility for legacy event handlers:

  • on_startup: A list of callables to run on startup.
  • on_shutdown: A list of callables to run on shutdown.
  • @app.on_event("startup") and @app.on_event("shutdown"): Decorators for event handlers.

These legacy handlers are internally managed by the _DefaultLifespan class in fastapi/routing.py. This class wraps the on_startup and on_shutdown logic into a standard lifespan context manager, ensuring they still execute even if a modern lifespan handler is not explicitly provided.

[!WARNING] The on_event method and the on_startup/on_shutdown parameters are marked as @deprecated in fastapi/applications.py. New code should exclusively use the lifespan context manager.

Implementation Details

The FastAPI class (in fastapi/applications.py) initializes its internal APIRouter with the provided lifespan. If no lifespan is provided, it defaults to None, and the router handles the execution of any legacy on_startup or on_shutdown lists passed during initialization.

# From fastapi/applications.py
self.router: routing.APIRouter = routing.APIRouter(
# ...
on_startup=on_startup,
on_shutdown=on_shutdown,
lifespan=lifespan,
# ...
)

This structure ensures that the application lifecycle is consistently managed regardless of whether the logic is defined at the application level or within individual routers.