Files
OCR/docengine/app/main.py
2026-06-01 21:49:53 +05:30

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,
)