FastAPI Dependencies Explained (with Examples)

Okay, here’s a comprehensive article on FastAPI Dependencies, explained with numerous examples, aiming for the requested word count:

FastAPI Dependencies Explained (with Examples)

FastAPI’s dependency injection system is one of its most powerful and distinguishing features. It’s not just a minor convenience; it’s a core architectural principle that promotes code reusability, testability, maintainability, and overall code clarity. This article dives deep into FastAPI dependencies, covering everything from basic usage to advanced concepts, all illustrated with practical examples.

1. What is Dependency Injection?

Before diving into FastAPI specifics, it’s crucial to understand the concept of dependency injection (DI) itself. In essence, DI is a design pattern where an object (or function, in FastAPI’s case) receives other objects (or functions, or data) that it depends on, rather than creating them itself.

Why is this useful?

  • Decoupling: Dependencies are provided from the outside, making components less tightly coupled. You can easily swap out implementations of a dependency without modifying the dependent code.
  • Testability: You can easily provide mock or test dependencies during testing, isolating the unit under test and making testing much more reliable and focused.
  • Reusability: The same dependency can be injected into multiple parts of your application, avoiding code duplication.
  • Maintainability: Changes to a dependency’s implementation only need to be made in one place, simplifying maintenance and reducing the risk of errors.
  • Readability: Dependencies are explicitly declared, making it clear what a function or class requires to operate.

2. Dependencies in FastAPI: The Basics

FastAPI leverages Python’s type hints to implement its dependency injection system. You declare a dependency as a parameter in your path operation function, and FastAPI handles the rest.

2.1 Simple Dependencies: Reusable Logic

Let’s start with the simplest form: a dependency that performs some common logic.

“`python
from fastapi import Depends, FastAPI, HTTPException

app = FastAPI()

async def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100):
“””
A dependency function that extracts common query parameters.
“””
return {“q”: q, “skip”: skip, “limit”: limit}

@app.get(“/items/”)
async def read_items(commons: dict = Depends(common_parameters)):
“””
Uses the common_parameters dependency to get query parameters.
“””
return commons

@app.get(“/users/”)
async def read_users(commons: dict = Depends(common_parameters)):
“””
Reuses the common_parameters dependency.
“””
return commons
“`

Explanation:

  • common_parameters: This is our dependency function. It takes three optional query parameters (q, skip, limit) and returns a dictionary containing their values.
  • Depends(common_parameters): In the read_items and read_users functions, we use Depends(common_parameters) as the type hint for the commons parameter. This tells FastAPI:
    • “When this path operation is called, first execute the common_parameters function.”
    • “Pass any relevant request information (query parameters, in this case) to common_parameters.”
    • “Take the return value of common_parameters and make it available as the commons parameter in the path operation function.”

Key Benefits Demonstrated:

  • Reusability: The common_parameters dependency is used in two different path operations, avoiding duplicated code.
  • Readability: It’s immediately clear that both read_items and read_users depend on the same set of common parameters.

2.2. Dependencies with Database Connections

A very common use case for dependencies is managing database connections. This ensures that connections are properly opened and closed, and that you’re not creating a new connection for every request.

“`python
from fastapi import Depends, FastAPI, HTTPException
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.orm import sessionmaker, declarative_base, Session

app = FastAPI()

— Database Setup (using SQLite for simplicity) —

DATABASE_URL = “sqlite:///./test.db”
engine = create_engine(DATABASE_URL, connect_args={“check_same_thread”: False}) # SQLite-specific
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()

class Item(Base):
tablename = “items”
id = Column(Integer, primary_key=True, index=True)
name = Column(String, index=True)
description = Column(String)

Base.metadata.create_all(bind=engine)

— Dependency for Database Session —

def get_db():
“””
Dependency that provides a database session.
Uses a ‘try/finally’ block to ensure the session is closed.
“””
db = SessionLocal()
try:
yield db # This is where the session is provided to the path operation
finally:
db.close()

— Path Operations —

@app.post(“/items/”)
async def create_item(name: str, description: str, db: Session = Depends(get_db)):
“””
Creates a new item in the database.
“””
db_item = Item(name=name, description=description)
db.add(db_item)
db.commit()
db.refresh(db_item)
return db_item

@app.get(“/items/{item_id}”)
async def read_item(item_id: int, db: Session = Depends(get_db)):
“””
Retrieves an item from the database by ID.
“””
db_item = db.query(Item).filter(Item.id == item_id).first()
if db_item is None:
raise HTTPException(status_code=404, detail=”Item not found”)
return db_item
“`

