Skip to content
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

List[Union[A,B,C]] types are casted to the wrong type if types are similar #2135

Closed
3 tasks done
1oglop1 opened this issue Nov 19, 2020 · 3 comments
Closed
3 tasks done
Labels
bug V1 Bug related to Pydantic V1.X

Comments

@1oglop1
Copy link

1oglop1 commented Nov 19, 2020

Checks

  • I added a descriptive title to this issue
  • I have searched (google, github) for similar issues and couldn't find anything
  • I have read and followed the docs and still think this is a bug

Bug

Output of python -c "import pydantic.utils; print(pydantic.utils.version_info())":

             pydantic version: 1.6.1
            pydantic compiled: True
                 install path: /Users/user/virtualenvs/testp-wbcFebeE-py3.8/lib/python3.8/site-packages/pydantic
               python version: 3.8.5 (default, Aug 10 2020, 10:25:56)  [Clang 11.0.0 (clang-1100.0.20.17)]
                     platform: macOS-10.14.6-x86_64-i386-64bit
     optional deps. installed: ['email-validator']

Hi, I'm writing a slack application which has multiple types which have the same fields and the only difference is the content of the field: https://api.slack.com/types/user

E.g: Difference between channel ID and user ID(A): CJ03ECZLG X UJ03ECZLG (user starts with U)

And for some reason Pydantic casts Channel (C) to the User (U) based on the order of types inside the annotation.

Am I using pydantic wrong or is the a legitimate bug?

If there is a better approach or workaround it would be much appreciated.
Thank you.

from pydantic import validator, BaseModel
from typing import Optional, Union, List


class U(BaseModel):
    id: str
    mention: Optional[str]

    @validator("mention", pre=True)
    def resolve_mention(cls, _, values):
        if 'id' in values:
            return f"<@{values['id']}>"


class B(BaseModel):
    name: str
    mention: Optional[str]

    @validator("mention", pre=True)
    def resolve_mention(cls, _, values):
        if 'name' in values:
            return f"{values['name']}"


class C(BaseModel):
    id: str
    mention: Optional[str]

    @validator("mention", pre=True)
    def resolve_mention(cls, _, values):
        if 'id' in values:
            return f"<#{values['id']}>"


class Final(BaseModel):
    # This does not work, why?
    entities: List[
        Union[
            U,
            B,
            C,  # C is casted to U
        ]
    ]


print(
    Final(
        entities=[
            U(id='UJ03ECZLG', mention='<@UJ03ECZLG>'),
            B(name='someone else', mention='someone else')
        ]
    )
)

print(
    Final(
        entities=[
            U(id='UJ03ECZLG', mention='<@UJ03ECZLG>'),
            B(name='someone else', mention='someone else'),
            C(id='CJ03ECZLG', mention='<@CJ03ECZLG>'),  # This is casted to U
        ]
    )
)

Actual result

entities=[U(id='UJ03ECZLG', mention='<@UJ03ECZLG>'), B(name='someone else', mention='someone else')]
entities=[U(id='UJ03ECZLG', mention='<@UJ03ECZLG>'), B(name='someone else', mention='someone else'), U(id='CJ03ECZLG', mention='<@CJ03ECZLG>')]

Expected result:

entities=[U(id='UJ03ECZLG', mention='<@UJ03ECZLG>'), B(name='someone else', mention='someone else')]
entities=[U(id='UJ03ECZLG', mention='<@UJ03ECZLG>'), B(name='someone else', mention='someone else'), C(id='CJ03ECZLG', mention='<@CJ03ECZLG>')]
@1oglop1 1oglop1 added the bug V1 Bug related to Pydantic V1.X label Nov 19, 2020
@PrettyWood
Copy link
Member

PrettyWood commented Nov 19, 2020

Hello @1oglop1
It's been a know issue for quite a long time now (last issue with same problem: #2079)
pydantic tries to coerce in the order of the union and your C instance can be coerced as U hence the result.
You can add a dumb Literal for each model (a bit like you would do with TypeScript to discrimate unions).
You can also have a look at #2092, which solves your problem (just need to replace Union by StrictUnion). Feedback is more than welcome on this PR :)
Cheers

@1oglop1
Copy link
Author

1oglop1 commented Nov 19, 2020

Oh, thank you for point me in the right direction. I've been searching for possible duplicates but could not find any.

@dmontagu
Copy link
Contributor

Though this now gets deprecation warnings due to use of @validator (which should eventually be migrated to @field_validator), it seems to me this example is now working properly in v2, with the last printed line being:

entities=[U(id='UJ03ECZLG', mention='<@UJ03ECZLG>'), B(name='someone else', mention='someone else'), C(id='CJ03ECZLG', mention='<#CJ03ECZLG>')]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug V1 Bug related to Pydantic V1.X
Projects
None yet
Development

No branches or pull requests

3 participants