fgmacedo / python-statemachine

Python Finite State Machines made easy.
MIT License
854 stars 84 forks source link

Bug with listener and decorator @mystate.exit #465

Closed FlyAgainMarion closed 2 months ago

FlyAgainMarion commented 2 months ago

Description

I have a state machine with a decorator @mystate.exit . If I add a listener then the transition breaks.

Everything is fine in the 2.3.1 version.

What I Did

Minimal example

import statemachine

class TestListener(object):
    def __init__(self):
        pass        

class MyMachine(statemachine.StateMachine):

    # ======================================== STATES ==========================

    first = statemachine.State("FIRST", initial=True)

    second = statemachine.State("SECOND")

    # ======================================= TRANSITIONS ======================

    first_selected = (
        second.to(first)
    )

    second_selected = (
        first.to(second)
    )

    # ========================== ACTIONS ==================================

    @first.exit
    def exit_first(self) -> None:
        print('exit SLEEPING')

m = MyMachine()
m.add_listener(TestListener())

m.send("second_selected")

Command : python3 test.py

Backtrace :

$ python3 test.py 
exit SLEEPING
Traceback (most recent call last):
  File "/home/mrevolle/Téléchargements/test.py", line 35, in <module>
    m.send("second_selected")
  File "/home/mrevolle/build/lib/python3.10/site-packages/statemachine/statemachine.py", line 314, in send
    result = self._async_send(event, *args, **kwargs)
  File "/home/mrevolle/build/lib/python3.10/site-packages/statemachine/statemachine.py", line 328, in _async_send
    return event_instance.trigger(self, *args, **kwargs)
  File "/home/mrevolle/build/lib/python3.10/site-packages/statemachine/event.py", line 27, in trigger
    return machine._processing_loop()
  File "/home/mrevolle/build/lib/python3.10/site-packages/statemachine/statemachine.py", line 121, in _processing_loop
    return self._engine._processing_loop()
  File "/home/mrevolle/build/lib/python3.10/site-packages/statemachine/engines/sync.py", line 72, in _processing_loop
    result = self._trigger(trigger_data)
  File "/home/mrevolle/build/lib/python3.10/site-packages/statemachine/engines/sync.py", line 104, in _trigger
    result = self._activate(event_data)
  File "/home/mrevolle/build/lib/python3.10/site-packages/statemachine/engines/sync.py", line 122, in _activate
    self.sm._get_callbacks(source.exit.key).call(*args, **kwargs)
  File "/home/mrevolle/build/lib/python3.10/site-packages/statemachine/callbacks.py", line 322, in call
    return [
  File "/home/mrevolle/build/lib/python3.10/site-packages/statemachine/callbacks.py", line 323, in <listcomp>
    callback.call(*args, **kwargs)
  File "/home/mrevolle/build/lib/python3.10/site-packages/statemachine/callbacks.py", line 269, in call
    value = self._callback(*args, **kwargs)
  File "/home/mrevolle/build/lib/python3.10/site-packages/statemachine/signature.py", line 65, in signature_adapter
    return method(*ba.args, **ba.kwargs)
TypeError: MyMachine.exit_first() missing 1 required positional argument: 'self'
fgmacedo commented 2 months ago

Hi @FlyAgainMarion , thanks for reporting this. I reproduced it locally and confirmed it as a regression bug. I'll try to figure out a fix and a hotfix release.

fgmacedo commented 2 months ago

@FlyAgainMarion until a fix is in place, consider using the new listeners param on constructor:

m = MyMachine(listeners=[TestListener()])