Issue 1:
First Check

I added a very descriptive title to this issue.

I used the GitHub search to find a similar issue and didn't find it.

I searched the FastAPI documentation, with the integrated search.

I already searched in Google "How to X in FastAPI" and didn't find any information.

I already read and followed all the tutorial in the docs and didn't find an answer.

I already checked if it is not related to FastAPI but to Pydantic.

I already checked if it is not related to FastAPI but to Swagger UI.

I already checked if it is not related to FastAPI but to ReDoc.

Commit to Help

I commit to help with one of those options 👆

Example Code

import typing

import fastapi
import httpx
import pydantic as pydantic
import pytest
from fastapi import FastAPI
from fastapi.encoders import jsonable_encoder
from pydantic import ValidationError
from starlette import status
from starlette.responses import JSONResponse

class ResponseOne(pydantic.BaseModel):
val: str

class ResponseTwo(pydantic.BaseModel):
val: str

app = FastAPI()
incorrect_response = {"abc": "def"}

@app.exception_handler(ValidationError)
async def validation_exception_handler(request: fastapi.Request, exc: ValidationError):
return JSONResponse(
status_code=status.HTTP_400_BAD_REQUEST,
content=jsonable_encoder({"detail": exc.errors()}),
)

@app.get("/union", response_model=typing.Union[ResponseOne, ResponseTwo])
async def union():
return incorrect_response

@app.get("/single", response_model=ResponseOne)
async def single():
return incorrect_response

@pytest.fixture
async def client():
async with httpx.AsyncClient(app=app, base_url="http://localhost") as client:
yield client

async def test_unprintable(client):
res = await client.get("/union")
assert res.status_code == 400

async def test_400(client):
res = await client.get("/single")
assert res.status_code == 400

Description

When response_model is set to a union of two possible models and I return something that does not match one of the two models, FastAPI returns a 500 error:

test_.py:30: in validation_exception_handler
content=jsonable_encoder({"detail": exc.errors()}),
../../../../../Library/Caches/pypoetry/virtualenvs/bla/lib/python3.9/site-packages/pydantic/error_wrappers.py:63: in errors
config = self.model.__pydantic_model__.__config__ # type: ignore
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = typing.Union[tests.test_.ResponseOne, tests.test_.ResponseTwo]
attr = '__pydantic_model__'

def __getattr__(self, attr):
# We are careful for copy and pickle.
# Also for simplicity we just don't relay all dunder names
if '__origin__' in self.__dict__ and not _is_dunder(attr):
return getattr(self.__origin__, attr)
> raise AttributeError(attr)
E AttributeError: __pydantic_model__

../../../../../.pyenv/versions/3.9.9/lib/python3.9/typing.py:710: AttributeError

Operating System

macOS

Operating System Details

No response

FastAPI Version

0.81.0

Python Version

3.9.9

Additional Context

It also does not work in FastAPI 0.7* and the pydantic version (1.9 vs 1.0) has no influence, either.

Issue 2:
First Check

I added a very descriptive title to this issue.

I used the GitHub search to find a similar issue and didn't find it.

I searched the FastAPI documentation, with the integrated search.

I already searched in Google "How to X in FastAPI" and didn't find any information.

I already read and followed all the tutorial in the docs and didn't find an answer.

I already checked if it is not related to FastAPI but to Pydantic.

I already checked if it is not related to FastAPI but to Swagger UI.

I already checked if it is not related to FastAPI but to ReDoc.

Commit to Help

I commit to help with one of those options 👆

Example Code

import uvicorn
from typing import Union

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

app = FastAPI()

class BaseItem(BaseModel):
name: str
type: str

class MyItem(BaseModel):
foo: str
num_foo: int
bar: str
num_bar: int

items = [
{"name": "kitty", "type": "cat"},
{
"foo": "foo",
"num_foo": "???", # <-- Validation will FAIL !
"bar": "bar",
"num_bar":"???" # <-- Validation will FAIL !
}
]

@app.middleware('http')
async def exception_handler(request: Request, call_next):
try:
response = await call_next(request)
except ValueError as ve:
print(ve.raw_errors)
errors = ve.errors() # <-- This raises AttributeError: __pydantic_model__
# on 'http://localhost:8000/items_union_model/1'
return JSONResponse(content=errors)
return response

@app.get("/items_single_model/{item_id}", response_model=MyItem)
async def read_item_single_model(item_id: int):
return items[item_id]

@app.get("/items_union_model/{item_id}", response_model=Union[BaseItem, MyItem]) # <-- Union type
async def read_item_union_model(item_id: int):
return items[item_id]

if __name__ == '__main__':
uvicorn.run(app, host='0.0.0.0')

Description

Summary

When data validation using response_model fails due to wrong data, I want to return back the error list to the requester.

I added exception_handler middleware to achieve this.

This works perfectly when the response_model is a single class (derived from BaseModel of Pydantic)

You can open http://localhost:8000/items_single_model/1 from the browser.

But when the response_model is Union type which is said to be supported by this example

AttributeError: __pydantic_model__ occurs when I call ValidationError.errors() method to get error information

You can open http://localhost:8000/items_union_model/1 and see the server log.

This could ultimately be a problem of Pydantic but the way passing the error data to Pydantic might be a problem

Is this expected behavior ? If so, How can I exact list or dict type of error data in this situation ?

Traceback

Traceback (most recent call last):
File "/home/jamie/.asdf/installs/python/3.8.10/lib/python3.8/site-packages/uvicorn/protocols/http/httptools_impl.py", line 398, in run_asgi
result = await app(self.scope, self.receive, self.send)
File "/home/jamie/.asdf/installs/python/3.8.10/lib/python3.8/site-packages/uvicorn/middleware/proxy_headers.py", line 45, in __call__
return await self.app(scope, receive, send)
File "/home/jamie/.asdf/installs/python/3.8.10/lib/python3.8/site-packages/fastapi/applications.py", line 208, in __call__
await super().__call__(scope, receive, send)
File "/home/jamie/.asdf/installs/python/3.8.10/lib/python3.8/site-packages/starlette/applications.py", line 112, in __call__
await self.middleware_stack(scope, receive, send)
File "/home/jamie/.asdf/installs/python/3.8.10/lib/python3.8/site-packages/starlette/middleware/errors.py", line 181, in __call__
raise exc from None
File "/home/jamie/.asdf/installs/python/3.8.10/lib/python3.8/site-packages/starlette/middleware/errors.py", line 159, in __call__
await self.app(scope, receive, _send)
File "/home/jamie/.asdf/installs/python/3.8.10/lib/python3.8/site-packages/starlette/middleware/base.py", line 25, in __call__
response = await self.dispatch_func(request, self.call_next)
File "./main.py", line 40, in exception_handler
errors = ve.errors() # <-- This raises AttributeError: __pydantic_model__
File "pydantic/error_wrappers.py", line 52, in pydantic.error_wrappers.ValidationError.errors
File "/home/jamie/.asdf/installs/python/3.8.10/lib/python3.8/typing.py", line 760, in __getattr__
raise AttributeError(attr)
AttributeError: __pydantic_model__

Operating System

Linux

Operating System Details

Ubuntu 20.04 (under Windows 10 WSL2)

FastAPI Version

0.68.0

Python Version

3.8.10

Additional Context

Pydantic version: 1.8.2