48 lines
1.4 KiB
Python
48 lines
1.4 KiB
Python
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field
|
|
from typing import Optional
|
|
from uuid import uuid4
|
|
|
|
from fastapi import Request
|
|
|
|
|
|
CONTRACT_VERSION = "v1alpha1"
|
|
DEFAULT_EXECUTOR_TYPE = "legacy_subprocess"
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class RequestContext:
|
|
"""Minimal request-scoped metadata passed into application services."""
|
|
|
|
request_id: str
|
|
contract_version: str = CONTRACT_VERSION
|
|
executor_type: str = DEFAULT_EXECUTOR_TYPE
|
|
api_key: Optional[str] = None
|
|
client_host: Optional[str] = None
|
|
is_local: bool = False
|
|
metadata: dict[str, str] = field(default_factory=dict)
|
|
|
|
|
|
def build_request_context(
|
|
request: Optional[Request] = None,
|
|
*,
|
|
api_key: Optional[str] = None,
|
|
request_id: Optional[str] = None,
|
|
contract_version: str = CONTRACT_VERSION,
|
|
executor_type: str = DEFAULT_EXECUTOR_TYPE,
|
|
metadata: Optional[dict[str, str]] = None,
|
|
) -> RequestContext:
|
|
"""Create a stable request context without leaking FastAPI internals into services."""
|
|
client_host = request.client.host if request and request.client else None
|
|
is_local = client_host in {"127.0.0.1", "::1", "localhost", "testclient"}
|
|
return RequestContext(
|
|
request_id=request_id or uuid4().hex,
|
|
contract_version=contract_version,
|
|
executor_type=executor_type,
|
|
api_key=api_key,
|
|
client_host=client_host,
|
|
is_local=is_local,
|
|
metadata=dict(metadata or {}),
|
|
)
|