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:
When a request contains invalid data, FastAPI internally raises a RequestValidationError . And it also includes a default exception handler for it. To override it, import the RequestValidationError and use it with @app.
In most cases Eloqua's APIs will respond to requests with the standard HTTP status code definitions. If the API fails to validate a request, it will respond with a validation error message (JSON or XML) describing the issue. 400: "There was a parsing error."
It is precisely because validation errors can be caused by such a wide range of reasons (i.e. the input's content, length, formatting, etc) that it is so important to let the user know exactly why their input failed because they'll otherwise have to try and work it out themselves.
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"
}
}
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}
),
)
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With