4

I would like to instantiate a typing Union of two classes derived from pydantic.BaseModel directly. However I get a TypeError: Cannot instantiate typing.Union.

All examples I have seen declare Union as an attribute of a class (for example here).

Below is the minimum example I would like to use.

from pydantic import BaseModel
from typing import Union

class A(BaseModel):
    a: int

class B(A):
    b: int

class C(A):
    c: str

MyUnion = Union[B, C, A]
mu = MyUnion(a=666, c='foo')  #  This command throws the TypeError

Is there a way to achieve this?

Here is the error I obtain

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-40-8163e3490185> in <module>
----> 1 MyUnion()

c:\program files\python37\lib\typing.py in __call__(self, *args, **kwargs)
    668             raise TypeError(f"Type {self._name} cannot be instantiated; "
    669                             f"use {self._name.lower()}() instead")
--> 670         result = self.__origin__(*args, **kwargs)
    671         try:
    672             result.__orig_class__ = self

c:\program files\python37\lib\typing.py in __call__(self, *args, **kwds)
    327
    328     def __call__(self, *args, **kwds):
--> 329         raise TypeError(f"Cannot instantiate {self!r}")
    330
    331     def __instancecheck__(self, obj):

TypeError: Cannot instantiate typing.Union
Guillaume Jacquenot
  • 11,217
  • 6
  • 43
  • 49
  • You cannot instantiate a Union, that doesn't even make sense. – juanpa.arrivillaga Jan 07 '20 at 17:30
  • A union represents „one of A, B or C“. It seems you want „all of A, B and C“, which is not a union. – MisterMiyagi Jan 07 '20 at 17:30
  • 1
    As per the above comments, a `Union` is a type hint, telling python what type a variable should be. It is not a class in its own right. Itdoes not represent the amalgamation of two types or classes into a new combined type/class. – Tom Dalton Jan 07 '20 at 17:33

5 Answers5

8

That's not how Union works.

Union is the same thing as the union in C.

It means that the variable can be either of type A or of type B.

For example

def f(a: Union[int, str]) -> None:
   ...

This means that a can be an int or a str, a subclass of those and nothing else.

LtWorf
  • 7,286
  • 6
  • 31
  • 45
  • No, it is not the same thing as a union in C at all. – juanpa.arrivillaga Jan 07 '20 at 17:41
  • 1
    Yes it is. But please if you say that I'm wrong you should also say what is wrong… – LtWorf Jan 08 '20 at 00:21
  • 3
    A union in C is a specific thing, a piece of memory that can be interpreted as *either one type or another* at *any given moment*. I.E. `union foo {int x; float y};` then something like `union foo; foo.x = 4;`, then I can do something like `printf( "%d\n", foo.x); printf("%d\n", foo.y)`, that is, the union acts like a struct with members, just that these members share memory. Python has nothing like this. While, from a type theory perspective, this could be considered a sum type, which is what Union means in Python, that doesn't mean Python Unions are like C unions. – juanpa.arrivillaga Jan 08 '20 at 00:31
  • Well in python everything is a pointer, so pointer to union… – LtWorf Jan 25 '21 at 11:24
  • python *doesn't have pointers*. This really has nothing to do with C unions – juanpa.arrivillaga Jan 25 '21 at 15:13
  • 1
    A C Union is both *at the same time*. A Python union is a type annotation that indicates a variable is either or, but not both at the same time – juanpa.arrivillaga Jan 25 '21 at 15:19
  • Python doesn't have pointers arithmetic, it does have pointers. It even has a function to compare pointer values… – LtWorf Jun 27 '22 at 21:03
  • No, Python does not have pointers (unless you are talking about`ctypes`) – juanpa.arrivillaga Jun 27 '22 at 21:56
  • `a = {}; b = a; b['aaa'] = 'aaa'; del a['aaa']` why doesn't that fail? – LtWorf Jul 02 '22 at 13:10
  • because, there is only one object with two different names in that namespace. – juanpa.arrivillaga Jul 02 '22 at 18:18
  • The name for this is: "pointer" – LtWorf Jul 03 '22 at 20:03
3

I know that I don't have exactly the same issue as the question, but for others that land here, I resolved my issue by changing Union(int, blah) to Union[int, blah]

The important thing being that I accidentally used parenthesis instead of square brackets. :/

ashbygeek
  • 759
  • 3
  • 20
3

What you are looking for is parse_obj_as:

https://pydantic-docs.helpmanual.io/usage/models/#parsing-data-into-a-specified-type

from pydantic import BaseModel, parse_obj_as
from typing import Union

class A(BaseModel):
    a: int

class B(A):
    b: int

class C(A):
    c: str

MyUnion = Union[B, C, A]
mu = parse_obj_as(MyUnion, {"a":666, "c":'foo'}) 
mu
# >>> C(a=666, c='foo')
eirki
  • 61
  • 3
1

It looks like what you want is a factory function, one that guesses which of A, B, or C to instantiate based on the keyword arguments presented in the call.

For example:

from pydantic import BaseModel
from typing import Union

class A(BaseModel):
    a: int

class B(A):
    b: int

class C(A):
    c: str

def a_b_or_c(**kwargs) -> Union[B, C, A]:
    if 'c' in kwargs:
        return C(**kwargs)
    elif 'b' in kwargs:
        return B(**kwargs)
    elif 'a' in kwargs:
        return A(**kwargs)
    else:
        raise Exception("I don't know what class you want")

my = a_b_or_c(a=666, c='foo')

a_b_or_c, of course, could do more extensive testing of the arguments found in kwargs, for example to prevent passing arguments that none of A, B, or C are expecting.

Guillaume Jacquenot
  • 11,217
  • 6
  • 43
  • 49
chepner
  • 497,756
  • 71
  • 530
  • 681
  • All of that can be achieved with this `typedload` module I wrote. It'd be something like `load(kwargs, Union[A, B, C])`, but the classes need to use typing or attrs as well to be recognised. – LtWorf Jan 08 '20 at 00:25
0

Do you want this behavior?

from dataclasses import dataclass
from typing import Union, List

from validated_dc import ValidatedDC


@dataclass
class I(ValidatedDC):
    i: int


@dataclass
class F(ValidatedDC):
    f: float


@dataclass
class S(ValidatedDC):
    s: str


@dataclass
class MyUnion(ValidatedDC):
    data: List[Union[I, F, S]]


my_union = MyUnion([{'i': 1}, {'s': 'S'}, {'f': 0.2}])
assert my_union.get_errors() is None
assert my_union == MyUnion(data=[I(i=1), S(s='S'), F(f=0.2)])

ValidatedDC: https://github.com/EvgeniyBurdin/validated_dc

Evgeniy_Burdin
  • 627
  • 5
  • 14