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 theState
is the initial one. There must be one and only one initial state in a statemachine. Defaults toFalse
.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 toFalse
.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()
orState.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')]
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.
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']
You can retrieve all final states.
>>> 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)]
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.Enum], initial: enum.Enum, final=None)[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!")
Note
Given that you assign the response of
States.from_enum
to a class level variable on your StateMachine you’re good to go, you can use any name to assign this response, on this example we used_
to indicate that the name does not matter. The variable of type States (class) will be inspected by the metaclass and the inner State instances assigned 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
- 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.
- Returns
A new instance of the States (class).
See also
See the example Enum campaign machine.