FastAPI Request Context
When building APIs with FastAPI, understanding how to manage and access data throughout the request lifecycle is essential. In this guide, we'll explore the concept of "request context" - how information can be stored, shared, and accessed during an API request.
What is Request Context?
Request context refers to the information and data that's associated with a specific HTTP request. This data persists throughout the entire lifecycle of the request - from when it first arrives at your server until the response is sent back to the client.
FastAPI provides several mechanisms to store and access this contextual information:
- Request state - attaching data directly to the request object
- Dependency injection - sharing dependencies across request handlers
- Request-scoped variables - variables that exist only within a single request
Let's explore each of these approaches in detail.
Request State
FastAPI allows you to attach custom data to the request object using the request.state
attribute.
Basic Usage of Request State
from fastapi import FastAPI, Request
app = FastAPI()
@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
# Store data in request.state
request.state.user_agent = request.headers.get("User-Agent")
request.state.client_host = request.client.host
# Process the request
response = await call_next(request)
return response
@app.get("/")
async def read_root(request: Request):
# Access data from request.state
return {
"user_agent": request.state.user_agent,
"client_host": request.state.client_host
}
In this example, we use a middleware to store the user agent and client IP address in the request state, making it accessible in any route handler that receives this request.
Dependency Injection for Context
FastAPI's dependency injection system is another powerful way to manage request context. Dependencies can be declared once and reused across multiple endpoints.
Creating a Context Dependency
from fastapi import FastAPI, Depends, Request
from typing import Optional
app = FastAPI()
async def get_user_context(request: Request):
"""Extract and return user context information from request."""
user_agent = request.headers.get("User-Agent", "")
auth_token = request.headers.get("Authorization", "").replace("Bearer ", "")
# You could validate the token, retrieve user info, etc.
# For this example, we'll just return some basic context
return {
"user_agent": user_agent,
"token": auth_token,
"ip_address": request.client.host
}
@app.get("/profile")
async def user_profile(context: dict = Depends(get_user_context)):
return {
"message": "Profile endpoint",
"client_info": context
}
@app.get("/settings")
async def user_settings(context: dict = Depends(get_user_context)):
return {
"message": "Settings endpoint",
"client_info": context
}
Both endpoints can now access the user context without duplicating code.
Request-Scoped Variables with Depends
FastAPI's Depends
can create "request-scoped singletons" - objects that are created once per request and shared across all dependencies.
Creating Request-Scoped Variables
from fastapi import FastAPI, Depends, Request
import uuid
from datetime import datetime
app = FastAPI()
# This will create a new UUID for each request
async def get_request_id():
return str(uuid.uuid4())
# This dependency uses the request_id dependency
async def get_request_metadata(
request_id: str = Depends(get_request_id),
request: Request = None
):
return {
"request_id": request_id,
"timestamp": datetime.now().isoformat(),
"path": request.url.path if request else None
}
@app.get("/items")
async def read_items(metadata: dict = Depends(get_request_metadata)):
# Process the request
return {
"items": ["Item1", "Item2"],
"metadata": metadata
}
@app.get("/items/{item_id}")
async def read_item(
item_id: str,
metadata: dict = Depends(get_request_metadata)
):
return {
"item_id": item_id,
"metadata": metadata
}
Output from /items
endpoint:
{
"items": ["Item1", "Item2"],
"metadata": {
"request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"timestamp": "2023-08-10T14:30:45.123456",
"path": "/items"
}
}
In this example, get_request_id
generates a new UUID for each request, and this same ID is used in all dependencies within that request.
Practical Example: Authentication Context
Here's a more practical example using request context for authentication:
from fastapi import FastAPI, Depends, HTTPException, Request
from fastapi.security import OAuth2PasswordBearer
from typing import Optional
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
# Fake database of users
fake_users_db = {
"johndoe": {
"username": "johndoe",
"full_name": "John Doe",
"email": "[email protected]",
"role": "admin"
}
}
async def get_current_user(token: str = Depends(oauth2_scheme)):
# In a real app, you would decode and verify the token
# For this example, we're using the token as the username
user = fake_users_db.get(token)
if not user:
raise HTTPException(
status_code=401,
detail="Invalid authentication credentials",
headers={"WWW-Authenticate": "Bearer"},
)
return user
class UserContext:
def __init__(self,
user: dict = Depends(get_current_user),
request: Request = None):
self.user = user
self.request_path = request.url.path if request else None
self.is_admin = user.get("role") == "admin"
self.timestamp = datetime.now().isoformat()
@app.get("/users/me")
async def read_own_info(context: UserContext = Depends(UserContext)):
return {
"user_info": context.user,
"request_info": {
"path": context.request_path,
"timestamp": context.timestamp,
"is_admin_access": context.is_admin
}
}
@app.get("/admin/users")
async def list_all_users(context: UserContext = Depends(UserContext)):
# Check if user has admin privileges
if not context.is_admin:
raise HTTPException(status_code=403, detail="Unauthorized access")
# Return all users (in a real app, would fetch from database)
return {
"users": list(fake_users_db.values()),
"accessed_by": context.user["username"],
"timestamp": context.timestamp
}
In this example, UserContext
combines:
- User authentication data
- Request metadata
- Authorization logic (admin check)
This context is then available to route handlers, making it easy to implement consistent authentication and authorization.
Advanced: Starlette Context
For more advanced cases, you might want to use Starlette's context (FastAPI is built on Starlette). This is useful for storing data that needs to be accessible from any part of your code during a request's lifecycle.
First, install the starlette-context package:
pip install starlette-context
Then use it in your FastAPI application:
from fastapi import FastAPI, Request
from starlette.middleware import Middleware
from starlette.middleware.base import BaseHTTPMiddleware
from starlette_context import context
from starlette_context.middleware import RawContextMiddleware
app = FastAPI(middleware=[
Middleware(RawContextMiddleware)
])
class LoggingMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request, call_next):
# Store data in context
context["request_id"] = str(uuid.uuid4())
context["start_time"] = time.time()
response = await call_next(request)
# You can access the context data here too
processing_time = time.time() - context["start_time"]
print(f"Request {context['request_id']} took {processing_time:.4f} seconds")
return response
app.add_middleware(LoggingMiddleware)
@app.get("/items")
async def read_items():
# Access context data from anywhere in the request lifecycle
request_id = context.get("request_id", "unknown")
# Your business logic here
items = ["Item1", "Item2"]
return {
"items": items,
"request_id": request_id
}
Summary
FastAPI provides several mechanisms for managing request context:
- Request State - Simple way to store data directly on the request object
- Dependency Injection - More organized approach for sharing data and logic
- Request-Scoped Variables - Variables shared within a single request
- Starlette Context - Advanced context management for complex applications
Understanding these approaches allows you to:
- Keep your code DRY (Don't Repeat Yourself)
- Maintain consistent behavior across endpoints
- Implement cross-cutting concerns like logging, authentication, and monitoring
- Create more maintainable and testable APIs
Additional Resources
- FastAPI official documentation on dependencies
- Starlette context documentation
- FastAPI middleware documentation
Exercises
- Create a middleware that tracks request processing time and adds it to the request state.
- Implement a dependency that provides database connection information for each request.
- Build a context manager for user authorization that checks permissions for different API endpoints.
- Create a logging system that includes request context information like request IDs and user information.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)