transitions icon indicating copy to clipboard operation
transitions copied to clipboard

Is this a bug?

Open zljubisic opened this issue 5 months ago • 0 comments

This code works fine:

from transitions import EventData, Machine
import time
import random
import datetime as dt
from rich import print

class Device:

    state: str
    _CURRENT_THRESHOLD = 3

    def __init__(self, name: str):
        self._name: str = name
        self._state: str = "unknown"
        self._state_started: dt.datetime = dt.datetime.now()

    @property
    def name(self) -> str:
        return self._name

    @property
    def state_started(self) -> dt.datetime:
        return self._state_started

    # def new_current(self, current: float) -> bool:
    #     raise RuntimeError("Should be overridden!")
    
    # def trigger(self, trigger_name: str) -> bool:
    #     raise RuntimeError("Should be overridden!")

    def is_connected(self, event: EventData) -> bool:
        if event.kwargs["current"] > self._CURRENT_THRESHOLD:
            return True
        else:
            return False


    def is_disconnected(self, event: EventData) -> bool:
        return not self.is_connected(event)

    def update_state_started(self, event):
        self._state_started = dt.datetime.now()

    def secs_in_state(self) -> float:
        return (dt.datetime.now() - self._state_started).total_seconds()


sm = Machine(
    model=None,
    states=["unknown", "connected", "disconnected"],
    transitions=[
        {
            "trigger": "new_current",
            "source": ["unknown", "disconnected"],
            "dest": "connected",
            "conditions": ["is_connected"],
        },
        {
            "trigger": "new_current",
            "source": ["unknown", "connected"],
            "dest": "disconnected",
            "conditions": ["is_disconnected"],
        },
    ],
    after_state_change=["update_state_started"],
    send_event=True,
    # model_override=True,
    initial="unknown",
)

svk1 = Device("svk1")
svk2 = Device("svk2")

models = [svk1, svk2]

for model in models:
    sm.add_model(model)

print("\nInitial state:")
for model in models:
    print(model.name, model.state, model.state_started)


svk1.new_current(current=0)
svk2.new_current(current=5)

print("\nFirst state:")
for model in models:
    print(model.name, model.state, model.state_started)

max_current = 5
print("\nOther states:")
for current in range(max_current):
    time.sleep(random.random())
    svk1_current = current
    svk2_current = max_current - current

    svk1.new_current(current=svk1_current)
    svk2.new_current(current=svk2_current)

    print(
        f"svk1: current={svk1_current}, state={svk1.state:>12}, started={svk1.state_started}, "
        f"svk2: current={svk2_current}, state={svk2.state:>12}, started={svk2.state_started}"
    )    

but mypy complains:

$ mypy src/state_machine/zoran/check_mypy.py
src/state_machine/zoran/check_mypy.py:86: error: "Device" has no attribute "new_current"  [attr-defined]
src/state_machine/zoran/check_mypy.py:87: error: "Device" has no attribute "new_current"  [attr-defined]
src/state_machine/zoran/check_mypy.py:100: error: "Device" has no attribute "new_current"  [attr-defined]
src/state_machine/zoran/check_mypy.py:101: error: "Device" has no attribute "new_current"  [attr-defined]
Found 4 errors in 1 file (checked 1 source file)

To mitigate it I thought that I just have to add commented lines (in essence use model_override=True, and prepare some methods) and that will help. But If I uncomment the lines, when I run the code I get:

Exception has occurred: TypeError
Machine.is_state() takes 3 positional arguments but 4 were given
  File "/home/zoran/state_machine/src/state_machine/zoran/check_mypy.py", line 86, in <module>
    svk1.new_current(current=0)
TypeError: Machine.is_state() takes 3 positional arguments but 4 were given

I used procedure described at https://github.com/pytransitions/transitions?tab=readme-ov-file#-typing-support, but maybe I have missed something.

I am also not sure if I need def trigger(self, trigger_name: str) -> bool: in my model or def new_current(self, current: float) -> bool: is enough.

Expected behavior I expect mypy not to show any errors if lines are uncommented and to be able to run the code with no errors.

Additional context I am using: Python 3.11.9 mypy 1.11.2 (compiled: yes) transitions 0.9.2

zljubisic avatar Sep 14 '24 18:09 zljubisic