Mypy not recognizing Class Types with inheritance

2.7k views Asked by At

I can't get Mypy to recognize the correct types here. All I'm trying to do is create a dict of names to classes, so I can get the class by providing a type_name that is its attribute. Maybe because the dataclass and dataclasses-json stuff is confusing it?

# mypy_test.py

from dataclasses import dataclass
from typing import Any, Dict, Type, TypeVar

from dataclasses_json import DataClassJsonMixin


@dataclass
class _BaseDataItem(DataClassJsonMixin):
    name: str  # functions as an ID. Subclasses should NOT modify its value after creation.
    type_name: str
    body: Any = None
    # etc ...


@dataclass
class DatanetItem(_BaseDataItem):
    type_name: str = "datanet"


@dataclass
class RedshiftItem(_BaseDataItem):
    type_name: str = "redshift"


# https://mypy.readthedocs.io/en/stable/kinds_of_types.html#the-type-of-class-objects
IT = TypeVar("IT", bound=_BaseDataItem)

items2classes: Dict[str, Type[IT]] = {
    c.type_name: c for c in (DatanetItem, RedshiftItem)
}


def create_obj(name: str, item_type: str) -> _BaseDataItem:
    klass = items2classes[item_type]
    obj = klass(name)
    return obj

Example usage:

In [1]: import mypy_test as m

In [2]: m.create_obj('123','datanet')
Out[2]: DatanetItem(name='123', type_name='datanet', body=None)

In [3]: m.create_obj('mytable','redshift')
Out[3]: RedshiftItem(name='mytable', type_name='redshift', body=None)

Even though I thought I followed the instructions in the docs, for some reason MyPy keeps giving me this error:

mypy_test.py:28: error: Type variable "mypy_test.IT" is unbound
mypy_test.py:28: note: (Hint: Use "Generic[IT]" or "Protocol[IT]" base class to bind "IT" inside a class)
mypy_test.py:28: note: (Hint: Use "IT" in function signature to bind "IT" inside a function)
mypy_test.py:35: error: Cannot instantiate type "Type[IT?]"
Found 2 errors in 1 file (checked 1 source file)
1

There are 1 answers

1
Michael0x2a On

The reason why mypy is failing has nothing to do with dataclasses or dataclasses-json. If you try simplifying your example so that everything is a regular object, you'd still get the same error.

The reason for this is because unfortunately, you are attempting to use TypeVars for something they were never meant to do.

TypeVars are meant to be used within class definitions or function definitions: they let you "capture" the value of a type provided by the user and reuse in other parts of your class definition/function definition. This "capture" or "matching" is performed every time the user actually tries using your class or function.

But since items2class is not a part of a generic class or function, mypy will end up rightfully complaining that you are attempting to use a TypeVar outside of the context you mean to use it in.

Thankfully, the solution is relatively simple in this case: stop using generics and instead switch to having your dict be of type Dict[Str, Type[_BaseDataItem]]. Your subclasses are all of type _BaseDataItem to begin with, so they're valid items to insert into this dict.

If you want more precise type hints -- e.g. have mypy understand that the key "redshift" corresponds to exactly RedshiftItem when using your dict -- you can perhaps try experimenting with using TypedDicts. But I'm not confident this will actually be helpful in the context of your code.