Skip to content

X | Y union syntax breaks GenericModel #4146

@squarebridges

Description

@squarebridges

Bug

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

             pydantic version: 1.9.1
            pydantic compiled: True
                 install path: /home/paystone/.local/lib/python3.10/site-packages/pydantic
               python version: 3.10.2 (main, Jan 29 2022, 02:55:36) [GCC 10.2.1 20210110]
                     platform: Linux-5.18.2-arch1-1-x86_64-with-glibc2.31
     optional deps. installed: ['typing-extensions']

Python 3.10 incorporated PEP 604, which added syntax for writing Unions as X | Y, instead of typing.Union[X, Y]. It seems that when this syntax is used in combination with TypeVars as part of a GenericModel, the binding of TypeVars to their types fails.

Below is first a working example using typing.Union, followed by a non-working example using | syntax, with the stack trace. The example seeks to construct a Relationship GenericModel, and a BidirectionalMultiRelationship model which contains a list of either Relationship[A, B] or Relationship[B, A] models:

from typing import Union, Generic, TypeVar
from pydantic.generics import GenericModel

SourceT = TypeVar("SourceT")
TargetT = TypeVar("TargetT")

class Relationship(GenericModel, Generic[SourceT, TargetT]):
    source: SourceT
    target: TargetT

class BidirectionalMultiRelationship(GenericModel, Generic[SourceT, TargetT]):
    relationships: list[Union[Relationship[SourceT, TargetT], Relationship[TargetT, SourceT]]]
    
BidirectionalMultiRelationship[str, int]
from typing import Generic, TypeVar
from pydantic.generics import GenericModel

SourceT = TypeVar("SourceT")
TargetT = TypeVar("TargetT")

class Relationship(GenericModel, Generic[SourceT, TargetT]):
    source: SourceT
    target: TargetT

class BidirectionalMultiRelationship(GenericModel, Generic[SourceT, TargetT]):
    relationships: list[Relationship[SourceT, TargetT] | Relationship[TargetT, SourceT]]
    
BidirectionalMultiRelationship[str, int]
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Input In [2], in <cell line: 14>()
     11 class BidirectionalMultiRelationship(GenericModel, Generic[SourceT, TargetT]):
     12     relationships: Relationship[SourceT, TargetT] | Relationship[TargetT, SourceT]
---> 14 BidirectionalMultiRelationship[str, int]

File ~/.local/lib/python3.10/site-packages/pydantic/generics.py:137, in GenericModel.__class_getitem__(cls, params)
    133     _generic_types_cache[(cls, params[0])] = created_model
    135 # Recursively walk class type hints and replace generic typevars
    136 # with concrete types that were passed.
--> 137 _prepare_model_fields(created_model, fields, instance_type_hints, typevars_map)
    139 return created_model

File ~/.local/lib/python3.10/site-packages/pydantic/generics.py:356, in _prepare_model_fields(created_model, fields, instance_type_hints, typevars_map)
    353 assert field.type_.__class__ is DeferredType, field.type_.__class__
    355 field_type_hint = instance_type_hints[key]
--> 356 concrete_type = replace_types(field_type_hint, typevars_map)
    357 field.type_ = concrete_type
    358 field.outer_type_ = concrete_type

File ~/.local/lib/python3.10/site-packages/pydantic/generics.py:263, in replace_types(type_, type_map)
    261         origin_type = getattr(typing, type_._name)
    262     assert origin_type is not None
--> 263     return origin_type[resolved_type_args]
    265 # We handle pydantic generic models separately as they don't have the same
    266 # semantics as "typing" classes or generic aliases
    267 if not origin_type and lenient_issubclass(type_, GenericModel) and not type_.__concrete__:

TypeError: 'type' object is not subscriptable

On inspection I found that at the point that the exception is thrown, origin_type is types.UnionType, where it should be Relationship.

Apologies if I missed an existing issue for this. I couldn't find anything in my search.

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