StateMachine 2.3.2

July 01, 2024

What’s new in 2.3.2

Observers are now rebranded to Listeners. With expanted support for adding listeners when instantiating a state machine. This allows covering more use cases. We also improved the async support.

Improved async support

Since version 2.3.0, we have added async support. However, we encountered use cases, such as the async safety on Django ORM, which expects no running event loop and blocks if it detects one on the current thread.

To address this issue, we developed a solution that maintains a unified API for both synchronous and asynchronous operations while effectively handling these scenarios.

This is achieved through a new concept called “engine,” an internal strategy pattern abstraction that manages transitions and callbacks.

There are two engines:

SyncEngine

Activated if there are no async callbacks. All code runs exactly as it did before version 2.3.0.

AsyncEngine

Activated if there is at least one async callback. The code runs asynchronously and requires a running event loop, which it will create if none exists.

These engines are internal and are activated automatically by inspecting the registered callbacks in the following scenarios:

See also

See Async for more details.

Listeners at class initialization

Listeners are a way to generically add behavior to a state machine without changing its internal implementation.

Example:

>>> from tests.examples.traffic_light_machine import TrafficLightMachine

>>> class LogListener(object):
...     def __init__(self, name):
...         self.name = name
...
...     def after_transition(self, event, source, target):
...         print(f"{self.name} after: {source.id}--({event})-->{target.id}")
...
...     def on_enter_state(self, target, event):
...         print(f"{self.name} enter: {target.id} from {event}")


>>> sm = TrafficLightMachine(listeners=[LogListener("Paulista Avenue")])
Paulista Avenue enter: green from __initial__

>>> sm.cycle()
Running cycle from green to yellow
Paulista Avenue enter: yellow from cycle
Paulista Avenue after: green--(cycle)-->yellow


See also

See Listeners for more details.

Binding event triggers to external objects

Now it’s possible to bind events to external objets. One expected use case is in conjunction with the Mixins models, that wrap state machines internally. This way you don’t need to expose the state machine.

See also

See User workflow machine for an example binding event triggers with a state machine.

Bugfixes in 2.3.2

  • Fixes #446: Regression that broke sync callbacks interacting with Django ORM due to the added async support and Django’s async safety guards.

  • Fixes #449: Regression that did not trigger events in nested calls within an already running transition.

Deprecation notes

Statemachine class deprecations in 2.3.2

Deprecations that will be removed on the next major release:

  • StateMachine.add_observer is deprecated in favor of StateMachine.add_listener.

  • StateMachine.rtc option is deprecated. We’ll keep only the run-to-completion (RTC) model.