{ref}State, as the name says, holds the representation of a state in a {ref}StateChart.
.. autoclass:: statemachine.state.State
:noindex:
How to define and attach [](actions.md) to {ref}`States`.
A {ref}StateChart should have one and only one initial {ref}state.
The initial {ref}state is entered when the machine starts and the corresponding entering
state {ref}actions are called if defined.
All states should have at least one transition to and from another state.
If any states are unreachable from the initial state, an InvalidDefinition exception will be thrown.
>>> from statemachine import StateChart, State
>>> class TrafficLightMachine(StateChart):
... "A workflow machine"
... red = State('Red', initial=True, value=1)
... green = State('Green', value=2)
... orange = State('Orange', value=3)
... hazard = State('Hazard', value=4)
...
... cycle = red.to(green) | green.to(orange) | orange.to(red)
... blink = hazard.to.itself()
Traceback (most recent call last):
...
InvalidDefinition: There are unreachable states. The statemachine graph should have a single component. Disconnected states: ['hazard']StateChart will also check that all non-final states have an outgoing transition.
If any non-final state has no outgoing transitions, an InvalidDefinition exception is raised.
>>> from statemachine import StateChart, State
>>> class TrafficLightMachine(StateChart):
... "A workflow machine"
... red = State('Red', initial=True, value=1)
... green = State('Green', value=2)
... orange = State('Orange', value=3)
... hazard = State('Hazard', value=4)
...
... cycle = red.to(green) | green.to(orange) | orange.to(red)
... fault = red.to(hazard) | green.to(hazard) | orange.to(hazard)
Traceback (most recent call last):
...
InvalidDefinition: All non-final states should have at least one outgoing transition. These states have no outgoing transition: ['hazard']You can disable this check by setting validate_trap_states = False on the class:
>>> class TrafficLightMachine(StateChart):
... validate_trap_states = False
... red = State('Red', initial=True, value=1)
... green = State('Green', value=2)
... orange = State('Orange', value=3)
... hazard = State('Hazard', value=4)
...
... cycle = red.to(green) | green.to(orange) | orange.to(red)
... fault = red.to(hazard) | green.to(hazard) | orange.to(hazard)(final-state)=
You can explicitly set final states. Transitions from these states are not allowed and will raise exceptions.
>>> from statemachine import StateChart, State
>>> class CampaignMachine(StateChart):
... "A workflow machine"
... draft = State('Draft', initial=True, value=1)
... producing = State('Being produced', value=2)
... closed = State('Closed', final=True, value=3)
...
... add_job = draft.to.itself() | producing.to.itself() | closed.to(producing)
... produce = draft.to(producing)
... deliver = producing.to(closed)
Traceback (most recent call last):
...
InvalidDefinition: Cannot declare transitions from final state. Invalid state(s): ['closed']If you mark any states as final, StateChart will check that all non-final states have a path to reach at least one final state.
>>> class CampaignMachine(StateChart):
... "A workflow machine"
... draft = State('Draft', initial=True, value=1)
... producing = State('Being produced', value=2)
... abandoned = State('Abandoned', value=3)
... closed = State('Closed', final=True, value=4)
...
... add_job = draft.to.itself() | producing.to.itself()
... produce = draft.to(producing)
... abandon = producing.to(abandoned) | abandoned.to(abandoned)
... deliver = producing.to(closed)
Traceback (most recent call last):
...
InvalidDefinition: All non-final states should have at least one path to a final state. These states have no path to a final state: ['abandoned']You can disable this check by setting validate_final_reachability = False on the class.
You can query a list of all final states from your statemachine.
>>> class CampaignMachine(StateChart):
... "A workflow machine"
... draft = State('Draft', initial=True, value=1)
... producing = State('Being produced', value=2)
... closed = State('Closed', final=True, value=3)
...
... add_job = draft.to.itself() | producing.to.itself()
... produce = draft.to(producing)
... deliver = producing.to(closed)
>>> machine = CampaignMachine()
>>> machine.final_states
[State('Closed', id='closed', value=3, initial=False, final=True, parallel=False)]
>>> any(s in machine.final_states for s in machine.configuration)
FalseUse the is_terminated property to check whether the state machine has reached a final state
and the engine is no longer running. This is the recommended way to check for completion,
especially with compound and parallel states where multiple states can be active at once.
>>> machine.send("produce")
>>> machine.is_terminated
False
>>> machine.send("deliver")
>>> machine.is_terminated
TruePrefer `sm.is_terminated` over patterns like `sm.current_state.final` or
`any(s.final for s in sm.configuration)`. It works correctly for all state machine
topologies -- flat, compound, and parallel.
{ref}States can also be declared from standard Enum classes.
For this, use {ref}States (class) to convert your Enum type to a list of {ref}State objects.
.. automethod:: statemachine.states.States.from_enum
:noindex:
See the example {ref}`sphx_glr_auto_examples_enum_campaign_machine.py`.
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"}
TrueEntering a compound activates both the parent and its initial child. You can query
whether a state is compound using the is_compound property.
See {ref}`compound-states` for full details, nesting, and `done.state` events.
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
TrueSee {ref}`parallel-states` for full details and done events.
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
TrueUse HistoryState(type="deep") for deep history that remembers the exact leaf state
in nested compounds.
See {ref}`history-states` for shallow vs deep history and default transitions.
The configuration property returns the set of currently active states as an
OrderedSet[State]. With compound and parallel states, multiple states can be
active simultaneously:
>>> 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()
>>> {s.id for s in sm.configuration} == {"shire", "bag_end"}
TrueUse configuration_values for a set of the active state values (or IDs if no
custom value is defined):
>>> set(sm.configuration_values) == {"shire", "bag_end"}
TrueThe older `current_state` property is deprecated. Use `configuration` instead,
which works consistently for both flat and hierarchical state machines.