When responding with a list of JSON objects, if any (or all) of the models created from that JSON fails, I get a 500 response without validation details.
Steps to reproduce the behavior with a minimum self-contained file.
from datetime import date
from typing import List
from fastapi import FastAPI
from pydantic import BaseModel
class TheModel(BaseModel):
from_date: date
to_date: date
app = FastAPI()
@app.get(
'/',
response_model=List[TheModel],
)
def get():
malformed = {'from_date': None, 'to_date': None}
return [malformed] * 5
/ with a browser, wget, curl, etc.$ wget http://localhost:8080/
HTTP request sent, awaiting response... 500 Internal Server Error
(The output from running uvicorn)
Traceback (most recent call last):
File "/home/tzimmerman/envs/demo/lib/python3.7/site-packages/uvicorn/protocols/http/httptools_impl.py", line 376, in run_asgi
result = await app(self.scope, self.receive, self.send)
File "/home/tzimmerman/envs/demo/lib/python3.7/site-packages/fastapi/applications.py", line 140, in __call__
await super().__call__(scope, receive, send)
File "/home/tzimmerman/envs/demo/lib/python3.7/site-packages/starlette/applications.py", line 134, in __call__
await self.error_middleware(scope, receive, send)
File "/home/tzimmerman/envs/demo/lib/python3.7/site-packages/starlette/middleware/errors.py", line 178, in __call__
raise exc from None
File "/home/tzimmerman/envs/demo/lib/python3.7/site-packages/starlette/middleware/errors.py", line 156, in __call__
await self.app(scope, receive, _send)
File "/home/tzimmerman/envs/demo/lib/python3.7/site-packages/starlette/exceptions.py", line 73, in __call__
raise exc from None
File "/home/tzimmerman/envs/demo/lib/python3.7/site-packages/starlette/exceptions.py", line 62, in __call__
await self.app(scope, receive, sender)
File "/home/tzimmerman/envs/demo/lib/python3.7/site-packages/starlette/routing.py", line 590, in __call__
await route(scope, receive, send)
File "/home/tzimmerman/envs/demo/lib/python3.7/site-packages/starlette/routing.py", line 208, in __call__
await self.app(scope, receive, send)
File "/home/tzimmerman/envs/demo/lib/python3.7/site-packages/starlette/routing.py", line 41, in app
response = await func(request)
File "/home/tzimmerman/envs/demo/lib/python3.7/site-packages/fastapi/routing.py", line 140, in app
exclude_unset=response_model_exclude_unset,
File "/home/tzimmerman/envs/demo/lib/python3.7/site-packages/fastapi/routing.py", line 72, in serialize_response
raise ValidationError(errors, field.type_)
pydantic.error_wrappers.ValidationError: 10 validation errors for TheModel
response -> 0 -> from_date
none is not an allowed value (type=type_error.none.not_allowed)
response -> 0 -> to_date
none is not an allowed value (type=type_error.none.not_allowed)
response -> 1 -> from_date
none is not an allowed value (type=type_error.none.not_allowed)
response -> 1 -> to_date
none is not an allowed value (type=type_error.none.not_allowed)
response -> 2 -> from_date
none is not an allowed value (type=type_error.none.not_allowed)
response -> 2 -> to_date
none is not an allowed value (type=type_error.none.not_allowed)
response -> 3 -> from_date
none is not an allowed value (type=type_error.none.not_allowed)
response -> 3 -> to_date
none is not an allowed value (type=type_error.none.not_allowed)
response -> 4 -> from_date
none is not an allowed value (type=type_error.none.not_allowed)
response -> 4 -> to_date
none is not an allowed value (type=type_error.none.not_allowed)
I would expect 422 response with the validation error details.
@tazimmerman I looked into this and thought about it more, and I'm not sure it actually should be a 500 instead of a 422.
A 422 is intended to indicate that the user submitted something invalid as part of their request.
But in this case, it was your server's code that was responsible for producing the response that failed validation. So I think a 500 is actually probably right (since that is used to indicate an error in the server).
If you want FastAPI to handle this differently, you could just add an exception handler for ValidationError.
Hmm. You're right. During some early iterations, the 422 made sense as the validation errors were directly tied to the input from the client, but as I reworked some of the endpoints to rely less on client inputs, it never occurred to me that a 422 no longer made sense when it's not actually due to a client error.
The exception handler is a good suggestion, although now that I'm giving it more thought, I should probably construct the models, handling the validation errors myself, and return them directly in the endpoint. This way I can combine the legit results with the errors. In my case I don't want to abort the request when, for example, of 1 of 1,000 small-ish objects fail validation.
Sorry for wasting your time on this!
No problem! Thanks for contributing, and please keep posting any questions you have or bugs you notice.
Thanks for the help here @dmontagu ! :clap: :bow:
Thanks for reporting back and closing the issue @tazimmerman :+1: