Cheat SheetsFastAPIDependency Injection

Dependency Injection — Cheat Sheet

FastAPI · 3 topics. Download the PDF or the Instagram carousel and share it.

Cheat Sheet · AiCanCode.org
Dependency Injection
FastAPI3 topicsQuick revision reference
1

Depends — Dependency Injection Basics

Depends(fn) tells FastAPI: run fn first (resolving ITS parameters the same way) and hand me the result. Shared logic — pagination, auth, DB sessions — becomes declared-once, injected-everywhere.

  • Depends(fn): fn runs first, its params bind from the request, you get the result
  • Annotated[T, Depends(fn)] aliases make dependencies reusable one-liners
  • Dependencies nest into trees; FastAPI resolves the whole graph
  • Per-request caching: the same dependency runs once per request by default
Declare pagination once; every list endpoint inherits it
from typing import Annotated
from fastapi import Depends, FastAPI, Query

app = FastAPI()

# A dependency is just a function — its params bind like any endpoint's
def pagination(
    page: int = Query(default=1, ge=1),
    size: int = Query(default=20, ge=1, le=100),
) -> dict:
    return {"offset": (page - 1) * size, "limit": size}

# Annotated alias — define once, reuse as a type (modern style)
Pagination = Annotated[dict, Depends(pagination)]

@app.get("/students")
def list_students(p: Pagination):
    return {"slice": f"students[{p['offset']}:{p['offset'] + p['limit']}]"}

@app.get("/drives")
def list_drives(p: Pagination):            # same rules, zero duplication
    return {"slice": f"drives[{p['offset']}:{p['offset'] + p['limit']}]"}

# GET /students?page=3&size=50 → {"slice": "students[100:150]"}
# GET /students?size=500       → 422 (le=100) — enforced EVERYWHERE at once

# /docs shows page & size on both endpoints — dependencies are documented too
2

Dependencies with yield & Classes — Setup and Teardown

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.

  • yield dependencies: setup before, teardown after the response, finally-guaranteed
  • The get_db commit/rollback/close pattern is the canonical use
  • Class with __call__ = configurable dependency (limits, flags, permissions)
  • dependencies=[Depends(x)] runs a dependency for its effect without injecting a value
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.
3

DI Patterns — Auth Chains, Router Guards & Test Overrides

Real apps stack dependencies into auth chains (token → user → role check), guard whole routers with one line, and swap any node via dependency_overrides in tests — the architecture is the dependency graph.

  • Auth = dependency chain: token → user → role; endpoints declare their guard
  • require_role(*roles) is a dependency factory — closures carrying config
  • 401 = not authenticated, 403 = authenticated but not permitted
  • Router/app-level dependencies guard everything; dependency_overrides swaps nodes in tests
token → user → role: each endpoint declares its guard level
from typing import Annotated
from fastapi import Depends, FastAPI, HTTPException, Header

app = FastAPI()

FAKE_USERS = {"token-asha": {"name": "Asha", "role": "student"},
              "token-tpo":  {"name": "Rane", "role": "tpo"}}

def get_token(authorization: str | None = Header(default=None)) -> str:
    if not authorization or not authorization.startswith("Bearer "):
        raise HTTPException(401, "not authenticated")          # 401: who are you?
    return authorization.removeprefix("Bearer ")

def get_current_user(token: Annotated[str, Depends(get_token)]) -> dict:
    user = FAKE_USERS.get(token)                # JWT decode + DB load in real life
    if not user:
        raise HTTPException(401, "invalid or expired token")
    return user

CurrentUser = Annotated[dict, Depends(get_current_user)]

def require_role(*roles: str):                  # ← dependency FACTORY
    def checker(user: CurrentUser) -> dict:
        if user["role"] not in roles:
            raise HTTPException(403, "insufficient permissions")   # 403: not you
        return user
    return checker

@app.get("/profile")
def profile(user: CurrentUser):                          # any logged-in user
    return user

@app.post("/drives")
def create_drive(user: Annotated[dict, Depends(require_role("tpo"))]):
    return {"created_by": user["name"]}

# token-asha on POST /drives → 403 · no token → 401
# The signature IS the access-control documentation.
Learn this free with Aria, your AI tutor → AiCanCode.org/learn/fastapi