Skip to main content

Python Finite State Machines made easy.

Project description

https://img.shields.io/pypi/v/python-statemachine.svg Build status Coverage report Documentation Status Updates Join the chat at https://gitter.im/fgmacedo/python-statemachine

Python finite-state machines made easy.

Getting started

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

$ pip install python-statemachine

Define your state machine:

from statemachine import StateMachine, State

class TrafficLightMachine(StateMachine):
    green = State('Green', initial=True)
    yellow = State('Yellow')
    red = State('Red')

    slowdown = green.to(yellow)
    stop = yellow.to(red)
    go = red.to(green)

You can now create an instance:

>>> traffic_light = TrafficLightMachine()

And inspect about the current state:

>>> traffic_light.current_state
State('Green', identifier='green', value='green', initial=True)
>>> traffic_light.current_state == TrafficLightMachine.green == traffic_light.green
True

For each state, there’s a dynamically created property in the form is_<state.identifier>, that returns True if the current status matches the query:

>>> traffic_light.is_green
True
>>> traffic_light.is_yellow
False
>>> traffic_light.is_red
False

Query about metadata:

>>> [s.identifier for s in m.states]
['green', 'red', 'yellow']
>>> [t.identifier for t in m.transitions]
['go', 'slowdown', 'stop']

Call a transition:

>>> traffic_light.slowdown()

And check for the current status:

>>> traffic_light.current_state
State('Yellow', identifier='yellow', value='yellow', initial=False)
>>> traffic_light.is_yellow
True

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

>>> traffic_light.is_yellow
True
>>> traffic_light.slowdown()
Traceback (most recent call last):
...
TransitionNotAllowed: Can't slowdown when in Yellow.

You can also trigger events in an alternative way, calling the run(<transition.identificer>) method:

>>> traffic_light.is_yellow
True
>>> traffic_light.run('stop')
>>> traffic_light.is_red
True

A state machine can be instantiated with an initial value:

>>> machine = TrafficLightMachine(start_value='red')
>>> traffic_light.is_red
True

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.is_red
True
>>> obj.state
'red'
>>> obj.state = 'green'
>>> traffic_light.is_green
True
>>> traffic_light.slowdown()
>>> obj.state
'yellow'
>>> traffic_light.is_yellow
True

Callbacks

Callbacks when running events:

from statemachine import StateMachine, State

class TrafficLightMachine(StateMachine):
    "A traffic light machine"
    green = State('Green', initial=True)
    yellow = State('Yellow')
    red = State('Red')

    slowdown = green.to(yellow)
    stop = yellow.to(red)
    go = red.to(green)

    def on_slowdown(self):
        print('Calma, lá!')

    def on_stop(self):
        print('Parou.')

    def on_go(self):
        print('Valendo!')
>>> stm = TrafficLightMachine()
>>> stm.slowdown()
Calma, lá!
>>> stm.stop()
Parou.
>>> stm.go()
Valendo!

Or when entering/exiting states:

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)

    def on_enter_green(self):
        print('Valendo!')

    def on_enter_yellow(self):
        print('Calma, lá!')

    def on_enter_red(self):
        print('Parou.')
>>> stm = TrafficLightMachine()
>>> stm.cycle()
Calma, lá!
>>> stm.cycle()
Parou.
>>> stm.cycle()
Valendo!

Mixins

Your model can inherited from a custom mixin to auto-instantiate a state machine.

class CampaignMachineWithKeys(StateMachine):
    "A workflow machine"
    draft = State('Draft', initial=True, value=1)
    producing = State('Being produced', value=2)
    closed = State('Closed', value=3)
    cancelled = State('Cancelled', value=4)

    add_job = draft.to.itself() | producing.to.itself()
    produce = draft.to(producing)
    deliver = producing.to(closed)
    cancel = cancelled.from_(draft, producing)


class MyModel(MachineMixin):
    state_machine_name = 'CampaignMachineWithKeys'

    def __init__(self, **kwargs):
        for k, v in kwargs.items():
            setattr(self, k, v)
        super(MyModel, self).__init__()

    def __repr__(self):
        return "{}({!r})".format(type(self).__name__, self.__dict__)


