Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

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 :)

Metadata

Release files for python-stateengine 0.0.1a1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for python-stateengine 0.0.1a1
File Size Uploaded
python-stateengine-0.0.1a1.tar.gz 8.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for python-stateengine 0.0.1a1
File Interpreter ABI Platform
python_stateengine-0.0.1a1-py3-none-any.whl Python 3 none any Details

Total release size: 16.5 kB

Release files / python-stateengine-0.0.1a1.tar.gz

Download URL python-stateengine-0.0.1a1.tar.gz
Size 8.6 kB
Tags Source
SHA-256 checksum
How to use checksums
ff9478ce6ea53a906b2c0de91fa2a97a2498fc0019fbb2de4c2a22b614c38b42
BLAKE2b-256 checksum
How to use checksums
227b7bb7857411008e9429f55a2d34b861982ae460a8d8aefc369cc9f3575433
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/4.0.1 CPython/3.8.10

Release files / python_stateengine-0.0.1a1-py3-none-any.whl

Download URL python_stateengine-0.0.1a1-py3-none-any.whl
Size 7.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
9331341a6c091789175fd54910cf9ed53e03c2f7b7e8c80a2f54fc7fc74876bf
BLAKE2b-256 checksum
How to use checksums
b8292d98f8aac9b9055a0b92f15c98c1626109d9c3781e6e5f5f60e4a44a1cf1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/4.0.1 CPython/3.8.10

Release history Release notifications | RSS feed

This release

0.0.1a1 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page