Pythonic finite-state machines.
Automaton is an easy to use, pythonic finite-state machine module for Python 3.4 or greater. The goal here is to have something to define finite-state machines in a minimal, straightforward and elegant way that enforces clarity and correctness.
Automaton is available on pypi, so to install it just use:
$ pip3 install python-automaton
In order to define an automaton, just subclass a provided base:
from automaton import *
class TrafficLight(Automaton):
go = Event("red", "green")
slowdown = Event("green", "yellow")
stop = Event("yellow", "red")
You're done: you now have a new automaton definition that can be instantiated to an initial state:
crossroads = TrafficLight(initial_state="red")
assert crossroads.state == "red"
Each event (also known as transition) is a class attribute defined by its source state and destination state. When an event fires, the automaton changes its state from the source to the destination: you can fire an event by calling it:
crossroads.go()
assert crossroads.state == "green"
crossroads.slowdown()
assert crossroads.state == "yellow"
An alternative way, more convenient if triggering events progammatically, is to call the event()
method:
crossroads.event("stop")
assert crossroads.state == "red"
Automaton enforces correctness in two ways:
- checking that the requested event is valid, that is a transition from the current state to the destination state exists in the state machine definition;
- checking whether the state graph representing the automaton is connected or not (that is it must have only
one connected component).
So, if you try to trigger an invalid event...
crossroads = TrafficLight(initial_state="red")
crossroads.stop()
...you will end up with an error:
automaton.InvalidTransitionError: The specified event 'Event(source_states=('yellow',), dest_state='red')'
is invalid in current state 'red'.
Again, trying to define a class like this...
class BrokenTrafficLight(Automaton):
go = Event("red", "green")
slowdown = Event("green", "yellow")
# broken!
stop = Event("black", "purple")
...will trigger an error:
automaton.DefinitionError: The state graph contains 2 connected components:
['green', 'yellow', 'red'], ['purple', 'black']
When things are getting complex and it seems that our automata are becoming autonomous life forms grasping to escape our control, it could be useful to have a human friendly representation of their behaviour.
You can ask for the transition table...
transitiontable(TrafficLight, fmt='rst')
...and you will be presented with a nice reStructuredText
snippet:
======== ====== ========
Source Dest Event
======== ====== ========
green yellow slowdown
yellow red stop
red green go
======== ====== ========
You can ask for the state graph as well...
stategraph(TrafficLight, fmt='plantuml')
...and you'll end up with a proper PlantUML representation...
@startuml
[*] --> red
green --> yellow : slowdown
red --> green : go
yellow --> red : stop
@enduml
...that can of course be rendered through plantuml
:
Since automata are classes here, it would be great to have a textual representation of the automaton's behaviour in our docstrings. What about having one that updates itself in order to stay up-to-date with the actual code?
Here you have it:
class TrafficLight(Automaton):
""" This is a pretty standard traffic light.
This automaton follows the behaviour defined by
the following transition table:
{automaton:rst}
"""
go = Event("red", "green")
slowdown = Event("green", "yellow")
stop = Event("yellow", "red")
Using a standard format specifier with the automaton
keyword and the proper output format (e.g.: rst
), the automaton representation will be inserted in the docstring during the class definition, just where it should be:
>>> print(TrafficLight.__doc__)
""" This is a pretty standard traffic light.
This automaton follows the behaviour defined by
the following transition table:
======== ====== ========
Source Dest Event
======== ====== ========
green yellow slowdown
yellow red stop
red green go
======== ====== ========
"""
Easy!
You can find the full documentation at http://automaton.readthedocs.org.
- Fixed
README.rst
rendering on pypi.
- Enabled access to all event's attributes from automaton instances.
- New constructor parameter to initialize an automaton given an initial startup event.
- Misc bugs fixed.
- Tests cleanup.
- Improved reference and documentation.
- Severe distribution issue: package was missing some files.
- Tox testing:
py.test
was running against source files, not against the package installed intox
virtualenv.
- Custom format specifiers for
Automaton
definitions (classes and instances). - Auto-docstring completion: if requested, the automaton textual representation is automatically added to the
__doc__
class attribute.
- Refactored formatting functions to more streamlined and coherent interfaces.
- Removed package, now the whole library lives in one module file.
- Automaton representation as transition table or state-transition graph.
- Functions to retrieve incoming and outgoing events from a state or a set of states.