-
Notifications
You must be signed in to change notification settings - Fork 118
Description
Things to check first
-
I have searched the existing issues and didn't find my bug already reported there
-
I have checked that my bug is still present in the latest release
Typeguard version
4.3.0
Python version
3.10
What happened?
When trying to check if an object is of the type UnionType
, typeguard rejects regardless of whether the type matches.
It is important to note that I am not checking if an object is one of multiple types (using, for example does "hello"
match int | str
), I am looking at whether an object is a UnionType
(for example, if int | str
is a UnionType
). See the MWE for more information and to make clearer what my code is doing.
When using the |
operator when defining type-hints, it will either generate a Union
or UnionType
type object. If the two objects you are combining are standard types
, then it will create UnionType
but if they are complex constructs (such as a Literal
) then it will create a Union
object. I am not sure if it is as cut-and-dry as this, but this seems to be the general behaviour.
These are handled in typeguard
by the check_union()
and check_uniontype()
functions which are currently identical. These function looks through all of the arguments of the provided type and if one of them matches (dispatched to check_type_internal()
) then it returns and is considered to have passed. However in my code, I am using UnionType
on its own without any arguments and so this loop is never entered and the check_uniontype()
function always raises a TypeCheckError
.
From my understanding the only time a UnionType
object will be in use without arguments is when it is being used explicitly, and otherwise it will have been created via a |
operation between two types
. I also believe that a Union
would always have arguments as this a SpecialForm
and so other checkers such as mypy
would already fail in this instance and if your function is to take a general Union
construct, then it would have to take a SpecialForm
and check the subclassing because otherwise you would need to use Union[X,Y]
.
For this reason, I think a solution would be to include an early escape within the check_uniontype()
function if the args
is an empty list and if the value
is explicitly a UnionType
. I appreciate if further consideration is needed since the difference between Union
and UnionType
is probably more nuanced than I am assuming.
How can we reproduce the bug?
The following code demonstrates the issue (and hopefully will provide some insight into what I am trying to check).
from types import UnionType
from typeguard import typechecked
@typechecked
def is_uniontype(t: type | UnionType) -> bool:
if isinstance(t, UnionType):
return True
return False
def test_is_uniontype():
assert is_uniontype(int | float) # Should be correct as we are passing a UnionType object
A smaller example would be the following which calls the check_type()
function directly:
from types import UnionType
from typeguard import check_type
check_type(int | float, UnionType)