You may be looking for __origin__:
# * __origin__ keeps a reference to a type that was subscripted,
# e.g., Union[T, int].__origin__ == Union;`
import typing
typ = typing.Union[int, str]
if typ.__origin__ is typing.Union:
print('value type should be one of', typ.__args__)
elif typ.__origin__ is typing.Generic:
print('value type should be a structure of', typ.__args__[0])
else:
print('value type should be', typ)
>>>value type should be one of (<class 'int'>, <class 'str'>)
The best I could find to advocate the use of this undocumented attribute is this reassuring quote from Guido Van Rossum (dated 2016):
The best I can recommend is using
__origin__— if we were to change this attribute there would still have to be some other way to access the same information, and it would be easy to grep your code for occurrences of__origin__. (I’d be less worried about changes to__origin__than to__extra__.) You may also look at the internal functions_gorg()and_geqv()(these names will not be part of any public API, obviously, but their implementations are very simple and conceptually useful).
This caveat in the documentation seem to indicate that nothing is set in marble yet:
New features might be added and API may change even between minor releases if deemed necessary by the core developers.