Chapter 11 · Advanced Dependency Injection
Chapter goals: Master classes as dependencies, multi-level sub-dependency trees,
dependencies=[...]side-effect dependencies, global dependencies, andyielddependencies (including exception-handling pitfalls) to reach production-grade DI proficiency.
11.1 Classes as Dependencies
Any callable — functions are, classes are too. Creating an instance Cat(name="Mr Fluffy") is本质ually "calling" the class, so FastAPI analyzes the class's __init__ parameters and resolves them the same way as path function parameters:
from typing import Annotated, Optional
from fastapi import FastAPI, Depends
app = FastAPI()
class CommonQueryParams:
"""__init__ parameters are the dependency-declared request params"""
def __init__(self, q: Optional[str] = None, skip: int = 0, limit: int = 100):
self.q = q
self.skip = skip
self.limit = limit
@app.get("/items/")
async def list_items(
commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)],
):
# Get an instance: attributes are typed, autocomplete-friendly, far friendlier than a bare dict
return {"q": commons.q, "skip": commons.skip, "limit": commons.limit}Note a detail: Annotated[CommonQueryParams, Depends(CommonQueryParams)] has the class name appearing twice. What actually matters is the one inside Depends(...); the type annotation is just for the editor.
When the two are identical, you can use the shortcut Depends() with no argument:
@app.get("/items-short/")
async def list_items_short(
commons: Annotated[CommonQueryParams, Depends()], # FastAPI infers from the type annotation
):
return {"skip": commons.skip}Compared to returning a dict, class dependencies offer: auto-complete on attributes, easier unit testing (construct the instance directly), and the ability to attach methods carrying behavior.
11.2 Sub-Dependency Trees: Arbitrary Nesting Depth
Dependencies can themselves declare dependencies, forming a dependency tree of任意 depth. FastAPI resolves bottom-up:
from typing import Annotated, Optional
from fastapi import Cookie, Depends, FastAPI
app = FastAPI()
def query_extractor(q: Optional[str] = None):
return q
def query_or_cookie_extractor(
q: Annotated[str, Depends(query_extractor)], # Dependency declares another dependency
last_query: Annotated[Optional[str], Cookie()] = None,
):
if not q:
return last_query # No query string — fall back to the query saved in cookie last time
return q
@app.get("/items/")
async def read_query(
result: Annotated[str, Depends(query_or_cookie_extractor)],
):
return {"q_or_last": result}You declare only one dependency on the path function, but FastAPI knows it must first resolve query_extractor before calling the outer one. Security systems are built on exactly this: current_user → active_user → admin_user → endpoint permission chains.
Caching and "runs only once"
The request-level caching from Chapter 10 also applies in dependency trees: when multiple branches reference the same leaf, that leaf is resolved only once per request.
11.3 dependencies=[...]: Side-Effect-Only Dependencies
Some scenarios only need the dependency executed (e.g., verifying an API key) — you do not care about its return value. In this case, do not add a parameter to the path function signature; pass the dependencies list on the decorator instead:
from fastapi import Depends, FastAPI, Header, HTTPException
app = FastAPI()
async def verify_key(x_key: str = Header(...)):
if x_key != "preset-secret":
raise HTTPException(status_code=400, detail="X-Key invalid")
async def verify_token(x_token: str = Header(...)):
if x_token != "preset-token":
raise HTTPException(status_code=400, detail="X-Token invalid")
@app.get("/items/", dependencies=[Depends(verify_key), Depends(verify_token)])
async def read_items():
return [{"item_id": "Foo"}]dependencies=[...] runs the listed dependencies before the path function executes — ideal for cross-cutting concerns (auth, rate limiting, logging) that apply to a whole group of routes without polluting each function's signature.
11.4 Global Dependencies
Apply a dependency to every route in the app:
app = FastAPI(dependencies=[Depends(verify_key)])All path functions — including those in included routers — inherit the global dependency.
11.5 Yield Dependencies: Setup and Teardown
Dependencies can use yield to run setup code before the request and teardown code after:
from fastapi import Depends, FastAPI
app = FastAPI()
def get_db():
db = Session()
try:
yield db # Request phase: provide the resource
finally:
db.close() # Teardown phase: always runs, even if the request raises an exception
@app.get("/items/")
async def read_items(db=Depends(get_db)):
return db.query(Item).all()Exception handling in yield dependencies
If an exception occurs during the request phase (between yield and the end of the path function), the code after yield still runs — this is Python's try/finally semantics. Use this to your advantage for cleanup (closing DB sessions, releasing locks). But be careful: if the exception happens before yield (in the setup phase), the finally block still runs, which may operate on an uninitialized resource.
11.6 Chapter Summary
- Classes as dependencies:
__init__parameters become request params; instance attributes are type-safe; - Sub-dependency trees: dependencies can declare their own dependencies; FastAPI resolves bottom-up;
dependencies=[...]on decorators for side-effect-only execution (auth, rate limiting) without adding function parameters;- Global
dependencies=on FastAPI() applies to all routes; yielddependencies for setup/teardown (DB sessions, transaction management); exceptions trigger the finally block.
🧪 随堂测验
点击你认为正确的选项。答错时会展示正确答案与原因解析。
1. What is the shortcut for Depends() when the type annotation already specifies the class?
2. What does dependencies=[Depends(auth)] on a router do?
3. When does the code after yield in a yield dependency execute?
4. What is the advantage of a class dependency over a function dependency?
🛠️ Hands-On Exercises
- Write a
DatabaseSessionclass dependency with__init__(self, url: str); useDepends()shortcut in a route. - Build a sub-dependency tree:
get_token()→validate_token(token)→get_user(valided_token); verify each level resolves in order. - Write a yield dependency that opens a DB session, yields it, and closes it in finally; deliberately raise an exception in the route and verify the session is still closed.
Proceed to Chapter 12: Middleware and CORS.