I make FastAPI application I face to structural problem. For example, let's say there is exist this simple application
from fastapi import FastAPI, Header
from fastapi.responses import JSONResponse
from fastapi.testclient import TestClient
from pydantic import BaseModel, Field
app = FastAPI()
class PydanticSchema(BaseModel):
test_int: int = Field(..., ge=0)
class CustomException(Exception):
def __init__(self, status_code, msg):
self.status_code = status_code
self.msg = msg
@app.exception_handler(CustomException)
async def handle_custom_exception(request, exc: CustomException):
return JSONResponse(
status_code=exc.status_code, content={"error": exc.msg}
)
@app.post("/")
def index(
some_form: PydanticSchema,
some_header: str = Header("", alias="X-Header", max_length=3),
):
is_special_case = some_form.test_int == 42
if is_special_case:
raise CustomException(418, "Very special case")
return "ok"
And here is the problem.
I have THREE independent producers of errors: My manual raise, Pydantic Model, FastApi.
if __name__ == "__main__":
client = TestClient(app)
cases = (
({"X-Header": "abc"}, {"test_int": 0}),
({"X-Header": "abcdef"}, {"test_int": 0}),
({"X-Header": "abc"}, {"test_int": -1}),
({"X-Header": "abc"}, {"test_int": 42}),
)
for case in cases:
r = client.post("/", headers=case[0], json=case[1])
print(f"#####\n{case=}:\n{r.status_code=}\n{r.text=}")
My goal is to have consistent error responses
So I have to add exception handler for my CustomException
,
I have to add error_msg_templates
to PydanticModel.Config
I have to add incredibly stupid handler for FastApi exception and Pydantic exception to reformat them into my custom response. Becasuse fastapi overrides custom messages from pydantic templates.
Like so:
# ---snip---
from fastapi.exceptions import RequestValidationError
# ---snip---
class PydanticSchema(BaseModel):
test_int: int = Field(..., ge=0)
class Config:
error_msg_templates = {"value_error.number.not_ge": "Custom GE error"}
# ---snip---
@app.exception_handler(RequestValidationError)
async def fastapi_error_handler(request, exc: RequestValidationError):
errors = exc.errors()
error_wrapper = exc.raw_errors[0]
validation_error = error_wrapper.exc
from pydantic import error_wrappers as ew
if isinstance(validation_error, ew.ValidationError):
errors = validation_error.errors()
first_error = errors[0]
msg = first_error.get("msg")
# [!] Demonstration
etype = first_error.get("type")
if etype == "value_error.any_str.max_length":
msg = "Custom MAX length error"
return JSONResponse(status_code=400, content={"error": msg})
# ---snip---
It is unmaintainable! But custom error message is required - I can not rely ond default pydantic\fastapi error response schemas.
Does anyone one faced to this problem and have solved it gracefully?