简体   繁体   中英

How to change the default Pydantic error message using FastAPI?

Is there any way to change the default response from Pydantic so that "msg" is "message" ?

{
    "detail": [
        {
            "loc": [
                "body",
                "password"
            ],
            "msg": "Password should at least 8 characters long.",
            "type": "value_error"
        }
    ]
}

That looks like a JSON response and Pydantic by itself does not give out a JSON response for ValidationError 's. It should just be a regular raise -d Exception like this:

In [2]: from pydantic import BaseModel, constr

In [3]: class Credentials(BaseModel):
   ...:     password: constr(min_length=8)
   ...: 

In [4]: Credentials(password="xyz")
---------------------------------------------------------------------------
ValidationError                           Traceback (most recent call last)
Input In [4], in <cell line: 1>()
----> 1 Credentials(password="xyz")

...

ValidationError: 1 validation error for Credentials
password
  ensure this value has at least 8 characters (type=value_error.any_str.min_length; limit_value=8)

I think you are using FastAPI ( which has Pydantic integration ) and this JSON response is actually FastAPI's built-in error response when the request has ValidationError 's, as described in the FastAPI docs on Handling Errors . I can replicate a similar error format with this example:

from fastapi import FastAPI
from fastapi.responses import JSONResponse
from pydantic import BaseModel, constr

class Credentials(BaseModel):
    password: constr(min_length=8)

app = FastAPI()

@app.post("/login")
async def login(credentials: Credentials):
    print(credentials)  # This is just as an example!
    return JSONResponse(status_code=200)
$ curl -s --header "Content-Type: application/json" --request POST --data '{"password":"xyz"}' http://localhost:8000/login | jq
{
  "detail": [
    {
      "loc": [
        "body",
        "password"
      ],
      "msg": "ensure this value has at least 8 characters",
      "type": "value_error.any_str.min_length",
      "ctx": {
        "limit_value": 8
      }
    }
  ]
}

To change the response body, check the FastAPI docs on Use the RequestValidationError body that shows you can access the default detail list of errors, which you can then edit or copy over to a customized details list, and then set it as the content of the JSON error response.

from fastapi import FastAPI, Request, status
from fastapi.encoders import jsonable_encoder
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
from pydantic import BaseModel, constr

class Credentials(BaseModel):
    password: constr(min_length=8)

app = FastAPI()

@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
    # Get the original 'detail' list of errors
    details = exc.errors()
    modified_details = []
    # Replace 'msg' with 'message' for each error
    for error in details:
        modified_details.append(
            {
                "loc": error["loc"],
                "message": error["msg"],
                "type": error["type"],
            }
        )
    return JSONResponse(
        status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
        content=jsonable_encoder({"detail": modified_details}),
    )


@app.post("/login")
async def login(credentials: Credentials):
    print(credentials)  # Just as an example!
    return JSONResponse(status_code=200)
$ curl -s --header "Content-Type: application/json" --request POST --data '{"password":"xyz"}' http://localhost:8000/login | jq
{
  "detail": [
    {
      "loc": [
        "body",
        "password"
      ],
      "message": "ensure this value has at least 8 characters",
      "type": "value_error.any_str.min_length"
    }
  ]
}

Of course, you can also simply just define your own custom content and JSONResponse .

Building upon the previous answer , you can use a validation exception handler , as shown below:

from fastapi import Request, status
from fastapi.encoders import jsonable_encoder
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse

@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
    for error in exc.errors(): 
        error['message'] = error.pop('msg')
    
    return JSONResponse(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, content=jsonable_encoder({"detail": exc.errors()}))

Simple Decorator to adjust the Format!

A decorator is just a function which wraps around another function.

If you are using the @validate decorator, I find it that its easiest to make another decorator and place it above the original one. The new decorator can reformat the output.

In the example below I am using flask and flask-pydantic which is very comparable to fast-api.

from flask import Flask, request, make_response, jsonify
from flask_pydantic import validate
from pydantic import BaseModel
from typing import Optional
from functools import wraps

app = Flask(__name__)

def custom_error(fn):
    @wraps(fn)
    def wrapper(*args, **kwargs):
        res = fn(*args, **kwargs)
        err_json = res.get_json()

        # check if pydantic throws an error (if not continue normally)
        if (err_json is None) or ("validation_error" not in err_json):
            return res
        val_err = err_json["validation_error"]

        # here you implement the logic to change the msg format
        if "body_params" in val_err:
            final_msg = []
            for sing_err in val_err["body_params"]:
                final_msg.append(" ".join([sing_err["loc"][0], sing_err["msg"]]))

            final_msg = ". ".join(final_msg)

        # this is the final response format
        return make_response(jsonify({
            "instance": request.path,
            "errors": {
                "code": 'FLASK_PYDANTIC_VALIDATION_ERROR_STATUS_CODE',
                "reason": final_msg}
                }), 400)
    return wrapper

class ExampleValidator(BaseModel):
    some_int: int
    some_str: str
    opt_int: Optional[int]

@app.route("/test_end", methods=["POST"])
@custom_error
@validate(body=ExampleValidator)
def test_end():
    return make_response('Success', 200)

1- Add Config class to the schema that inheritance from BaseModel.

2- In Config add value_msg_templates dict and start to customize messages: key is error type and value is your new messages for this type.

class UserAuth(BaseModel):
    email: EmailStr = Field(..., description="user email", example="userTest@example.com",)
    username: str = Field(..., min_length=5, max_length=10, description="user username", example="userTest",)
    password: str = Field(..., description="user password", example="Tyu*&^54",)


    class Config:
        error_msg_templates = {
            'value_error.email': 'email address is not valid.',
        }

where is types:

在此处输入图像描述

3- add these lines to main.py or app.py file:


from fastapi import FastAPI, Request, status, HTTPException, Response
from fastapi.responses import JSONResponse
from fastapi.encoders import jsonable_encoder
from fastapi.exceptions import RequestValidationError
from pydantic import ValidationError
from pydantic.error_wrappers import ErrorWrapper 
.
.
.
app = FastAPI()
.
.
.
@app.exception_handler(RequestValidationError)
async def http_exception_accept_handler(request: Request, exc: RequestValidationError) -> Response:
    raw_errors = exc.raw_errors
    error_wrapper: ErrorWrapper = raw_errors[0]
    validation_error: ValidationError = error_wrapper.exc
    overwritten_errors = validation_error.errors()
    return JSONResponse(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
                        content={"detail": jsonable_encoder(overwritten_errors)},
                        )
.
.
.

Finish:)

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