Builder pattern with generic and typehinting
Posted by gidorah5@reddit | Python | View on Reddit | 3 comments
Hello redditors,
I've been playing around with a builder pattern in Python, and I was trying to achieve a builder pattern with correct typehinting, However, I can't seem to get some of my types working.
The goal is to create a pipeline, that can have a variable amout of steps. The pipeline have a TIn and TOut type that should be infered from the inner steps (TIn being the input of the first step, and TOut the output of the last step)
Here is my current implementation:
TIn = TypeVar("TIn")
TOut = TypeVar("TOut")
TNext = TypeVar("TNext")
class Step(ABC, Generic[TIn, TOut]):
def execute(self, data: TIn) -> TOut:
...
def create_process(step: Step[TIn, TOut]) -> "Process[TIn, TOut]":
return Process.start_static(step)
class Process(Generic[TIn, TOut]):
def __init__(self, steps: list[Step] | None = None):
self.steps: list[Step] = steps or []
@classmethod
def start_class(cls, step: Step[TIn, TOut]) -> "Process[TIn, TOut]":
return cls([step])
@staticmethod
def start_static(step: Step[TIn, TOut]) -> "Process[TIn, TOut]":
return Process([step])
def add_step(self, step: Step[TOut, TNext]) -> "Process[TIn, TNext]":
return Process(self.steps + [step])
def execute(self, data: TIn) -> TOut:
current = data
for step in self.steps:
print(type(step))
current = step.execute(current)
return cast(TOut, current)
class IntToStr(Step[int, str]):
def execute(self, data: int) -> str:
return str(data)
class StrToBool(Step[str, bool]):
def execute(self, data: str) -> bool:
return data != ""
process = create_process(IntToStr()).add_step(StrToBool())
# ^^ type Process[int, bool]
process = Process().add_step(IntToStr()).add_step(StrToBool())
# ^^ type Process[Unknown, bool]
process = Process.start_static(IntToStr()).add_step(StrToBool())
# ^^ type Process[Unknown, bool]
process = Process.start_class(IntToStr()).add_step(StrToBool())
# ^^ type Process[Unknown, bool]
process.execute(1)
As you can see, the only way I've been able to correctly infer the input type is by using a method outside of my class.
I'm not sure what is causing this, and I was wondering if anyone knew a workaround this issue, or am I doomed to use a Factory method.
I would believe that the issue is caused because TIn is not defined for the first step, thus issuing an Unknown.
Have a great day y'all !
nicholashairs@reddit
Aside: for the class / static methods you likely could be using Self instead of quoting Process.
Also based on the quoting, at the top of your file you might want to
from __future__ import annotationsaikii@reddit
How far the inference goes really depends on the type checker you're using.
If I use reveal_type like this:
Then:
[int, bool],[Unknown, bool],[int, bool],[int, bool]which is slightly better than your result but still incompleteAs far as I understand PEP 484 defines type annotations overall, but there is no official spec regarding what you should expect from type inference, so it's really up to the tool you're using. You can probably configure some strict mode saying "never fallback to any" depending on your type checker.
No_Soy_Colosio@reddit
Mom I'm scared