28

I'm looking for some library or example of code to format FastAPI validation messages into human-readable format. E.g. this endpoint:

@app.get("/")
async def hello(name: str):
    return {"hello": name}

Will produce the next json output if we miss name query parameter:

{ 
    "detail":[ 
        { 
            "loc":[ 
                "query",
                "name"
            ],
            "msg":"field required",
            "type":"value_error.missing"
        }
    ]
}

So my questions is, how to:

  1. Transform it into something like "name field is required" (for all kinds of possible errors) to show in toasts.
  2. Use it to display form validation messages
  3. Generate forms themselves from api description if it's possible
1
  • Please see related answers here, here and here, as well as have a look at this, this and this. Commented Nov 3, 2022 at 11:04

4 Answers 4

19

FastAPI has a great Exception Handling, so you can customize your exceptions in many ways.

You can raise an HTTPException, HTTPException is a normal Python exception with additional data relevant for APIs. But you can't return it you need to raise it because it's a Python exception

from fastapi import HTTPException
...
@app.get("/")
async def hello(name: str):
    if not name:
        raise HTTPException(status_code=404, detail="Name field is required")
    return {"Hello": name}

By adding name: str as a query parameter it automatically becomes required so you need to add Optional

from typing import Optional
...
@app.get("/")
async def hello(name: Optional[str] = None):
    error = {"Error": "Name field is required"}
    if name:
        return {"Hello": name}
    return error

$ curl 127.0.0.1:8000/?name=imbolc
{"Hello":"imbolc"}
...
$ curl 127.0.0.1:8000
{"Error":"Name field is required"}

But in your case, and i think this is the best way to handling errors in FastAPI overriding the validation_exception_handler:

from fastapi import FastAPI, 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):
    return JSONResponse(
        status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
        content=jsonable_encoder({"detail": exc.errors(), "Error": "Name field is missing"}),
    )
...
@app.get("/")
async def hello(name: str):
    return {"hello": name}

You will get a response like this:

$ curl 127.0.0.1:8000

 {
   "detail":[
      {
         "loc":[
            "query",
            "name"
         ],
         "msg":"field required",
         "type":"value_error.missing"
      }
   ],
   "Error":"Name field is missing"
}

You can customize your content however if you like:

{
"Error":"Name field is missing",
   "Customize":{
      "This":"content",
      "Also you can":"make it simpler"
   }
}
Sign up to request clarification or add additional context in comments.

4 Comments

Not one of a great ideas to override validation_exception_handler and hardcode an error message in there; basically you can apply it only on a hello world example..
@Michele,, you can basically handle all the cases since you have access to the Request object or the RequestValidationError .
@Erez, you have validation out-of-box without doing anything, yes FastAPI is great at error handling, if your expectation is the handle all the cases error cases without writing any code, I have bad news for you.
jsonable_encoder did it for me, AssertionError and ValueError don't get encoded alone on a JSONResponse
17

I reached here with a similar question - and I ended up handling the RequestValidationError to give back a response where every field is an array of the issues with that field. The response to your request would become (with a status_code=400)

    {
        "detail": "Invalid request",
        "errors": {"name": ["field required"]}
    }

that's quite handy to manage on the frontend for snackbar notifications and flexible enough.

Here's the handler


from collections import defaultdict

from fastapi import status
from fastapi.encoders import jsonable_encoder
from fastapi.responses import JSONResponse


@app.exception_handler(RequestValidationError)
async def custom_form_validation_error(request, exc):
    reformatted_message = defaultdict(list)
    for pydantic_error in exc.errors():
        loc, msg = pydantic_error["loc"], pydantic_error["msg"]
        filtered_loc = loc[1:] if loc[0] in ("body", "query", "path") else loc
        field_string = ".".join(filtered_loc)  # nested fields with dot-notation
        reformatted_message[field_string].append(msg)

    return JSONResponse(
        status_code=status.HTTP_400_BAD_REQUEST,
        content=jsonable_encoder(
            {"detail": "Invalid request", "errors": reformatted_message}
        ),
    )

If you want then to change the error schema in the Swagger doc, define the model:

class ValidationErrorResponse(BaseModel):
    detail: str = Field("Invalid request", description="The general error message")
    errors: dict[str, list[str]] = Field(
        description="Detailed field-specific errors",
        example={"field_name": ["Field related error 1", "Field related error 2"]},
    )

And change the response model in the relevant endpoints where you do the form validation, i.e.:

@app.post("/form")
def receive_form(..., responses={400: {"model": ValidationErrorResponse}}):
    ....

2 Comments

This is great -- were you able to get your openapi spec to show the schema you created as the validation error schema?
Good point @Jonathon - I've updated the response (after discovering I can't use code-blocks in comments 😅). You can define a ValidationErrorResponse model and use it to define the 400 responses on the relevant endpoints
2

I think the best I can come up with is actually PlainTextResponse

Adding these:

from fastapi.exceptions import RequestValidationError

@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request, exc):
    return PlainTextResponse(str(exc), status_code=400)

You get a more human-friendly error message like these in plain text:

1 validation error
path -> item_id
  value is not a valid integer (type=type_error.integer)

It's well documented in FastAPI docs here.

Comments

1

Building on the other answers here, you can catch the RequestValidationError, and build a nice formatted string for any error. Unlike others, it will print nice messages for any error (missing fields, REGEX issues, length, etc)

This returns a message like Name: String should have at most 120 characters while still returning the structured errors in another field.

@app.exception_handler(RequestValidationError)
async def validation_exception_handler(
    request: Request, exc: RequestValidationError
):
    # Write user friendly error messages
    error_messages = []
    for error in exc.errors():
        field = error["loc"][-1]  # Get the field name
        message = error["msg"]
        error_messages.append(f"{field.capitalize()}: {message}")

    return JSONResponse(
        status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
        content={
            "message": ".\n".join(error_messages),
            "source_errors": exc.errors(),
        },
    )

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.