pytransitions / transitions

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

AsyncMachine transitions fail when a list of `State` objects are passed in to the FSM. #630

Closed mcvady closed 9 months ago

mcvady commented 9 months ago

Thank you for taking the time to report a bug! Your support is essential for the maintenance of this project. Please fill out the following fields to ease bug hunting and resolving this issue as soon as possible:

Describe the bug

AsyncMachine transitions fail when a list of State objects are passed in to the FSM.

Minimal working example

The following example works as expected. First A is printed. Then the transition is awaited/occurs, then B is printed.

import asyncio

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

class AsyncModel:
    """Async Model"""

STATES = [
    "A",
    "B",
]

TRANSITIONS = [{"trigger": "progress", "source": "A", "dest": "B"}]

async def main():
    model = AsyncModel()
    _ = AsyncMachine(model=model, states=STATES, transitions=TRANSITIONS, initial="A")

    print(model.state)
    await model.progress()
    print(model.state)

if __name__ == "__main__":
    asyncio.get_event_loop().run_until_complete(main())

If STATES is defined as a list of dictionaries, the example still works fine. e.g:

STATES = [
    {"name": "A"},
    {"name": "B"},
]

However when STATES is a list of State objects, e.g:

STATES = [
    State(name="A"),
    State(name="B"),
]

... now awaiting the transition coroutine blows up:

A
/home/mcvady/tmp/.venv/lib/python3.10/site-packages/transitions/core.py:135: 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
Traceback (most recent call last):
  File "/home/mcvady/tmp/async-example.py", line 25, in <module>
    asyncio.get_event_loop().run_until_complete(main())
  File "/usr/lib/python3.10/asyncio/base_events.py", line 649, in run_until_complete
    return future.result()
  File "/home/mcvady/tmp/async-example.py", line 21, in main
    await model.progress()
  File "/home/mcvady/tmp/.venv/lib/python3.10/site-packages/transitions/extensions/asyncio.py", line 177, in trigger
    return await self.machine.process_context(func, model)
  File "/home/mcvady/tmp/.venv/lib/python3.10/site-packages/transitions/extensions/asyncio.py", line 392, in process_context
    res = await self._process_async(func, model)
  File "/home/mcvady/tmp/.venv/lib/python3.10/site-packages/transitions/extensions/asyncio.py", line 447, in _process_async
    return await trigger()
  File "/home/mcvady/tmp/.venv/lib/python3.10/site-packages/transitions/extensions/asyncio.py", line 183, in _trigger
    await self._process(event_data)
  File "/home/mcvady/tmp/.venv/lib/python3.10/site-packages/transitions/extensions/asyncio.py", line 201, in _process
    event_data.result = await trans.execute(event_data)
  File "/home/mcvady/tmp/.venv/lib/python3.10/site-packages/transitions/extensions/asyncio.py", line 126, in execute
    await self._change_state(event_data)
  File "/home/mcvady/tmp/.venv/lib/python3.10/site-packages/transitions/extensions/asyncio.py", line 138, in _change_state
    await event_data.machine.get_state(self.source).exit(event_data)
TypeError: object NoneType can't be used in 'await' expression

Expected behavior Passing in list of explicit State objects should work the same using dictionaries or strings.

Additional context Using Python 3.10.12.

aleneum commented 9 months ago

Hello @mcvady,

this is unfortunately a common pitfall. AsyncMachine requires AsyncState as a state class. Simple State instances do not feature async processing.

from transitions.extensions.asyncio import AsyncState

Each machine class has a state_cls class property that is used to create state instances. Similarly, event_cls and transition_cls are used for the creation of events and transitions.

mcvady commented 9 months ago

Ah, I see @aleneum. Thank you for your work and the quick reply.