mypy declaring incompatible types, despit the fact that all types are explicit and using Union

Question:

from enum import Enum

class MyEnum(Enum):
    FIRST = 1, 'first'
    SECOND = 2 , 'second'

__myenum_int_ref: Dict[int, MyEnum] = {k.value[0]: k for k in MyEnum}
__myenum_str_ref: Dict[str, MyEnum] = {k.value[1]: k for k in MyEnum}
__myenum_str_ref.update({k.name: k for k in MyEnum})

__flags_ref: Dict[Union[Type[str], Type[int]], Dict[Union[str, int], MyEnum]] = {
    str: __myenum_str_ref,
    int: __myenum_int_ref,
}

When I run mypy I get this error

function.py:14: error: Dict entry 0 has incompatible type "Type[str]": "Dict[str, MyEnum]"; expected "Union[Type[str], Type[int]]": "Dict[Union[str, int], MyEnum]"
function.py:15: error: Dict entry 1 has incompatible type "Type[int]": "Dict[int, MyEnum]"; expected "Union[Type[str], Type[int]]": "Dict[Union[str, int], MyEnum]"

I have explicitly described that the keys can be of type int and of type str, I inserted in the dictionary a key type int and a key type str.

I know that it is correct because on python 3.10, using Type[int|str] is the preferred way and mypy does accept it perfectly, and it is the "sugar syntax" added so you don’t have to write Union[Type[int], Type[str]].
But I need to use python 3.9, and I am stuck with the use of Union for types, and mypy is complaining that it is incompatible even when I am dealing with explicit types.

Asked By: Lucas Coppio

||

Answers:

Because your value type is Dict[str, MyEnum] or Dict[int, MyEnum], you should use Union[Dict[str, MyEnum], Dict[int, MyEnum]] instead of Dict[Union[str, int], MyEnum]:

Dict[Union[Type[str], Type[int]], Union[Dict[str, MyEnum], Dict[int, MyEnum]]]
# in Python3.9
dict[Union[type[str], type[int]], Union[dict[str, MyEnum], dict[int, MyEnum]]]
# in Python3.10+
dict[type[str] | type[int], dict[str, MyEnum] | dict[int, MyEnum]]

Update:

Using Protocol, you can pass the mypy check:

from typing import Protocol, TypeVar

_T = TypeVar('_T')


class FlagsRef(Protocol[_T]):

    def __getitem__(self, item: type[_T]) -> dict[_T, MyEnum]: ...


__flags_ref: FlagsRef = {   # pass
    str: __myenum_str_ref,
    int: __myenum_int_ref,
}

int_ref: dict[int, MyEnum] = __flags_ref[int]   # pass
Answered By: Mechanic Pig
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.