How do I define a `typing.Union` dynamically?
Question:
I am using Typeguard in a couple if projects for type checking at run time in Python. It works pretty well.
I have encountered a situation where the type of a function parameter is a typing.Union
made up of a few dynamically collected data types. E.g.
def find_datatypes():
# some stuff ...
return (str, int) # dynamically generated list / tuple
datatypes = find_datatypes()
Now I want to generate a typing.Union
from datatypes
for eventual use in a function. I expected unpacking syntax to work:
my_union = typing.Union[*datatypes]
@typeguard.typechecked
def some_function(param: my_union):
pass
However, it did not:
my_union = typing.Union[*datatypes]
^
SyntaxError: invalid syntax
How would I achieve what I want?
Answers:
You can kind of do it:
my_union = typing.Union[datatypes]
At runtime, thing[x, y]
is already equivalent to thing[(x, y)]
.
That said, there are limitations to keep in mind. Particularly, when using string annotations, my_union
will have to be available in some_function
‘s global namespace for typeguard
or anything else to be able to resolve the annotation at runtime. That restricts a lot of closure use cases, and a lot of attempts to add annotations dynamically. (String annotations may become the default eventually, but the devs are considering other options, and the plans are currently unclear.)
Also, as you might expect, mypy will not consider any of this valid.
I am generating OpenAPI spec automatically using pydantic.
import typing
from pydantic import BaseModel
class Parent(BaseModel):
@classmethod
def get_subclasses(cls):
return tuple(cls.__subclasses__())
class Child1(Parent):
pass
class Child2(Parent):
pass
datatypes = typing.Union[Parent.get_subclasses()]
It works only because get_subclasses
returns a tuple. I’m posting this answer because __subclasses__
call returns a list and I was scratching my head for a moment because I couldn’t understand why @user2357112 solution is not working in my case. Actually, it did, but I didn’t notice they used a tuple.
Otherwise, you will run into
TypeError: Union[arg, ...]: each arg must be a type. Got [<class '__main__.Child1'>, <class '__main__.Child2'>].
I am using Typeguard in a couple if projects for type checking at run time in Python. It works pretty well.
I have encountered a situation where the type of a function parameter is a typing.Union
made up of a few dynamically collected data types. E.g.
def find_datatypes():
# some stuff ...
return (str, int) # dynamically generated list / tuple
datatypes = find_datatypes()
Now I want to generate a typing.Union
from datatypes
for eventual use in a function. I expected unpacking syntax to work:
my_union = typing.Union[*datatypes]
@typeguard.typechecked
def some_function(param: my_union):
pass
However, it did not:
my_union = typing.Union[*datatypes]
^
SyntaxError: invalid syntax
How would I achieve what I want?
You can kind of do it:
my_union = typing.Union[datatypes]
At runtime, thing[x, y]
is already equivalent to thing[(x, y)]
.
That said, there are limitations to keep in mind. Particularly, when using string annotations, my_union
will have to be available in some_function
‘s global namespace for typeguard
or anything else to be able to resolve the annotation at runtime. That restricts a lot of closure use cases, and a lot of attempts to add annotations dynamically. (String annotations may become the default eventually, but the devs are considering other options, and the plans are currently unclear.)
Also, as you might expect, mypy will not consider any of this valid.
I am generating OpenAPI spec automatically using pydantic.
import typing
from pydantic import BaseModel
class Parent(BaseModel):
@classmethod
def get_subclasses(cls):
return tuple(cls.__subclasses__())
class Child1(Parent):
pass
class Child2(Parent):
pass
datatypes = typing.Union[Parent.get_subclasses()]
It works only because get_subclasses
returns a tuple. I’m posting this answer because __subclasses__
call returns a list and I was scratching my head for a moment because I couldn’t understand why @user2357112 solution is not working in my case. Actually, it did, but I didn’t notice they used a tuple.
Otherwise, you will run into
TypeError: Union[arg, ...]: each arg must be a type. Got [<class '__main__.Child1'>, <class '__main__.Child2'>].