Dependencies with yield & Classes — Setup and Teardown
IntermediateA yield dependency runs setup, hands over the resource, then runs cleanup after the response — the DB-session pattern. Class dependencies carry configuration; both compose with everything else.
Overview
Resources need closing. A dependency written with yield instead of return splits into three acts: code before yield is setup, the yielded value is what gets injected, and code after yield (in a finally) runs after the response is sent — guaranteed cleanup even when the endpoint raises. This is exactly how database sessions work in every production FastAPI app, and the try/except around the yield is where commit-on-success/rollback-on-error lives. Class dependencies solve a different problem: a dependency that needs configuration. A class whose __call__ takes request parameters becomes a parameterised dependency factory — one RateLimiter class, many limits. Together, yield and class dependencies cover almost every real resource-management need without a plugin.
yield — the Resource Lifecycle Pattern
Setup → yield → cleanup, with the cleanup guaranteed by finally. Raising HTTPException after yield is allowed (before the response streams); the DB-session version of this pattern is the most copied snippet in FastAPI.
from fastapi import Depends, FastAPI
app = FastAPI()
class FakeSession: # stand-in for SQLAlchemy Session
def __init__(self): print("OPEN session")
def commit(self): print("COMMIT")
def rollback(self): print("ROLLBACK")
def close(self): print("CLOSE session")
def add(self, x): pass
def get_db():
db = FakeSession() # 1. setup — runs before endpoint
try:
yield db # 2. injected value; endpoint runs here
db.commit() # 3a. endpoint succeeded → commit
except Exception:
db.rollback() # 3b. endpoint raised → rollback
raise # re-raise so handlers still run
finally:
db.close() # 4. ALWAYS — even on errors
@app.post("/students")
def create_student(payload: dict, db: FakeSession = Depends(get_db)):
db.add(payload)
return {"ok": True}
# Success log: OPEN session → (endpoint) → COMMIT → CLOSE session
# Failure log: OPEN session → (endpoint raises) → ROLLBACK → CLOSE session
#
# The same shape manages anything with a lifecycle:
# temp files, redis pipelines, distributed locks, tracing spans.Class Dependencies — Configurable and Stateful
An instance with __call__ is a callable, so Depends accepts it — and the constructor carries configuration. One class, many configured dependencies: the pattern behind rate limiters, feature flags, and permission checks.
import time
from fastapi import Depends, FastAPI, HTTPException, Request
app = FastAPI()
class RateLimiter:
"""N requests per window per client IP (in-memory demo; Redis in prod)."""
def __init__(self, limit: int, window_s: int = 60):
self.limit, self.window = limit, window_s
self.hits: dict[str, list[float]] = {}
def __call__(self, request: Request): # ← called per request
ip = request.client.host
now = time.time()
bucket = [t for t in self.hits.get(ip, []) if now - t < self.window]
if len(bucket) >= self.limit:
raise HTTPException(429, f"limit {self.limit}/{self.window}s exceeded")
bucket.append(now)
self.hits[ip] = bucket
# One class → differently configured dependencies:
public_limit = RateLimiter(limit=100)
otp_limit = RateLimiter(limit=3, window_s=300) # OTPs: 3 per 5 min
@app.get("/search", dependencies=[Depends(public_limit)])
def search(q: str = ""):
return {"q": q}
@app.post("/otp/send", dependencies=[Depends(otp_limit)])
def send_otp(phone: str):
return {"sent_to": phone}
# dependencies=[...] (no parameter) = "run for effect, I don't need the value"
# 4th OTP within 5 min → 429 {"detail": "limit 3/300s exceeded"}Key Points to Remember
- 1yield dependencies: setup before, teardown after the response, finally-guaranteed
- 2The get_db commit/rollback/close pattern is the canonical use
- 3Class with __call__ = configurable dependency (limits, flags, permissions)
- 4dependencies=[Depends(x)] runs a dependency for its effect without injecting a value
Interview Questions
Sign in to ask AriaWalk through the get_db yield dependency — what runs when the endpoint raises?
Build a reusable rate-limit dependency where each endpoint sets its own limit.
Where does a yield dependency's cleanup run relative to sending the response?
Ask Aria about Dependencies with yield & Classes — Setup and Teardown
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.