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

Created on 19 Nov 2020  路  2Comments  路  Source: samuelcolvin/pydantic

Checks

  • [x] I added a descriptive title to this issue
  • [x] I have searched (google, github) for similar issues and couldn't find anything
  • [x] 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>')]
bug

Most helpful comment

Hello @1oglop1
It's been a know issue for quite a long time now (last issue with same problem: https://github.com/samuelcolvin/pydantic/issues/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

All 2 comments

Hello @1oglop1
It's been a know issue for quite a long time now (last issue with same problem: https://github.com/samuelcolvin/pydantic/issues/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

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

Was this page helpful?
0 / 5 - 0 ratings

Related issues

demospace picture demospace  路  26Comments

samuelcolvin picture samuelcolvin  路  30Comments

chopraaa picture chopraaa  路  18Comments

jasonkuhrt picture jasonkuhrt  路  21Comments

maxrothman picture maxrothman  路  26Comments