Explanation:

  • Database Setup: This code sets up a simple SQLite database using SQLAlchemy. This part isn’t directly related to dependencies, but it provides the context for our database-related dependency.
  • get_db(): This is our crucial dependency function. It:
    • Creates a new database session (SessionLocal()).
    • Uses yield db to provide the session to the path operation function. This is a generator function, which is a key concept for dependencies that need to perform cleanup (like closing a database connection).
    • The finally block ensures that db.close() is always called, even if an exception occurs within the path operation. This is essential for releasing database resources.
  • db: Session = Depends(get_db): In our path operations (create_item and read_item), we use Depends(get_db) to get a database session. FastAPI handles calling get_db, providing the session to the function, and ensuring the session is closed afterward.

Key Benefits Demonstrated:

  • Resource Management: The get_db dependency ensures that database connections are properly managed, preventing resource leaks.
  • Testability: You could easily create a mock database session for testing, allowing you to test your path operations without needing a real database.
  • Centralized Logic: Database connection logic is centralized in the get_db function, making it easy to modify or update.

2.3. Dependencies with Classes

Dependencies don’t have to be functions; they can also be classes. This is useful when your dependency has state or needs to perform more complex initialization.

“`python
from fastapi import Depends, FastAPI, HTTPException

app = FastAPI()

class ItemService:
def init(self, item_type: str = “default”):
self.item_type = item_type

def get_item(self, item_id: int):
    if item_id < 0:
      raise HTTPException(status_code=400, detail="Item ID must be positive")
    return {"item_id": item_id, "item_type": self.item_type}

async def get_item_service(item_type: str | None = “default”):
return ItemService(item_type=item_type)

@app.get(“/items/{item_id}”)
async def read_item(item_id: int, item_service: ItemService = Depends(get_item_service)):
return item_service.get_item(item_id)

@app.get(“/items2/{item_id}”) #alternative way using Depends directly on a Class
async def read_item_alt(item_id: int, item_service: ItemService = Depends(ItemService)):
return item_service.get_item(item_id)
“`

Explanation:

  • ItemService: This class represents a service that handles item-related operations. It has an __init__ method that takes an optional item_type parameter.
  • get_item_service(): This is a dependency function that creates an instance of ItemService, allowing to set custom parameters
  • Depends(ItemService)/Depends(get_item_service): We use Depends with the ItemService class (or an instance creating function) as the argument. FastAPI:
    • Creates an instance of ItemService (if needed, it calls the __init__ method, injecting any parameters from the request if defined, like item_type for the creating function).
    • Makes this instance available as the item_service parameter in the path operation function.
  • read_item_alt: shows an alternative where the Class is used directly as a dependency. FastAPI will instanciate it by looking if there are any constructor arguments, and try to inject them (as it does for path operation parameters)

Key Benefits Demonstrated:

  • Stateful Dependencies: The ItemService class can hold state (like item_type), which can be useful for caching or other more complex logic.
  • Initialization Logic: The __init__ method allows you to perform initialization when the dependency is created.

2.4. Dependencies with Sub-dependencies

Dependencies can depend on other dependencies! This allows you to build complex dependency chains.

“`python
from fastapi import Depends, FastAPI, Header, HTTPException

app = FastAPI()

async def verify_token(x_token: str = Header(…)):
“””
Dependency to verify a token passed in the X-Token header.
“””
if x_token != “fake-super-secret-token”:
raise HTTPException(status_code=400, detail=”X-Token header invalid”)
return x_token

async def verify_key(x_key: str = Header(…)):
“””
Dependency to verify a key passed in the X-Key header.
“””
if x_key != “fake-super-secret-key”:
raise HTTPException(status_code=400, detail=”X-Key header invalid”)
return x_key

async def get_user(x_token:str = Depends(verify_token),x_key:str= Depends(verify_key)):
“””
Dependency to get the user, which itself depends on verifying the token and key.
“””
# In a real application, you would use the token/key to look up the user
# in a database or other user store.
return {“username”: “johndoe”, “token”: x_token, “key”: x_key}

@app.get(“/users/me”)
async def read_users_me(user: dict = Depends(get_user)):
“””
Get the current user, requiring both a valid token and key.
“””
return user
“`

Explanation:

  • verify_token and verify_key: These are simple dependencies that check the X-Token and X-Key headers, respectively. They raise an HTTPException if the headers are invalid.
  • get_user: This dependency depends on both verify_token and verify_key.
    • x_token: str = Depends(verify_token): This says, “First, execute verify_token, and make its return value available as x_token.”
    • x_key: str = Depends(verify_key): Same logic for verify_key and x_key.
  • read_users_me: This path operation depends on get_user. FastAPI will automatically execute verify_token, verify_key, and then get_user before calling read_users_me.

Key Benefits Demonstrated:

  • Dependency Chains: You can create complex dependencies by having dependencies depend on other dependencies.
  • Order of Execution: FastAPI handles the order of execution automatically, ensuring that dependencies are resolved in the correct order.
  • Error Handling: If any dependency in the chain raises an HTTPException, the request will be aborted, and the appropriate error response will be returned.

3. Advanced Dependency Concepts

Now let’s explore some more advanced features of FastAPI’s dependency system.

3.1. yield Dependencies with Cleanup (Context Managers)

We already saw this with the database example, but it’s worth emphasizing. The yield keyword in a dependency function allows you to:

  1. Provide a value to the dependent function.
  2. Execute code after the dependent function has finished, even if an exception occurred.

This is perfect for managing resources like database connections, file handles, or network connections. It’s essentially a way to use Python’s context manager (with statement) functionality within FastAPI’s dependency system.

“`python
from fastapi import Depends, FastAPI

app = FastAPI()

async def get_resource():
resource = {“data”: “This is a resource”} # Simulate acquiring a resource
try:
yield resource
finally:
print(“Cleaning up resource…”) # This will always be executed

@app.get(“/use-resource”)
async def use_resource(resource: dict = Depends(get_resource)):
print(f”Using resource: {resource}”)
# raise Exception(“Something went wrong!”) # Uncomment to test cleanup
return {“message”: “Resource used successfully”}

“`

Explanation:

  • The get_resource function simulates acquiring a resource.
  • yield resource provides the resource to use_resource.
  • The finally block guarantees that “Cleaning up resource…” will be printed, even if you uncomment the raise Exception line.

3.2. Dependencies with use_cache=False

By default, FastAPI caches the results of dependency functions for the same request. This means that if a dependency is used multiple times within the same request, it will only be executed once. This is usually desirable for performance reasons.

However, there are cases where you don’t want this behavior. For example, if your dependency generates a random number or retrieves the current time, you’ll want it to be executed every time it’s used.

“`python
from fastapi import Depends, FastAPI
import random
from datetime import datetime

app = FastAPI()

async def get_random_number():
return random.randint(1, 100)

async def get_current_time():
return datetime.now()

@app.get(“/random”)
async def read_random(
random1: int = Depends(get_random_number),
random2: int = Depends(get_random_number),
random3: int = Depends(get_random_number, use_cache=False),
random4: int = Depends(get_random_number, use_cache=False),
):
return {
“random1”: random1, # Will be the same as random2
“random2”: random2, # Will be the same as random1
“random3”: random3, # Will likely be different from random1/2
“random4”: random4, # Will likely be different from random1/2/3
}

@app.get(“/time”)
async def read_time(
time1: datetime = Depends(get_current_time),
time2: datetime = Depends(get_current_time, use_cache=False)
):
return {
“time1”: time1,
“time2”: time2
}
“`

Explanation:

  • get_random_number: This dependency generates a random number.
  • Depends(get_random_number, use_cache=False): We explicitly tell FastAPI not to cache the result of this dependency. This means that random3 and random4 will likely have different values.
  • Without use_cache=False, random1 and random2 would have the same value because the result of get_random_number would be cached for the first call and reused for the second.
  • The /time route is showing that without use_cache=False, the values can still differ as FastAPI only caches within the lifespan of the request processing. Two different requests, will generate different results.

3.3. Global Dependencies

You can apply dependencies globally to all path operations in your application, or to a specific group of path operations using routers.

“`python
from fastapi import Depends, FastAPI, Header, HTTPException, APIRouter

app = FastAPI()

async def verify_token(x_token: str = Header(…)):
if x_token != “fake-super-secret-token”:
raise HTTPException(status_code=400, detail=”X-Token header invalid”)
return x_token

Global dependency for the entire app

app = FastAPI(dependencies=[Depends(verify_token)])

— Router with its own dependencies —

router = APIRouter(
prefix=”/items”,
tags=[“items”],
dependencies=[Depends(verify_token)], # Could be other dependencies for this router
responses={404: {“description”: “Not found”}},
)

@router.get(“/”)
async def read_items():
return [{“name”: “Item 1”}, {“name”: “Item 2”}]

@router.get(“/{item_id}”)
async def read_item(item_id: int):
return {“item_id”: item_id}

app.include_router(router)

@app.get(“/users/”)
async def read_users():
return [{“username”: “johndoe”}]
“`

Explanation:

  • app = FastAPI(dependencies=[Depends(verify_token)]): This applies the verify_token dependency to all path operations in the app. Every request will now require a valid X-Token header.
  • router = APIRouter(..., dependencies=[Depends(verify_token)], ...): This applies the verify_token dependency to all path operations within the router. You can have different global dependencies for different routers.

3.4. Using Path, Query, Body, Cookie, and Header Parameters as Dependencies

FastAPI allows you to use the same functions you use to extract path, query, body, cookie, and header parameters as dependencies. This is a concise way to validate and extract these parameters.

“`python
from fastapi import Depends, FastAPI, Query, Path, Body, Cookie, Header, HTTPException

app = FastAPI()

async def pagination_parameters(
skip: int = Query(0, ge=0), limit: int = Query(100, le=100)
):
return {“skip”: skip, “limit”: limit}

@app.get(“/items/”)
async def read_items(pagination: dict = Depends(pagination_parameters)):
return {“message”: f”Items with skip={pagination[‘skip’]} and limit={pagination[‘limit’]}”}

@app.get(“/items_alt/”) #alternative using Depends directly
async def read_items_alt(skip: int = Depends(Query(0, ge=0)), limit: int = Depends(Query(100, le=100))):
return {“message”: f”Items with skip={skip} and limit={limit}”}

@app.get(“/items/{item_id}”)
async def read_item(item_id: int = Path(…, title=”The ID of the item to get”, ge=1)):
return {“item”: item_id}

@app.post(“/items/”)
async def create_item(
name: str = Body(…),
description: str | None = Body(None),
price: float = Body(…, gt=0),
tax: float | None = Body(None),

):
return {“name”: name, “description”: description, “price”: price, “tax”: tax}

@app.get(“/items_cookie/”)
async def read_items_cookie(ads_id: str | None = Cookie(None)):
return {“ads_id”: ads_id}

@app.get(“/items_header/”)
async def read_items_header(user_agent: str | None = Header(None)):
return {“User-Agent”: user_agent}

“`

Explanation:

  • We define a dependency function, pagination_parameters, that uses Query to define and validate the skip and limit query parameters. This function is then used as a dependency in read_items.
  • read_items_alt demonstrates an alternative, a bit more verbose, but sometimes clearer way of writing the same.
  • read_item shows the usage of Path directly in the function parameter definition. This is equivalent to a dependency. FastAPI extracts the item_id from the path, validates it (ensuring it’s greater than or equal to 1), and passes it to the function.
  • create_item uses Body for receiving data. It is also the standard and recommended way.
  • read_items_cookie and read_items_header extract data from the Cookies and the Headers of the request.

