简体   繁体   中英

How to customize error schema in FastApi and Pydantic?

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?

Why not FastAPI's Exception_handler argument? You can view the status code of the response when this error occurs, then add an exception handler. Exception handlers have access to the Request & Exception objects, which are the request that caused the exception, and the exception raised, respectively.

from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from fastapi.exceptions import RequestValidationError

def val_err(request: Request, exception: Exception):
    if not isinstance(exception, RequestValidationError):
        return
    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})
    

exception_handlers = {422: val_err}
app = FastAPI(exception_handlers=exception_handlers)

You can refer to my other answer which dissects the code and explains it in a bit more detail.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM