Module documentation

Python State Machine

The goal of this library is to give you a close to the State Pattern simplicity with much more flexibility. And, if needed, the full state machine functionality, including FSM, HSM, PDA and other tasty things.

Goals:
  • Provide a State Pattern-like behavior with more flexibility
  • Be explicit and don’t add any code to objects
  • Handle directly any kind of event (not only strings) - parsing strings is cool again!
  • Keep it simple, even for someone who’s not very familiar with the FSM terminology

class pysm.pysm.AnyEvent[source]

Bases: object

hash(object()) doesn’t work in MicroPython therefore the need for this class.

class pysm.pysm.Event(name, input=None, **cargo)[source]

Bases: object

Triggers actions and transition in StateMachine.

Events are also used to control the flow of data propagated to states within the states hierarchy.

Event objects have the following attributes set after an event has been dispatched:

Attributes:

state_machine

A StateMachine instance that is handling the event (the one whose pysm.pysm.StateMachine.dispatch() method is called)

propagate

An event is propagated from a current leaf state up in the states hierarchy until it encounters a handler that can handle the event. To propagate it further, it has to be set to True in a handler.

Parameters:
  • name (Hashable) – Name of an event. It may be anything as long as it’s hashable.
  • input (Hashable) – Optional input. Anything hashable.
  • **cargo – Keyword arguments for an event, used to transport data to handlers. It’s added to an event as a cargo property of type dict. For enter and exit events, the original event that triggered a transition is passed in cargo as source_event entry.

Example Usage:

state_machine.dispatch(Event('start'))
state_machine.dispatch(Event('start', key='value'))
state_machine.dispatch(Event('parse', input='#', entity=my_object))
state_machine.dispatch(Event('%'))
state_machine.dispatch(Event(frozenset([1, 2])))
class pysm.pysm.State(name)[source]

Bases: object

Represents a state in a state machine.

enter and exit handlers are called whenever a state is entered or exited respectively. These action names are reserved only for this purpose.

It is encouraged to extend this class to encapsulate a state behavior, similarly to the State Pattern.

Once it’s extended, the preferred way of adding an event handlers is through the register_handlers() hook. Usually, there’s no need to create the __init__() in a subclass.

Parameters:name (str) – Human readable state name

Example Usage:

# Extending State to encapsulate state-related behavior. Similar to the
# State Pattern.
class Running(State):
    def on_enter(self, state, event):
        print('Running state entered')

    def on_jump(self, state, event):
        print('Jumping')

    def on_dollar(self, state, event):
        print('Dollar found!')

    def register_handlers(self):
        self.handlers = {
            'enter': self.on_enter,
            'jump': self.on_jump,
            '$': self.on_dollar
        }
# Different way of attaching handlers. A handler may be any function as
# long as it takes `state` and `event` args.
def another_handler(state, event):
    print('Another handler')

running = State('running')
running.handlers = {
    'another_event': another_handler
}
is_substate(state)[source]

Check whether the state is a substate of self.

Also self is considered a substate of self.

Parameters:state (State) – State to verify
Returns:True if state is a substate of self, False otherwise
Return type:bool
register_handlers()[source]

Hook method to register event handlers.

It is used to easily extend State class. The hook is called from within the base State.__init__(). Usually, the __init__() doesn’t have to be created in a subclass.

Event handlers are kept in a dict, with events’ names as keys, therefore registered events may be of any hashable type.

Handlers take two arguments:

  • state: The current state that is handling an event. The same
    handler function may be attached to many states, therefore it is helpful to get the handling state’s instance.
  • event: An event that triggered the handler call. If it is an
    enter or exit event, then the source event (the one that triggered the transition) is passed in event’s cargo property as cargo.source_event.

Example Usage:

class On(State):
    def handle_my_event(self, state, event):
        print('Handling an event')

    def register_handlers(self):
        self.handlers = {
            'my_event': self.handle_my_event,
            '&': self.handle_my_event,
            frozenset([1, 2]): self.handle_my_event
        }
class pysm.pysm.StateMachine(name)[source]

Bases: pysm.pysm.State

State machine controls actions and transitions.

To provide the State Pattern-like behavior, the formal state machine rules may be slightly broken, and instead of creating an internal transition for every action that doesn’t require a state change, event handlers may be added to states. These are handled first when an event occurs. After that the actual transition is called, calling enter/exit actions and other transition actions. Nevertheless, internal transitions are also supported.

So the order of calls on an event is as follows:

  1. State’s event handler
  2. condition callback
  3. before callback
  4. exit handlers
  5. action callback
  6. enter handlers
  7. after callback

If there’s no handler in states or transition for an event, it is silently ignored.

If using nested state machines, all events should be sent to the root state machine.

Attributes:

state

Current, local state (instance of State) in a state machine.

stack

Stack that can be used if the Pushdown Automaton (PDA) functionality is needed.

state_stack

Stack of previous local states in a state machine. With every transition, a previous state (instance of State) is pushed to the state_stack. Only StateMachine.STACK_SIZE (32 by default) are stored and old values are removed from the stack.

leaf_state_stack

Stack of previous leaf states in a state machine. With every transition, a previous leaf state (instance of State) is pushed to the leaf_state_stack. Only StateMachine.STACK_SIZE (32 by default) are stored and old values are removed from the stack.

