mirror of
https://github.com/grillazz/fastapi-sqlalchemy-asyncpg.git
synced 2025-08-26 16:40:40 +03:00
add excpetion_handlers module
This commit is contained in:
parent
71d90aa1e3
commit
ee637b53e0
3
app/exception_handlers/__init__.py
Normal file
3
app/exception_handlers/__init__.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from app.exception_handlers.registry import register_exception_handlers
|
||||||
|
|
||||||
|
__all__ = ["register_exception_handlers"]
|
34
app/exception_handlers/base.py
Normal file
34
app/exception_handlers/base.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# app/exception_handlers/base.py
|
||||||
|
import orjson
|
||||||
|
from fastapi import Request
|
||||||
|
from fastapi.responses import JSONResponse
|
||||||
|
from rotoger import AppStructLogger
|
||||||
|
|
||||||
|
logger = AppStructLogger().get_logger()
|
||||||
|
|
||||||
|
|
||||||
|
class BaseExceptionHandler:
|
||||||
|
"""Base class for all exception handlers with common functionality."""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def extract_request_info(request: Request):
|
||||||
|
"""Extract common request information."""
|
||||||
|
request_path = request.url.path
|
||||||
|
try:
|
||||||
|
raw_body = await request.body()
|
||||||
|
request_body = orjson.loads(raw_body) if raw_body else None
|
||||||
|
except orjson.JSONDecodeError:
|
||||||
|
request_body = None
|
||||||
|
|
||||||
|
return request_path, request_body
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def log_error(cls, message, request_info, **kwargs):
|
||||||
|
"""Log error with standardized format."""
|
||||||
|
request_path, request_body = request_info
|
||||||
|
await logger.aerror(
|
||||||
|
message,
|
||||||
|
request_url=request_path,
|
||||||
|
request_body=request_body,
|
||||||
|
**kwargs
|
||||||
|
)
|
24
app/exception_handlers/database.py
Normal file
24
app/exception_handlers/database.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
from fastapi import Request
|
||||||
|
from fastapi.responses import JSONResponse
|
||||||
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
|
|
||||||
|
from app.exception_handlers.base import BaseExceptionHandler
|
||||||
|
|
||||||
|
|
||||||
|
class SQLAlchemyExceptionHandler(BaseExceptionHandler):
|
||||||
|
"""Handles SQLAlchemy database exceptions."""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def handle_exception(cls, request: Request, exc: SQLAlchemyError) -> JSONResponse:
|
||||||
|
request_info = await cls.extract_request_info(request)
|
||||||
|
|
||||||
|
await cls.log_error(
|
||||||
|
"Database error occurred",
|
||||||
|
request_info,
|
||||||
|
sql_error=repr(exc)
|
||||||
|
)
|
||||||
|
|
||||||
|
return JSONResponse(
|
||||||
|
status_code=500,
|
||||||
|
content={"message": "A database error occurred. Please try again later."}
|
||||||
|
)
|
11
app/exception_handlers/registry.py
Normal file
11
app/exception_handlers/registry.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
from fastapi import FastAPI
|
||||||
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
|
from fastapi.exceptions import ResponseValidationError
|
||||||
|
|
||||||
|
from app.exception_handlers.database import SQLAlchemyExceptionHandler
|
||||||
|
from app.exception_handlers.validation import ResponseValidationExceptionHandler
|
||||||
|
|
||||||
|
def register_exception_handlers(app: FastAPI) -> None:
|
||||||
|
"""Register all exception handlers with the FastAPI app."""
|
||||||
|
app.add_exception_handler(SQLAlchemyError, SQLAlchemyExceptionHandler.handle_exception)
|
||||||
|
app.add_exception_handler(ResponseValidationError, ResponseValidationExceptionHandler.handle_exception)
|
39
app/exception_handlers/validation.py
Normal file
39
app/exception_handlers/validation.py
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
from fastapi import Request
|
||||||
|
from fastapi.exceptions import ResponseValidationError
|
||||||
|
from fastapi.responses import JSONResponse
|
||||||
|
|
||||||
|
from app.exception_handlers.base import BaseExceptionHandler
|
||||||
|
|
||||||
|
|
||||||
|
class ResponseValidationExceptionHandler(BaseExceptionHandler):
|
||||||
|
"""Handles response validation exceptions."""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def handle_exception(cls, request: Request, exc: ResponseValidationError) -> JSONResponse:
|
||||||
|
request_info = await cls.extract_request_info(request)
|
||||||
|
errors = exc.errors()
|
||||||
|
|
||||||
|
# Check if this is a None/null response case
|
||||||
|
is_none_response = False
|
||||||
|
for error in errors:
|
||||||
|
if error.get("input") is None and "valid dictionary" in error.get("msg", ""):
|
||||||
|
is_none_response = True
|
||||||
|
break
|
||||||
|
|
||||||
|
await cls.log_error(
|
||||||
|
"Response validation error occurred",
|
||||||
|
request_info,
|
||||||
|
validation_errors=errors,
|
||||||
|
is_none_response=is_none_response
|
||||||
|
)
|
||||||
|
|
||||||
|
if is_none_response:
|
||||||
|
return JSONResponse(
|
||||||
|
status_code=404,
|
||||||
|
content={"no_response": "The requested resource was not found"}
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return JSONResponse(
|
||||||
|
status_code=422,
|
||||||
|
content={"response_format_error": errors}
|
||||||
|
)
|
Loading…
x
Reference in New Issue
Block a user