Guillaume Jacquenot
Guillaume Jacquenot

Reputation: 11717

Direct instantiation of `typing.Union` in Python

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

Upvotes: 5

Views: 18708

Answers (6)

Konstantin
Konstantin

Reputation: 2629

While you cannot instantiate a union using its "constructor", as you are trying to do in the example, you can instantiate (or validate, in pydantic terms) it from a dictionary.

type_adapter = pydantic.TypeAdapter(MyUnion)
type_adapter.validate_python({'a': 666, 'c': 'foo'})

Check out pydantic documentation on unions and search for type_adapter.

Upvotes: 0

eirki
eirki

Reputation: 81

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')

Upvotes: 5

ashbygeek
ashbygeek

Reputation: 761

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. :/

Upvotes: 3

Evgeniy_Burdin
Evgeniy_Burdin

Reputation: 703

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

Upvotes: 0

chepner
chepner

Reputation: 531155

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.

Upvotes: 1

LtWorf
LtWorf

Reputation: 7598

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.

Upvotes: 10

Related Questions