Skip to content

Chapter 11 · Advanced Dependency Injection ​

Chapter goals: Master classes as dependencies, multi-level sub-dependency trees, dependencies=[...] side-effect dependencies, global dependencies, and yield dependencies (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:

python
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:

python
@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:

python
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:

python
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:

python
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:

python
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;
  • yield dependencies 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 ​

  1. Write a DatabaseSession class dependency with __init__(self, url: str); use Depends() shortcut in a route.
  2. Build a sub-dependency tree: get_token() → validate_token(token) → get_user(valided_token); verify each level resolves in order.
  3. 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.