Pydantic: Generic union of GenericModel does not seem to work

Created on 28 Jan 2020  路  5Comments  路  Source: samuelcolvin/pydantic

             pydantic version: 1.4
            pydantic compiled: True
                 install path: /home/(...)/venv/lib/python3.7/site-packages/pydantic
               python version: 3.7.5 (default, Nov 20 2019, 09:21:52)  [GCC 9.2.1 20191008]
                     platform: Linux-5.3.0-26-generic-x86_64-with-Ubuntu-19.10-eoan
     optional deps. installed: []

Python typing allows to create generic type alias, like:

T = TypeVar('T')
SomeNewType = Union[List[T], Set[T]]

It should be also possible to create Union of two GenericModels:

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

print(pydantic.utils.version_info())

T = TypeVar('T')


class ModelA(GenericModel, Generic[T]):
    pass


class ModelB(GenericModel, Generic[T]):
    pass


UnionModel = Union[ModelA[T], ModelB[T]]


def func(arg: UnionModel[str]):
    pass

However, this code results in:

Traceback (most recent call last):
  File "app/test.py", line 21, in <module>
    def func(arg: UnionModel[str]):
  File "/usr/lib/python3.7/typing.py", line 254, in inner
    return func(*args, **kwds)
  File "/usr/lib/python3.7/typing.py", line 630, in __getitem__
    _check_generic(self, params)
  File "/usr/lib/python3.7/typing.py", line 204, in _check_generic
    raise TypeError(f"{cls} is not a generic class")
TypeError: typing.Union[__main__.ModelA[T], __main__.ModelB[T]] is not a generic class
bug

Most helpful comment

Unfortunately, if you look at typing._collect_type_vars, the typing module has hard-coded looking up parameters (for Union and some others) by requiring any type that may have parameters be an instance of _GenericAlias.

Since we override __class_getitem__ in order to handle the creation of the concrete model classes, we no longer get this for free by inheritance from Generic. So the Union thinks it doesn't have type vars.


As a workaround, I think everything should work fine (i.e., compatibly with mypy, etc.) if you just use:

def func(arg: "UnionModel[str]"):
    pass

but let us know if that doesn't work for you.


I think it might be possible to fix this with a (likely-heavy) refactor of GenericModel to make use of a super(cls, Generic).__class_getitem__ (or however you do classmethod super with multiple inheritance...). But if you look at the implementation of _GenericAlias, it's pretty hairy, so such a refactor would not only likely be hard, but I also suspect it would be likely to introduce subtle bugs that would take a while to identify.

I defer to @samuelcolvin in terms of priorities, but given the workaround, I think this is probably not worth the (likely substantial) effort to address.

If anyone wants to take a stab at such a refactor, I'd be happy to review a PR, but be warned that it won't be easy!

All 5 comments

Unfortunately, if you look at typing._collect_type_vars, the typing module has hard-coded looking up parameters (for Union and some others) by requiring any type that may have parameters be an instance of _GenericAlias.

Since we override __class_getitem__ in order to handle the creation of the concrete model classes, we no longer get this for free by inheritance from Generic. So the Union thinks it doesn't have type vars.


As a workaround, I think everything should work fine (i.e., compatibly with mypy, etc.) if you just use:

def func(arg: "UnionModel[str]"):
    pass

but let us know if that doesn't work for you.


I think it might be possible to fix this with a (likely-heavy) refactor of GenericModel to make use of a super(cls, Generic).__class_getitem__ (or however you do classmethod super with multiple inheritance...). But if you look at the implementation of _GenericAlias, it's pretty hairy, so such a refactor would not only likely be hard, but I also suspect it would be likely to introduce subtle bugs that would take a while to identify.

I defer to @samuelcolvin in terms of priorities, but given the workaround, I think this is probably not worth the (likely substantial) effort to address.

If anyone wants to take a stab at such a refactor, I'd be happy to review a PR, but be warned that it won't be easy!

I want to use such Union as fastapi response model (it automatically generates docs for types), so I don't think putting type as literal will work.

You should be able to just use Union[ModelA[str], ModelB[str]] for that (you could create a convenience method to generate the union if you really don't want to repeat the parameter).

I defer to @dmontagu, his understanding of GenericModels inner workings is better than mine.

Feel free to close this.

@peku33 I'm going to close the issue, but feel free to reopen it if Union[ModelA[str], ModelB[str]] (or a convenience function) won't work for you.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

maxrothman picture maxrothman  路  26Comments

jasonkuhrt picture jasonkuhrt  路  19Comments

marlonjan picture marlonjan  路  37Comments

cazgp picture cazgp  路  34Comments

samuelcolvin picture samuelcolvin  路  30Comments