""" Request context using ContextVar for async-safe user/conversation tracking. ContextVar provides task-local storage that automatically propagates through async calls, eliminating the need to thread user identity through every function. Usage: # At request entry (router): token = current_user.set(request.user or get_default_user()) try: await service.process(request) finally: current_user.reset(token) # Anywhere in the codebase: from src.core.context import get_user user = get_user() # Returns current request's user """ from contextvars import ContextVar def get_default_user() -> str: """ Get default user from config (environment-aware). - development/testing: llm_tester (isolated test scope) - production: jpmschweitzer (real user) """ # Import here to avoid circular dependency from src.core.config import config return config.effective_default_user # Request-scoped context variables (async-safe, isolated per request) # Note: ContextVar default is evaluated at definition, so we use a sentinel # and resolve the real default in get_user() _USER_NOT_SET = "__user_not_set__" current_user: ContextVar[str] = ContextVar("current_user", default=_USER_NOT_SET) current_conversation: ContextVar[str | None] = ContextVar( "current_conversation", default=None ) def get_user() -> str: """ Get current user from request context. Returns: User identifier for the current request. Falls back to environment-aware default if not set. Example: user = get_user() # "llm_tester" (dev) or "jpmschweitzer" (prod) """ user = current_user.get() if user == _USER_NOT_SET: return get_default_user() return user def get_conversation_id() -> str | None: """ Get current conversation ID from request context. Returns: Conversation ID if set, None otherwise. Example: conv_id = get_conversation_id() # "conv_abc123" or None """ return current_conversation.get() class RequestContext: """ Context manager for setting request-scoped context. Provides a cleaner alternative to manual token management. Usage: async with RequestContext(user="alice", conversation_id="conv_123"): # All code here sees user="alice" result = await some_service.process() """ def __init__( self, user: str | None = None, conversation_id: str | None = None, ): """ Initialize request context. Args: user: User identifier (defaults to environment-aware user if None) conversation_id: Conversation ID (optional) """ self.user = user or get_default_user() self.conversation_id = conversation_id self._user_token = None self._conv_token = None async def __aenter__(self) -> "RequestContext": """Set context variables on entry.""" self._user_token = current_user.set(self.user) self._conv_token = current_conversation.set(self.conversation_id) return self async def __aexit__(self, exc_type, exc_val, exc_tb) -> None: """Reset context variables on exit.""" if self._user_token is not None: current_user.reset(self._user_token) if self._conv_token is not None: current_conversation.reset(self._conv_token) def __enter__(self) -> "RequestContext": """Sync context manager entry (for non-async code).""" self._user_token = current_user.set(self.user) self._conv_token = current_conversation.set(self.conversation_id) return self def __exit__(self, exc_type, exc_val, exc_tb) -> None: """Sync context manager exit.""" if self._user_token is not None: current_user.reset(self._user_token) if self._conv_token is not None: current_conversation.reset(self._conv_token)