pytransitions / transitions

A lightweight, object-oriented finite state machine implementation in Python with many extensions
MIT License
5.49k stars 525 forks source link

AsyncMachine is calling non async transitions from core.py #577

Closed jorritsmit closed 2 years ago

jorritsmit commented 2 years ago

Describe the bug When using an asyncmachine it seems that most of the callbacks are correctly called from the async module, but they eventually call a non async function in core.py:

transitions\core.py:134: RuntimeWarning: coroutine 'AsyncMachine.callbacks' was never awaited event_data.machine.callbacks(self.on_exit, event_data)

Minimal working example

from transitions.extensions.asyncio import AsyncMachine as Machine
from transitions import State
import asyncio

class StateMachine(Machine):
    states = [
        State(name="stopped", on_enter=["entered_stopped"]),
        State(name="started", on_enter=["entered_started"]),
    ]
    trans = [
        {
            "trigger": "start",
            "source": "stopped",
            "dest": "started",
            "conditions": "check_start",
        },
        {
            "trigger": "stop",
            "source": "started",
            "dest": "stopped",
            "conditions": "check_stop",
        },
    ]

    def __init__(self):
        super().__init__(
            self,
            states=self.states,
            initial=self.initial,
            transitions=self.trans,
            auto_transitions=True,
            ignore_invalid_triggers=True,
            queued=True,
        )

    # Callbacks
    # On entered functions
    async def entered_stopped(self):
        await asyncio.sleep(1)

    async def entered_started(self):
        await asyncio.sleep(1)

    async def check_start(self):
        await asyncio.sleep(1)
        return True

    async def check_stop(self):
        await asyncio.sleep(1)
        return True

    async def run(self):
        """Runs the state machine."""
        a = self.get_triggers(self.state)
        triggers = [x for x in a if not x.startswith("to_")]
        for trig in triggers:
            if await self.trigger(trig):
                break

if __name__ == "__main__":
    sm = StateMachine()
    while True:
        asyncio.run(sm.run())

logs debug

2022-06-24 10:40:55,412 - transitions.extensions.asyncio - DEBUG - Executed machine preparation callbacks before conditions.
2022-06-24 10:40:55,413 - transitions.extensions.asyncio - DEBUG - Initiating transition from state stopped to state started...
2022-06-24 10:40:55,414 - transitions.extensions.asyncio - DEBUG - Executed callbacks before conditions.
2022-06-24 10:40:56,420 - transitions.extensions.asyncio - DEBUG - Executed callback before transition.
2022-06-24 10:40:56,421 - transitions.core - DEBUG - Exiting state stopped. Processing callbacks...
2022-06-24 10:40:56,424 - transitions.core - INFO - Finished processing state stopped exit callbacks.
2022-06-24 10:40:56,425 - transitions.extensions.asyncio - DEBUG - Executed machine finalize callbacks

traceback

2022-06-24 10:40:55,412 - transitions.extensions.asyncio - DEBUG - Executed machine preparation callbacks before conditions.
2022-06-24 10:40:55,413 - transitions.extensions.asyncio - DEBUG - Initiating transition from state stopped to state started...
2022-06-24 10:40:55,414 - transitions.extensions.asyncio - DEBUG - Executed callbacks before conditions.
2022-06-24 10:40:56,420 - transitions.extensions.asyncio - DEBUG - Executed callback before transition.
2022-06-24 10:40:56,421 - transitions.core - DEBUG - Exiting state stopped. Processing callbacks...
transitions\core.py:134: RuntimeWarning: coroutine 'AsyncMachine.callbacks' was never awaited
  event_data.machine.callbacks(self.on_exit, event_data)
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
2022-06-24 10:40:56,424 - transitions.core - INFO - Finished processing state stopped exit callbacks.
2022-06-24 10:40:56,425 - transitions.extensions.asyncio - DEBUG - Executed machine finalize callbacks
Traceback (most recent call last):
  ## My module traceback stuff edited out ##
  File "transitions\extensions\asyncio.py", line 177, in trigger  File "transitions\extensions\asyncio.py", line 201, in _process
    if await trans.execute(event_data):
  File "transitions\extensions\asyncio.py", line 130, in execute
    await self._change_state(event_data)
  File "transitions\extensions\asyncio.py", line 142, in _change_state
    await event_data.machine.get_state(self.source).exit(event_data)
honeytrap15 commented 2 years ago

I had the same issue, and solved by using AsyncState instead of State.

from transitions.extensions.asyncio import AsyncMachine as Machine, AsyncState as State

...
jorritsmit commented 2 years ago

Thanks!