History states – Gollum’s dual personality

This example demonstrates history pseudo-states using StateChart. A history state records the active child of a compound state when it is exited. Re-entering via the history state restores the previously active child instead of starting from the initial child.

Both shallow history (HistoryState()) and deep history (HistoryState(type="deep")) are shown.

from statemachine import HistoryState
from statemachine import State
from statemachine import StateChart


class PersonalityMachine(StateChart):
    """Gollum's dual personality with shallow history.

    The ``personality`` compound has two children: ``smeagol`` and ``gollum``.
    When Gollum leaves the ``personality`` state and returns via the history
    pseudo-state, the previously active personality is restored.
    """

    class personality(State.Compound):
        smeagol = State("Smeagol", initial=True)
        gollum = State("Gollum")
        h = HistoryState()

        dark_side = smeagol.to(gollum)
        light_side = gollum.to(smeagol)

    outside = State("Outside")
    leave = personality.to(outside)
    return_via_history = outside.to(personality.h)
  • statechart history machine
  • statechart history machine

Shallow history remembers the last child

sm = PersonalityMachine()
print(f"Initial: {sorted(sm.configuration_values)}")
assert "smeagol" in sm.configuration_values

# Switch to Gollum, then leave
sm.send("dark_side")
print(f"Gollum active: {sorted(sm.configuration_values)}")
assert "gollum" in sm.configuration_values

sm.send("leave")
print(f"Left: {sorted(sm.configuration_values)}")
assert {"outside"} == set(sm.configuration_values)

# Return via history -> Gollum is restored
sm.send("return_via_history")
print(f"History restored: {sorted(sm.configuration_values)}")
assert "gollum" in sm.configuration_values
assert "personality" in sm.configuration_values
Initial: ['personality', 'smeagol']
Gollum active: ['gollum', 'personality']
Left: ['outside']
History restored: ['gollum', 'personality']

Multiple exit/reentry cycles

History updates each time the compound is exited.

sm.send("light_side")
print(f"Switched to Smeagol: {sorted(sm.configuration_values)}")
assert "smeagol" in sm.configuration_values

sm.send("leave")
sm.send("return_via_history")
print(f"Smeagol restored: {sorted(sm.configuration_values)}")
assert "smeagol" in sm.configuration_values
Switched to Smeagol: ['personality', 'smeagol']
Smeagol restored: ['personality', 'smeagol']

Deep history with nested compounds

Deep history remembers the exact leaf state in nested compounds.

class DeepPersonalityMachine(StateChart):
    """A machine with nested compounds and deep history."""

    class realm(State.Compound):
        class inner(State.Compound):
            entrance = State("Entrance", initial=True)
            chamber = State("Chamber")

            explore = entrance.to(chamber)

        assert isinstance(inner, State)
        h = HistoryState(type="deep")  # type: ignore[has-type]
        bridge = State("Bridge", final=True)
        flee = inner.to(bridge)

    outside = State("Outside")
    escape = realm.to(outside)
    return_deep = outside.to(realm.h)  # type: ignore[has-type]


sm2 = DeepPersonalityMachine()
print(f"\nDeep history initial: {sorted(sm2.configuration_values)}")
assert "entrance" in sm2.configuration_values

# Move to the inner leaf state
sm2.send("explore")
print(f"Explored chamber: {sorted(sm2.configuration_values)}")
assert "chamber" in sm2.configuration_values

# Exit and return via deep history
sm2.send("escape")
print(f"Escaped: {sorted(sm2.configuration_values)}")
assert {"outside"} == set(sm2.configuration_values)

sm2.send("return_deep")
print(f"Deep history restored: {sorted(sm2.configuration_values)}")
assert "chamber" in sm2.configuration_values
assert "inner" in sm2.configuration_values
assert "realm" in sm2.configuration_values
Deep history initial: ['entrance', 'inner', 'realm']
Explored chamber: ['chamber', 'inner', 'realm']
Escaped: ['outside']
Deep history restored: ['chamber', 'inner', 'realm']