Files
plantillas-proyectos/backend/core/error_handlers.py
Galindo97 af99e43ee5 Merge remote-tracking branch 'origin/development' into feature/Invoice-movements
# Conflicts:
#	backend/api/v1/modules/a76/reports/importacion/facturas/usa/service.py
#	backend/core/celery_app.py
#	frontend/src/lib/api/dashboard/a76/general_catalogs/company.ts
2026-02-20 10:09:19 -06:00

201 lines
5.7 KiB
Python

"""
Manejadores globales de excepciones para FastAPI
"""
import logging
from typing import Any, Dict
from fastapi import Request, status, HTTPException
from fastapi.responses import JSONResponse
from fastapi.encoders import jsonable_encoder
from fastapi.exceptions import RequestValidationError
from sqlalchemy.exc import IntegrityError, SQLAlchemyError
from .exceptions import BaseAPIException
logger = logging.getLogger(__name__)
async def base_exception_handler(
request: Request,
exc: BaseAPIException,
) -> JSONResponse:
"""
Manejador para todas las excepciones personalizadas de la API
"""
logger.warning(
f"API Exception: {exc.error_code} - {exc.message}",
extra={
"path": request.url.path,
"method": request.method,
"status_code": exc.status_code,
},
)
# Log detailed errors if they exist
if hasattr(exc, "errors") and exc.errors:
logger.warning(f"Validation errors details: {exc.errors}")
return JSONResponse(
status_code=exc.status_code,
content=jsonable_encoder(exc.to_dict()),
)
async def validation_exception_handler(
request: Request,
exc: RequestValidationError,
) -> JSONResponse:
"""
Manejador para errores de validación de Pydantic/FastAPI
"""
errors = []
for error in exc.errors():
field = ".".join(str(loc) for loc in error["loc"] if loc != "body")
errors.append(
{
"field": field,
"message": error["msg"],
"type": error["type"],
}
)
logger.warning(
f"Validation Error en {request.url.path}",
extra={"errors": errors},
)
return JSONResponse(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
content={
"error": "VALIDATION_ERROR",
"message": "Error de validación en los datos recibidos",
"status_code": status.HTTP_422_UNPROCESSABLE_ENTITY,
"errors": errors,
},
)
async def integrity_error_handler(
request: Request,
exc: IntegrityError,
) -> JSONResponse:
"""
Manejador para errores de integridad de la base de datos
"""
logger.error(
f"Database Integrity Error: {str(exc.orig)}",
extra={
"path": request.url.path,
"method": request.method,
},
)
orig_msg = str(exc.orig).lower()
# Check for unique/duplicate key violations (English and Spanish)
if any(kw in orig_msg for kw in ["unique constraint", "duplicate key", "duplicada", "unicidad", "ya existe"]):
error_message = "El registro ya existe. Verifica los campos únicos (Año, Aduana, Patente, Número, etc.)."
# Check for foreign key violations (English and Spanish)
elif any(kw in orig_msg for kw in ["foreign key", "foránea", "referencia"]):
error_message = "Referencia inválida a otro registro. Verifica las categorías y catálogos seleccionados."
# Check for not null violations (English and Spanish)
elif any(kw in orig_msg for kw in ["not null", "no nulo", "valor nulo"]):
error_message = "Falta un campo requerido. Asegúrate de llenar todos los datos obligatorios."
else:
error_message = "Error de integridad en la base de datos"
return JSONResponse(
status_code=status.HTTP_409_CONFLICT,
content={
"error": "DATABASE_INTEGRITY_ERROR",
"message": error_message,
"status_code": status.HTTP_409_CONFLICT,
},
)
async def sqlalchemy_error_handler(
request: Request,
exc: SQLAlchemyError,
) -> JSONResponse:
"""
Manejador para errores generales de SQLAlchemy
"""
logger.error(
f"Database Error: {str(exc)}",
extra={
"path": request.url.path,
"method": request.method,
},
exc_info=True,
)
return JSONResponse(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
content={
"error": "DATABASE_ERROR",
"message": "Error en la operación de base de datos",
"status_code": status.HTTP_500_INTERNAL_SERVER_ERROR,
},
)
async def http_exception_handler(
request: Request,
exc: HTTPException,
) -> JSONResponse:
"""
Manejador para HTTPException de FastAPI
"""
return JSONResponse(
status_code=exc.status_code,
content={
"error": "HTTP_ERROR",
"message": exc.detail,
"status_code": exc.status_code,
},
)
async def general_exception_handler(
request: Request,
exc: Exception,
) -> JSONResponse:
"""
Manejador para excepciones no capturadas
"""
logger.error(
f"Unhandled Exception: {str(exc)}",
extra={
"path": request.url.path,
"method": request.method,
},
exc_info=True,
)
return JSONResponse(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
content={
"error": "INTERNAL_SERVER_ERROR",
"message": "Error interno del servidor",
"status_code": status.HTTP_500_INTERNAL_SERVER_ERROR,
},
)
def register_exception_handlers(app) -> None:
"""
Registra todos los manejadores de excepciones en la aplicación FastAPI
Args:
app: Instancia de FastAPI
"""
app.add_exception_handler(BaseAPIException, base_exception_handler)
app.add_exception_handler(HTTPException, http_exception_handler)
app.add_exception_handler(RequestValidationError, validation_exception_handler)
app.add_exception_handler(IntegrityError, integrity_error_handler)
app.add_exception_handler(SQLAlchemyError, sqlalchemy_error_handler)
app.add_exception_handler(Exception, general_exception_handler)