I have a couple of data processors that have implemented an interface whose method "process" takes over the processing part with a result to be returned. The result should be a dictionary that is compliant to a given structure. That's why I have defined TypedDict's for each processor result. Maybe I haven't understood TypedDict correctly, but I want to achieve something like this:
import abc
from typing import TypedDict
class AProcessorResult(TypedDict):
param1: str
param2: int
class BProcessorResult(TypedDict):
paramA: str
paramB: str
paramC: float
class IProcessor(abc.ABC):
def process(self) -> <Dictionary mypy-checked against processor result structure>:
raise NotImplementedError
class AProcessor(IProcessor):
def process(self) -> <Dictionary mypy-checked against AProcessorResult structure>:
result: AProcessorResult = {
'param1': 'value1',
'param2': 0
}
return result
class BProcessor(IProcessor):
def process(self) -> <Dictionary mypy-checked against BProcessorResult structure>:
result: BProcessorResult = {
'param1': 'value1',
'param2': 1
}
return result
def main() -> None:
aProcessor: AProcessor = AProcessor()
aProcessor.process() # <- Should be successful during the MyPy check
bProcessor: BProcessor = BProcessor()
bProcessor.process() # <- Should fail during the MyPy check
if __name__ == '__main__':
main()
If I return ordinary Dict's from the "process" methods, MyPy complains about the type incompatibility:
Incompatible return value type (got "AProcessorResult", expected "Dict[Any, Any]")
I don't want to use dataclasses at this point, because the dictionaries will be "dataclassified" in a later stage anyway and require some additional dict processing before.
Is there a neat way to achieve this in a most abstract and generic way?
I am using Python 3.8.
You can use generics to achieve that. Basically, you define IProcess to return some generic type and define which concrete type it is using IProcessor[ConcreteType].
import abc
from typing import TypedDict, Any, Generic, TypeVar
T = TypeVar("T")
class AProcessorResult(TypedDict):
param1: str
param2: int
class BProcessorResult(TypedDict):
paramA: str
paramB: str
paramC: float
class IProcessor(abc.ABC, Generic[T]):
def process(self) -> T:
raise NotImplementedError
class AProcessor(IProcessor[AProcessorResult]):
def process(self) -> AProcessorResult:
result: AProcessorResult = {
'param1': 'value1',
'param2': 0
}
return result
class BProcessor(IProcessor[BProcessorResult]):
def process(self) -> BProcessorResult:
result: BProcessorResult = {
'param1': 'value1',
'param2': 1
}
return result
def main() -> None:
aProcessor: AProcessor = AProcessor()
aProcessor.process() # <- Should be successful during the MyPy check
bProcessor: BProcessor = BProcessor()
bProcessor.process() # <- Should fail during the MyPy check
if __name__ == '__main__':
main()
if you want to be more strict and force the return type to be always a mapping type, you can bound the type var as
Mapping
from typing import Mapping
T = TypeVar("T", bound=Mapping[str, Any])
You can playaround with those solutions here