leaf_state
See the leaf_state property.
root_machine
See the root_machine property.
Parameters:name (str) – Human readable state machine name

Note

StateMachine extends State and therefore it is possible to always use a StateMachine instance instead of the State. This wouldn’t be a good practice though, as the State class is designed to be as small as possible memory-wise and thus it’s more memory efficient. It is valid to replace a State with a StateMachine later on if there’s a need to extend a state with internal states.

Note

For the sake of speed thread safety isn’t guaranteed.

Example Usage:

state_machine = StateMachine('root_machine')
state_on = State('On')
state_off = State('Off')
state_machine.add_state('Off', initial=True)
state_machine.add_state('On')
state_machine.add_transition(state_on, state_off, events=['off'])
state_machine.add_transition(state_off, state_on, events=['on'])
state_machine.initialize()
state_machine.dispatch(Event('on'))
add_state(state, initial=False)[source]

Add a state to a state machine.

If states are added, one (and only one) of them has to be declared as initial.

Parameters:
  • state (State) – State to be added. It may be an another StateMachine
  • initial (bool) – Declare a state as initial
add_states(*states)[source]

Add states to the StateMachine.

To set the initial state use set_initial_state().

Parameters:states (State) – A list of states to be added
add_transition(from_state, to_state, events, input=None, action=None, condition=None, before=None, after=None)[source]

Add a transition to a state machine.

All callbacks take two arguments - state and event. See parameters description for details.

It is possible to create conditional if/elif/else-like logic for transitions. To do so, add many same transition rules with different condition callbacks. First met condition will trigger a transition, if no condition is met, no transition is performed.

Parameters:
  • from_state (State) – Source state
  • to_state (State, None) –

    Target state. If None, then it’s an internal transition

  • events (Iterable of Hashable) – List of events that trigger the transition
  • input (None, Iterable of Hashable) – List of inputs that trigger the transition. A transition event may be associated with a specific input. i.e.: An event may be parse and an input associated with it may be $. May be None (default), then every matched event name triggers a transition.
  • action (Callable) –

    Action callback that is called during the transition after all states have been left but before the new one is entered.

    action callback takes two arguments:

    • state: Leaf state before transition
    • event: Event that triggered the transition
  • condition (Callable) –

    Condition callback - if returns True transition may be initiated.

    condition callback takes two arguments:

    • state: Leaf state before transition
    • event: Event that triggered the transition
  • before (Callable) –

    Action callback that is called right before the transition.

    before callback takes two arguments:

    • state: Leaf state before transition
    • event: Event that triggered the transition
  • after (Callable) –

    Action callback that is called just after the transition

    after callback takes two arguments:

    • state: Leaf state after transition
    • event: Event that triggered the transition
dispatch(event)[source]

Dispatch an event to a state machine.

If using nested state machines (HSM), it has to be called on a root state machine in the hierarchy.

Parameters:event (Event) – Event to be dispatched
initial_state

Get the initial state in a state machine.

Returns:Initial state in a state machine
Return type:State
initialize()[source]

Initialize states in the state machine.

After a state machine has been created and all states are added to it, initialize() has to be called.

If using nested state machines (HSM), initialize() has to be called on a root state machine in the hierarchy.

is_substate(state)

Check whether the state is a substate of self.

Also self is considered a substate of self.

Parameters:state (State) – State to verify
Returns:True if state is a substate of self, False otherwise
Return type:bool
leaf_state

Get the current leaf state.

The state property gives the current, local state in a state machine. The leaf_state goes to the bottom in a hierarchy of states. In most cases, this is the property that should be used to get the current state in a state machine, even in a flat FSM, to keep the consistency in the code and to avoid confusion.

Returns:Leaf state in a hierarchical state machine
Return type:State
register_handlers()

Hook method to register event handlers.

It is used to easily extend State class. The hook is called from within the base State.__init__(). Usually, the __init__() doesn’t have to be created in a subclass.

Event handlers are kept in a dict, with events’ names as keys, therefore registered events may be of any hashable type.

Handlers take two arguments:

  • state: The current state that is handling an event. The same
    handler function may be attached to many states, therefore it is helpful to get the handling state’s instance.
  • event: An event that triggered the handler call. If it is an
    enter or exit event, then the source event (the one that triggered the transition) is passed in event’s cargo property as cargo.source_event.

Example Usage:

class On(State):
    def handle_my_event(self, state, event):
        print('Handling an event')

    def register_handlers(self):
        self.handlers = {
            'my_event': self.handle_my_event,
            '&': self.handle_my_event,
            frozenset([1, 2]): self.handle_my_event
        }
revert_to_previous_leaf_state(event=None)[source]

Similar to set_previous_leaf_state() but the current leaf_state is not saved on the stack of states. It allows to perform transitions further in the history of states.

root_machine

Get the root state machine in a states hierarchy.

Returns:Root state in the states hierarchy
Return type:StateMachine
set_initial_state(state)[source]

Set an initial state in a state machine.

Parameters:state (State) – Set this state as initial in a state machine
set_previous_leaf_state(event=None)[source]

Transition to a previous leaf state. This makes a dynamic transition to a historical state. The current leaf_state is saved on the stack of historical leaf states when calling this method.

Parameters:event (Event) – (Optional) event that is passed to states involved in the transition
exception pysm.pysm.StateMachineException[source]

Bases: exceptions.Exception

All StateMachine exceptions are of this type.