from __future__ import annotations from contextlib import asynccontextmanager from collections.abc import AsyncGenerator from typing import Any from fastapi import FastAPI, Request, status from fastapi.exceptions import RequestValidationError from fastapi.responses import JSONResponse from app.api.router import api_v1_router from app.core.config import settings from app.core.exceptions import DocEngineException from app.events.handlers import on_shutdown, on_startup from app.middleware.audit import AuditMiddleware from app.middleware.cors import setup_cors from app.middleware.metrics import setup_metrics from app.middleware.rate_limit import RateLimitMiddleware @asynccontextmanager async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]: """Application lifespan manager.""" on_startup() yield on_shutdown() app = FastAPI( title=settings.app_name, description="Document Template Recognition and Reconstruction System", version=settings.app_version, docs_url="/docs", redoc_url="/redoc", openapi_url="/openapi.json", lifespan=lifespan, ) # Setup middleware (order matters: last added = first executed) setup_cors(app) app.add_middleware(AuditMiddleware) app.add_middleware(RateLimitMiddleware) # Setup Prometheus metrics setup_metrics(app) # Include API routes app.include_router(api_v1_router) # Exception handlers @app.exception_handler(DocEngineException) async def docengine_exception_handler(request: Request, exc: DocEngineException) -> JSONResponse: """Handle application-specific exceptions.""" return JSONResponse( status_code=exc.status_code, content={ "detail": exc.detail, "error_code": type(exc).__name__, "extra": exc.extra if exc.extra else None, }, ) @app.exception_handler(RequestValidationError) async def validation_exception_handler(request: Request, exc: RequestValidationError) -> JSONResponse: """Handle request validation errors.""" errors = [] for error in exc.errors(): errors.append({ "field": ".".join(str(loc) for loc in error.get("loc", [])), "message": error.get("msg", ""), "type": error.get("type", ""), }) return JSONResponse( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, content={ "detail": "Request validation failed", "error_code": "ValidationError", "errors": errors, }, ) @app.exception_handler(Exception) async def general_exception_handler(request: Request, exc: Exception) -> JSONResponse: """Handle unexpected exceptions.""" return JSONResponse( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, content={ "detail": "An unexpected error occurred" if settings.is_production else str(exc), "error_code": "InternalServerError", }, ) if __name__ == "__main__": import uvicorn uvicorn.run( "app.main:app", host=settings.app_host, port=settings.app_port, reload=not settings.is_production, workers=1 if settings.app_debug else settings.app_workers, )