""" FastAPI application entry point. Following best practices from github.com/zhanymkanov/fastapi-best-practices Main responsibilities: - Application configuration - Middleware setup - Exception handlers - Router registration - Lifecycle management """ from contextlib import asynccontextmanager from typing import AsyncGenerator from fastapi import FastAPI, Request, status from fastapi.exceptions import RequestValidationError from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse from src.chat.router import router as chat_router from src.core.config import config from src.core.exceptions import AppException from src.core.logging_config import get_logger from src.core.router import router as core_router from src.core.startup import initialize_application from src.models.router import router as models_router from src.responses.router import router as responses_router # Get structured logger logger = get_logger(__name__) @asynccontextmanager async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]: """ Application lifespan manager. Handles startup and shutdown logic. """ # Startup logger.info( "application_starting", app_name=config.APP_NAME, version=config.APP_VERSION, environment=config.ENVIRONMENT.value, ollama_host=str(config.OLLAMA_HOST), ollama_model=config.OLLAMA_DEFAULT_MODEL, redis_url=config.redis_url, log_format=config.log_format, ) # Initialize application (register household members, etc.) initialize_application() yield # Shutdown logger.info("application_shutdown") def create_application() -> FastAPI: """ Application factory. Creates and configures the FastAPI application. Following best practice of using factory pattern. """ # Create app application = FastAPI( title=config.APP_NAME, version=config.APP_VERSION, lifespan=lifespan, debug=config.DEBUG, ) # Add middleware application.add_middleware( CORSMiddleware, allow_origins=config.CORS_ORIGINS, allow_credentials=config.CORS_ALLOW_CREDENTIALS, allow_methods=config.CORS_ALLOW_METHODS, allow_headers=config.CORS_ALLOW_HEADERS, ) # Register exception handlers register_exception_handlers(application) # Include routers application.include_router(core_router) # Health and root endpoints application.include_router(chat_router, prefix=config.API_PREFIX) application.include_router(models_router, prefix=config.API_PREFIX) application.include_router(responses_router, prefix=config.API_PREFIX) # Responses API return application def register_exception_handlers(application: FastAPI) -> None: """ Register global exception handlers. Provides consistent error responses compatible with OpenAI API. """ @application.exception_handler(AppException) async def app_exception_handler( request: Request, exc: AppException, ) -> JSONResponse: """Handle custom application exceptions.""" logger.error( "application_exception", error_message=exc.message, error_type=exc.__class__.__name__, status_code=exc.status_code, details=exc.details, path=request.url.path, ) return JSONResponse( status_code=exc.status_code, content={ "error": { "message": exc.message, "type": exc.__class__.__name__, "details": exc.details, } }, ) @application.exception_handler(RequestValidationError) async def validation_exception_handler( request: Request, exc: RequestValidationError, ) -> JSONResponse: """Handle Pydantic validation errors.""" logger.error( "validation_error", errors=exc.errors(), path=request.url.path, ) return JSONResponse( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, content={ "error": { "message": "Invalid request", "type": "invalid_request_error", "details": exc.errors(), } }, ) @application.exception_handler(Exception) async def general_exception_handler( request: Request, exc: Exception, ) -> JSONResponse: """Handle unexpected exceptions.""" logger.exception( "unexpected_error", error_type=type(exc).__name__, error_message=str(exc), path=request.url.path, ) return JSONResponse( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, content={ "error": { "message": "An internal error occurred", "type": "internal_server_error", } }, ) # Create application instance app = create_application()