Finite State Machine
Lightweight, decorator-based Python implementation of a Finite State Machine.
Table of Contents
Installation
pip install finite-state-machine
Usage
Subclass StateMachine
and set the state
instance variable:
from finite_state_machine import StateMachine, transition
class LightSwitch(StateMachine):
def __init__(self):
self.state = "off"
super().__init__()
The transition
decorator can be used to specify valid state transitions
with an optional parameter for conditions
.
States can be of type: string
, int
, bool
, Enum
, or IntEnum
.
Can specify a single sate or a list of states for the source
parameter;
can only specify a single state as the target
target.
All condition functions need to return True
for the transition to occur,
else a ConditionsNotMet
exception will be raised.
Condition functions require the same positional position and
keyword arguments present in the transition function.
@transition(source="off", target="on", conditions=[light_is_off])
def turn_on(self):
# transition function
# logic to define what happens to "complete a transition"
# ex. update database record,
...
def light_is_off(machine):
# condition function, first param will always be the state machine class
# return a boolean to specify if a transition is valid
return machine.state == "off"
Can also specify an on_error
state to handle situations
where the transition function raises an exception:
@transition(source="off", target="on", on_error="failed")
def turn_on(self):
raise ValueError
Example
from finite_state_machine import StateMachine, transition
class Turnstile(StateMachine):
initial_state = "close"
def __init__(self):
self.state = self.initial_state
super().__init__()
@transition(source=["close", "open"], target="open")
def insert_coin(self):
pass
@transition(source="open", target="close")
def pass_thru(self):
pass
REPL
In [2]: turnstile = Turnstile()
In [3]: turnstile.state
Out[3]: 'close'
In [4]: turnstile.insert_coin()
In [5]: turnstile.state
Out[5]: 'open'
In [6]: turnstile.insert_coin()
In [7]: turnstile.state
Out[7]: 'open'
In [8]: turnstile.pass_thru()
In [9]: turnstile.state
Out[9]: 'close'
In [10]: turnstile.pass_thru()
---------------------------------------------------------------------------
InvalidStartState Traceback (most recent call last)
<ipython-input-10-6abc6f4be1cd> in <module>
----> 1 turnstile.pass_thru()
~/state_machine.py in _wrapper(*args, **kwargs)
32
33 if self.state not in source:
---> 34 raise InvalidStartState
35
36 for condition in conditions:
InvalidStartState:
The examples folder contains additional workflows.
Asynchronous Support
finite-state-machine
can be used to build
both synchronous and asynchronous State Machines.
The @transition
decorator supports transition functions
and condition functions as follows:
Synchronous transition function | Asynchronous transition function | |
---|---|---|
Synchronous condition function | ||
Asynchronous condition function |
State Diagram
State Machine workflows can be visualized using a state diagram.
finite-state-machine
generates diagrams using
Mermaid Markdown syntax,
which can be viewed using the
Mermaid Live Editor.
Use the fsm_draw_state_diagram
command and point to
State Machine workflow class
that inherits from StateMachine
.
# class parameter is required
$ fsm_draw_state_diagram --class examples.turnstile:Turnstile
# initial_state parameter is optional
$ fsm_draw_state_diagram --class examples.turnstile:Turnstile --initial_state close
Contributing
- Clone repo
- Create a virtual environment
pip install -r requirements_dev.txt
- Install pre-commit
- Set up pre-commit hooks in repo:
pre-commit install
To install a package locally for development, run:
flit install [--symlink] [--python path/to/python]
Running Tests
pytest
Inspiration
This project is inspired by django-fsm. I wanted a decorator-based state machine without having to use Django.