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

Pydantic __root__ model - incorrect handling #911

Closed
peku33 opened this issue Jan 23, 2020 · 12 comments · Fixed by #1524
Closed

Pydantic __root__ model - incorrect handling #911

peku33 opened this issue Jan 23, 2020 · 12 comments · Fixed by #1524

Comments

@peku33
Copy link

peku33 commented Jan 23, 2020

Describe the bug

https://pydantic-docs.helpmanual.io/usage/models/#custom-root-types
Pydantic allows to create models with only __root__ field. In such scenario the model behaves as transparent wrapper for this single type.

When such model is used in response (request also?) fastapi does not treat it correctly and renders it as object with __root__ field.

Object is treated correctly by pydantic itself.

To Reproduce

from typing import List
from fastapi import FastAPI
from pydantic.main import BaseModel

app = FastAPI()


class RootTestClass(BaseModel):
    __root__: List[str]


@app.get("/")
async def root():
    response = RootTestClass(__root__=['a', 'b', 'c'])
    print(response.json())  # ["a", "b", "c"] so it's OK
    print(RootTestClass.schema())  # {'title': 'RootTestClass', 'type': 'array', 'items': {'type': 'string'}} this is also OK
    return response  # Wrong value in http response

Expected behavior

The response should be:

["a", "b", "c"]

but at the moment is:

{"__root__":["a","b","c"]}

Screenshots

N/A

Environment

  • OS: Linux
  • FastAPI Version: 0.47.1
  • Python version: Python 3.7.5

Additional context

N/A

@peku33 peku33 added the bug Something isn't working label Jan 23, 2020
@dmontagu
Copy link
Collaborator

If anyone wants to submit a PR to fix this I'd be happy to review it. (I think it's worth handling this properly.)

@peku33
Copy link
Author

peku33 commented Jan 28, 2020

For now created issue for pydantic (pydantic/pydantic#1193) as it looks like it is more broken there than here.

@tiangolo
Copy link
Owner

I wouldn't recommend using __root__ in FastAPI. __root__ allows using other types in Pydantic apart from things with key values, like lists.

But in FastAPI, everywhere you can use a Pydantic model you can also use what would be the (arguably?) most "Pythonic" way, using typing. So you can do List[SomeModel]. Instead of having to create a SomeModelWrapper that users __root__.

__root__ is valid and useful in Pydantic standalone as there's no other way to achieve what it does. But in FastAPI the preferred way is to use standard types that have Pydantic models as type parameters (the thing inside List[]).

Given that, as it's still valid Pydantic, I would be happy to support it if someone wants to add a PR with it (as @dmontagu says).

@merc
Copy link

merc commented Feb 16, 2020

@tiangolo I understands that response_model=Dict[str, str] instead of a wrapped model with __root__ is viable, however is there a way to include an example, perhaps similar to the schema_extra section that can be attach to response_model ?

@sidmani
Copy link

sidmani commented Feb 23, 2020

@tiangolo Supporting pydantic root types would allow a single validator (defined in the wrapper class) to be run on all objects of a certain type- otherwise, the validator must be specified in each object that has a child of that type (as far as I can tell- I'm new to fastAPI, please let me know if there's a better way).

@davideasaf
Copy link

as @sidmani also mentioned, I'm running into wanting the ability to be able to say:

pydantic_list_as_root.dict()

and the above output a dict. Rather than having to manually loop through my List[pydantic_entity] and call dict() on each one.

However, I do appreciate what @tiangolo is trying to achieve by keeping things as pythonic as possible, but I would imagine that many if not all FastAPI implementations heavily rely on Pydantic for defining schemas. Therefore, I think it would be a great idea to embrace all/most of its capabilities.

@tiangolo
Copy link
Owner

tiangolo commented Jun 5, 2020

Yeah, I would be happy to support it if someone wants to add a PR with it.

@tiangolo
Copy link
Owner

This should be fixed in #1524 by @patrickkwang 🎉 🚀

Available in FastAPI 0.57.0 (released in a couple of hours).

I'm re-opening this to let the original issue author confirm and close it directly 😄

@github-actions
Copy link
Contributor

Assuming the original issue was solved, it will be automatically closed now. But feel free to add more comments or create new issues.

@snow6oy
Copy link

snow6oy commented Mar 7, 2021

When a custom root type is used together with a nested model, FastAPI does not seem to export the nested model. For example response_model=Dict[str, AnotherModel] gives the class definition for AnotherModel and none of the Pydantic export methods seem to help.

Might it be better if Pydantic was offered on an opt-in basis?

@phy25
Copy link

phy25 commented Mar 7, 2021

@snow6oy You should create another issue since the original issue has been resolved (and I am not entirely sure if this is a Pydantic or FastAPI issue) or no one will pay attention to the reply to a closed issue.

@andyhasit
Copy link

andyhasit commented Jan 15, 2022

The fix in #1524 is not working for me as I'm using multiple levels of nested models with custom roots. This causes an error in serialize_response at field.validate which is before the call to jsonable_encoder (where #1524 takes effect).

Its's an easy fix though I'm not sure if it renders #1524 redundant?

PR here: #4428

Temp fix for those in a hurry:

class DictModel(BaseModel):
    # Just so you can use this.
    def items(self):
        return self.__root__.items()

    # This is a temp fix until https://github.com/tiangolo/fastapi/pull/4428 is approved
    def dict(self, *args, **kwargs):
        d = super().dict( *args, **kwargs)
        if "__root__" in d:
            d = d["__root__"]
        return d

class B(DictModel):
     __root__ = str

class A(DictModel):
     __root__ = Dict[str, B]

@tiangolo tiangolo added question Question or problem reviewed and removed bug Something isn't working labels Feb 22, 2023
@tiangolo tiangolo changed the title [BUG] Pydantic __root__ model - incorrect handling Pydantic __root__ model - incorrect handling Feb 24, 2023
@tiangolo tiangolo reopened this Feb 28, 2023
Repository owner locked and limited conversation to collaborators Feb 28, 2023
@tiangolo tiangolo converted this issue into discussion #7734 Feb 28, 2023

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Projects
None yet
Development

Successfully merging a pull request may close this issue.

9 participants