Skip to content

First instantiation of generic Union model defines type coercion of models instantiated later #4474

@sveinugu

Description

@sveinugu

Initial Checks

  • I have searched GitHub for a duplicate issue and I'm sure this is something new
  • I have searched Google & StackOverflow for a solution and couldn't find anything
  • I have read and followed the docs and still think this is a bug
  • I am confident that the issue is with pydantic (not my code, or another library in the ecosystem like FastAPI or mypy)

Description

When pydantic generic models are instantiated with Unions, the order of a particular set of types are fixed by the first instantiation, e.g.:

from typing import Generic, TypeVar, Union

from pydantic.generics import GenericModel

NumberT = TypeVar('NumberT')


class NumberModel(GenericModel, Generic[NumberT]):
    data: NumberT


FloatOrIntModel = NumberModel[Union[float, int]]
print(FloatOrIntModel(data='1').data)

IntOrFloatModel = NumberModel[Union[int, float]]
print(IntOrFloatModel(data='1').data)

prints

1.0
1.0

While changing the order of the instantiations changes type coercion of both models:

1
1

In pydantic, type coercion of Union models depends on the order of the types. However, as explained in the documentation on Unions:

typing.Union also ignores order when defined, so Union[int, float] == Union[float, int] which can lead to unexpected behaviour when combined with matching based on the Union type order inside other type definitions

I think this should be considered a bug in pydantic, even though the user is warned against 'unexpected behaviour' combined with other code that depend on Union type equality. The unexpected behaviour in this case is caused by pydantic itself, there is no "matching based on the Union type order inside other type definitions" in the example code, nor are any third-party code imported. It is not difficult to envision situations where this dependency could cause very hard-to-find bugs.

It seems the cause of the error is the _generic_types_cache in generics.py, which caches the first Union model and uses this as the blueprint of later matching models (i.e. with the same set of types).

Setting smart_union to True has no impact.

Related to #2835.

Example Code

No response

Python, Pydantic & OS Version

pydantic version: 1.10.1
            pydantic compiled: True
                 install path: /Users/sveinugu/Library/Caches/pypoetry/virtualenvs/unifair-myuXak-6-py3.10/lib/python3.10/site-packages/pydantic
               python version: 3.10.4 | packaged by conda-forge | (main, Mar 24 2022, 17:42:03) [Clang 12.0.1 ]
                     platform: macOS-11.2.3-arm64-arm-64bit
     optional deps. installed: ['typing-extensions']

Affected Components

Metadata

Metadata

Assignees

No one assigned

    Labels

    bug V1Bug related to Pydantic V1.X

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions