Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot use a pydantic dataclass nested in a BaseModel as a response_model #4402

Closed
9 tasks done
ypsah opened this issue Jan 9, 2022 · 6 comments
Closed
9 tasks done
Labels
question Question or problem question-migrate

Comments

@ypsah
Copy link

ypsah commented Jan 9, 2022

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

from fastapi import FastAPI
from pydantic import BaseModel
from pydantic.dataclasses import dataclass


app = FastAPI()


@dataclass
class Item:
    name: str


class Box(BaseModel):
    item: Item


@app.get("/item", response_model=Item)
async def item() -> Item:
    return Item(name="test")


@app.get("/box", response_model=Box)
async def box() -> Box:
    return Box(item=await item())

Description

The item endpoint works fine, but the box one fails with a ValidationError:

$ uvicorn main:app
INFO:     Started server process [13673]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO:     127.0.0.1:46020 - "GET /docs HTTP/1.1" 200 OK
INFO:     127.0.0.1:46020 - "GET /openapi.json HTTP/1.1" 200 OK
INFO:     127.0.0.1:46020 - "GET /item HTTP/1.1" 200 OK
INFO:     127.0.0.1:46020 - "GET /box HTTP/1.1" 500 Internal Server Error
ERROR:    Exception in ASGI application
Traceback (most recent call last):
  File "/home/guest/.local/share/virtualenvs/tmp.KhplXQz0Am-XrtSmM4b/lib/python3.10/site-packages/uvicorn/protocols/http/h11_impl.py", line 373, in run_asgi
    result = await app(self.scope, self.receive, self.send)
  File "/home/guest/.local/share/virtualenvs/tmp.KhplXQz0Am-XrtSmM4b/lib/python3.10/site-packages/uvicorn/middleware/proxy_headers.py", line 75, in __call__
    return await self.app(scope, receive, send)
  File "/home/guest/.local/share/virtualenvs/tmp.KhplXQz0Am-XrtSmM4b/lib/python3.10/site-packages/fastapi/applications.py", line 208, in __call__
    await super().__call__(scope, receive, send)
  File "/home/guest/.local/share/virtualenvs/tmp.KhplXQz0Am-XrtSmM4b/lib/python3.10/site-packages/starlette/applications.py", line 112, in __call__
    await self.middleware_stack(scope, receive, send)
  File "/home/guest/.local/share/virtualenvs/tmp.KhplXQz0Am-XrtSmM4b/lib/python3.10/site-packages/starlette/middleware/errors.py", line 181, in __call__
    raise exc
  File "/home/guest/.local/share/virtualenvs/tmp.KhplXQz0Am-XrtSmM4b/lib/python3.10/site-packages/starlette/middleware/errors.py", line 159, in __call__
    await self.app(scope, receive, _send)
  File "/home/guest/.local/share/virtualenvs/tmp.KhplXQz0Am-XrtSmM4b/lib/python3.10/site-packages/starlette/exceptions.py", line 82, in __call__
    raise exc
  File "/home/guest/.local/share/virtualenvs/tmp.KhplXQz0Am-XrtSmM4b/lib/python3.10/site-packages/starlette/exceptions.py", line 71, in __call__
    await self.app(scope, receive, sender)
  File "/home/guest/.local/share/virtualenvs/tmp.KhplXQz0Am-XrtSmM4b/lib/python3.10/site-packages/starlette/routing.py", line 656, in __call__
    await route.handle(scope, receive, send)
  File "/home/guest/.local/share/virtualenvs/tmp.KhplXQz0Am-XrtSmM4b/lib/python3.10/site-packages/starlette/routing.py", line 259, in handle
    await self.app(scope, receive, send)
  File "/home/guest/.local/share/virtualenvs/tmp.KhplXQz0Am-XrtSmM4b/lib/python3.10/site-packages/starlette/routing.py", line 61, in app
    response = await func(request)
  File "/home/guest/.local/share/virtualenvs/tmp.KhplXQz0Am-XrtSmM4b/lib/python3.10/site-packages/fastapi/routing.py", line 234, in app
    response_data = await serialize_response(
  File "/home/guest/.local/share/virtualenvs/tmp.KhplXQz0Am-XrtSmM4b/lib/python3.10/site-packages/fastapi/routing.py", line 137, in serialize_response
    raise ValidationError(errors, field.type_)
pydantic.error_wrappers.ValidationError: 1 validation error for Box
response -> item
  value is not a valid dict (type=type_error.dict)

Operating System

Linux

Operating System Details

No response

FastAPI Version

0.71.0

Python Version

3.10.1

Additional Context

No response

@ypsah ypsah added the question Question or problem label Jan 9, 2022
@harunyasar
Copy link

As far as I understand, FastAPI doesn't let us to mix BaseModel with either pydantic dataclass or standard library dataclass. You can only mix same type of models.

import dataclasses

from pydantic.dataclasses import dataclass


@dataclass  # or @dataclasses.dataclass
class Item:
    name: str

@dataclass  # or @dataclasses.dataclass
class Box:
    item: Item

@app.get("/item", response_model=Item)
async def item() -> Item:
    return Item(name="test")


@app.get("/box", response_model=Box)
async def box() -> Box:
    return Box(item=await item())

@ypsah
Copy link
Author

ypsah commented Jan 9, 2022

This isn't what fastapi's documentation implies:

You can also combine dataclasses with other Pydantic models, inherit from them, include them in your own models, etc.

ref: https://fastapi.tiangolo.com/advanced/dataclasses/?h=dataclasses#learn-more

pydantic's documentation gives an explicit example mixing the two here.

Also creating an instance of Box manually works without any hiccups, and I can even validate it myself:

>>> Box.validate(Box(item=Item(name="test")))
Box(item=Item(name="test")))

