Reputation: 11717
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
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
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
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
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
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
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