Skip to main content

Finite State Machines in Pure Python!

Project description

StateEngine

Finite state machines in Python

Example Usage

"""
Using StateEngine to simulate the change of physical states of water
"""

from StateEngine import StateEngine

water_states = StateEngine()


@water_states.state_handler("solid")
def ice(sm_input):
    if sm_input == "melting":
        print("turning to liquid")
        return "liquid"
    elif sm_input == "sublimation":
        print("turning to gas")
        return "gas"
    else:
        print("remaining as a solid")
        return "solid"


@water_states.state_handler("liquid", default=True)
def water(sm_input):
    if sm_input == "freezing":
        print("turning to solid")
        return "solid"
    elif sm_input == "boiling":
        print("turning to gas")
        return "gas"
    else:
        print("remaining as a liquid")
        return "liquid"


@water_states.state_handler("gas")
def vapour(sm_input):
    if sm_input == "condensing":
        print("turning to liquid")
        return "liquid"
    elif sm_input == "depositing":
        print("turning to solid")
        return "solid"
    else:
        print("remaining as a gas")
        return "gas"


if __name__ == "__main__":
    state = None
    while True:
        user_input = input("> ")
        state = water_states.execute(state, user_input)

Installation

The stateengine package is not on PyPi yet. To use stateengine, clone the repository and put it in the working directory of your project:

git clone https://github.com/aymanimtyaz/stateengine.git

Usage

Creating a state machine

A state machine can be created by creating a state machine object. The constructor does not take any arguments:

from stateengine import StateEngine

state_machine = StateEngine()
...

Assigning states to state handlers

States can be assigned to state handlers using the state_handler() decorator function as:

...
@state_machine.state_handler(state="example_state")
def example_state_handler(input):
	... 

A default state can be assigned by passing default=True as an argument to state_handler. Note that there can only be one default state.

...
@state_machine.state_handler(state="example_state", default=True)
def example_state_handler(input):
	... 

The state handler function should only return other states that have been registered with a state handler, signifying a state transition.

Assigning multiple states to a single state handler

Multiple states can also be assigned to a handler. This can be done by stacking the state_handler decorator:

...
@state_machine.state_handler("state_1")
@state_machine.state_handler("state_2")
@state_machine.state_handler("state_3")
def a_handler_function(input):
	...
	return ...
...

The order in which the decorators for each state are stacked does not matter.

Running the state machine

The state machine can be executed by passing it a state and an input. It should be run after all the state handlers have been defined. A state machine can be run using execute() as:

...
some_state = ...
some_input_1 = ...; some_input_2 = ...; ...
new_state = state_machine.execute(
	state=some_state, some_input_1=some_input_1, some_input_2=some_input_2, ...)
...

A new state will be returned depending on the input and the logic defined in the corresponding handler. A practical way to make use of the state machine would be to run it in a loop, or as a response to an event such as an HTTP request

Accessing the current state using the state_handler property

current_state is a property that can be used to access the current state from within a handler function. This may seem pointless at first glance, but becomes really useful when a handler is assigned to more than one state:

...
@state_machine.state_handler("state_1")
@state_machine.state_handler("state_2")
def a_handler_function(input):
	print(f"The current state is {state_machine.current_state}")
	return ...
...

A note on None states

The default handler (if defined) for a state machine can be executed by passing the corresponding state value OR None as the state argument to execute(). If a default state handler is not defined and a None is passed, an exception will be raised.

Important points

  • A handler function should only return states. Furthermore, it should only return states that are registered to a state handler. Returning an unregistered state will raise a NoHandlerAssociation exception.
  • All handlers for a state machine should have the same type of input (number of inputs)
  • The states can only be of types str and int. An InvalidStateType exception will be raised otherwise.
  • States must be unique. Two state handlers can not have the same state argument. A StateHandlerClash exception will be raised otherwise.
  • Only one default state can exist for a state machine. Trying to assign more than one default state handler will raise a DefaultStateHandlerClash exception.
  • A default state is not necessary. However, if state=None is passed to execute, and a default state handler is not defined. A NoDefaultState exception will be raised.
  • The current_state property can only be accessed from within a handler context, that is, inside a handler function. Trying to access it from outside a handler function will raise a OutsideHandlerContext exception.

Good practices

  • The states' names should reflect what their handlers are supposed to do. This will make it easy to maintain and debug the state machine code in the future.

To Do

  • Make unpacking inputs to state handlers more Pythonic.
  • Allow a handler to handle multiple states.
  • Improve API documentation.
  • Add use cases in the docs.

Examples of StateEngine in use

If you have any questions or suggestions about StateEngine, you can open up an issue or create a PR if you've made some improvements . You can also email me at aymanimtyaz@gmail.com :)

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

python-stateengine-0.0.1a1.tar.gz (8.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

python_stateengine-0.0.1a1-py3-none-any.whl (7.8 kB view details)

Uploaded Python 3

File details

Details for the file python-stateengine-0.0.1a1.tar.gz.

File metadata

  • Download URL: python-stateengine-0.0.1a1.tar.gz
  • Upload date:
  • Size: 8.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.1 CPython/3.8.10

File hashes

Hashes for python-stateengine-0.0.1a1.tar.gz
Algorithm Hash digest
SHA256 ff9478ce6ea53a906b2c0de91fa2a97a2498fc0019fbb2de4c2a22b614c38b42
MD5 865f886254654e8e1e073115b998c0c7
BLAKE2b-256 227b7bb7857411008e9429f55a2d34b861982ae460a8d8aefc369cc9f3575433

See more details on using hashes here.

File details

Details for the file python_stateengine-0.0.1a1-py3-none-any.whl.

File metadata

File hashes

Hashes for python_stateengine-0.0.1a1-py3-none-any.whl
Algorithm Hash digest
SHA256 9331341a6c091789175fd54910cf9ed53e03c2f7b7e8c80a2f54fc7fc74876bf
MD5 8d354f33d0c7ef5a32d97ea425bb3fba
BLAKE2b-256 b8292d98f8aac9b9055a0b92f15c98c1626109d9c3781e6e5f5f60e4a44a1cf1

See more details on using hashes here.

Supported by

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