Skip to main content

Python Finite State Machines made easy.

Reason this release was yanked:

Found a bug. The 1.0.3 is the bugfix

Project description

https://img.shields.io/pypi/v/python-statemachine.svg https://img.shields.io/pypi/dm/python-statemachine.svg Build status Coverage report Documentation Status GitHub commits since last release (main)

Python finite-state machines made easy.

Welcome to python-statemachine, an intuitive and powerful state machine framework designed for a great developer experience.

🚀 With StateMachine, you can easily create complex, dynamic systems with clean, readable code.

💡 Our framework makes it easy to understand and reason about the different states, events and transitions in your system, so you can focus on building great products.

🔒 python-statemachine also provides robust error handling and ensures that your system stays in a valid state at all times.

A few reasons why you may consider using it:

  • 📈 python-statemachine is designed to help you build scalable, maintainable systems that can handle any complexity.

  • 💪 You can easily create and manage multiple state machines within a single application.

  • 🚫 Prevents common mistakes and ensures that your system stays in a valid state at all times.

Getting started

To install Python State Machine, run this command in your terminal:

$ pip install python-statemachine

To generate diagrams from your machines, you’ll also need pydot and Graphviz. You can install this library already with pydot dependency using the extras install option. See our docs for more details.

$ pip install python-statemachine[diagrams]

Define your state machine:

>>> from statemachine import StateMachine, State
>>> class TrafficLightMachine(StateMachine):
...     "A traffic light machine"
...     green = State("Green", initial=True)
...     yellow = State("Yellow")
...     red = State("Red")
...
...     cycle = green.to(yellow) | yellow.to(red) | red.to(green)
...
...     slowdown = green.to(yellow)
...     stop = yellow.to(red)
...     go = red.to(green)
...
...     def before_cycle(self, event_data=None):
...         message = event_data.kwargs.get("message", "")
...         message = ". " + message if message else ""
...         return "Running {} from {} to {}{}".format(
...             event_data.event,
...             event_data.transition.source.id,
...             event_data.transition.target.id,
...             message,
...         )
...
...     def on_enter_red(self):
...         print("Don't move.")
...
...     def on_exit_red(self):
...         print("Go ahead!")

You can now create an instance:

>>> traffic_light = TrafficLightMachine()

Then start sending events:

>>> traffic_light.cycle()
'Running cycle from green to yellow'

You can inspect about the current state:

>>> traffic_light.current_state.id
'yellow'

Or get a complete state repr for debugging purposes:

>>> traffic_light.current_state
State('Yellow', id='yellow', value='yellow', initial=False, final=False)

The State instance can also be checked by equality:

>>> traffic_light.current_state == TrafficLightMachine.yellow
True
>>> traffic_light.current_state == traffic_light.yellow
True

But for your convenience, can easily ask if a state is active at any time:

>>> traffic_light.green.is_active
False
>>> traffic_light.yellow.is_active
True
>>> traffic_light.red.is_active
False

Easily iterate over all states:

>>> [s.id for s in traffic_light.states]
['green', 'red', 'yellow']

Or over events:

>>> [t.name for t in traffic_light.events]
['cycle', 'go', 'slowdown', 'stop']

Call an event by it’s name:

>>> traffic_light.cycle()
Don't move.
'Running cycle from yellow to red'

Or sending an event with the event name:

>>> traffic_light.send('cycle')
Go ahead!
'Running cycle from red to green'
>>> traffic_light.green.is_active
True

You can’t run a transition from an invalid state:

>>> traffic_light.go()
Traceback (most recent call last):
statemachine.exceptions.TransitionNotAllowed: Can't go when in Green.

Keeping the same state as expected:

>>> traffic_light.green.is_active
True

And you can pass arbitrary positional or keyword arguments to the event, and they will be propagated to all actions and callbacks:

>>> traffic_light.cycle(message="Please, now slowdon.")
'Running cycle from green to yellow. Please, now slowdon.'

Models

