Fastapi: [BUG] Expected a 422 response when model validation fails, but got a 500 instead.

Created on 5 Dec 2019  路  4Comments  路  Source: tiangolo/fastapi

Describe the bug

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.

To Reproduce

Steps to reproduce the behavior with a minimum self-contained file.

  1. Create a file with:
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
  1. Visit the endpoint / 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)
  1. It returns a 500 response
  2. But I expected it to return a 422 response

Expected behavior

I would expect 422 response with the validation error details.

Environment

  • OS: [Linux]
  • FastAPI Version [0.44.1]
  • Python [3.7.3]
  • Starlette [0.12.9]
  • Pydantic [1.2]
  • Uvicorn [0.9.1]
bug

All 4 comments

@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:

Was this page helpful?
0 / 5 - 0 ratings