"""FastAPI application entry point.""" from contextlib import asynccontextmanager from fastapi import FastAPI, Request, status from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse from fastapi.exceptions import RequestValidationError import time from src.core.config import settings from src.core.logging import setup_logging, get_logger from src.api.routes import verification, health # Setup logging setup_logging() logger = get_logger(__name__) @asynccontextmanager async def lifespan(app: FastAPI): """Application lifespan manager.""" logger.info(f"Starting {settings.app_name} v{settings.app_version}") logger.info(f"Environment: {settings.environment}") logger.info(f"API running on {settings.api_host}:{settings.api_port}") yield logger.info("Shutting down application") # Create FastAPI app app = FastAPI( title=settings.app_name, version=settings.app_version, description="API for verifying BibTeX references against academic databases", lifespan=lifespan, docs_url="/docs", redoc_url="/redoc", openapi_url="/openapi.json", ) # CORS middleware app.add_middleware( CORSMiddleware, allow_origins=settings.cors_origins_list, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # Request timing middleware @app.middleware("http") async def add_process_time_header(request: Request, call_next): """Add processing time to response headers.""" 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 # Exception handlers @app.exception_handler(RequestValidationError) async def validation_exception_handler(request: Request, exc: RequestValidationError): """Handle validation errors.""" logger.warning(f"Validation error: {exc}") return JSONResponse( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, content={ "error": "ValidationError", "message": "Invalid request data", "details": exc.errors(), }, ) @app.exception_handler(Exception) async def general_exception_handler(request: Request, exc: Exception): """Handle general exceptions.""" logger.exception(f"Unhandled exception: {exc}") return JSONResponse( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, content={ "error": "InternalServerError", "message": "An unexpected error occurred", }, ) # Include routers app.include_router(verification.router) app.include_router(health.router) # Root endpoint @app.get("/", tags=["root"]) async def root(): """Root endpoint.""" return { "name": settings.app_name, "version": settings.app_version, "environment": settings.environment, "docs": "/docs", "health": "/api/v1/health", } if __name__ == "__main__": import uvicorn uvicorn.run( "main:app", host=settings.api_host, port=settings.api_port, reload=settings.is_development, log_level=settings.log_level.lower(), )