# Fineas
A simple, thread-safe, decorator-based, transition-focused Finite State Machine implementation.
Possible Use Cases:
- Configuration management from multiple sources.
- Keeping track of state during complex system startup.
- Tracking state while parsing.
```python
from fineas import state_machine
@state_machine(initial_state='new', store_history=True)
class TestMachine:
def __init__(self):
self.config = None
@state_machine.transition(
source=['new', 'invalid_configuration'],
dest='configured',
error_state='invalid_configuration')
def got_config(self, config):
# validate config
self.config = config
@state_machine.transition(source='configured', dest='scheduled')
def ready(self):
pass
@state_machine.transition(
source='scheduled',
dest='scheduled',
error_state='canceled',
failed_state='retry')
def run(self, fail_transition):
# do work
status = self._do_work()
if not status:
fail_transition()
@state_machine.transition(
source='retry',
dest='run',
error_state='canceled',
failed_state='too_many_failures'
)
def try_again(self, times, fail_transition):
for i in range(times):
if self._do_work():
return
fail_transition()
@state_machine.transition(
source=['retry', 'too_many_failures'],
dest='cancelled'
)
def abandon(self):
pass
@state_machine.transition(
source='too_many_failures',
dest='configured'
)
def reconfigure(self, config):
self.config = config
def _do_work(self):
pass
t = TestMachine()
t.got_config(None)
t.ready()
t.run()
t.try_again(3)
t.abandon()
print(t.history)
print(t.state)
```
## Quickstart
1) Decorate a class with `@fineas.state_machine(initial_state='new')`. You must pass a value for
`initial_state`.
2) Define a method that implements the work required to transition form one or more source states
to a single destination state. Decorate that method with
`@fineas.transition(source='new', dest='ready')`.
3) That's it! Each instance of your decorated class is its own state machine. You may check its
current state with its `state` attribute, and, if you've enabled `record_history`, you can access
its transition history with its `history` attribute.
## Overview
To turn each instance of a class into a state machine, simply decorate it with
`@fineas.state_machine()`. You must pass an `initial_state` value to `@fineas.state_machine()`.
This will be the state every instance of your type starts in. You can also enable recording state
transitions with the `record_history` flag; this is useful while developing finite state machines.
Every transition in your state machine is represented by a method inside the class you annotated
with `@fineas.state_machihe()`. To turn a method into a transition, decorate it with
`@fineas.transition()` and supply one or more source states for the transition, and exactly one
destination state. You can also define a state to transition to if an exception is raised inside
your method (there is also a flag to enable or disable re-raising that exception). If your method
accepts a parameter named `fail_transition`, its value will be a callable your method can invoke to
cause the transition to fail while still allowing your method to return a value to its caller. You
may also pass a `fail_state` parameter to the decorator, and when `fail_transition` is invoked, your
instance will be transitioned to the given state.
When any method annotated with `@fineas.transistion()` is called, the following steps happen:
1) Acquire a lock over the receiving instance.
1) Fineas validates that the receiving instance's state is in the sources passed to
`@fineas.transition()`
- If it is not, a TransitionException is raised.
1) The decorated method is invoked, passing `fail_transistion` if able.
1) If the decorated method raises an exception:
- If `error_state` was passed, immediately transition to that state.
- If `reraise_error` is True, re-raise the exception.
- Return.
1) If `fail_transition` was called:
- If 'fail_state' was passed, immediately transition to that state.
1) If no exception was raised and `fail_transition` was not called, transition to the destination
state and return the value returned by the decorated method.
## Requirements
Python 3.6 or higher
wrapt 1.12.x
## Release Notes
1.0.1 (12 March, 2021):
- Minor Cleanup
- Move to poetry
- Basic test coverage
- Bump to 1.0.0 due to no issues being found in the last year.
0.1.0 (07 March, 2021):
- Initial Release