What makes you think mixing the two is not supported?

@ypsah ypsah changed the title Cannot use a pydantic dataclass nested in a BaseModel as a model_response Cannot use a pydantic dataclass nested in a BaseModel as a response_model Jan 9, 2022
@harunyasar
Copy link

harunyasar commented Jan 9, 2022

Neither pydantic nor FastAPI documentation made me think that mixing different types of models is not supported. I have only tried to mix BaseModel with pydantic dataclass and BaseModel with stdlib dataclass. Each time I tried I got the same error unless I returned a dict object explicitly Box(item=await item()).dict(). That's why I'm saying, apparently, FastAPI (not the pydantic) doesn't let us mix different types. I haven't checked the links you pointed out above but will do it later on.

I have checked the documentation but I couldn't see anything that says you could mix a BaseModel and a dataclass. I guess the intention was there to say that you could either use BaseModel or dataclass as an request or response model. I have checked the source code as well.

return res.dict(
by_alias=True,
exclude_unset=exclude_unset,
exclude_defaults=exclude_defaults,
exclude_none=exclude_none,
)

Since Box is a BaseModel, it immediately returns a dict object {"item": Item(name="test")} and it is not a valid dict.

If you call as I showed you above your response will be a dict object and dataclass.asdict() method of the dataclass will be called.

fastapi/fastapi/routing.py

Lines 101 to 102 in 672c55a

elif dataclasses.is_dataclass(res):
return dataclasses.asdict(res)

Eventually, this will also be a dict object that can be validated and accepted by the Box response object.

I might be wrong. I hope there will be someone else that can have a better explanation.

@ypsah
Copy link
Author

ypsah commented Jan 11, 2022

Thanks for the pointers in the codebase! 👍

I am not sure this is the whole story, but it is definitely a step forward.
I think it is enough for me to carry on and send a PR to either pydantic or fastapi, maybe both.


I have checked the documentation but I couldn't see anything that says you could mix a BaseModel and a dataclass.

Well, there is the part that I posted in my first response that says:

You can also combine dataclasses with other Pydantic models, inherit from them, include them in your own models, etc.

With a link to that sentence.
Did you check it out?


For the record, editing a post does not trigger any notification.
I think it would have been better in this case to add a new comment.

@harunyasar
Copy link

harunyasar commented Jan 12, 2022

Yes, I did. Like I explained above, those were my findings and I believe _prepare_response_content might need an improvement. If you are going to create a PR that can fix the issue it would be great and I would be glad to see. Apart from that, until having seen a better explanation I don’t have much to say.

@databasedav databasedav mentioned this issue Apr 26, 2022
2 tasks
@databasedav
Copy link

this is a bug pydantic/pydantic#3764

Repository owner locked and limited conversation to collaborators Feb 28, 2023
@tiangolo tiangolo converted this issue into discussion #8539 Feb 28, 2023

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Labels
question Question or problem question-migrate
Projects
None yet
Development

No branches or pull requests

4 participants