FastAPI Dependency Overrides
When building APIs with FastAPI, you'll often use dependency injection to provide resources, validate inputs, or enforce security. But what happens when you want to replace these dependencies in certain contexts like testing or debugging? This is where dependency overrides come in.
In this guide, we'll explore how to use FastAPI's dependency override system to substitute dependencies at runtime, making your code more testable and flexible.
What are Dependency Overrides?
Dependency overrides allow you to temporarily replace a dependency with another function or class. This is particularly useful for:
- Testing your API without accessing real external services
- Simulating different execution scenarios
- Customizing behavior for different environments
- Debugging complex dependency chains
FastAPI provides a built-in mechanism to override dependencies using the app.dependency_overrides
dictionary.
Basic Dependency Override Example
Let's start with a simple example to understand how dependency overrides work.
First, let's create a basic API with a dependency:
from fastapi import FastAPI, Depends
app = FastAPI()
# Original dependency
def get_db():
# In a real application, this might set up a database connection
print("Opening real database connection")
db = {"name": "production_db", "data": ["real", "production", "data"]}
yield db
print("Closing real database connection")
@app.get("/items/")
async def read_items(db: dict = Depends(get_db)):
return {"items": db["data"], "db_name": db["name"]}
Now, let's say we want to test this API without connecting to the actual database. We can create a mock database dependency:
# Mock dependency for testing
def get_test_db():
print("Opening test database connection")
db = {"name": "test_db", "data": ["test", "fake", "data"]}
yield db
print("Closing test database connection")
# Override the dependency for testing
app.dependency_overrides[get_db] = get_test_db
With this override in place, any endpoint that depends on get_db
will now use get_test_db
instead. The output of our endpoint would change from:
{"items": ["real", "production", "data"], "db_name": "production_db"}
to:
{"items": ["test", "fake", "data"], "db_name": "test_db"}
Using Dependency Overrides in Tests
One of the most common use cases for dependency overrides is in testing. Here's how you might use them in a pytest test case:
from fastapi.testclient import TestClient
from myapp.main import app, get_db
client = TestClient(app)
def get_test_db():
# Test database setup
db = {"name": "test_db", "data": ["test", "mock", "data"]}
yield db
def test_read_items():
# Override the dependency for this test
app.dependency_overrides[get_db] = get_test_db
response = client.get("/items/")
assert response.status_code == 200
assert response.json() == {"items": ["test", "mock", "data"], "db_name": "test_db"}
# Clear the override after the test
app.dependency_overrides = {}
The important parts to note:
- We set the override before making the test request
- We clear all overrides after the test using
app.dependency_overrides = {}
Overriding Complex Dependencies
Let's look at a more realistic example with database sessions and authentication:
from fastapi import FastAPI, Depends, HTTPException, status
from sqlalchemy.orm import Session
from typing import Optional
app = FastAPI()
# Database dependency
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
# Auth dependency
def get_current_user(db: Session = Depends(get_db)):
# This would normally verify a token and return a user
user = db.query(User).first()
if not user:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
return user
@app.get("/users/me")
def read_user_me(current_user = Depends(get_current_user)):
return current_user
For testing, we might want to override both dependencies:
# Test database dependency
def get_test_db():
db = TestingSessionLocal()
try:
yield db
finally:
db.close()
# Test user dependency
def get_test_user():
# No database needed, just return a test user
return {"id": 1, "username": "testuser", "is_active": True}
def test_read_user_me():
# Override dependencies
app.dependency_overrides[get_db] = get_test_db
app.dependency_overrides[get_current_user] = get_test_user
response = client.get("/users/me")
assert response.status_code == 200
assert response.json()["username"] == "testuser"
# Clear overrides
app.dependency_overrides = {}
Using Lambda Functions for Simple Overrides
For simple dependencies, you can use lambda functions for even more concise overrides:
# Original dependency
def get_api_key(api_key: str):
if api_key != "valid_key":
raise HTTPException(status_code=403, detail="Invalid API key")
return api_key
# Override with a lambda
app.dependency_overrides[get_api_key] = lambda: "test_key"
Contextual Dependency Overrides
Sometimes you may want to apply dependency overrides only in certain contexts. You can create a context manager for this purpose:
from contextlib import contextmanager
@contextmanager
def override_dependency(app: FastAPI, original, override):
app.dependency_overrides[original] = override
try:
yield
finally:
del app.dependency_overrides[original]
# Usage
with override_dependency(app, get_db, get_test_db):
# The dependency is overridden only within this block
client.get("/items/")
Best Practices for Dependency Overrides
Here are some recommendations when working with dependency overrides:
-
Clear overrides after testing: Always reset
app.dependency_overrides
after your tests to prevent side effects.pythondef teardown_function():
app.dependency_overrides = {} -
Keep mock dependencies similar: Make sure your mock dependencies return data in the same format as the original dependencies.
-
Be careful with global state: Dependency overrides modify global app state, so be cautious when running parallel tests.
-
Use scoped fixtures in pytest: If you're using pytest, consider using fixtures with proper scope:
python@pytest.fixture(autouse=True)
def setup_and_teardown():
# Set up overrides
app.dependency_overrides[get_db] = get_test_db
yield
# Clean up
app.dependency_overrides = {}
Real-World Example: Overriding External API Clients
Here's a practical example showing how to override an external API client dependency:
from fastapi import FastAPI, Depends
import httpx
app = FastAPI()
# Original dependency - real API client
class WeatherAPIClient:
def __init__(self):
self.api_key = "real_api_key"
self.base_url = "https://api.weather.com"
async def get_temperature(self, city: str):
async with httpx.AsyncClient() as client:
response = await client.get(
f"{self.base_url}/temperature",
params={"city": city, "api_key": self.api_key}
)
data = response.json()
return data["temperature"]
def get_weather_client():
return WeatherAPIClient()
@app.get("/weather/{city}")
async def get_weather(city: str, client: WeatherAPIClient = Depends(get_weather_client)):
temperature = await client.get_temperature(city)
return {"city": city, "temperature": temperature}
# Mock client for testing
class MockWeatherAPIClient:
async def get_temperature(self, city: str):
# Simulate responses based on city
if city == "London":
return 15.5
elif city == "New York":
return 20.0
else:
return 25.0
# For tests
def get_mock_weather_client():
return MockWeatherAPIClient()
# In tests
def test_london_weather():
app.dependency_overrides[get_weather_client] = get_mock_weather_client
response = client.get("/weather/London")
assert response.status_code == 200
assert response.json() == {"city": "London", "temperature": 15.5}
app.dependency_overrides = {}
Summary
Dependency overrides in FastAPI provide a powerful mechanism for substituting dependencies at runtime, which is especially useful for testing and development:
- Use
app.dependency_overrides[original_dependency] = new_dependency
to replace dependencies - Always clear overrides after tests with
app.dependency_overrides = {}
- Create mock dependencies that mirror the interface of the original ones
- Consider using context managers or pytest fixtures to manage overrides
- For simple cases, lambda functions can provide quick overrides
By mastering dependency overrides, you'll make your FastAPI applications more testable and adaptable to different environments.
Further Resources and Exercises
Resources
Exercises
-
Basic Override: Create a FastAPI app with a simple database dependency, and write a test that overrides it.
-
Authentication Override: Create an authentication system with
get_current_user
dependency, and override it in tests to simulate different user roles. -
Context Manager: Implement a context manager similar to the
override_dependency
example and use it to manage multiple dependency overrides. -
Complex Chain: Create a chain of dependencies (where one depends on another) and override different parts of the chain to see how it affects the application behavior.
-
Conditional Overrides: Write a system that chooses different dependency implementations based on environment variables, using overrides.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)