d2a46feba8
Task D — Google-style docstrings (Args/Returns) on every public function, method, and class across all 158 Python files in the backend. Zero ruff D violations (pydocstyle Google convention). Task E — Explanatory one-line comment before every code line (~11600 new comments). ruff check passes clean after isort re-sort.
75 lines
2.6 KiB
Python
75 lines
2.6 KiB
Python
"""Request context middleware — captures client IP and User-Agent per request."""
|
|
|
|
# Import Awaitable, Callable from collections.abc
|
|
from collections.abc import Awaitable, Callable
|
|
|
|
# Import ContextVar from contextvars
|
|
from contextvars import ContextVar
|
|
|
|
# Import Request from fastapi
|
|
from fastapi import Request
|
|
|
|
# Import BaseHTTPMiddleware from starlette.middleware.base
|
|
from starlette.middleware.base import BaseHTTPMiddleware
|
|
|
|
# Import Response from starlette.responses
|
|
from starlette.responses import Response
|
|
|
|
# Assign request_ip = ContextVar("request_ip", default="")
|
|
request_ip: ContextVar[str] = ContextVar("request_ip", default="")
|
|
# Assign request_user_agent = ContextVar("request_user_agent", default="")
|
|
request_user_agent: ContextVar[str] = ContextVar("request_user_agent", default="")
|
|
|
|
|
|
# Define function resolve_client_ip
|
|
def resolve_client_ip(request: Request) -> str:
|
|
"""Extract the real client IP, honouring ``X-Forwarded-For`` when present.
|
|
|
|
Args:
|
|
request (Request): The incoming Starlette/FastAPI request.
|
|
|
|
Returns:
|
|
str: The resolved client IP address, or ``"unknown"`` when unavailable.
|
|
"""
|
|
# Assign forwarded = request.headers.get("X-Forwarded-For")
|
|
forwarded = request.headers.get("X-Forwarded-For")
|
|
# Check: forwarded
|
|
if forwarded:
|
|
# Return forwarded.split(",")[0].strip()
|
|
return forwarded.split(",")[0].strip()
|
|
# Check: request.client
|
|
if request.client:
|
|
# Return request.client.host
|
|
return request.client.host
|
|
# Return "unknown"
|
|
return "unknown"
|
|
|
|
|
|
# Define class RequestContextMiddleware
|
|
class RequestContextMiddleware(BaseHTTPMiddleware):
|
|
"""Middleware that captures client IP and User-Agent into context variables."""
|
|
|
|
# Define async function dispatch
|
|
async def dispatch(
|
|
self,
|
|
# Entry: request
|
|
request: Request,
|
|
# Entry: call_next
|
|
call_next: Callable[[Request], Awaitable[Response]],
|
|
) -> Response:
|
|
"""Store client IP and User-Agent in context vars for the current request.
|
|
|
|
Args:
|
|
request (Request): The incoming HTTP request.
|
|
call_next (Callable[[Request], Awaitable[Response]]): The next middleware or route handler.
|
|
|
|
Returns:
|
|
Response: The HTTP response produced by the downstream handler.
|
|
"""
|
|
# Call request_ip.set()
|
|
request_ip.set(resolve_client_ip(request))
|
|
# Call request_user_agent.set()
|
|
request_user_agent.set(request.headers.get("User-Agent", ""))
|
|
# Return await call_next(request)
|
|
return await call_next(request)
|