3.5. Using Annotated for More Complex Dependencies (FastAPI 0.95.0 and later)

For more complex dependency declarations, especially when you need to combine multiple parameters or add metadata, FastAPI provides the Annotated type (introduced in Python 3.9 and available through the typing_extensions backport for earlier versions). Annotated allows you to attach metadata to a type hint. FastAPI leverages this for dependency injection.

“`python
from typing import Annotated

from fastapi import Depends, FastAPI, Query, HTTPException

app = FastAPI()

async def complex_dependency(
q: Annotated[str | None, Query(max_length=50)] = None,
skip: Annotated[int, Query(ge=0)] = 0,
limit: Annotated[int, Query(le=100)] = 100,
):
if q is None and skip == 0 and limit == 100:
return {}
return {“q”: q, “skip”: skip, “limit”: limit}
@app.get(“/items_annotated”)
async def read_items_annotated(
params: Annotated[dict, Depends(complex_dependency)] = {}
):
return params

@app.get(“/items_annotated_alt”) # alternative using the dependency directly on each parameter
async def read_items_annotated_alt(
q: Annotated[str | None, Query(max_length=50)] = None,
skip: Annotated[int, Query(ge=0)] = 0,
limit: Annotated[int, Query(le=100)] = 100,
):
return {“q”:q, “skip”: skip, “limit”: limit}
“`

Explanation:

  • Annotated[str | None, Query(max_length=50)]: This means:
    • The type is str | None (a string or None).
    • It’s a query parameter (Query).
    • It has a maximum length of 50 (max_length=50).
  • read_items_annotated: defines a params dictionary using the dependency complex_dependency.
  • read_items_annotated_alt: achieves the same functionality of the previous one, but by declaring each parameter using Annotated and Depends directly.
  • FastAPI uses the metadata provided by Annotated to understand how to handle the dependency (e.g., as a query parameter, with validation rules).

Key Advantages of Annotated:

  • Clarity: It makes it very clear what kind of parameter you’re dealing with (query, path, body, etc.) and its associated metadata.
  • Flexibility: You can combine multiple parameters and validation rules in a single dependency.
  • Readability: It improves the readability of your code, especially for complex dependencies.

3.6. Security Dependencies (OAuth2, API Keys)

FastAPI provides built-in utilities for implementing common security patterns, such as OAuth2 and API key authentication, using dependencies.

“`python
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm

app = FastAPI()

OAuth2PasswordBearer for handling token authentication

oauth2_scheme = OAuth2PasswordBearer(tokenUrl=”token”)

Dummy user database (replace with your actual user storage)

users = {
“johndoe”: {“username”: “johndoe”, “hashed_password”: “verysecretpassword”}, # In reality, store hashed passwords!
}

async def get_current_user(token: str = Depends(oauth2_scheme)):
“””
Dependency to get the current user based on the provided token.
“””
user = users.get(token) #in a real case, the token would be checked against a DB
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=”Invalid authentication credentials”,
headers={“WWW-Authenticate”: “Bearer”},
)
return user

@app.post(“/token”)
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
“””
Dummy login endpoint (for demonstration purposes only).
In a real application, you would validate the username/password
against a database and generate a JWT or other secure token.
“””
user = users.get(form_data.username)
if not user or form_data.password != user[“hashed_password”]:
raise HTTPException(status_code=400, detail=”Incorrect username or password”)
# In a real application, you would generate a JWT here.
return {“access_token”: form_data.username, “token_type”: “bearer”}

@app.get(“/users/me”)
async def read_users_me(current_user: dict = Depends(get_current_user)):
“””
Protected endpoint that requires a valid token.
“””
return current_user

“`

Explanation:

  • OAuth2PasswordBearer(tokenUrl="token"): This creates an instance of OAuth2PasswordBearer, which is a dependency that handles OAuth2 password flow. It expects a token to be passed in the Authorization header with the Bearer scheme (e.g., Authorization: Bearer mytoken). tokenUrl="token" specifies the endpoint where clients can obtain a token.
  • get_current_user: This dependency uses oauth2_scheme to get the token from the request. It then looks up the user in a (dummy) user database. If the token is invalid, it raises an HTTPException with a 401 status code.
  • login: This endpoint simulates a login process. It takes username and password using OAuth2PasswordRequestForm (which is also a dependency!), validates the credentials, and returns a dummy token. Important: This is a simplified example. In a real application, you would:
    • Hash passwords securely (never store plain text passwords!).
    • Use a library like python-jose or PyJWT to generate and verify JSON Web Tokens (JWTs).
  • read_users_me: This endpoint is protected by the get_current_user dependency. Only requests with a valid token will be able to access this endpoint.

4. Testing Dependencies

One of the biggest advantages of dependency injection is improved testability. You can easily override dependencies during testing to provide mock objects or control their behavior.

“`python
from fastapi.testclient import TestClient
from fastapi import Depends, FastAPI, HTTPException
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.orm import sessionmaker, declarative_base, Session

— Same database setup as in the previous example —

DATABASE_URL = “sqlite:///./test.db”
engine = create_engine(DATABASE_URL, connect_args={“check_same_thread”: False}) # SQLite-specific
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()

class Item(Base):
tablename = “items”
id = Column(Integer, primary_key=True, index=True)
name = Column(String, index=True)
description = Column(String)

Base.metadata.create_all(bind=engine)

— Same dependency as before —

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

app = FastAPI()
@app.post(“/items/”)
async def create_item(name: str, description: str, db: Session = Depends(get_db)):
db_item = Item(name=name, description=description)
db.add(db_item)
db.commit()
db.refresh(db_item)
return db_item

@app.get(“/items/{item_id}”)
async def read_item(item_id: int, db: Session = Depends(get_db)):
db_item = db.query(Item).filter(Item.id == item_id).first()
if db_item is None:
raise HTTPException(status_code=404, detail=”Item not found”)
return db_item

— Test Code —

client = TestClient(app)

def override_get_db():
“””
Override the get_db dependency for testing.
This creates an in-memory database for each test.
“””
engine = create_engine(“sqlite:///:memory:”) # In-memory SQLite database
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base.metadata.create_all(bind=engine)
db = SessionLocal()
try:
yield db
finally:
db.close()

Override the dependency in the app

app.dependency_overrides[get_db] = override_get_db

def test_create_item():
response = client.post(“/items/”, json={“name”: “Test Item”, “description”: “This is a test item”})
assert response.status_code == 200
assert response.json()[“name”] == “Test Item”

def test_read_item():
# First, create an item
response = client.post(“/items/”, json={“name”: “Another Item”, “description”: “Another description”})
item_id = response.json()[“id”]

# Then, try to read it
response = client.get(f"/items/{item_id}")
assert response.status_code == 200
assert response.json()["name"] == "Another Item"

def test_read_item_not_found():
response = client.get(“/items/999”) # Assuming no item with ID 999 exists
assert response.status_code == 404
“`

Explanation:

  • TestClient: FastAPI provides TestClient for easily making requests to your application during testing.
  • override_get_db: This function overrides the original get_db dependency. It creates an in-memory SQLite database, ensuring that each test starts with a clean database.
  • app.dependency_overrides[get_db] = override_get_db: This is the key line. It tells FastAPI, “Whenever get_db is requested as a dependency, use override_get_db instead.”
  • test_create_item, test_read_item, test_read_item_not_found: These are test functions that use TestClient to make requests and assert the expected responses. Because we’ve overridden get_db, these tests use the in-memory database, making them isolated and reliable.

5. Conclusion

FastAPI’s dependency injection system is a powerful and flexible tool that significantly improves the quality, testability, and maintainability of your code. By understanding and utilizing dependencies effectively, you can build robust, scalable, and well-structured API applications. This article has covered the core concepts and many advanced features, providing a solid foundation for using dependencies in your FastAPI projects. Remember to leverage the yield keyword for resource management, use_cache=False when needed, global dependencies for common requirements, and Annotated for complex scenarios. And most importantly, always remember to test your dependencies using FastAPI’s dependency_overrides mechanism.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top