Skip to content

Async conditions are not awaited when using certain condition expressions #535

@nimobeeren

Description

@nimobeeren

When a statemachine has at least one async condition callback and this condition is used in a transition cond parameter using certain condition expressions (see below), Python warns that a coroutine was never awaited, and may cause transition conditions to be evaluated incorrectly.

Minimal repro:

import asyncio
from statemachine import State, StateMachine


class AsyncConditionBug(StateMachine):
    init = State(initial=True)

    go = init.to.itself(cond="not cond_false")
    
    async def cond_false(self):
        return False

async def main():
    sm = AsyncConditionBug()
    await sm.send("go") # type: ignore


if __name__ == "__main__":
    asyncio.run(main())

yields the following output (scroll to the right to see "coroutine was never awaited" warning):

/Users/nimo.beeren/Development/io/efteling/sprookjesboom/.venv/lib/python3.13/site-packages/statemachine/spec_parser.py:37: RuntimeWarning: coroutine 'callable_method.<locals>.signature_adapter' was never awaited
  return not predicate(*args, **kwargs)
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
Traceback (most recent call last):
  File "/Users/nimo.beeren/Development/io/efteling/sprookjesboom/repro.py", line 25, in <module>
    asyncio.run(main())
    ~~~~~~~~~~~^^^^^^^^
  File "/Users/nimo.beeren/.local/share/uv/python/cpython-3.13.1-macos-aarch64-none/lib/python3.13/asyncio/runners.py", line 194, in run
    return runner.run(main)
           ~~~~~~~~~~^^^^^^
  File "/Users/nimo.beeren/.local/share/uv/python/cpython-3.13.1-macos-aarch64-none/lib/python3.13/asyncio/runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
  File "/Users/nimo.beeren/.local/share/uv/python/cpython-3.13.1-macos-aarch64-none/lib/python3.13/asyncio/base_events.py", line 720, in run_until_complete
    return future.result()
           ~~~~~~~~~~~~~^^
  File "/Users/nimo.beeren/Development/io/efteling/sprookjesboom/repro.py", line 21, in main
    await sm.send("go") # type: ignore
          ~~~~~~~^^^^^^
  File "/Users/nimo.beeren/Development/io/efteling/sprookjesboom/.venv/lib/python3.13/site-packages/statemachine/statemachine.py", line 312, in send
    result = event_instance(*args, **kwargs)
  File "/Users/nimo.beeren/Development/io/efteling/sprookjesboom/.venv/lib/python3.13/site-packages/statemachine/event.py", line 133, in __call__
    result = machine._processing_loop()
  File "/Users/nimo.beeren/Development/io/efteling/sprookjesboom/.venv/lib/python3.13/site-packages/statemachine/statemachine.py", line 112, in _processing_loop
    return self._engine.processing_loop()
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
  File "/Users/nimo.beeren/Development/io/efteling/sprookjesboom/.venv/lib/python3.13/site-packages/statemachine/engines/sync.py", line 67, in processing_loop
    result = self._trigger(trigger_data)
  File "/Users/nimo.beeren/Development/io/efteling/sprookjesboom/.venv/lib/python3.13/site-packages/statemachine/engines/sync.py", line 98, in _trigger
    raise TransitionNotAllowed(trigger_data.event, state)
statemachine.exceptions.TransitionNotAllowed: Can't go when in Init.

It seems like the coroutine returned by cond_false is not awaited, but is evaluated as a truthy value, which disallows the transition. Removing async from def cond_false fixes it.

Adding a second condition lets us explore more cases:

    async def cond_true(self):
        return True

Changing the cond parameter of the go transition to the following values:

  • not cond_false: as above
  • ⚠️ cond_true and cond_true: prints "coroutine was never awaited" warning but the transition is correctly allowed
  • cond_true or cond_false: no warning or error, seems correct

It seems like not and and are triggering the issue while or is fine.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions