https://github.com/alysivji/finite-state-machine
Lightweight, decorator-based Python implementation of a Finite State Machine
https://github.com/alysivji/finite-state-machine
finite-state-machine hacktoberfest hacktoberfest2020 python state-diagram state-machine-workflow
Last synced: 6 months ago
JSON representation
Lightweight, decorator-based Python implementation of a Finite State Machine
- Host: GitHub
- URL: https://github.com/alysivji/finite-state-machine
- Owner: alysivji
- License: mit
- Created: 2020-06-08T21:38:23.000Z (almost 6 years ago)
- Default Branch: main
- Last Pushed: 2023-05-22T23:35:03.000Z (almost 3 years ago)
- Last Synced: 2025-04-09T19:19:30.172Z (12 months ago)
- Topics: finite-state-machine, hacktoberfest, hacktoberfest2020, python, state-diagram, state-machine-workflow
- Language: Python
- Homepage:
- Size: 2.22 MB
- Stars: 112
- Watchers: 5
- Forks: 12
- Open Issues: 6
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README

# Finite State Machine
[](https://pypi.org/project/finite-state-machine/)
[](https://www.python.org/download/releases/3.6.0/)
[](https://opensource.org/licenses/MIT)
[](https://github.com/ambv/black)
[](https://github.com/alysivji/finite-state-machine/actions?query=workflow%3A%22build%22)
[](https://codecov.io/gh/alysivji/finite-state-machine)
Lightweight, decorator-based Python implementation of a [Finite State Machine](https://en.wikipedia.org/wiki/Finite-state_machine).
#### Table of Contents
- [Installation](#installation)
- [Usage](#usage)
- [Example](#example)
- [Asynchronous Support](#asynchronous-support)
- [State Diagram](#state-diagram)
- [Contributing](#contributing)
- [Inspiration](#inspiration)
## Installation
```console
pip install finite-state-machine
```
## Usage
Subclass `StateMachine` and set the `state` instance variable:
```python
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.
```python
@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:
```python
@transition(source="off", target="on", on_error="failed")
def turn_on(self):
raise ValueError
```
## Example
```python
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
```console
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)
in
----> 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](/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](https://en.wikipedia.org/wiki/State_diagram).
`finite-state-machine` generates diagrams using
[Mermaid Markdown syntax](https://mermaid-js.github.io),
which can be viewed using the
[Mermaid Live Editor](https://mermaid-js.github.io/mermaid-live-editor).
Use the `fsm_draw_state_diagram` command and point to
State Machine workflow class
that inherits from `StateMachine`.
```console
# 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
1. Clone repo
1. Create a virtual environment
1. `pip install -r requirements_dev.txt`
1. Install [pre-commit](https://pre-commit.com/)
1. Set up pre-commit hooks in repo: `pre-commit install`
To install a package locally for development, run:
```console
flit install [--symlink] [--python path/to/python]
```
### Running Tests
```console
pytest
```
## Inspiration
This project is inspired by
[django-fsm](https://github.com/viewflow/django-fsm/).
I wanted a decorator-based state machine without having to use Django.