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?

Asked By: s-m-e

||

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.

Answered By: user2357112

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'>].
Answered By: Tom Wojcik