Skip to content

Latest commit

 

History

History
167 lines (124 loc) · 5.14 KB

File metadata and controls

167 lines (124 loc) · 5.14 KB

States

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

.. autoclass:: statemachine.state.State
    :noindex:
How to define and attach [](actions.md) to {ref}`States`.

Initial state

A {ref}StateMachine should have one and only one initial {ref}state.

If not specified, the default initial state is the first child state in document order.

The initial {ref}state is entered when the machine starts and the corresponding entering state {ref}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.

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']
`strict_states=True` will become the default behaviour in future versions.

(final-state)=

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.

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']
`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

{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`.