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:
- Startup: Code before the
yieldstatement runs when the application starts. - State Sharing: The
yieldstatement can optionally return a dictionary. The keys in this dictionary are merged into the application state and become accessible in request handlers. - Shutdown: Code after the
yieldstatement 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_eventmethod and theon_startup/on_shutdownparameters are marked as@deprecatedinfastapi/applications.py. New code should exclusively use thelifespancontext 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.