"""Current-request capture for FastAPI. FastAPI has no global request proxy, so CurrentRequestMiddleware stores the active Starlette Request in a ContextVar. The accessors below read it lazily — in particular get_route_template() must run after routing (inside the endpoint, which is when the authorization check fires), since scope["route"] is only populated once Starlette has matched a route. This module is the unit a service wires up: add the middleware, pass request_tags as the MigrationAuthorizationBackend extra_tags_getter. """ from contextvars import ContextVar from owscontext import get_request_context from starlette.requests import Request from starlette.types import ASGIApp, Receive, Scope, Send _current_request: ContextVar[Request | None] = ContextVar( "current_request", default=None ) class CurrentRequestMiddleware: """Store the active Starlette Request in a ContextVar for request_tags().""" def __init__(self, app: ASGIApp) -> None: self.app = app async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: if scope["type"] != "http": await self.app(scope, receive, send) return token = _current_request.set(Request(scope, receive)) try: await self.app(scope, receive, send) finally: _current_request.reset(token) def get_current_request() -> Request | None: """Return the active Starlette Request, or None outside a request.""" return _current_request.get() def get_route_template() -> str | None: """Matched route template (e.g. /tracks/{track_id}); falls back to the raw path for unmatched routes, or None outside a request. Keeps metric tags low-cardinality by preferring the template over the concrete path. """ request = _current_request.get() if request is None: return None route = request.scope.get("route") return route.path if route is not None else request.url.path def request_tags() -> list[str]: """Datadog tags describing the active request, for use as extra_tags_getter.""" request = _current_request.get() if request is None: return [] context = get_request_context() has_auth_header = bool(context and context.authorization) return [ f"method:{request.method}", f"endpoint:{get_route_template()}", f"has_authorization_header:{str(has_auth_header).lower()}", ]