Skip to main content

Self-service finite-state machines for the programmer on the go.

Reason this release was yanked:

Incorrect requires-python metadata caused errors for 3.7 users.

Project description

Automat

Documentation Status Build Status Coverage Status

Self-service finite-state machines for the programmer on the go.

Automat is a library for concise, idiomatic Python expression of finite-state automata (particularly deterministic finite-state transducers).

Read more here, or on Read the Docs, or watch the following videos for an overview and presentation

Why use state machines?

Sometimes you have to create an object whose behavior varies with its state, but still wishes to present a consistent interface to its callers.

For example, let's say you're writing the software for a coffee machine. It has a lid that can be opened or closed, a chamber for water, a chamber for coffee beans, and a button for "brew".

There are a number of possible states for the coffee machine. It might or might not have water. It might or might not have beans. The lid might be open or closed. The "brew" button should only actually attempt to brew coffee in one of these configurations, and the "open lid" button should only work if the coffee is not, in fact, brewing.

With diligence and attention to detail, you can implement this correctly using a collection of attributes on an object; hasWater, hasBeans, isLidOpen and so on. However, you have to keep all these attributes consistent. As the coffee maker becomes more complex - perhaps you add an additional chamber for flavorings so you can make hazelnut coffee, for example - you have to keep adding more and more checks and more and more reasoning about which combinations of states are allowed.

Rather than adding tedious if checks to every single method to make sure that each of these flags are exactly what you expect, you can use a state machine to ensure that if your code runs at all, it will be run with all the required values initialized, because they have to be called in the order you declare them.

You can read about state machines and their advantages for Python programmers in more detail in this excellent article by Jean-Paul Calderone.

What makes Automat different?

There are dozens of libraries on PyPI implementing state machines. So it behooves me to say why yet another one would be a good idea.

Automat is designed around this principle: while organizing your code around state machines is a good idea, your callers don't, and shouldn't have to, care that you've done so. In Python, the "input" to a stateful system is a method call; the "output" may be a method call, if you need to invoke a side effect, or a return value, if you are just performing a computation in memory. Most other state-machine libraries require you to explicitly create an input object, provide that object to a generic "input" method, and then receive results, sometimes in terms of that library's interfaces and sometimes in terms of classes you define yourself.

For example, a snippet of the coffee-machine example above might be implemented as follows in naive Python:

class CoffeeMachine(object):
    def brewButton(self) -> None:
        if self.hasWater and self.hasBeans and not self.isLidOpen:
            self.heatTheHeatingElement()
            # ...

With Automat, you'd begin with a typing.Protocol that describes all of your inputs:

from typing import Protocol

class CoffeeBrewer(Protocol):
    def brewButton(self) -> None:
        "The user pressed the 'brew' button."
    def putInBeans(self) -> None:
        "The user put in some beans."

We'll then need a concrete class to contain the shared core of state shared among the different states:

from dataclasses import dataclass

@dataclass
class BrewerCore:
    heatingElement: HeatingElement

Next, we need to describe our state machine, including all of our states. For simplicity's sake let's say that the only two states are noBeans and haveBeans:

from automat import TypeMachineBuilder

builder = TypeMachineBuilder(CoffeeBrewer, BrewerCore)
noBeans = builder.state("noBeans")
haveBeans = builder.state("haveBeans")

Next we can describe a simple transition; when we put in beans, we move to the haveBeans state, with no other behavior.

# When we don't have beans, upon putting in beans, we will then have beans
noBeans.upon(CoffeeBrewer.putInBeans).to(haveBeans).returns(None)

And then another transition that we describe with a decorator, one that does have some behavior, that needs to heat up the heating element to brew the coffee:

@haveBeans.upon(CoffeeBrewer.brewButton).to(noBeans)
def heatUp(inputs: CoffeeBrewer, core: BrewerCore) -> None:
    """
    When we have beans, upon pressing the brew button, we will then not have
    beans any more (as they have been entered into the brewing chamber) and
    our output will be heating the heating element.
    """
    print("Brewing the coffee...")
    core.heatingElement.turnOn()

Then we finalize the state machine by building it, which gives us a callable that takes a BrewerCore and returns a synthetic CoffeeBrewer

newCoffeeMachine = builder.build()
>>> coffee = newCoffeeMachine(BrewerCore(HeatingElement()))
>>> machine.putInBeans()
>>> machine.brewButton()
Brewing the coffee...

All of the inputs are provided by calling them like methods, all of the output behaviors are automatically invoked when they are produced according to the outputs specified to upon and all of the states are simply opaque tokens.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

automat-24.8.0.tar.gz (128.6 kB view details)

Uploaded Source

Built Distribution

Automat-24.8.0-py3-none-any.whl (42.6 kB view details)

Uploaded Python 3

File details

Details for the file automat-24.8.0.tar.gz.

File metadata

  • Download URL: automat-24.8.0.tar.gz
  • Upload date:
  • Size: 128.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/5.1.1 CPython/3.12.5

File hashes

Hashes for automat-24.8.0.tar.gz
Algorithm Hash digest
SHA256 f55735fdae5f461176ba2c8be7438ee06e47c4028ad80d1b4d5abef96d9d5863
MD5 7bbcbba1684d62c801baec1f865a7e87
BLAKE2b-256 60b47002a0ac39e80a9e62f1228f60a8f7f4525a22cbaf804647a2ab8e0172d9

See more details on using hashes here.

File details

Details for the file Automat-24.8.0-py3-none-any.whl.

File metadata

  • Download URL: Automat-24.8.0-py3-none-any.whl
  • Upload date:
  • Size: 42.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/5.1.1 CPython/3.12.5

File hashes

Hashes for Automat-24.8.0-py3-none-any.whl
Algorithm Hash digest
SHA256 0a6f26e0d52fb84afcfcbabb7d86ca6dbe0b2bb64735ad95df39c103bf8de091
MD5 027343c60964d242ef4e3b2113ad3f34
BLAKE2b-256 43afb2b07e32cb8523b41447ba8b64ae1d905839ac7e1a1b1cbe899988ae4e44

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page