I have the following abstract Data class and some concrete subclasses:
import abc
from typing import TypeVar, Generic, Union
from pydantic import BaseModel
T = TypeVar('T')
class Data(BaseModel, abc.ABC, Generic[T]):
def handle(self, data: T) -> None:
raise NotImplementedError()
class StringData(Data[str]):
name: str = "string"
def handle(self, data: str) -> None:
print("String data:", data)
class IntData(Data[int]):
name: str = "int"
def handle(self, data: int) -> None:
print("Int data:", data)
Now I want a DataManager class that can hold any Data subclass and delegate handle calls.
Note that I must use a Union instead of the abstract class so that model_dump_json actually produces the correct result. It’s not ideal, but it seems like there’s no way around it.
U = TypeVar('U')
class DataManager(BaseModel, Generic[U]):
my_data: Union[StringData, IntData] # Good
my_data: Data # NOT GOOD!
So after this step, let's say we have:
class DataManager(BaseModel, Generic[U]):
my_data: Union[StringData, IntData]
def handle(self, data: U) -> None:
self.my_data.handle(data)
class StringDataManager(DataManager[str]):
pass
StringDataManager(my_data=StringData()).handle(data="some-string")
This seems valid because each DataManager subclass corresponds to the type of its data. So when calling handle, everything works at runtime — for example, using as string manager will set 'U' to string that will be pass 'string' to the StringData handle function perfectly. However, mypy fails here because it thinks we’re passing U, even though we are actually passing the correct type.
Argument 1 to "handle" of "StringData" has incompatible type "U"; expected "str" [arg-type]
Argument 1 to "handle" of "IntData" has incompatible type "U"; expected "int" [arg-type]