mirror of
https://github.com/grillazz/fastapi-sqlalchemy-asyncpg.git
synced 2025-08-26 16:40:40 +03:00
lint and format
This commit is contained in:
parent
9c7db17da8
commit
978041c6ee
@ -1,82 +0,0 @@
|
|||||||
import orjson
|
|
||||||
from fastapi import FastAPI, Request
|
|
||||||
from fastapi.exceptions import ResponseValidationError
|
|
||||||
from fastapi.responses import JSONResponse
|
|
||||||
from rotoger import AppStructLogger
|
|
||||||
from sqlalchemy.exc import SQLAlchemyError
|
|
||||||
|
|
||||||
logger = AppStructLogger().get_logger()
|
|
||||||
|
|
||||||
|
|
||||||
# TODO: add reasoning for this in readme plus higligh using re-raise in db session
|
|
||||||
async def sqlalchemy_exception_handler(
|
|
||||||
request: Request, exc: SQLAlchemyError
|
|
||||||
) -> JSONResponse:
|
|
||||||
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
|
|
||||||
|
|
||||||
await logger.aerror(
|
|
||||||
"Database error occurred",
|
|
||||||
sql_error=repr(exc),
|
|
||||||
request_url=request_path,
|
|
||||||
request_body=request_body,
|
|
||||||
)
|
|
||||||
|
|
||||||
return JSONResponse(
|
|
||||||
status_code=500,
|
|
||||||
content={"message": "A database error occurred. Please try again later."},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def response_validation_exception_handler(
|
|
||||||
request: Request, exc: ResponseValidationError
|
|
||||||
) -> JSONResponse:
|
|
||||||
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
|
|
||||||
|
|
||||||
errors = exc.errors()
|
|
||||||
|
|
||||||
# Check if this is a None/null response case
|
|
||||||
is_none_response = False
|
|
||||||
for error in errors:
|
|
||||||
# Check for null input pattern
|
|
||||||
if error.get("input") is None and "valid dictionary" in error.get("msg", ""):
|
|
||||||
is_none_response = True
|
|
||||||
break
|
|
||||||
|
|
||||||
await logger.aerror(
|
|
||||||
"Response validation error occurred",
|
|
||||||
validation_errors=errors,
|
|
||||||
request_url=request_path,
|
|
||||||
request_body=request_body,
|
|
||||||
is_none_response=is_none_response,
|
|
||||||
)
|
|
||||||
|
|
||||||
if is_none_response:
|
|
||||||
# Return 404 when response is None (resource not found)
|
|
||||||
return JSONResponse(
|
|
||||||
status_code=404,
|
|
||||||
content={"no_response": "The requested resource was not found"},
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
# Return 422 when response exists but doesn't match expected format
|
|
||||||
return JSONResponse(
|
|
||||||
status_code=422,
|
|
||||||
content={"response_format_error": errors},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def register_exception_handlers(app: FastAPI) -> None:
|
|
||||||
"""Register all exception handlers with the FastAPI app."""
|
|
||||||
app.add_exception_handler(SQLAlchemyError, sqlalchemy_exception_handler)
|
|
||||||
app.add_exception_handler(
|
|
||||||
ResponseValidationError, response_validation_exception_handler
|
|
||||||
)
|
|
@ -1,3 +1,3 @@
|
|||||||
from app.exception_handlers.registry import register_exception_handlers
|
from app.exception_handlers.registry import register_exception_handlers
|
||||||
|
|
||||||
__all__ = ["register_exception_handlers"]
|
__all__ = ["register_exception_handlers"]
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import orjson
|
import orjson
|
||||||
|
from attrs import define, field
|
||||||
from fastapi import Request
|
from fastapi import Request
|
||||||
from rotoger import AppStructLogger
|
from rotoger import AppStructLogger
|
||||||
from attrs import define, field
|
|
||||||
|
|
||||||
|
|
||||||
logger = AppStructLogger().get_logger()
|
logger = AppStructLogger().get_logger()
|
||||||
|
|
||||||
@ -10,6 +9,7 @@ logger = AppStructLogger().get_logger()
|
|||||||
@define(slots=True)
|
@define(slots=True)
|
||||||
class RequestInfo:
|
class RequestInfo:
|
||||||
"""Contains extracted request information."""
|
"""Contains extracted request information."""
|
||||||
|
|
||||||
path: str = field()
|
path: str = field()
|
||||||
body: dict = field(default=None)
|
body: dict = field(default=None)
|
||||||
|
|
||||||
@ -39,5 +39,5 @@ class BaseExceptionHandler:
|
|||||||
message,
|
message,
|
||||||
request_url=request_info.path,
|
request_url=request_info.path,
|
||||||
request_body=request_info.body,
|
request_body=request_info.body,
|
||||||
**kwargs
|
**kwargs,
|
||||||
)
|
)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
from fastapi import Request
|
from fastapi import Request
|
||||||
from fastapi.responses import JSONResponse
|
from fastapi.responses import JSONResponse
|
||||||
from sqlalchemy.exc import SQLAlchemyError
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
|
|
||||||
from app.exception_handlers.base import BaseExceptionHandler
|
from app.exception_handlers.base import BaseExceptionHandler
|
||||||
|
|
||||||
|
|
||||||
@ -8,16 +9,16 @@ class SQLAlchemyExceptionHandler(BaseExceptionHandler):
|
|||||||
"""Handles SQLAlchemy database exceptions."""
|
"""Handles SQLAlchemy database exceptions."""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def handle_exception(cls, request: Request, exc: SQLAlchemyError) -> JSONResponse:
|
async def handle_exception(
|
||||||
|
cls, request: Request, exc: SQLAlchemyError
|
||||||
|
) -> JSONResponse:
|
||||||
request_info = await cls.extract_request_info(request)
|
request_info = await cls.extract_request_info(request)
|
||||||
|
|
||||||
await cls.log_error(
|
await cls.log_error(
|
||||||
"Database error occurred",
|
"Database error occurred", request_info, sql_error=repr(exc)
|
||||||
request_info,
|
|
||||||
sql_error=repr(exc)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
status_code=500,
|
status_code=500,
|
||||||
content={"message": "A database error occurred. Please try again later."}
|
content={"message": "A database error occurred. Please try again later."},
|
||||||
)
|
)
|
||||||
|
@ -1,11 +1,16 @@
|
|||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from sqlalchemy.exc import SQLAlchemyError
|
|
||||||
from fastapi.exceptions import ResponseValidationError
|
from fastapi.exceptions import ResponseValidationError
|
||||||
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
|
|
||||||
from app.exception_handlers.database import SQLAlchemyExceptionHandler
|
from app.exception_handlers.database import SQLAlchemyExceptionHandler
|
||||||
from app.exception_handlers.validation import ResponseValidationExceptionHandler
|
from app.exception_handlers.validation import ResponseValidationExceptionHandler
|
||||||
|
|
||||||
|
|
||||||
def register_exception_handlers(app: FastAPI) -> None:
|
def register_exception_handlers(app: FastAPI) -> None:
|
||||||
"""Register all exception handlers with the FastAPI app."""
|
"""Register all exception handlers with the FastAPI app."""
|
||||||
app.add_exception_handler(SQLAlchemyError, SQLAlchemyExceptionHandler.handle_exception)
|
app.add_exception_handler(
|
||||||
app.add_exception_handler(ResponseValidationError, ResponseValidationExceptionHandler.handle_exception)
|
SQLAlchemyError, SQLAlchemyExceptionHandler.handle_exception
|
||||||
|
)
|
||||||
|
app.add_exception_handler(
|
||||||
|
ResponseValidationError, ResponseValidationExceptionHandler.handle_exception
|
||||||
|
)
|
||||||
|
@ -9,14 +9,18 @@ class ResponseValidationExceptionHandler(BaseExceptionHandler):
|
|||||||
"""Handles response validation exceptions."""
|
"""Handles response validation exceptions."""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def handle_exception(cls, request: Request, exc: ResponseValidationError) -> JSONResponse:
|
async def handle_exception(
|
||||||
|
cls, request: Request, exc: ResponseValidationError
|
||||||
|
) -> JSONResponse:
|
||||||
request_info = await cls.extract_request_info(request)
|
request_info = await cls.extract_request_info(request)
|
||||||
errors = exc.errors()
|
errors = exc.errors()
|
||||||
|
|
||||||
# Check if this is a None/null response case
|
# Check if this is a None/null response case
|
||||||
is_none_response = False
|
is_none_response = False
|
||||||
for error in errors:
|
for error in errors:
|
||||||
if error.get("input") is None and "valid dictionary" in error.get("msg", ""):
|
if error.get("input") is None and "valid dictionary" in error.get(
|
||||||
|
"msg", ""
|
||||||
|
):
|
||||||
is_none_response = True
|
is_none_response = True
|
||||||
break
|
break
|
||||||
|
|
||||||
@ -24,16 +28,15 @@ class ResponseValidationExceptionHandler(BaseExceptionHandler):
|
|||||||
"Response validation error occurred",
|
"Response validation error occurred",
|
||||||
request_info,
|
request_info,
|
||||||
validation_errors=errors,
|
validation_errors=errors,
|
||||||
is_none_response=is_none_response
|
is_none_response=is_none_response,
|
||||||
)
|
)
|
||||||
|
|
||||||
if is_none_response:
|
if is_none_response:
|
||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
status_code=404,
|
status_code=404,
|
||||||
content={"no_response": "The requested resource was not found"}
|
content={"no_response": "The requested resource was not found"},
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
status_code=422,
|
status_code=422, content={"response_format_error": errors}
|
||||||
content={"response_format_error": errors}
|
)
|
||||||
)
|
|
||||||
|
@ -27,13 +27,17 @@ async def test_add_stuff(client: AsyncClient):
|
|||||||
)
|
)
|
||||||
response = await client.post("/stuff", json=stuff)
|
response = await client.post("/stuff", json=stuff)
|
||||||
assert response.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR
|
assert response.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||||
assert response.json() == snapshot({'message':'A database error occurred. Please try again later.'})
|
assert response.json() == snapshot(
|
||||||
|
{"message": "A database error occurred. Please try again later."}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def test_get_stuff(client: AsyncClient):
|
async def test_get_stuff(client: AsyncClient):
|
||||||
response = await client.get(f"/stuff/nonexistent")
|
response = await client.get("/stuff/nonexistent")
|
||||||
assert response.status_code == status.HTTP_404_NOT_FOUND
|
assert response.status_code == status.HTTP_404_NOT_FOUND
|
||||||
assert response.json() == snapshot({'no_response':'The requested resource was not found'})
|
assert response.json() == snapshot(
|
||||||
|
{"no_response": "The requested resource was not found"}
|
||||||
|
)
|
||||||
stuff = StuffFactory.build(factory_use_constructors=True).model_dump(mode="json")
|
stuff = StuffFactory.build(factory_use_constructors=True).model_dump(mode="json")
|
||||||
await client.post("/stuff", json=stuff)
|
await client.post("/stuff", json=stuff)
|
||||||
name = stuff["name"]
|
name = stuff["name"]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user