Skip to content

Commit

Permalink
refactor: simplified usage of the lib + updated docs + pyproject.toml
Browse files Browse the repository at this point in the history
  • Loading branch information
woile committed Aug 26, 2018
1 parent 0da5e78 commit df47143
Show file tree
Hide file tree
Showing 18 changed files with 210 additions and 166 deletions.
1 change: 1 addition & 0 deletions .bumpversion.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ commit = True
tag = True

[bumpversion:file:setup.py]
[bumpversion:file:pyproject.toml]

[bumpversion:file:docs/conf.py]

Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
*.py[cod]

.vscode/
pyproject.lock
# C extensions
*.so
.pypirc
Expand Down
21 changes: 13 additions & 8 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
language: python
python: '3.5'
sudo: false
matrix:
include:
- python: 2.7
env: TOXENV=py27,docs
- python: 3.4
env: TOXENV=py34
- python: 3.5
env: TOXENV=py35
- python: 3.6
env: TOXENV=py36,codecov,check,docs
- python: pypy
env: TOXENV=pypy
env:
global:
- LD_PRELOAD=/lib/x86_64-linux-gnu/libSegFault.so
- SEGFAULT_SIGNALS=all
matrix:
- TOXENV=check
- TOXENV=docs
- TOXENV=py27,codecov
- TOXENV=py35,codecov
before_install:
- python --version
- uname -a
- lsb_release -a
install:
- pip install tox
- pip install tox tox-travis
- virtualenv --version
- easy_install --version
- pip --version
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
Changelog
=========

Unreleased
-----------

* BREAKING: Simpler implementation
* Docs and readme updated

1.0.0 (2018-07-03)
-----------------------------------------

Expand Down
3 changes: 1 addition & 2 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
graft docs
graft examples
graft src
graft fsm
graft ci
graft tests

include .bumpversion.cfg
include .coveragerc
include .cookiecutterrc
include .editorconfig
include .isort.cfg
include *.yml

include AUTHORS.rst
Expand Down
53 changes: 46 additions & 7 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,17 @@ Minimal state machine

* Free software: BSD license

.. contents::
:depth: 2

Usage
=====

.. code-block:: python
import fsm
class MyModel(fsm.FiniteStateMachineMixin):
class MyTasks(fsm.FiniteStateMachineMixin):
"""An example to test the state machine.
Contains transitions to everywhere, nowhere and specific states.
Expand All @@ -80,16 +83,12 @@ Usage
"""Initialize setting a state."""
self.state = state
def current_state(self):
"""Overriden."""
return self.state
def on_before_pending(self):
print("I'm going to a pending state")
::

In [4]: m = MyModel(state='created')
In [4]: m = MyTasks(state='created')

In [5]: m.change_state('pending')
I'm going to a pending state
Expand Down Expand Up @@ -134,6 +133,46 @@ Installation

pip install fsmpy


Django integration
==================

.. code-block:: python
import fsm
from django.db import models
class MyModel(models.Model, fsm.FiniteStateMachineMixin):
"""An example to test the state machine.
Contains transitions to everywhere, nowhere and specific states.
"""
CHOICES = (
('created', 'CREATED'),
('pending', 'PENDING'),
('running', 'RUNNING'),
('success', 'SUCCESS'),
('failed', 'FAILED'),
('retry', 'RETRY'),
)
state_machine = {
'created': '__all__',
'pending': ('running',),
'running': ('success', 'failed'),
'success': None,
'failed': ('retry',),
'retry': ('pending', 'retry'),
}
state = models.CharField(max_length=30, choices=CHOICES, default='created')
def on_change_state(self, previous_state, next_state, **kwargs):
self.save()
Documentation
=============

Expand All @@ -142,7 +181,7 @@ https://pyfsm.readthedocs.org/
Development
===========

To run the all tests run::
To run the tests run::

tox

Expand Down
5 changes: 2 additions & 3 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,8 @@
'issue': ('https://github.com/Woile/pyfsm/issues/%s', '#'),
'pr': ('https://github.com/Woile/pyfsm/pull/%s', 'PR #'),
}
import sphinx_py3doc_enhanced_theme
html_theme = "sphinx_py3doc_enhanced_theme"
html_theme_path = [sphinx_py3doc_enhanced_theme.get_html_theme_path()]
# import sphinx_rtd_theme
html_theme = "sphinx_rtd_theme"
html_theme_options = {
'githuburl': 'https://github.com/Woile/pyfsm/'
}
Expand Down
5 changes: 5 additions & 0 deletions docs/reference/fsm.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ fsm module
:undoc-members:
:show-inheritance:

.. autoclass:: fsm.BaseFiniteStateMachineMixin
:members:
:undoc-members:
:show-inheritance:


Module contents
---------------
Expand Down
2 changes: 1 addition & 1 deletion docs/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
sphinx>=1.3
sphinx-py3doc-enhanced-theme==2.4.0
sphinx_rtd_theme
requests[security]
-e .
15 changes: 9 additions & 6 deletions docs/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,9 @@ Then add the Mixin to any class where the state machine is required.
state = 'my_first_state'
def current_state(self):
"""Overriden."""
return self.state
Instanciate the class and use it:
Instanciate the class and use it. Remember that in order to work as intended, :code:`change_state`
must be used to transition from one state to the other.

.. code::
Expand All @@ -46,4 +43,10 @@ Instanciate the class and use it:
False
>>> foo.get_valid_transitions()
('my_second_state',)
('my_second_state',)
You can also use :code:`BaseFiniteStateMachineMixin` for more flexibility.
Implementing :code:`current_state` and :code:`set_state` is required.
Doing this allows using more complex behavior, but it is **not recommended**.
2 changes: 1 addition & 1 deletion src/fsm/__init__.py → fsm/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Initializing package."""

