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 |
|---|---|---|
|
|
Human-readable display name. Defaults to the attribute name, capitalized. |
|
|
Custom value for this state, accessible via |
|
|
Marks this as the initial state. Exactly one per machine (or per compound). |
|
|
Marks this as a final (accepting) state. No outgoing transitions allowed. |
|
|
Callback(s) to run when entering this state. See State actions. |
|
|
Callback(s) to run when leaving this state. See State actions. |
|
|
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
Statesclass from an enumeration.Consider an
Enumtype 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_enumto 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
Enumis 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
FalsetoTrue.- 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 toTrue.
- Returns:
A new instance of the States (class).
See also
See the example Enum campaign machine.