If you need to persist the current state on another object, or you’re using the state machine to control the flow of another object, you can pass this object to the StateMachine constructor:

>>> class MyModel(object):
...     def __init__(self, state):
...         self.state = state
...
>>> obj = MyModel(state='red')
>>> traffic_light = TrafficLightMachine(obj)
>>> traffic_light.red.is_active
True
>>> obj.state
'red'
>>> obj.state = 'green'
>>> traffic_light.green.is_active
True
>>> traffic_light.slowdown()
>>> obj.state
'yellow'
>>> traffic_light.yellow.is_active
True

A more useful example

A simple didactic state machine for controlling an Order:

>>> class OrderControl(StateMachine):
...     waiting_for_payment = State("Waiting for payment", initial=True)
...     processing = State("Processing")
...     shipping = State("Shipping")
...     completed = State("Completed", final=True)
...
...     add_to_order = waiting_for_payment.to(waiting_for_payment)
...     receive_payment = (
...         waiting_for_payment.to(processing, cond="payments_enough")
...         | waiting_for_payment.to(waiting_for_payment, unless="payments_enough")
...     )
...     process_order = processing.to(shipping, cond="payment_received")
...     ship_order = shipping.to(completed)
...
...     def __init__(self):
...         self.order_total = 0
...         self.payments = []
...         self.payment_received = False
...         super(OrderControl, self).__init__()
...
...     def payments_enough(self, amount):
...         return sum(self.payments) + amount >= self.order_total
...
...     def before_add_to_order(self, amount):
...         self.order_total += amount
...         return self.order_total
...
...     def before_receive_payment(self, amount):
...         self.payments.append(amount)
...         return self.payments
...
...     def after_receive_payment(self):
...         self.payment_received = True
...
...     def on_enter_waiting_for_payment(self):
...         self.payment_received = False

You can use this machine as follows.

>>> control = OrderControl()
>>> control.add_to_order(3)
3
>>> control.add_to_order(7)
10
>>> control.receive_payment(4)
[4]
>>> control.current_state.id
'waiting_for_payment'
>>> control.process_order()
Traceback (most recent call last):
...
statemachine.exceptions.TransitionNotAllowed: Can't process_order when in Waiting for payment.
>>> control.receive_payment(6)
[4, 6]
>>> control.current_state.id
'processing'
>>> control.process_order()
>>> control.ship_order()
>>> control.payment_received
True
>>> control.order_total
10
>>> control.payments
[4, 6]
>>> control.completed.is_active
True

There’s a lot more to cover, please take a look at our docs: https://python-statemachine.readthedocs.io.

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-statemachine-1.0.2.tar.gz (309.9 kB view details)

Uploaded Source

Built Distribution

python_statemachine-1.0.2-py2.py3-none-any.whl (24.7 kB view details)

Uploaded Python 2 Python 3

File details

Details for the file python-statemachine-1.0.2.tar.gz.

File metadata

  • Download URL: python-statemachine-1.0.2.tar.gz
  • Upload date:
  • Size: 309.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.2 CPython/3.8.1

File hashes

Hashes for python-statemachine-1.0.2.tar.gz
Algorithm Hash digest
SHA256 5e39653fb664b9a25de7a9b1a5e21292ba5d8f4fcbb1474ec77c837b09aca294
MD5 1289ce73b22fe00daba31649e96b2e6a
BLAKE2b-256 dde9725b42bff8f98130a3163574c1e9ee8d3e5de613e74a92146d469c0c3869

See more details on using hashes here.

File details

Details for the file python_statemachine-1.0.2-py2.py3-none-any.whl.

File metadata

File hashes

Hashes for python_statemachine-1.0.2-py2.py3-none-any.whl
Algorithm Hash digest
SHA256 8a69b5b792b5802ddde749b5fb8574720b66f879898c075d52680587412412a9
MD5 58fa2631be880fdad923ddf5a02023c7
BLAKE2b-256 25ddf7367fe3d1cb1a6cd688a679be223d494621f6bf868fd65c014bb3225989

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