Skip to content

Chapter 10 · Dependency Injection Basics ​

Chapter goals: Understand what dependency injection solves, learn to declare dependencies with Depends, reuse dependencies with Annotated type aliases, and master sync/async dependency mixing and request-level caching behavior.

10.1 What Dependency Injection Solves ​

"Dependency Injection" (DI) means: the path operation function only declares what it needs, and the framework is responsible for constructing and injecting it. Official listed use cases:

  • Shared logic across multiple endpoints (pagination parsing, common query processing);
  • Shared database connections/sessions;
  • Enforcing security, authentication, and role checks.

Without DI, every endpoint duplicates the same code; with DI, write the logic once and FastAPI auto-calls and injects the result on each request.

10.2 First Dependency: Depends ​

A dependency (a "dependable") is a plain function whose parameters are structurally identical to the path operation function's:

python
from typing import Annotated
from fastapi import FastAPI, Depends

app = FastAPI()


async def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100):
    # Dependency function: declares q/skip/limit as query params and packs them into a dict
    return {"q": q, "skip": skip, "limit": limit}


@app.get("/items/")
async def list_items(
    commons: Annotated[dict, Depends(common_parameters)],  # Declare the dependency
):
    return {"items": ["a", "b"], **commons}


@app.get("/users/")
async def list_users(
    commons: Annotated[dict, Depends(common_parameters)],  # Same dependency reused
):
    return {"users": ["u1"], **commons}

Key rule: pass the function itself to Depends() — do not add parentheses (write Depends(common_parameters) not Depends(common_parameters())).

On each request, FastAPI:

  1. Calls common_parameters() with the correct parameters (where q/skip/limit are parsed and validated as query params);
  2. Assigns the return value to the path function's commons parameter.

Note: params declared in the dependency (q/skip/limit) automatically appear in the OpenAPI docs — you can see and debug them in /docs.

10.3 Reusing with Annotated Type Aliases ​

Both endpoints above write Annotated[dict, Depends(common_parameters)] — still repetitive. Since Annotated is pure Python standard syntax, store it as a type alias variable:

python
from typing import Annotated
from fastapi import FastAPI, Depends

Commons = Annotated[dict, Depends(common_parameters)]  # Type alias, not a FastAPI feature

app = FastAPI()


@app.get("/items/")
async def list_items(commons: Commons):     # One line
    return {"items": ["a"], **commons}


@app.get("/users/")
async def list_users(commons: Commons):
    return {"users": ["u1"], **commons}

The official recommendation from 0.95.0 onward. Compared to the old default-value style (commons: dict = Depends(common_parameters)), Annotated's advantage: type information is fully preserved — editor autocomplete, mypy static checking all work correctly, and alias reuse in large codebases is extremely convenient.

10.4 Mixing Sync and Async Dependencies Freely ​

Dependencies, like path functions, support both async def and plain def, and they mix arbitrarily — an async def path function can use a sync dependency and vice versa; FastAPI auto-selects the correct event loop/thread pool execution path.

Selection principle (consistent with Chapter 16):

  • Dependency internally does I/O with an async client (e.g., AsyncSession) → async def;
  • Dependency is pure computation or calls a synchronous blocking library (e.g., legacy psycopg2) → plain def (avoid blocking the event loop).
python
import time
from fastapi import Depends, FastAPI

app = FastAPI()


def sync_dep() -> dict:
    time.sleep(0.01)          # Simulate a synchronous blocking call; FastAPI dispatches to thread pool
    return {"source": "sync"}


async def async_dep() -> dict:
    await asyncio.sleep(0.01)  # Non-blocking async I/O
    return {"source": "async"}

10.5 Request-Level Caching ​

By default, a dependency is re-executed on every path function call that depends on it — even within the same request. To cache a dependency to execute only once per request:

python
from fastapi import Depends, FastAPI

app = FastAPI()

_call_count = 0


def counting_dep() -> dict:
    global _call_count
    _call_count += 1
    return {"call_number": _call_count}


@app.get("/items/")
async def get_items(dep: dict = Depends(counting_dep)):
    return dep


@app.get("/items/{item_id}")
async def get_item(item_id: int, dep: dict = Depends(counting_dep)):
    return {"item_id": item_id, **dep}

Visiting /items/ then /items/1 in the same request: counting_dep runs twice (two path functions). But if two dependencies in the same function both depend on the same sub-dependency, that sub-dependency runs only once — request-level caching by identity.

10.6 Chapter Summary ​

  • DI solves code duplication: declare what you need, framework constructs and injects;
  • Depends(func) passes the function itself, not a call;
  • Annotated[Type, Depends(...)] type aliases eliminate repetition;
  • Sync and async dependencies mix freely; FastAPI handles execution context;
  • Dependencies are cached per-request by identity — shared sub-dependencies run once.

🧪 随堂测验

点击你认为正确的选项。答错时会展示正确答案与原因解析。

1. What does Depends() do when passed a function?

2. Why use Annotated[type, Depends(...)] instead of param: type = Depends(...)?

3. If two path functions in the same request both depend on the same sub-dependency, how many times does the sub-dependency execute?

4. When should you use async def vs plain def for a dependency?

🛠️ Hands-On Exercises ​

  1. Write a dependency get_pagination(skip: int = 0, limit: int = 10) returning {"skip": skip, "limit": limit}; use it in two endpoints via Annotated alias.
  2. Create a current_user dependency that reads a Cookie and returns a user dict; use it in three endpoints; verify it runs only once per request.
  3. Mix a sync dependency (plain def with time.sleep) and an async dependency (async def with asyncio.sleep) in the same app; verify both work correctly.

Proceed to Chapter 11: Advanced Dependency Injection.