States

See also

New to statecharts? See Core concepts for an overview of how states, transitions, events, and actions fit together.

A state represents a distinct mode or condition of the system at a given point in time. States are the building blocks of a statechart — you define them as class attributes, and the library handles initialization, validation, and lifecycle management.

>>> from statemachine import State, StateChart

>>> class TrafficLight(StateChart):
...     green = State(initial=True)
...     yellow = State()
...     red = State()
...
...     cycle = green.to(yellow) | yellow.to(red) | red.to(green)

>>> sm = TrafficLight()
>>> "green" in sm.configuration_values
True

State parameters

Parameter

Default

Description

name

""

Human-readable display name. Defaults to the attribute name, capitalized.

value

None

Custom value for this state, accessible via configuration_values.

initial

False

Marks this as the initial state. Exactly one per machine (or per compound).

final

False

Marks this as a final (accepting) state. No outgoing transitions allowed.

enter

None

Callback(s) to run when entering this state. See State actions.

exit

None

Callback(s) to run when leaving this state. See State actions.

invoke

None

Background work spawned on entry, cancelled on exit. See invoke-actions.

>>> class CampaignMachine(StateChart):
...     draft = State("Draft", value=1, initial=True)
...     producing = State("Being produced", value=2)
...     closed = State("Closed", value=3, final=True)
...
...     produce = draft.to(producing)
...     deliver = producing.to(closed)

>>> sm = CampaignMachine()
>>> sm.send("produce")
>>> list(sm.configuration_values)
[2]

Initial state

A StateChart must have exactly one initial state. The initial state is entered when the machine starts, and the corresponding enter actions are called.

Final state

A final state signals that the machine has completed its work. No outgoing transitions are allowed from a final state.

>>> sm = CampaignMachine()
>>> sm.send("produce")
>>> sm.send("deliver")
>>> sm.is_terminated
True

You can query the list of all declared final states:

>>> sm.final_states
[State('Closed', id='closed', value=3, initial=False, final=True, parallel=False)]

See also

See Validations for the checks the library performs at class definition time — including final state reachability, unreachable states, and trap states.

Compound states

Added in version 3.0.0.

Compound states contain inner child states, enabling hierarchical state machines. Define them using the State.Compound inner class syntax:

>>> from statemachine import State, StateChart

>>> class Journey(StateChart):
...     class shire(State.Compound):
...         bag_end = State(initial=True)
...         green_dragon = State()
...         visit_pub = bag_end.to(green_dragon)
...     road = State(final=True)
...     depart = shire.to(road)

>>> sm = Journey()
>>> set(sm.configuration_values) == {"shire", "bag_end"}
True

Entering a compound activates both the parent and its initial child. You can query whether a state is compound using the is_compound property.

See also

See Automatic events for completion events when a compound state’s final child is reached.

Parallel states

Added in version 3.0.0.

Parallel states activate all child regions simultaneously. Each region operates independently. Define them using State.Parallel:

>>> from statemachine import State, StateChart

>>> class WarOfTheRing(StateChart):
...     class war(State.Parallel):
...         class quest(State.Compound):
...             start = State(initial=True)
...             end = State(final=True)
...             go = start.to(end)
...         class battle(State.Compound):
...             fighting = State(initial=True)
...             won = State(final=True)
...             victory = fighting.to(won)

>>> sm = WarOfTheRing()
>>> "start" in sm.configuration_values and "fighting" in sm.configuration_values
True

See also

See Automatic events for how done.state events work with parallel states (all regions must reach a final state).

History pseudo-states

Added in version 3.0.0.

A history pseudo-state records the active child of a compound state when it is exited. Re-entering via the history state restores the previously active child. Import and use HistoryState inside a State.Compound:

>>> from statemachine import HistoryState, State, StateChart

>>> class WithHistory(StateChart):
...     class mode(State.Compound):
...         a = State(initial=True)
...         b = State()
...         h = HistoryState()
...         switch = a.to(b)
...     outside = State()
...     leave = mode.to(outside)
...     resume = outside.to(mode.h)

>>> sm = WithHistory()
>>> sm.send("switch")
>>> sm.send("leave")
>>> sm.send("resume")
>>> "b" in sm.configuration_values
True

Use HistoryState(type="deep") for deep history that remembers the exact leaf state in nested compounds.

See also

See Querying the configuration for how to inspect which states are currently active at runtime.

States from Enum types

States can also be declared from standard Enum classes.

For this, use States (class) to convert your Enum type to a list of States objects.

classmethod States.from_enum(enum_type: type[Enum], initial, final=None, use_enum_instance: bool = True)[source]

Creates a new instance of the States class from an enumeration.

Consider an Enum type that declares our expected states:

>>> class Status(Enum):
...     pending = 1
...     completed = 2

A StateChart that uses this enum can be declared as follows:

>>> from statemachine import StateChart
>>> class ApprovalMachine(StateChart):
...
...     _ = States.from_enum(Status, initial=Status.pending, final=Status.completed)
...
...     finish = _.pending.to(_.completed)
...
...     def on_enter_completed(self):
...         print("Completed!")

Tip

When you assign the result of States.from_enum to a class-level variable in your StateChart, you’re all set. You can use any name for this variable. In this example, we used _ to show that the name doesn’t matter. The metaclass will inspect the variable of type States (class) and automatically assign the inner States instances to the state machine.

Everything else is similar, the Enum is only used to declare the States instances.

>>> sm = ApprovalMachine()
>>> sm.pending.is_active
True
>>> sm.send("finish")
Completed!
>>> sm.completed.is_active
True
>>> sm.current_state_value
<Status.completed: 2>

If you need to use the raw enum value instead of the enum instance, you can set use_enum_instance=False:

>>> states = States.from_enum(Status, initial=Status.pending, use_enum_instance=False)
>>> states.completed.value
2

Changed in version 3.0.0: The default changed from False to True.

Parameters:
  • enum_type – An enumeration containing the states of the machine.

  • initial – The initial state of the machine.

  • final – A set of final states of the machine.

  • use_enum_instance – If True, the value of the state will be the enum item instance, otherwise the enum item value. Defaults to True.

Returns:

A new instance of the States (class).

See also

See the example Enum campaign machine.