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)
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.