"""Factory for creating FastAPI applications with consistent setup.""" # FILE: libs/app_factory.py from collections.abc import AsyncIterator, Awaitable, Callable from contextlib import asynccontextmanager from typing import Any from fastapi import FastAPI, HTTPException, Request from fastapi.responses import JSONResponse from libs.config import BaseAppSettings, get_default_settings from libs.observability import setup_observability from libs.schemas import ErrorResponse from libs.security import get_current_user, get_tenant_id from libs.security.middleware import TrustedProxyMiddleware def create_trusted_proxy_middleware( internal_cidrs: list[str], disable_auth: bool = False ) -> TrustedProxyMiddleware: """Create a TrustedProxyMiddleware instance with the given internal CIDRs.""" # This is a factory function that will be called by FastAPI's add_middleware # We return a partial function that creates the middleware def middleware_factory(app: Any) -> TrustedProxyMiddleware: return TrustedProxyMiddleware(app, internal_cidrs, disable_auth) return middleware_factory # type: ignore def create_app( # pylint: disable=too-many-arguments,too-many-positional-arguments service_name: str, title: str, description: str, version: str = "1.0.0", settings_class: type[BaseAppSettings] = BaseAppSettings, custom_settings: dict[str, Any] | None = None, startup_hooks: list[Callable[[], Awaitable[None]]] | None = None, shutdown_hooks: list[Callable[[], Awaitable[None]]] | None = None, ) -> tuple[FastAPI, BaseAppSettings]: """Create a FastAPI application with standard configuration""" # Create settings settings_kwargs = {"service_name": service_name} if custom_settings: settings_kwargs.update(custom_settings) settings = get_default_settings(**settings_kwargs) if settings_class != BaseAppSettings: # Use custom settings class settings = settings_class(**settings_kwargs) # type: ignore # Create lifespan context manager @asynccontextmanager async def lifespan( app: FastAPI, ) -> AsyncIterator[None]: # pylint: disable=unused-argument # Startup setup_observability(settings) if startup_hooks: for hook in startup_hooks: await hook() yield # Shutdown if shutdown_hooks: for hook in shutdown_hooks: await hook() # Create FastAPI app app = FastAPI( title=title, description=description, version=version, lifespan=lifespan ) # Add middleware app.add_middleware( TrustedProxyMiddleware, internal_cidrs=settings.internal_cidrs, disable_auth=getattr(settings, "disable_auth", False), ) # Add exception handlers @app.exception_handler(HTTPException) async def http_exception_handler( request: Request, exc: HTTPException ) -> JSONResponse: """Handle HTTP exceptions with RFC7807 format""" return JSONResponse( status_code=exc.status_code, content=ErrorResponse( type=f"https://httpstatuses.com/{exc.status_code}", title=exc.detail, status=exc.status_code, detail=exc.detail, instance=str(request.url), trace_id=getattr(request.state, "trace_id", None), ).model_dump(), ) # Add health endpoints @app.get("/healthz") async def health_check() -> dict[str, str]: """Health check endpoint""" return { "status": "healthy", "service": settings.service_name, "version": version, } @app.get("/readyz") async def readiness_check() -> dict[str, str]: """Readiness check endpoint""" return {"status": "ready", "service": settings.service_name, "version": version} @app.get("/livez") async def liveness_check() -> dict[str, str]: """Liveness check endpoint""" return {"status": "alive", "service": settings.service_name, "version": version} return app, settings # Dependency factories def get_user_dependency() -> Any: """Get user dependency function""" return get_current_user() def get_tenant_dependency() -> Any: """Get tenant dependency function""" return get_tenant_id()