from .exceptions import InvalidTransition # NOQA
from .fsm import FiniteStateMachineMixin # NOQA
from .fsm import FiniteStateMachineMixin, BaseFiniteStateMachineMixin # NOQA

__version__ = "1.0.1"
3 changes: 2 additions & 1 deletion src/fsm/exceptions.py → fsm/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
__all__ = ['InvalidTransition']
__all__ = ["InvalidTransition"]


class InvalidTransition(Exception):
"""Moving from an state to another is not possible."""

pass
54 changes: 43 additions & 11 deletions src/fsm/fsm.py → fsm/fsm.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
from .exceptions import InvalidTransition

__all__ = ['FiniteStateMachineMixin']
__all__ = ["FiniteStateMachineMixin", "BaseFiniteStateMachineMixin"]


class FiniteStateMachineMixin:
"""Mixins which adds the behavior of a state_machine.
class BaseFiniteStateMachineMixin:
"""Base Mixin to add a state_machine behavior.
Represents the state machine for the object.
Expand All @@ -17,13 +17,23 @@ class FiniteStateMachineMixin:
'another_state': ('some_state', 'one_more_state')
'one_more_state': None
}
Requires the implementation of :code:`current_state` and :code:`set_state`
"""

state_machine = None

def current_state(self):
"""Returns the current state in the FSM."""
raise NotImplementedError('Subclass must implement this method!')
raise NotImplementedError("Subclass must implement this method!")

def set_state(self, state):
"""Update the internal state field.
:param state: type depends on the definition of the states.
:type state: str or int
"""
raise NotImplementedError("Subclass must implement this method!")

def can_change(self, next_state):
"""Validates if the next_state can be executed or not.
Expand All @@ -46,7 +56,7 @@ def get_valid_transitions(self):
current = self.current_state()
valid_transitions = self.state_machine[current]

if valid_transitions == '__all__':
if valid_transitions == "__all__":
return self.state_machine.keys()

return self.state_machine[current]
Expand All @@ -72,7 +82,8 @@ def on_change_state(self, previous_state, next_state, **kwargs):
pass

def change_state(self, next_state, **kwargs):
"""Performs a transition from current state to the given next state if possible.
"""Performs a transition from current state to the given next state if
possible.
Callbacks will be exacuted before an after changing the state.
Specific state callbacks will also be called if they are implemented
Expand All @@ -87,23 +98,44 @@ def change_state(self, next_state, **kwargs):
previous_state = self.current_state()

if not self.can_change(next_state):
msg = "The transition from {0} to {1} is not valid".format(previous_state,
next_state)
msg = "The transition from {0} to {1} is not valid".format(
previous_state, next_state
)
raise InvalidTransition(msg)

name = 'pre_{0}'.format(next_state)
name = "pre_{0}".format(next_state)
callback = getattr(self, name, None)
if callback:
callback(**kwargs)

self.on_before_change_state(previous_state, next_state, **kwargs)

self.state = next_state
self.set_state(next_state)

name = 'post_{0}'.format(next_state)
name = "post_{0}".format(next_state)
callback = getattr(self, name, None)
if callback:
callback(**kwargs)

self.on_change_state(previous_state, next_state, **kwargs)
return next_state


class FiniteStateMachineMixin(BaseFiniteStateMachineMixin):
"""A drop in implementation. Ready to be used.
Replace :code:`FIELD_NAME` in order to automatically retrieve or set
from a different field.
In order to use with django, just add a field :code:`state`
or as defined in :code:`FIELD_NAME`
and remember to use :code:`change_state` instead of simply assigning it
"""

FIELD_NAME = "state"

def current_state(self):
return getattr(self, self.FIELD_NAME)

def set_state(self, state):
setattr(self, self.FIELD_NAME, state)
23 changes: 23 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[tool.poetry]
name = "fsmpy"
version = "1.0.1"
description = "Minimal state machine"
authors = ["Santiago Fraire Willemoes <santiwilly@gmail.com>"]
license = "MIT"
readme = "README.rst"
keywords=["finite", "state", "machine", "minimal"]
classifiers=[
'Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers',
]
homepage = "https://github.com/Woile/pyfsm"
repository = "https://github.com/Woile/pyfsm"
documentation = "https://pyfsm.readthedocs.io/en/latest/"

[tool.poetry.dependencies]
python = "*"

[tool.poetry.dev-dependencies]
tox = "^3.2"
bumpversion = "^0.5.3"
flake8 = "^3.5"

0 comments on commit df47143

Please sign in to comment.