Application Lifespan and State
When you need to perform actions before your application starts accepting requests—such as connecting to a database or loading a machine learning model—and clean up those resources when the application stops, you use the lifespan parameter. This ensures that expensive resources are only loaded once and are properly disposed of, preventing memory leaks or dangling connections.
Defining a Lifespan Context Manager
The preferred way to manage the application lifecycle in FastAPI is by using an asynccontextmanager. This single function replaces the deprecated startup and shutdown events by wrapping the entire life of the application in a context.
Pass your lifespan function to the FastAPI constructor using the lifespan parameter:
from contextlib import asynccontextmanager
from fastapi import FastAPI
def load_model():
return lambda x: x * 42
# 1. Define the lifespan
@asynccontextmanager
async def lifespan(app: FastAPI):
# Logic here runs BEFORE the app starts taking requests
model = load_model()
print("Model loaded")
yield {"model": model}
# Logic here runs AFTER the app finishes handling requests
print("Cleaning up resources")
# 2. Pass it to the FastAPI app
app = FastAPI(lifespan=lifespan)
Internally, the FastAPI class (defined in fastapi/applications.py) stores this handler in its internal router. When the ASGI server (like Uvicorn) signals the application to start, FastAPI executes the code up to the yield. When the server signals a shutdown, it resumes execution after the yield.
Sharing State with Path Operations
The dictionary you yield in your lifespan function is used to populate the application State. This state is persistent across the entire life of the application and is accessible in every request via request.state.
In the example below, the model yielded by the lifespan is used inside a path operation:
from fastapi import FastAPI, Request
@app.get("/predict")
async def predict(request: Request, x: float):
# Access the state yielded by the lifespan
model = request.state.model
result = model(x)
return {"result": result}
FastAPI uses the State class (inherited from Starlette) to store these values. While you can also use dependencies for resource management, the lifespan state is ideal for global resources that must exist before any request is processed.
Router-Level Lifespans
FastAPI allows you to define lifespans on an APIRouter as well. This is useful for modular applications where specific components (like a /users module) have their own setup requirements.
When you include a router with a lifespan into a FastAPI app, the lifespans are merged.
from fastapi import APIRouter, FastAPI
@asynccontextmanager
async def router_lifespan(app: FastAPI):
yield {"router_data": "some_value"}
router = APIRouter(lifespan=router_lifespan)
app = FastAPI()
app.include_router(router)
As demonstrated in tests/test_router_events.py, if multiple lifespans yield state dictionaries, FastAPI merges them into the same request.state object. If there are conflicting keys, the state from the parent FastAPI application takes precedence over the state from included routers.
Testing Lifespan Events
To trigger lifespan events during testing, use the TestClient as a context manager. If you call TestClient without the with block, the startup and shutdown logic will not execute.
This pattern is used extensively in FastAPI's own test suite (e.g., docs_src/app_testing/tutorial004_py310.py):
from fastapi.testclient import TestClient
def test_app_lifecycle():
# The 'with' block triggers the lifespan startup
with TestClient(app) as client:
response = client.get("/predict?x=2")
assert response.status_code == 200
assert response.json() == {"result": 84}
# Exiting the 'with' block triggers the lifespan shutdown
Deprecated Event Handlers
FastAPI previously used @app.on_event("startup") and @app.on_event("shutdown") decorators. These are now deprecated in favor of the lifespan parameter. The FastAPI class in fastapi/applications.py still supports on_startup and on_shutdown parameters for backward compatibility, but they are internally superseded by the lifespan mechanism.
If you use the deprecated handlers, they will still run, but they do not support the unified state sharing provided by the yield statement in a lifespan context manager.