States

State, as the name says, holds the representation of a state in a StateMachine.

class statemachine.state.State(name: str = '', value: Any = None, initial: bool = False, final: bool = False, enter: Any = None, exit: Any = None)[source]

A State in a StateMachine describes a particular behavior of the machine. When we say that a machine is “in” a state, it means that the machine behaves in the way that state describes.

Parameters:
  • name

    A human-readable representation of the state. Default is derived from the name of the variable assigned to the state machine class. The name is derived from the id using this logic:

    name = id.replace("_", " ").capitalize()
    

  • value – A specific value to the storage and retrieval of states. If specified, you can use It to map a more friendly representation to a low-level value.

  • initial – Set True if the State is the initial one. There must be one and only one initial state in a statemachine. Defaults to False.

  • final – Set True if represents a final state. A machine can have optionally many final states. Final states have no Transition starting from It. Defaults to False.

  • enter – One or more callbacks assigned to be executed when the state is entered. See Actions.

  • exit – One or more callbacks assigned to be executed when the state is exited. See Actions.

State is a core component on how this library implements an expressive API to declare StateMachines.

>>> from statemachine import State

Given a few states…

>>> draft = State("Draft", initial=True)
>>> producing = State("Producing")
>>> closed = State('Closed', final=True)

Transitions are declared using the State.to() or State.from_() (reversed) methods.

>>> draft.to(producing)
TransitionList([Transition(State('Draft', ...

The result is a TransitionList. Don’t worry about this internal class. But the good thing is that it implements the OR operator to combine transitions, so you can use the | syntax to compound a list of transitions and assign to the same event.

You can declare all transitions for a state in one single line …

>>> transitions = draft.to(draft) | producing.to(closed)

… and you can append additional transitions for a state to previous definitions.

>>> transitions |= closed.to(draft)
>>> [(t.source.name, t.target.name) for t in transitions]
[('Draft', 'Draft'), ('Producing', 'Closed'), ('Closed', 'Draft')]

There are handy shortcuts that you can use to express this same set of transitions.

The first one, draft.to(draft), is also called a Self transition, and can be expressed using an alternative syntax:

>>> draft.to.itself()
TransitionList([Transition(State('Draft', ...

You can even pass a list of target states to declare at once all transitions starting from the same state.

>>> transitions = draft.to(draft, producing, closed)
>>> [(t.source.name, t.target.name) for t in transitions]
[('Draft', 'Draft'), ('Draft', 'Producing'), ('Draft', 'Closed')]

Sometimes it’s easier to use the State.from_() method:

>>> transitions = closed.from_(draft, producing, closed)
>>> [(t.source.name, t.target.name) for t in transitions]
[('Draft', 'Closed'), ('Producing', 'Closed'), ('Closed', 'Closed')]

See also

How to define and attach Actions to States.

Initial state

A StateMachine should have one and only one initial State.

The initial State is entered when the machine starts and the corresponding entering state Actions are called if defined.

State Transitions

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 StateMachine, State

>>> class TrafficLightMachine(StateMachine):
...     "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']

StateMachine will also check that all non-final states have an outgoing transition, and warn you if any states would result in the statemachine becoming trapped in a non-final state with no further transitions possible.

Note

This will currently issue a warning, but can be turned into an exception by setting strict_states=True on the class.

>>> from statemachine import StateMachine, State

>>> class TrafficLightMachine(StateMachine, strict_states=True):
...     "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']

Warning

strict_states=True will become the default behaviour in future versions.

Final state

You can explicitly set final states. Transitions from these states are not allowed and will raise exceptions.

>>> from statemachine import StateMachine, State

>>> class CampaignMachine(StateMachine):
...     "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, StateMachine will check that all non-final states have a path to reach at least one final state.

Note

This will currently issue a warning, but can be turned into an exception by setting strict_states=True on the class.

>>> class CampaignMachine(StateMachine, strict_states=True):
...     "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']

Warning

strict_states=True will become the default behaviour in future versions.

You can query a list of all final states from your statemachine.

>>> class CampaignMachine(StateMachine):
...     "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)]

>>> machine.current_state in machine.final_states
False

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 State objects.

classmethod States.from_enum(enum_type: Type[Enum], initial, final=None, use_enum_instance: bool = False)[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 StateMachine that uses this enum can be declared as follows:

>>> from statemachine import StateMachine
>>> class ApprovalMachine(StateMachine):
...
...     _ = 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 StateMachine, 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 State instances to the state machine.

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

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

If you need to use the enum instance as the state value, you can set the use_enum_instance=True:

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

Deprecated since version 2.3.3: On the next major release, use_enum_instance=True will be the default.

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 False.

Returns:

A new instance of the States (class).

See also

See the example Enum campaign machine.