model = MyModel(state='draft')
assert isinstance(model.statemachine, campaign_machine)
assert model.state == 'draft'
assert model.statemachine.current_state == model.statemachine.draft

model.statemachine.cancel()
assert model.state == 'cancelled'

History

0.8.0 (2020-01-23)

  • Add support for Python 3.7 and 3.8 (adding to test matrix).

  • Update development requirements.

  • State machine names should now be fully qualified for mixins, simple names are deprecated and will no longer be supported on a future version.

  • Development: Adding mypy linter.

  • Add support for State machine inheritance. Thanks @rschrader.

  • Add support for reverse transitions: transition = state_a.from_(state_b). Thanks @romulorosa.

  • Fix current state equal to destination on enter events. Thanks @robnils and @joshuacc1.

Breaking changes:

  • Drop official support for Python 3.4 (removing from test matrix, code may still work).

0.7.1 (2019-01-18)

  • Fix Django integration for registry loading statemachine modules on Django1.7+.

0.7.0 (2018-04-01)

  • New event callbacks: on_enter_<state> and on_exit_<state>.

0.6.2 (2017-08-25)

  • Fix README.

0.6.1 (2017-08-25)

  • Fix deploy issues.

0.6.0 (2017-08-25)

  • Auto-discovering statemachine/statemachines under a Django project when they are requested using the mixin/registry feature.

0.5.1 (2017-07-24)

  • Fix bug on CombinedTransition._can_run not allowing transitions to run if there are more than two transitions combined.

0.5.0 (2017-07-13)

  • Custom exceptions.

  • Duplicated definition of on_execute callback is not allowed.

  • Fix bug on StateMachine.on_<transition.identifier> being called with extra self param.

0.4.2 (2017-07-10)

  • Python 3.6 support.

  • Drop official support for Python 3.3.

  • Transition can be used as decorator for on_execute callback definition.

  • Transition can point to multiple destination states.

0.3.0 (2017-03-22)

  • README getting started section.

  • Tests to state machine without model.

0.2.0 (2017-03-22)

  • State can hold a value that will be assigned to the model as the state value.

  • Travis-CI integration.

  • RTD integration.

0.1.0 (2017-03-21)

  • First release on PyPI.

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-0.8.0.tar.gz (28.5 kB view details)

Uploaded Source

Built Distribution

python_statemachine-0.8.0-py2.py3-none-any.whl (12.1 kB view details)

Uploaded Python 2 Python 3

File details

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

File metadata

  • Download URL: python-statemachine-0.8.0.tar.gz
  • Upload date:
  • Size: 28.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.1.1 pkginfo/1.5.0.1 requests/2.22.0 setuptools/45.1.0 requests-toolbelt/0.9.1 tqdm/4.41.1 CPython/3.8.0

File hashes

Hashes for python-statemachine-0.8.0.tar.gz
Algorithm Hash digest
SHA256 f6a70c1f4ed8b1198487eac09114ec9ea1166be6d793996f8c1b92d590a9ebae
MD5 cf7ec1635bbb30c532d79009027bb09d
BLAKE2b-256 f4e56b848e23ef075c38c81c9c094decb38a00338db6d4b55a50e0b3017073c8

See more details on using hashes here.

File details

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

File metadata

  • Download URL: python_statemachine-0.8.0-py2.py3-none-any.whl
  • Upload date:
  • Size: 12.1 kB
  • Tags: Python 2, Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.1.1 pkginfo/1.5.0.1 requests/2.22.0 setuptools/45.1.0 requests-toolbelt/0.9.1 tqdm/4.41.1 CPython/3.8.0

File hashes

Hashes for python_statemachine-0.8.0-py2.py3-none-any.whl
Algorithm Hash digest
SHA256 b816ec61639ddd3b1d3a8ad06a62840d30b9e7176b4b94fc183cebd1c7d5caf2
MD5 cb83bed9ff31dcfd0b5493dcf3e3f256
BLAKE2b-256 558726556c71e3069dc443ffbed321b1a1534180b9c41fd153e1fb8b911030ab

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