"""WealthWise FastAPI application factory. This module initializes and configures the FastAPI application with: - Middleware configuration (CORS, logging, error handling) - API router registration - Lifecycle event handlers (startup/shutdown) - Exception handlers """ import time from contextlib import asynccontextmanager from fastapi import FastAPI, Request, status from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.gzip import GZipMiddleware from fastapi.responses import JSONResponse from app.api.v1.api import api_router from app.core.config import get_settings from app.core.db import check_db_connection, close_engine settings = get_settings() @asynccontextmanager async def lifespan(app: FastAPI): """Application lifespan context manager. Handles startup and shutdown events: - Startup: Verify database connectivity, initialize caches - Shutdown: Close database connections, cleanup resources Args: app: FastAPI application instance Yields: None: Application runs during this period """ # Startup print(f"Starting {settings.PROJECT_NAME} v{settings.VERSION}") # Verify database connectivity on startup db_healthy = await check_db_connection() if not db_healthy: print("WARNING: Database connection failed on startup!") else: print("Database connection established") yield # Shutdown print(f"Shutting down {settings.PROJECT_NAME}") await close_engine() print("Database connections closed") def create_application() -> FastAPI: """Create and configure FastAPI application. Returns: Configured FastAPI application instance """ application = FastAPI( title=settings.PROJECT_NAME, description="Production-grade financial analytics platform API", version=settings.VERSION, openapi_url=f"{settings.API_V1_STR}/openapi.json", docs_url=f"{settings.API_V1_STR}/docs", redoc_url=f"{settings.API_V1_STR}/redoc", lifespan=lifespan, ) # CORS Middleware # Explicitly allow Authorization header for JWT Bearer tokens application.add_middleware( CORSMiddleware, allow_origins=settings.cors_origins_list, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], # Includes Authorization header expose_headers=["X-Process-Time"], # Expose custom headers ) # Gzip compression for responses application.add_middleware(GZipMiddleware, minimum_size=1000) # Request timing middleware @application.middleware("http") async def add_process_time_header(request: Request, call_next): """Add X-Process-Time header to all responses.""" start_time = time.time() response = await call_next(request) process_time = time.time() - start_time response.headers["X-Process-Time"] = str(process_time) return response # Include API routers application.include_router( api_router, prefix=settings.API_V1_STR, ) # Root endpoint @application.get("/") async def root(): """Root endpoint - API information.""" return { "name": settings.PROJECT_NAME, "version": settings.VERSION, "docs": f"{settings.API_V1_STR}/docs", "health": f"{settings.API_V1_STR}/health", } # Global exception handlers @application.exception_handler(Exception) async def global_exception_handler(request: Request, exc: Exception): """Handle uncaught exceptions.""" return JSONResponse( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, content={ "error": "Internal server error", "message": str(exc) if settings.DEBUG else "An unexpected error occurred", }, ) return application # Create the application instance app = create_application()