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.