Home/Learn/FastAPI/Dependencies with yield & Classes — Setup and Teardown

Dependencies with yield & Classes — Setup and Teardown

Intermediate
Dependency Injection

A 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.

Before yield = setup; after = teardown; finally = guaranteed
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.

__init__ carries config, __call__ runs per request
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 Aria
1

Walk through the get_db yield dependency — what runs when the endpoint raises?

MediumRazorpay
2

Build a reusable rate-limit dependency where each endpoint sets its own limit.

HardJio
3

Where does a yield dependency's cleanup run relative to sending the response?

HardMicrosoft

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.

Loading discussion…