| | """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() |
| | 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") |
| |
|
| |
|
| | |
| | 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", |
| | ) |
| |
|
| | |
| | app.add_middleware( |
| | CORSMiddleware, |
| | allow_origins=settings.cors_origins_list, |
| | allow_credentials=True, |
| | allow_methods=["*"], |
| | allow_headers=["*"], |
| | ) |
| |
|
| |
|
| | |
| | @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 |
| |
|
| |
|
| | |
| | @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", |
| | }, |
| | ) |
| |
|
| |
|
| | |
| | app.include_router(verification.router) |
| | app.include_router(health.router) |
| |
|
| |
|
| | |
| | @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(), |
| | ) |
| |
|