Note
Go to the end to download the full example code.
Compound states – Quest through Middle-earth¶
This example demonstrates compound (hierarchical) states using StateChart.
A compound state contains inner child states, allowing you to model nested behavior.
When a compound state is entered, both the parent and its initial child become active. Transitions within a compound change the active child while the parent stays active. Exiting a compound removes all descendants.
from statemachine import State
from statemachine import StateChart
class QuestMachine(StateChart):
"""A quest through Middle-earth with compound states.
The journey has two compound regions: the ``shire`` (with locations to visit)
and ``rivendell`` (with council activities). A ``wilderness`` state connects them.
"""
class shire(State.Compound):
bag_end = State("Bag End", initial=True)
green_dragon = State("The Green Dragon")
visit_pub = bag_end.to(green_dragon)
class rivendell(State.Compound):
council = State("Council of Elrond", initial=True)
forging = State("Reforging Narsil", final=True)
begin_forging = council.to(forging)
wilderness = State("Wilderness")
destination = State("Quest continues", final=True)
depart_shire = shire.to(wilderness)
arrive_rivendell = wilderness.to(rivendell)
done_state_rivendell = rivendell.to(destination)
Starting the quest¶
When the machine starts, the shire compound and its initial child bag_end
are both active.
sm = QuestMachine()
print(f"Active states: {sorted(sm.configuration_values)}")
assert {"shire", "bag_end"} == set(sm.configuration_values)
Active states: ['bag_end', 'shire']
Transitioning within a compound¶
Moving within a compound changes the active child. The parent stays active.
sm.send("visit_pub")
print(f"After visiting pub: {sorted(sm.configuration_values)}")
assert "shire" in sm.configuration_values
assert "green_dragon" in sm.configuration_values
assert "bag_end" not in sm.configuration_values
After visiting pub: ['green_dragon', 'shire']
Exiting a compound¶
Leaving a compound removes the parent and all children.
sm.send("depart_shire")
print(f"In the wilderness: {sorted(sm.configuration_values)}")
assert {"wilderness"} == set(sm.configuration_values)
In the wilderness: ['wilderness']
Entering another compound¶
Entering rivendell activates its initial child council.
sm.send("arrive_rivendell")
print(f"At Rivendell: {sorted(sm.configuration_values)}")
assert {"rivendell", "council"} == set(sm.configuration_values)
At Rivendell: ['council', 'rivendell']
done.state event¶
When the final child of a compound is reached, a done.state.{parent} event
fires automatically, triggering the transition to destination.
sm.send("begin_forging")
print(f"Quest continues: {sorted(sm.configuration_values)}")
assert {"destination"} == set(sm.configuration_values)
Quest continues: ['destination']