108 lines
3.1 KiB
Python
108 lines
3.1 KiB
Python
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,
|
|
)
|