FastAPI Path Dependencies
Introduction
Path dependencies are a powerful feature in FastAPI that allow you to create reusable components that run before your route handlers. They can validate input data, extract information from requests, or perform any necessary preprocessing. By attaching these dependencies to specific paths, you can ensure that certain operations happen every time a particular endpoint is accessed, leading to cleaner, more maintainable code.
In this tutorial, we'll explore how path dependencies work in FastAPI, how to create and use them, and how they can improve your API development workflow.
Understanding Path Dependencies
Path dependencies in FastAPI are functions that are executed before the route handler function. They can:
- Validate request parameters
- Extract information from the request
- Perform authentication and authorization
- Share common code across multiple endpoints
- Return data that can be used by the route handler
Let's dive into how these dependencies work.
Basic Path Dependency Example
Here's a simple example of a path dependency in action:
from fastapi import FastAPI, Depends, HTTPException
app = FastAPI()
async def verify_token(token: str):
if token != "mysecrettoken":
raise HTTPException(status_code=401, detail="Invalid token")
return token
@app.get("/items/")
async def read_items(token: str = Depends(verify_token)):
return {"token": token, "message": "Token is valid"}
In this example, the verify_token
function is a dependency that checks whether the provided token is valid. The route handler only executes if the token passes verification.
Example request:
curl -X GET "http://localhost:8000/items/?token=mysecrettoken"
Response:
{
"token": "mysecrettoken",
"message": "Token is valid"
}
If an invalid token is provided:
curl -X GET "http://localhost:8000/items/?token=wrongtoken"
Response:
{
"detail": "Invalid token"
}
Path Dependencies vs Query Parameters
While our first example used a query parameter, path dependencies can also interact with path parameters, which is where they get their name:
from fastapi import FastAPI, Depends, HTTPException, Path
app = FastAPI()
async def verify_item_exists(item_id: int = Path(...)):
valid_items = {1: "Hammer", 2: "Screwdriver", 3: "Wrench"}
if item_id not in valid_items:
raise HTTPException(status_code=404, detail="Item not found")
return {"item_id": item_id, "item_name": valid_items[item_id]}
@app.get("/items/{item_id}")
async def read_item(item: dict = Depends(verify_item_exists)):
return item
In this example, the dependency receives the path parameter directly and verifies that the item exists before the route handler runs.
Creating Reusable Dependencies
One of the main benefits of dependencies is reusability. You can define a dependency once and use it in multiple routes:
from fastapi import FastAPI, Depends, HTTPException, Header
app = FastAPI()
async def get_current_user(authorization: str = Header(None)):
if not authorization:
raise HTTPException(status_code=401, detail="Authorization header missing")
if authorization != "Bearer mysecrettoken":
raise HTTPException(status_code=401, detail="Invalid authentication token")
return {"username": "johndoe"}
@app.get("/users/me")
async def read_user_me(current_user: dict = Depends(get_current_user)):
return current_user
@app.get("/items/")
async def read_items(current_user: dict = Depends(get_current_user)):
return {"user": current_user, "items": ["Item1", "Item2"]}
Now both endpoints reuse the same authentication logic.
Dependencies with Dependencies
FastAPI allows you to create dependencies that themselves depend on other dependencies, creating a chain:
from fastapi import FastAPI, Depends, HTTPException, Header
app = FastAPI()
async def verify_token(authorization: str = Header(None)):
if not authorization:
raise HTTPException(status_code=401, detail="Authorization header missing")
if authorization != "Bearer mysecrettoken":
raise HTTPException(status_code=401, detail="Invalid authentication token")
return authorization
async def get_current_user(token: str = Depends(verify_token)):
# In a real app, you'd decode the token and fetch the user
return {"username": "johndoe", "email": "[email protected]"}
@app.get("/users/me")
async def read_user_me(current_user: dict = Depends(get_current_user)):
return current_user
In this example, get_current_user
depends on verify_token
, creating a chain of dependencies.
Path Operation Decorators with Dependencies
You can also add dependencies to a path operation decorator, which is useful when you want the dependency to run but don't need to use its return value:
from fastapi import FastAPI, Depends, HTTPException, Header
app = FastAPI()
async def verify_admin(authorization: str = Header(None)):
if not authorization:
raise HTTPException(status_code=401, detail="Authorization header missing")
if authorization != "Bearer admin-token":
raise HTTPException(status_code=403, detail="Admin access required")
return True
@app.get("/admin/", dependencies=[Depends(verify_admin)])
async def admin_portal():
return {"message": "Welcome to the admin portal"}
With this approach, the verify_admin
dependency will run when the endpoint is accessed, but its return value is ignored.
Practical Example: API Rate Limiting
Let's implement a simple rate limiting system using dependencies:
from fastapi import FastAPI, Depends, HTTPException, Request
import time
from collections import defaultdict
app = FastAPI()
# Simple in-memory rate limit store
rate_limiter = defaultdict(list)
RATE_LIMIT = 5 # requests per minute
async def check_rate_limit(request: Request):
client_ip = request.client.host
now = time.time()
# Remove requests older than 60 seconds
rate_limiter[client_ip] = [t for t in rate_limiter[client_ip] if now - t < 60]
# Check if rate limit exceeded
if len(rate_limiter[client_ip]) >= RATE_LIMIT:
raise HTTPException(
status_code=429,
detail="Rate limit exceeded. Try again in a minute."
)
# Record this request
rate_limiter[client_ip].append(now)
return True
@app.get("/limited-resource/", dependencies=[Depends(check_rate_limit)])
async def limited_resource():
return {"message": "This is a rate-limited resource"}
This example implements a basic rate limiter that allows each IP address to make only 5 requests per minute to the /limited-resource/
endpoint.
Router-level Dependencies
You can also apply dependencies to an entire router, making them run for all routes in that router:
from fastapi import FastAPI, Depends, HTTPException, APIRouter
app = FastAPI()
async def verify_api_key(api_key: str = None):
if not api_key or api_key != "valid_key":
raise HTTPException(status_code=403, detail="Invalid API key")
return api_key
# Create a router with dependencies
api_router = APIRouter(dependencies=[Depends(verify_api_key)])
@api_router.get("/items/")
async def read_items():
return {"items": ["Item1", "Item2"]}
@api_router.get("/users/")
async def read_users():
return {"users": ["User1", "User2"]}
# Include the router in the app
app.include_router(api_router, prefix="/api")
Now both /api/items/
and /api/users/
will require a valid API key.
Using Classes as Dependencies
For more complex dependencies, you can use classes with the __call__
method:
from fastapi import FastAPI, Depends
app = FastAPI()
class DatabaseConnection:
def __init__(self):
# In a real app, you'd initialize your database connection here
self.db_connected = True
def __call__(self):
if not self.db_connected:
raise Exception("Database not connected")
# In a real app, you'd return a database session here
return {"status": "connected"}
db_dependency = DatabaseConnection()
@app.get("/db-status/")
async def get_db_status(db: dict = Depends(db_dependency)):
return db
This approach is particularly useful when your dependency needs to be configured or has its own internal state.
Summary
Path dependencies in FastAPI are a powerful tool for:
- Code reuse across multiple endpoints
- Validating input data and parameters
- Authentication and authorization
- Preprocessing requests before they reach your route handlers
- Implementing cross-cutting concerns like logging or rate limiting
By leveraging dependencies effectively, you can write cleaner, more modular FastAPI applications that are easier to maintain and extend.
Additional Resources
- FastAPI Official Documentation on Dependencies
- FastAPI Security and Authentication
- FastAPI Dependency Injection
Exercises
- Create a path dependency that logs all requests to an endpoint, including the method, path, and timestamp.
- Implement a dependency that validates that a user has permission to access a specific resource based on a user ID and resource ID.
- Create a caching dependency that stores responses for GET requests and returns the cached response if the same request is made within 60 seconds.
By mastering path dependencies in FastAPI, you'll be able to create more robust, maintainable APIs with cleaner code organization and better separation of concerns.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)