Giter Club home page Giter Club logo

finite-state-machine's Introduction

Finite State Machine Banner

Finite State Machine

Latest Release Supports Python 3.6+ License: MIT Code Style: Black

Build Status codecov

Lightweight, decorator-based Python implementation of a Finite State Machine.

Table of Contents

Installation

pip install finite-state-machine

Usage

Subclass StateMachine and set the state instance variable:

from finite_state_machine import StateMachine, transition

class LightSwitch(StateMachine):
    def __init__(self):
        self.state = "off"
        super().__init__()

The transition decorator can be used to specify valid state transitions with an optional parameter for conditions. States can be of type: string, int, bool, Enum, or IntEnum. Can specify a single sate or a list of states for the source parameter; can only specify a single state as the target target. All condition functions need to return True for the transition to occur, else a ConditionsNotMet exception will be raised. Condition functions require the same positional position and keyword arguments present in the transition function.

    @transition(source="off", target="on", conditions=[light_is_off])
    def turn_on(self):
        # transition function
        # logic to define what happens to "complete a transition"
        # ex. update database record,
        ...

def light_is_off(machine):
    # condition function, first param will always be the state machine class
    # return a boolean to specify if a transition is valid
    return machine.state == "off"

Can also specify an on_error state to handle situations where the transition function raises an exception:

    @transition(source="off", target="on", on_error="failed")
    def turn_on(self):
        raise ValueError

Example

from finite_state_machine import StateMachine, transition

class Turnstile(StateMachine):
    initial_state = "close"

    def __init__(self):
        self.state = self.initial_state
        super().__init__()

    @transition(source=["close", "open"], target="open")
    def insert_coin(self):
        pass

    @transition(source="open", target="close")
    def pass_thru(self):
        pass

REPL

In [2]: turnstile = Turnstile()

In [3]: turnstile.state
Out[3]: 'close'

In [4]: turnstile.insert_coin()

In [5]: turnstile.state
Out[5]: 'open'

In [6]: turnstile.insert_coin()

In [7]: turnstile.state
Out[7]: 'open'

In [8]: turnstile.pass_thru()

In [9]: turnstile.state
Out[9]: 'close'

In [10]: turnstile.pass_thru()
---------------------------------------------------------------------------
InvalidStartState                         Traceback (most recent call last)
<ipython-input-10-6abc6f4be1cd> in <module>
----> 1 turnstile.pass_thru()

~/state_machine.py in _wrapper(*args, **kwargs)
     32
     33             if self.state not in source:
---> 34                 raise InvalidStartState
     35
     36             for condition in conditions:

InvalidStartState:

The examples folder contains additional workflows.

Asynchronous Support

finite-state-machine can be used to build both synchronous and asynchronous State Machines. The @transition decorator supports transition functions and condition functions as follows:

Synchronous transition function Asynchronous transition function
Synchronous condition function
Asynchronous condition function

State Diagram

State Machine workflows can be visualized using a state diagram.

finite-state-machine generates diagrams using Mermaid Markdown syntax, which can be viewed using the Mermaid Live Editor.

Use the fsm_draw_state_diagram command and point to State Machine workflow class that inherits from StateMachine.

# class parameter is required
$ fsm_draw_state_diagram --class examples.turnstile:Turnstile

# initial_state parameter is optional
$ fsm_draw_state_diagram --class examples.turnstile:Turnstile --initial_state close

Contributing

  1. Clone repo
  2. Create a virtual environment
  3. pip install -r requirements_dev.txt
  4. Install pre-commit
  5. Set up pre-commit hooks in repo: pre-commit install

To install a package locally for development, run:

flit install [--symlink] [--python path/to/python]

Running Tests

pytest

Inspiration

This project is inspired by django-fsm. I wanted a decorator-based state machine without having to use Django.

finite-state-machine's People

Contributors

alysivji avatar lucas-andre avatar mgetka avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

finite-state-machine's Issues

Different source and target for same event

Hello!

Let's assume the following state machine:

stateDiagram-v2
    [*] --> A
    A --> B: event_2
    A --> C: event_3
    B --> A: event_1
    B --> C: event_4
    C --> D: event_4
    D --> A: event_1
Loading

So event_1 triggers the transition from:

  • B --> A
  • D --> A

But event_4 triggers the transition from (different source and target in each case):

  • B --> C
  • C --> D

If I understood correctly this would result in the following code:

from finite_state_machine import StateMachine, transition


class ExampleStateMachine(StateMachine):
    initial_state = "A"

    def __init__(self):
        self.state = self.initial_state
        super().__init__()

    @transition(source=["B", "D"], target="A")
    def event_1(self):
        print("Transitioning to A by event_1")

    @transition(source="A", target="B")
    def event_2(self):
        print("Transitioning to B by event_2")

    @transition(source="A", target="C")
    def event_3(self):
        print("Transitioning to C by event_3")

    @transition(source="B", target="C")
    @transition(source="C", target="D")
    def event_4(self):
        pass

The problem is with event_4 since it would require two different transitions

  @transition(source="C", target="D") 
  @transition(source="B", target="C")
  def event_4(self):
      pass

But when I try to run this machine, I get the following error message:

fsm = ExampleStateMachine()
fsm.event_2()
fsm.event_1()
fsm.event_2()
fsm.event_4()  # <- InvalidStartState exception raised here
fsm.event_4()
fsm.event_1()
fsm.event_3()
fsm.event_4()
fsm.event_1()
finite_state_machine.exceptions.InvalidStartState: Current state is B. event_4 allows transitions from ['C'].

Now I am not sure where my thinking error is. Is my code wrong or are multiple transitions really not supported?

Thanks a lot!

Generate visualization

It's easier to talk about state machines when you can visualize them. Output both GraphViz and Mermaid script.

Find all conditions that do not pass

Current behavior

When a list of conditions is passed to the transition decorator, an exception is raised when the first not True condition is found

Desired behavior

Find all conditions that are not True

Need to think about

Might be good to deprecate ConditionNotMet for ConditionsNotMet

Handle exceptions

If an exception occurs while running a function, users should be able to specify what state to transition into.

fsm_draw_state_diagram command

Hello,

I am not really clear on the 'fsm_draw_state_diagram' command and how to generate a state diagram after implementing your State Machine code. I am trying to generate a state machine diagram for the code below using your implementation. The comment below from one of the documentations wasn't really clear to me and it reads thus:
...................................................................................................................................................................................................................................................................
"State Machine workflows can be visualized using a state diagram.
finite-state-machine generates diagrams using Mermaid Markdown syntax, which can be viewed using the Mermaid Live Editor.
Use the fsm_draw_state_diagram command and point to State Machine workflow class that inheritences from StateMachine.

class parameter is required

$ fsm_draw_state_diagram --class examples.turnstile:Turnstile

initial_state parameter is optional

$ fsm_draw_state_diagram --class examples.turnstile:Turnstile --initial_state close
.................................................................................................................................................................................................................................................................

How do I go about this? I already have my code written up. Just need to finish up with the visualization of the state diagram. Will appreciate any help.

asyncio implementation

Looking at the source code de decorator don't seem to work when wrapping an async function

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.