Skip to main content

FastAPI Depends and the Request Object

· 7 min read
Serhii Hrekov
software engineer, creator, artist, programmer, projects founder

While dependencies primarily focus on injecting services or validated data, they can also gain direct access to the raw HTTP request object. This is an advanced technique useful for accessing metadata, non-standard headers, client information, or the complete request body/form data before it's processed by the route.

To access the request object, you simply declare a parameter with the type hint Request in your dependency function. FastAPI's Dependency Injection system automatically resolves the current request and injects it.

Practical Use Cases for Request-Aware Dependencies

The following examples demonstrate how to use the Request object within a dependency for various complex tasks.

from fastapi import FastAPI, Depends, Request, Header, HTTPException
from typing import Annotated
import time
import json

app = FastAPI()

# --- Utility Functions (Mocks) ---
def generate_unique_id():
"""Generates a unique request ID (mocking uuid)."""
return f"req-{int(time.time() * 1000)}"

# --- 1. Use Case: Injecting a Correlation ID ---
def get_correlation_id(
request: Request,
x_trace_id: Annotated[str | None, Header(alias="X-Trace-ID")] = None
) -> str:
"""
Retrieves the correlation ID from a header or generates a new one.
"""
if x_trace_id:
return x_trace_id

# Access client metadata from the Request object
client_host = request.client.host if request.client else "unknown"
return f"{generate_unique_id()}-{client_host.split('.')[-1]}"

CorrelationID = Annotated[str, Depends(get_correlation_id)]

@app.get("/trace_test")
def trace_test_route(trace_id: CorrelationID):
return {"message": "Request processed.", "trace_id": trace_id}

Use Case: Whitelisting Client IPs

You can enforce access control by checking the client's host address directly against a list of authorized IPs using the Request.client.host attribute.

AUTHORIZED_IPS = ["127.0.0.1", "192.168.0.1"]

def check_ip_whitelist(request: Request):
"""
Checks if the client's host is in the authorized list.
"""
client_ip = request.client.host if request.client else "unknown"

if client_ip not in AUTHORIZED_IPS:
print(f"BLOCKING unauthorized access from: {client_ip}")
raise HTTPException(
status_code=403,
detail=f"IP {client_ip} is not authorized."
)
print(f"Authorized access from: {client_ip}")

IPGuard = Annotated[None, Depends(check_ip_whitelist)]

@app.get("/internal", dependencies=[Depends(check_ip_whitelist)])
def internal_route():
# Only executes if the client's IP is whitelisted
return {"data": "Secure internal metrics."}

Use Case: Debugging and Context Logging

Dependencies can log contextual information about the request that isn't typically available directly in the route handler, such as the full URL path, HTTP method, and client metadata.

def log_request_context(request: Request):
"""Logs the request method, path, and client address."""
log_data = {
"method": request.method,
"path": request.url.path,
"client": request.client.host if request.client else "N/A"
}
# In a real application, this would go to a logger.
print(f"[CONTEXT LOG] {json.dumps(log_data)}")

# Return the log data if needed, or None for side-effect only
return log_data

RequestContext = Annotated[dict, Depends(log_request_context)]

@app.post("/submit")
def submit_data(context: RequestContext):
# The logging dependency runs on every request
return {"status": "received", "context_logged": context}

Use Case: Accessing Raw Request Body (Advanced)

While FastAPI normally processes the body into Pydantic models, you can use the Request object to access the raw byte content of the request body. This is necessary if you need to perform custom processing (e.g., streaming, hash calculation) before model parsing.

import hashlib

async def calculate_body_hash(request: Request):
"""
Reads the raw body and calculates its SHA256 hash.
NOTE: Reading the body consumes it. This pattern requires care
if the route handler also needs the body.
"""
try:
# Use await request.body() to get the bytes
body_bytes = await request.body()
return hashlib.sha256(body_bytes).hexdigest()
except Exception as e:
print(f"Error reading body: {e}")
return "hash_error"

BodyHash = Annotated[str, Depends(calculate_body_hash)]

# NOTE: Since the dependency consumes the body, the route must not try to
# parse a Pydantic model directly unless the dependency is designed to
# store and re-inject the body.
@app.put("/upload")
async def upload_file(hash_value: BodyHash):
# Route logic relies on the hash calculated in the dependency
return {"message": "Body hash calculated successfully.", "sha256": hash_value}

Use Case: Accessing Request Headers and Query Parameters Directly

While using Header and Query functions is the standard and preferred way to access these elements, the Request object gives you access to the raw header and query dictionaries for custom inspection or meta-checks.

def inspect_raw_query(request: Request):
"""Inspects the raw headers and query parameters dictionaries."""

# Access raw header dictionary (usually case-insensitive in FastAPI, but raw here)
content_type = request.headers.get('content-type', 'none')

# Access raw query parameters dictionary
raw_query = dict(request.query_params)

return {
"content_type": content_type,
"raw_query_keys": list(raw_query.keys())
}

RawInspection = Annotated[dict, Depends(inspect_raw_query)]

@app.get("/inspect")
def inspect_route(raw_data: RawInspection):
"""Example URL: /inspect?test=1&limit=5"""
return {"inspection": raw_data}

Sources and Further Reading

  1. FastAPI Documentation - Using the Request Object Directly
  2. Starlette Documentation - Request Object Attributes
  3. FastAPI Documentation - Header and Query Parameters
  4. FastAPI Documentation - Dependencies with async