from fastapi import FastAPI, Request, Depends, Query from fastapi.responses import RedirectResponse, JSONResponse from uuid import UUID, uuid4 import logging import traceback from typing import Callable, Optional from anyio.to_thread import run_sync logger = logging.getLogger(__name__) class RedirectToContext(Exception): def __init__(self, url: str): self.url = url logger.info(f"Redirect to Context: {url}") super().__init__(f"Redirect to Context: {url}") class ContextRouteManager: def __init__(self, app: FastAPI): self.app = app self.setup_handlers() def setup_handlers(self): @self.app.exception_handler(RedirectToContext) async def handle_context_redirect(request: Request, exc: RedirectToContext): logger.info(f"Handling redirect to {exc.url}") return RedirectResponse(url=exc.url, status_code=307) def ensure_context( self, route_name: str = "context_id" ) -> Callable[[Request], Optional[UUID]]: logger.info(f"Setting up context dependency for route parameter: {route_name}") async def _ensure_context_dependency(request: Request) -> Optional[UUID]: logger.info( f"Entering ensure_context_dependency, Request URL: {request.url}" ) logger.info(f"Path params: {request.path_params}") path_params = request.path_params route_value = path_params.get(route_name) logger.info(f"route_value: {route_value!r}, type: {type(route_value)}") if ( route_value is None or not isinstance(route_value, str) or not route_value.strip() ): logger.info(f"route_value is invalid, generating new UUID") path = request.url.path.rstrip("/") new_context = await run_sync(uuid4) redirect_url = f"{path}/{new_context}" logger.info(f"Redirecting to {redirect_url}") raise RedirectToContext(redirect_url) logger.info(f"Attempting to parse route_value as UUID: {route_value}") try: route_context = await run_sync(UUID, route_value) logger.info(f"Successfully parsed UUID: {route_context}") return route_context except ValueError as e: logger.error( f"Failed to parse UUID from route_value: {route_value!r}, error: {str(e)}" ) path = request.url.path.rstrip("/") new_context = await run_sync(uuid4) redirect_url = f"{path}/{new_context}" logger.info(f"Invalid UUID, redirecting to {redirect_url}") raise RedirectToContext(redirect_url) return _ensure_context_dependency def route_pattern(self, path: str, *dependencies, **kwargs): logger.info(f"Registering route: {path}") ensure_context = self.ensure_context() def decorator(func): all_dependencies = list(dependencies) all_dependencies.append(Depends(ensure_context)) logger.info( f"Route {path} registered with dependencies: {all_dependencies}" ) return self.app.get(path, dependencies=all_dependencies, **kwargs)(func) return decorator app = FastAPI(redirect_slashes=True) @app.exception_handler(Exception) async def global_exception_handler(request: Request, exc: Exception): logger.error(f"Unhandled exception: {str(exc)}") logger.error(f"Request URL: {request.url}, Path params: {request.path_params}") logger.error(f"Stack trace: {''.join(traceback.format_tb(exc.__traceback__))}") return JSONResponse( status_code=500, content={"error": "Internal server error", "detail": str(exc)} ) @app.middleware("http") async def log_requests(request: Request, call_next): logger.info( f"Incoming request: {request.method} {request.url}, Path params: {request.path_params}" ) response = await call_next(request) return response context_router = ContextRouteManager(app) @context_router.route_pattern("/api/history/{context_id}") async def get_history( request: Request, context_id: UUID = Depends(context_router.ensure_context()), agent_type: str = Query(..., description="Type of agent to retrieve history for"), ): logger.info(f"{request.method} {request.url.path} with context_id: {context_id}") return {"context_id": str(context_id), "agent_type": agent_type} @app.get("/api/history") async def redirect_history( request: Request, agent_type: str = Query(..., description="Type of agent to retrieve history for"), ): path = request.url.path.rstrip("/") new_context = uuid4() redirect_url = f"{path}/{new_context}?agent_type={agent_type}" logger.info(f"Redirecting from /api/history to {redirect_url}") return RedirectResponse(url=redirect_url, status_code=307) if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=8900)