How do I define a `typing.Union` dynamically?

Viewed 1876

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?

2 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 (which will become the default at some point - currently scheduled for Python 3.11), 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.

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'>].
Related