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
NoHandlerAssociationexception. - All handlers for a state machine should have the same type of input (number of inputs)
- The states can only be of types
strandint. AnInvalidStateTypeexception will be raised otherwise. - States must be unique. Two state handlers can not have the same state argument. A
StateHandlerClashexception 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
DefaultStateHandlerClashexception. - A default state is not necessary. However, if
state=Noneis passed toexecute, and a default state handler is not defined. ANoDefaultStateexception will be raised. - The
current_stateproperty 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 aOutsideHandlerContextexception.
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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ff9478ce6ea53a906b2c0de91fa2a97a2498fc0019fbb2de4c2a22b614c38b42
|
|
| MD5 |
865f886254654e8e1e073115b998c0c7
|
|
| BLAKE2b-256 |
227b7bb7857411008e9429f55a2d34b861982ae460a8d8aefc369cc9f3575433
|
File details
Details for the file python_stateengine-0.0.1a1-py3-none-any.whl.
File metadata
- Download URL: python_stateengine-0.0.1a1-py3-none-any.whl
- Upload date:
- Size: 7.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/4.0.1 CPython/3.8.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9331341a6c091789175fd54910cf9ed53e03c2f7b7e8c80a2f54fc7fc74876bf
|
|
| MD5 |
8d354f33d0c7ef5a32d97ea425bb3fba
|
|
| BLAKE2b-256 |
b8292d98f8aac9b9055a0b92f15c98c1626109d9c3781e6e5f5f60e4a44a1cf1
|