I have created the following Protocol class:
from __future__ import annotations
from dataclasses import dataclass
from typing import Dict, Optional, Protocol, runtime_checkable
@runtime_checkable
class PipelineProcess(Protocol):
def update(self):
...
def set_inputs(self, inputs: Dict[str, int]):
...
def get_outputs(self) -> Dict[str, int] | None:
...
I use it for this example process and others which are built the same way:
@dataclass
class ExampleProcess:
in_value: Optional[int | None] = None
out_value: Optional[int | None] = None
def update(self):
assert(self.in_value is not None)
self.out_value = self.in_value * 2
def set_inputs(self, values: Dict[str, int]):
assert "value" in values.keys()
self.in_value = values["value"]
def get_outputs(self) -> Dict[str, int]:
return {"value": self.out_value}
If I analyze this with mypy I got the following error:
error: Dict entry 0 has incompatible type "str": "Optional[int]"; expected "str": "int"
To solve this I used dict[str, Optional[int]] as the return type of the get_outputs method, which seems a bit weird, since I used Optional for the dataclass attribute.
Or is the Union for Optional redundant and I have to use Optional[int] always instead of int because it implicitly says None is also valid?
Since
out_valueis declared to be of the union typeint | None, setting it as value of a dictionary expected to be of typedict[str, int]is incorrect. So you need to assure the type checker (and yourself) that by the time you put into it the dictionaryout_valueis in fact anint. This can be easily accomplished with anassertinsideget_outputs:Side note: Contrasting this with the other two
assertstatements in your code, I wonder why you put them there. Whileget_outputswould still work without the additionalassert, the other two methods would still fail more or less transparently, if you removed theassertstatements.updatewould raiseTypeError: unsupported operand type(s) for *: 'NoneType' and 'int'and point to the offending lineself.out_value = self.in_value * 2.set_inputswould raiseKeyError: 'value'pointing toself.in_value = values["value"].But I guess if you wanted to be very explicit or even substitute a custom exception class, you could do it like this:
EDIT: Just realized that the first
assertinsideupdateactually does serve the same purpose as the one I suggested forget_outputs. Only the second one inset_inputsis technically unnecessary.But there you see that you can assure the static type checker in different ways. One is an
if-branch based on the type distinction and one is just a straight up assertion.