Skip to main content

Dependency Injection

FastAPI implements a hierarchical Dependency Injection (DI) system that allows developers to share logic across different path operations, manage resource lifecycles, and enforce security requirements. This system is built around the Depends class and its specialized subclass, Security, both defined in fastapi.params.

The DI system is designed to solve the problem of code duplication for common tasks like database session management, authentication, and request parameter validation. By defining these tasks as dependencies, FastAPI can automatically resolve them, inject the results into path operation functions, and handle cleanup.

The Dependency Injection Mechanism

At its core, dependency injection in FastAPI involves defining a callable (a function or a class) and telling FastAPI to execute it before the path operation. This is done using the Depends class, typically within a type hint using Annotated.

When a path operation is called, FastAPI analyzes the parameters. If it finds a Depends instance, it resolves that dependency (and any sub-dependencies) before calling the operation function.

from typing import Annotated
from fastapi import Depends, FastAPI

app = FastAPI()

async def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100):
return {"q": q, "skip": skip, "limit": limit}

@app.get("/items/")
async def read_items(commons: Annotated[dict, Depends(common_parameters)]):
return commons

In this example from docs_src/dependencies/tutorial001_an_py310.py, common_parameters is a dependency. FastAPI extracts the query parameters q, skip, and limit from the request, passes them to common_parameters, and then injects the returned dictionary into the read_items function.

Class-Based Dependencies

FastAPI also supports using classes as dependencies. When a class is passed to Depends, FastAPI instantiates the class. The parameters of the class's __init__ method are treated as sub-dependencies, allowing for complex state management and grouping of related logic.

class CommonQueryParams:
def __init__(self, q: str | None = None, skip: int = 0, limit: int = 100):
self.q = q
self.skip = skip
self.limit = limit

@app.get("/items/")
async def read_items(commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]):
return commons

As seen in docs_src/dependencies/tutorial002_an_py310.py, this approach allows the dependency to maintain state within an object instance, which can be cleaner than passing around dictionaries.

Resource Management with Yield

For dependencies that require setup and teardown—such as database sessions or network connections—FastAPI supports yield dependencies. This pattern ensures that resources are properly released even if an exception occurs during the request processing.

async def get_db():
db = DBSession()
try:
yield db
finally:
db.close()

In docs_src/dependencies/tutorial008_an_py310.py, the code before the yield is executed before the path operation, and the code in the finally block is executed after the response has been sent. This guarantees that db.close() is called regardless of whether the request succeeded or failed.

Security and OAuth2 Scopes

The Security class is a specialized version of Depends designed for authentication and authorization. While it functions similarly to Depends, it adds a scopes parameter used for OAuth2 scope requirements.

class Security(Depends):
scopes: Sequence[str] | None = None

The Security class serves two primary purposes in FastAPI:

  1. OpenAPI Documentation: It allows FastAPI to document the specific security scopes required for a path operation in the generated OpenAPI schema.
  2. Scope Enforcement: It provides a mechanism to pass required scopes to a security dependency.

When using Security, the dependency function can request SecurityScopes (from fastapi.security) to access the list of scopes required by the current operation and all its parent dependencies.

async def get_current_user(
security_scopes: SecurityScopes,
token: Annotated[str, Depends(oauth2_scheme)]
):
# Logic to verify token and check security_scopes.scopes
...

@app.get("/users/me/items/")
async def read_own_items(
current_user: Annotated[User, Security(get_current_user, scopes=["items"])],
):
return [{"item_id": "Foo", "owner": current_user.username}]

In this pattern from docs_src/security/tutorial005_an_py310.py, the get_current_user function receives the "items" scope via the security_scopes parameter, allowing it to perform fine-grained authorization.

Dependency Resolution and Caching

FastAPI builds a dependency graph for every request. If multiple dependencies (or the path operation itself) require the same dependency, FastAPI caches the result by default to avoid redundant executions.

The Depends class includes a use_cache boolean (defaulting to True). If a dependency is used multiple times in the same request—for example, a get_current_user dependency used by both the path operation and another sub-dependency—FastAPI will execute it once and reuse the result. Setting use_cache=False forces FastAPI to re-execute the dependency every time it is encountered in the graph.

Decoupling with Dependency Overrides

A significant advantage of this DI system is the ability to override dependencies during testing. The FastAPI application class provides a dependency_overrides attribute, which is a dictionary mapping original dependencies to their replacements.

This is frequently used in tests to replace real database sessions with test databases or to mock external API calls, as demonstrated in tests/test_dependency_overrides.py:

from fastapi import Depends, FastAPI
from fastapi.testclient import TestClient

app = FastAPI()

async def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100):
return {"q": q, "skip": skip, "limit": limit}

@app.get("/items/")
async def read_items(commons: Annotated[dict, Depends(common_parameters)]):
return commons

client = TestClient(app)

def test_override():
app.dependency_overrides[common_parameters] = lambda: {"q": "override", "skip": 5, "limit": 10}
response = client.get("/items/")
assert response.json() == {"q": "override", "skip": 5, "limit": 10}
app.dependency_overrides = {}

This mechanism allows for high testability without modifying the production path operation logic.