A regular dependency returns a value. A yield dependency yields a value, runs the handler, then resumes after the yield to clean up. Think of it like a context manager (with block) wrapped around the request.
In simple language — anywhere we’d write with session() as s: in a script, we use a yield dependency in FastAPI. It guarantees teardown — close the DB session, release the lock, log the timing — no matter what happens in the handler.
The shape
from fastapi import Depends
def get_db():
db = SessionLocal()
try:
yield db # <-- value injected into handler
finally:
db.close() # <-- runs AFTER the response is built
The yield splits the function in two:
- Code before
yieldruns on request start (setup). - The value after
yieldis injected. - Code after
yieldruns on request end (teardown), even if the handler raised.
Lifecycle in pictures
yieldopen db session, start timer, acquire lock
the yielded value is injected as the param
may raise an exception
yieldclose session, release lock — ALWAYS runs (use
finally)
The canonical use case — SQLAlchemy session
This is in every FastAPI codebase that talks to a DB:
from sqlalchemy.orm import Session, sessionmaker
SessionLocal = sessionmaker(bind=engine, autocommit=False, autoflush=False)
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.get("/users/{user_id}")
def get_user(user_id: int, db: Session = Depends(get_db)):
user = db.query(User).filter(User.id == user_id).first()
if not user:
raise HTTPException(404)
return user
Every request gets its own session. The finally block closes it — whether the handler returned cleanly, raised HTTPException, or blew up with an unexpected error.
Important: always put cleanup in finally, not after the bare yield. If we skip finally, an exception in the handler skips the cleanup.
Exception handling INSIDE the yield
We can catch exceptions raised in the handler and react before re-raising (or instead of re-raising). The pattern — wrap the yield itself.
def db_session_with_rollback():
db = SessionLocal()
try:
yield db
db.commit() # success path
except Exception:
db.rollback() # something went wrong in the handler
raise # let FastAPI's error handlers see it
finally:
db.close()
In simple language — yield propagates handler exceptions back into our dependency. We can catch them, do something (rollback, log), and either swallow or re-raise. If we swallow it, FastAPI thinks the handler succeeded — usually not what we want, so almost always raise.
Stacking yield dependencies
Multiple yield deps work like nested context managers. Setup happens outermost-in, teardown happens innermost-out.
def get_logger(request: Request):
log = make_logger(request_id=request.headers.get("x-request-id"))
log.info("request start")
try:
yield log
finally:
log.info("request end")
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.post("/orders")
def create_order(
order: OrderIn,
db: Session = Depends(get_db),
log = Depends(get_logger),
):
log.info("creating order")
return repository.save(db, order)
get_logger setupget_db setuphandler runs
get_db closeget_logger closeYield + raise after teardown? Not allowed
We can’t raise HTTPException from the teardown side (after yield). By that point the response is already constructed and being sent. If we need to abort, do it in the handler or in the setup (before yield).
Async yield works the same
For async code, use async def:
async def get_db():
async with AsyncSessionLocal() as db:
yield db
Or manual:
async def get_redis():
r = await aioredis.create_redis_pool(...)
try:
yield r
finally:
r.close()
await r.wait_closed()
Combining with Depends caching
Same per-request caching applies. If three places in the request use Depends(get_db), the session is opened once and torn down once at the end. We don’t get three sessions just because three places asked for one.
The takeaway — yield dependencies are FastAPI’s answer to “I need setup AND teardown around the request”. Always put cleanup in finally. Catch exceptions in the dep if we need to react (rollback). Stack them freely — teardown order is LIFO, exactly like nested context managers.