Transitions and events

A state machine is typically composed of a set of State, Transition, Event, and Actions. A state is a representation of the system’s current condition or behavior. A transition represents the change in the system’s state in response to an event or condition. An event is a trigger that causes the system to transition from one state to another, and action is any side-effect, which is the way a StateMachine can cause things to happen in the outside world.

Consider this traffic light machine as an example:

TrafficLightMachine

There’re three transitions, one starting from green to yellow, another from yellow to red, and another from red back to green. All these transitions are triggered by the same Event called cycle.

This state machine could be expressed in python-statemachine as:

 1"""
 2
 3-------------------
 4Reusing transitions
 5-------------------
 6
 7This example helps to turn visual the different compositions of how to declare
 8and bind :ref:`transitions` to :ref:`event`.
 9
10.. note::
11
12    Even sharing the same transition instance, only the transition actions associated with the
13    event will be called.
14
15
16TrafficLightMachine
17   The same transitions are bound to more than one event.
18
19TrafficLightIsolatedTransitions
20    We define new transitions, thus, isolating the connection
21    between states.
22
23"""
24
25from statemachine import State
26from statemachine import StateMachine
27
28
29class TrafficLightMachine(StateMachine):
30    "A traffic light machine"
31
32    green = State(initial=True)
33    yellow = State()
34    red = State()
35
36    slowdown = green.to(yellow)
37    stop = yellow.to(red)
38    go = red.to(green)
39
40    cycle = slowdown | stop | go
41
42    def before_slowdown(self):
43        print("Slowdown")
44
45    def before_cycle(self, event: str, source: State, target: State, message: str = ""):
46        message = ". " + message if message else ""
47        return f"Running {event} from {source.id} to {target.id}{message}"
48
49    def on_enter_red(self):
50        print("Don't move.")
51
52    def on_exit_red(self):
53        print("Go ahead!")
54
55
56# %%
57# Run a transition
58
59sm = TrafficLightMachine()
60sm.send("cycle")
61
62
63# %%
64
65
66class TrafficLightIsolatedTransitions(StateMachine):
67    "A traffic light machine"
68
69    green = State(initial=True)
70    yellow = State()
71    red = State()
72
73    slowdown = green.to(yellow)
74    stop = yellow.to(red)
75    go = red.to(green)
76
77    cycle = green.to(yellow) | yellow.to(red) | red.to(green)
78
79    def before_slowdown(self):
80        print("Slowdown")
81
82    def before_cycle(self, event: str, source: State, target: State, message: str = ""):
83        message = ". " + message if message else ""
84        return f"Running {event} from {source.id} to {target.id}{message}"
85
86    def on_enter_red(self):
87        print("Don't move.")
88
89    def on_exit_red(self):
90        print("Go ahead!")
91
92
93# %%
94# Run a transition
95
96sm2 = TrafficLightIsolatedTransitions()
97sm2.send("cycle")

In line 18, you can say that this code defines three transitions:

  • green.to(yellow)

  • yellow.to(red)

  • red.to(green)

And these transitions are assigned to the Event cycle defined at the class level.

Transitions

In an executing state machine, a Transition is a transfer from one state to another. In a StateMachine, a Transition tells us what happens when an Event occurs.

Tip

A transition can define Actions that will be executed whenever that transition is executed.

Transitions can be filtered with Guards allowing you to add conditions when a transition may be executed.

An action associated with an event (before, on, after), will be assigned to all transitions bounded that uses the event as trigger.

Hint

Usually you don’t need to import and use a Transition class directly in your code, one of the most powerful features of this library is how transitions and events can be expressed linking directly from/to State instances.

Self transition

A transition that goes from a state to itself.

Syntax:

>>> draft = State("Draft")

>>> draft.to.itself()
TransitionList([Transition(State('Draft', ...

Internal transition

It’s like a Self transition.

But in contrast to a self-transition, no entry or exit actions are ever executed as a result of an internal transition.

Syntax:

>>> draft = State("Draft")

>>> draft.to.itself(internal=True)
TransitionList([Transition(State('Draft', ...

Example:

>>> class TestStateMachine(StateMachine):
...     initial = State(initial=True)
...
...     external_loop = initial.to.itself(on="do_something")
...     internal_loop = initial.to.itself(internal=True, on="do_something")
...
...     def __init__(self):
...         self.calls = []
...         super().__init__()
...
...     def do_something(self):
...         self.calls.append("do_something")
...
...     def on_exit_initial(self):
...         self.calls.append("on_exit_initial")
...
...     def on_enter_initial(self):
...         self.calls.append("on_enter_initial")

Usage:

>>> sm = TestStateMachine()

>>> sm._graph().write_png("docs/images/test_state_machine_internal.png")

>>> sm.calls.clear()

>>> sm.external_loop()

>>> sm.calls
['on_exit_initial', 'do_something', 'on_enter_initial']

>>> sm.calls.clear()

>>> sm.internal_loop()

>>> sm.calls
['do_something']

TestStateMachine

Note

The internal transition is represented the same way as an entry/exit action, where the event name is used to describe the transition.

Event

An event is an external signal that something has happened. They are send to a state machine and allow the state machine to react.

An event starts a Transition, which can be thought of as a “cause” that initiates a change in the state of the system.

In python-statemachine, an event is specified as an attribute of the state machine class declaration or directly on the Event parameter on a Transition.

Triggering events

Triggering an event on a state machine means invoking or sending a signal, initiating the process that may result in executing a transition.

This process usually involves

  1. checking the current state

  2. evaluating any guard conditions associated with the transition

  3. executing any actions associated with the transition and (current and target) states

  4. finally updating the current state.

See also

See Actions and Validators and guards.

You can invoke the event in an imperative syntax:

>>> machine = TrafficLightMachine()

>>> machine.cycle()
'Running cycle from green to yellow'

>>> machine.current_state.id
'yellow'

Or in an event-oriented style, events are send:

>>> machine.send("cycle")
Don't move.
'Running cycle from yellow to red'

>>> machine.current_state.id
'red'

You can also pass positional and keyword arguments, that will be propagated to the actions and guards. In this example, the :code:TrafficLightMachine implements an action that echoes back the parameters informed.

 1    Even sharing the same transition instance, only the transition actions associated with the
 2    event will be called.
 3
 4
 5TrafficLightMachine
 6   The same transitions are bound to more than one event.
 7
 8TrafficLightIsolatedTransitions
 9    We define new transitions, thus, isolating the connection
10    between states.

This action is executed before the transition associated with cycle event is activated. You can raise an exception at this point to stop a transition from completing.

>>> machine.current_state.id
'red'

>>> machine.cycle()
Go ahead!
'Running cycle from red to green'

>>> machine.current_state.id
'green'