Lifespan Context Manager (@asynccontextmanager) Startup and Shutdown Events
- Last updated
- Prerequisites:
- Python `@contextlib.asynccontextmanager`
- Database connection pool initialization
- fastapi
- lifespan
- startup
- shutdown
- asynccontextmanager
- starlette
Read at your depth
The practical view
The `lifespan` context manager handles application startup and shutdown in FastAPI, replacing deprecated `@app.on_event('startup')` and `@app.on_event('shutdown')`. Define an async context manager: `@asynccontextmanager async def lifespan(app: FastAPI): # startup logic; yield; # shutdown logic`. Pass it to your app: `app = FastAPI(lifespan=lifespan)`.
The same idea in other frameworks
`@asynccontextmanager async def lifespan(app: FastAPI): ... yield ...`.`OnApplicationBootstrap` and `OnApplicationShutdown` lifecycle hooks.Spring `@PostConstruct` and `@PreDestroy` / `Lifecycle` beans.Express server boot with `process.on('SIGTERM')` listeners.Legacy vs modern
Deprecated @app.on_event vs Modern Lifespan Context Manager
Deprecated `on_event` split startup and shutdown across separate decorators; `lifespan` manages lifecycle in a single context manager.
@app.on_event('startup') ... @app.on_event('shutdown') ... // ❌ Deprecated in FastAPI@asynccontextmanager async def lifespan(app: FastAPI): yield // ✅ NEW: Unified state & teardownInterview gotchas
Context
Interviewers test your knowledge of ASGI protocol evolution and state management.
The mechanical answer
`@app.on_event` split initialization and teardown into disconnected functions, forcing shared resources (like database pools or ML models) to be stored in error-prone global variables. `lifespan` uses a standard Python async context manager (`@asynccontextmanager`), keeping setup and teardown within the same lexical scope and guaranteeing that exceptions in startup cleanly trigger teardown logic in `finally` blocks.
Trap
Using `@app.on_event` in modern FastAPI will emit deprecation warnings.
Context
Checks knowledge of state attachment on `app.state`.
The mechanical answer
Attach initialized resources to **`app.state`**: `app.state.redis = redis_client`. Inside any route handler, access it via the `Request` object: `request.app.state.redis`, or inject it using a dependency `def get_redis(request: Request): return request.app.state.redis`.
Trap
Avoid using module-level global variables for state initialized in lifespan.