Metadata-Version: 2.1
Name: Fusion-State-Machine
Version: 0.1.4
Summary: Utility to turn your class into a state machine
Author-email: Jisus17 <cjisus4@gmail.com>
License: MIT License
        
        Copyright (c) 2019 Shuttl
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
        
Project-URL: Homepage, https://github.com/Jisus17/Fusion-State-Machine/
Keywords: state-machine,states-machine-controller,machine-state-controller,machine-state
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Topic :: Software Development :: Build Tools
Classifier: License :: OSI Approved :: GNU General Public License (GPL)
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: dev

# Fusion StateMachine

Many thanks to the original [creator](https://github.com/Shuttl-Tech) of this wonderful library, Fusion StateMachine intended for the development of applications for real-time event lighting via DMX, the starting point is a fork from the [simple-state-machine](
https://github.com/Shuttl-Tech/simple-state-machine) library 

[![Upload Python Package](https://github.com/Jisus17/Fusion-State-Machine/actions/workflows/python-publish.yml/badge.svg?event=release)](https://github.com/Jisus17/Fusion-State-Machine/actions/workflows/python-publish.yml)
It is a python library with which you can `decorate` your class to make it a state machine.
<!-- toc -->

- [Installation](#installation)
- [State Machine](#state-machine)
- [Basic Usage](#basic-usage)
  * [What if my function has 2(or more) possible transitions?](#what-if-my-function-has-2or-more-possible-transitions)
  * [Error Handling](#error-handling)
- [Developers Guide](#developers-guide)
  * [Setup](#setup)
  * [Testing Mode](#testing-mode)
<!-- tocstop -->

## Installation

```bash
pip install simple-state-machine
```

## State Machine

A state machine which has a finite set of states is called a finite state machine. The one which doesn't have finite states is called a non-finite state machine.

A finite state machine is a mathematical concept in which the machine has a state and uses transitions to move from one state to another. Finite state machines can be of 2 types - deterministic and non-deterministic

A deterministic finite state machine produces the same result after given transitions, hence we can "determine" and so the name. In a non-deterministic finite state machine, for some state and input symbol, the next state may be nothing or one or two or more possible states.

This library provides a **deterministic finite state machine**.

## Basic Usage


```python
from state_machine.machine import machine, transition


@machine(states=["earth", "space"])
class Rocket(object):
    def __init__(self):
        self.moving = False
          
    # Mandatory to define this method
    def load_state(self):
        return "space" if self.moving else "earth"

    @transition(sources=["earth"], destination="space")
    def launch(self):
        self.moving = True
            
rocket = Rocket()
# assert m.current_state == "earth"
rocket.launch()
# assert m.current_state == "space"

rocket.launch()
# InvalidMoveError: Current state - space is not in source states - earth
```

### Note
A data member `current_state` is added to your class' object. You may use its value to assert in decisions or in your tests.

### What if my function has 2(or more) possible transitions?
It is quite common to have functions which have 2 or more possible transitions. 

For eg. You are collecting a payment for an order. If the payment is valid, you want to complete the payment, else you want to fail it.

```python
from state_machine.machine import machine, transition


@machine(states=["INIT", "PAYMENT_IN_PROGRESS", "PAYMENT_COMPLETE", "PAYMENT_FAILED"])
class Order:
    def __init__(self):
        self.payment = ""
        
    def load_state(self):
        if not self.payment:
            return "INIT"
        
        if self.payment == "SUCCESS":
            return "PAYMENT_COMPLETE"
        
        if self.payment == "FAILED":
            return "PAYMENT_FAILED"
        
        return "PAYMENT_IN_PROGRESS"
        
    @transition(sources=["INIT"], destination="PAYMENT_IN_PROGRESS")
    def create_payment_request(self):
        self.payment = "IN_PROGRESS"
        
    def collect_payment(self, payment):
        assert self.load_state() == "PAYMENT_IN_PROGRESS"
        
        if payment == "SUCCESS":
            self.payment_success()
        elif payment == "FAILED":
            self.payment_failed()
            
    @transition(sources=["PAYMENT_IN_PROGRESS"], destination="PAYMENT_COMPLETE")
    def payment_success(self):
        self.payment = "SUCCESS"
        
    @transition(sources=["PAYMENT_IN_PROGRESS"], destination="PAYMENT_FAILED")
    def payment_failed(self):
        self.payment = "FAILED"
        
        
order1 = Order()
# assert order.current_state == "INIT"
order1.create_payment_request()
# assert order.current_state == "PAYMENT_IN_PROGRESS"
order1.collect_payment("SUCCESS")
# assert order.current_state == "PAYMENT_COMPLETE"


order2 = Order()
# assert order.current_state == "INIT"
order2.create_payment_request()
# assert order.current_state == "PAYMENT_IN_PROGRESS"
order2.collect_payment("FAILED")
# assert order.current_state == "PAYMENT_FAILED"
```

####  Note
Decision is not a concept in deterministic FSMs, hence we won't be supporting it. This means you will have to assert the current state for a decision accordingly.

### Error Handling
The library raises the following errors.

   1. `LoaderNotFound` - If your class doesn't implement a **load_state** method.
   2. `LoaderException` - If the **load_state** method threw an exception.
   3. `MachineNotFound` - If you used a **transition** decorator on a method whose class is not **machine** decorated.
   4. `InvalidStateError` - This is raised when the state machine encounters an unknown state. The state machine raises this error in 2 situations. One, when **load_state** returns an invalid state. And, when you pass invalid source or destination in a **transition**.
   5. `InvalidMoveError` - It is raised when you enter a transition with a *non-source* state.
   6. `UnintendedOperationError` - The state machine calls **load_state** function after each transition to ensure that the state has been updated to the destination. If a transition doesn't update the state of the machine to destination, this error is raised. For eg-
   
```python

@transition(source=["earth"], destination="sky")
def launch(self):
    pass # the function didn't do anything.
```

## Developer's Guide
### Setup
We use [pipenv](https://pipenv.readthedocs.io) to manage python packages. To setup your dev environment, locate yourself in the project directory and execute 
```
pipenv install --dev
pipenv shell
```

### Testing mode
```
make tests
```
