Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 22 additions & 82 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Basic Usage
.. code:: python


@acts_as_state_machine
@acts_as_state_machine()
class Person():
name = 'Billy'

Expand All @@ -41,33 +41,33 @@ Basic Usage
sleep = Event(from_states=(running, cleaning), to_state=sleeping)

@before('sleep')
def do_one_thing(self):
def do_one_thing(self, param):
print "{} is sleepy".format(self.name)

@before('sleep')
def do_another_thing(self):
print "{} is REALLY sleepy".format(self.name)
def do_another_thing(self, param):
print "{} is REALLY sleepy and will sleep {}".format(self.name, param)

@after('sleep')
def snore(self):
def snore(self, param):
print "Zzzzzzzzzzzz"

@after('sleep')
def big_snore(self):
print "Zzzzzzzzzzzzzzzzzzzzzz"
def big_snore(self, param):
print "Zzzzzzzzzzzzzzzzzzzzzz (%r)"%param

person = Person()
print person.current_state == Person.sleeping # True
print person.is_sleeping # True
print person.is_running # False
person.run()
print person.is_running # True
person.sleep()
person.sleep('a long time')

# Billy is sleepy
# Billy is REALLY sleepy
# Billy is REALLY sleepy and will sleep a long time
# Zzzzzzzzzzzz
# Zzzzzzzzzzzzzzzzzzzzzz
# Zzzzzzzzzzzzzzzzzzzzzz (a long time)

print person.is_sleeping # True

Expand All @@ -79,6 +79,7 @@ Before / After Callback Decorators

You can add callback hooks that get executed before or after an event
(see example above).
If a event is called with parameters, all the before/after callback must be defined with a compatible signature

*Important:* if the *before* event causes an exception or returns
``False``, the state will not change (transition is blocked) and the
Expand All @@ -90,87 +91,26 @@ Blocks invalid state transitions
An *InvalidStateTransition Exception* will be thrown if you try to move
into an invalid state.

ORM support
-----------

We have basic support for `mongoengine`_, and `sqlalchemy`_.

Mongoengine
~~~~~~~~~~~

Just have your object inherit from ``mongoengine.Document`` and
state\_machine will add a StringField for state.

*Note:* You must explicitly call #save to persist the document to the
datastore.

.. code:: python

@acts_as_state_machine
class Person(mongoengine.Document):
name = mongoengine.StringField(default='Billy')

sleeping = State(initial=True)
running = State()
cleaning = State()

run = Event(from_states=sleeping, to_state=running)
cleanup = Event(from_states=running, to_state=cleaning)
sleep = Event(from_states=(running, cleaning), to_state=sleeping)

@before('sleep')
def do_one_thing(self):
print "{} is sleepy".format(self.name)
Name of the state field
~~~~~~~~~~~~~~~~~~~~~~~

@before('sleep')
def do_another_thing(self):
print "{} is REALLY sleepy".format(self.name)

@after('sleep')
def snore(self):
print "Zzzzzzzzzzzz"

@after('sleep')
def snore(self):
print "Zzzzzzzzzzzzzzzzzzzzzz"


person = Person()
person.save()
eq_(person.current_state, Person.sleeping)
assert person.is_sleeping
assert not person.is_running
person.run()
assert person.is_running
person.sleep()
assert person.is_sleeping
person.run()
person.save()

person2 = Person.objects(id=person.id).first()
assert person2.is_running

.. _mongoengine: http://mongoengine.org/
.. _sqlalchemy: http://www.sqlalchemy.org/

Sqlalchemy
~~~~~~~~~~
The default name of the state field is "aasm_state".
If the field already exist in the object il will be used as so (beware to initialize it with the default state value yourself in the init function)
If you want to change it, you just have to give it as an argument to the acts_as_state_machine decorator:
@acts_as_state_machine('my_field_name')
class Person():
...

All you need to do is have sqlalchemy manage your object. For example:
ORM support
-----------

.. code:: python
It should be done independently

from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
@acts_as_state_machine
class Puppy(Base):
...

Issues / Roadmap:
-----------------

- Allow multiple state\_machines per object
- Be able to configure the state field

Questions / Issues
------------------
Expand Down
4 changes: 2 additions & 2 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@
# built documents.
#
# The short X.Y version.
version = '0.2.9'
version = '0.3.0'
# The full version, including alpha/beta/rc tags.
release = '0.2.9'
release = '0.3.0'

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
Expand Down
103 changes: 21 additions & 82 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Basic Usage
.. code:: python


@acts_as_state_machine
@acts_as_state_machine()
class Person():
name = 'Billy'

Expand All @@ -41,33 +41,33 @@ Basic Usage
sleep = Event(from_states=(running, cleaning), to_state=sleeping)

@before('sleep')
def do_one_thing(self):
def do_one_thing(self, param):
print "{} is sleepy".format(self.name)

@before('sleep')
def do_another_thing(self):
print "{} is REALLY sleepy".format(self.name)
def do_another_thing(self, param):
print "{} is REALLY sleepy and will sleep {}".format(self.name, param)

@after('sleep')
def snore(self):
def snore(self, param):
print "Zzzzzzzzzzzz"

@after('sleep')
def big_snore(self):
print "Zzzzzzzzzzzzzzzzzzzzzz"
def big_snore(self, param):
print "Zzzzzzzzzzzzzzzzzzzzzz (%r)"%param

person = Person()
print person.current_state == Person.sleeping # True
print person.is_sleeping # True
print person.is_running # False
person.run()
print person.is_running # True
person.sleep()
person.sleep('a long time')

# Billy is sleepy
# Billy is REALLY sleepy
# Billy is REALLY sleepy and will sleep a long time
# Zzzzzzzzzzzz
# Zzzzzzzzzzzzzzzzzzzzzz
# Zzzzzzzzzzzzzzzzzzzzzz (a long time)

print person.is_sleeping # True

Expand All @@ -79,6 +79,7 @@ Before / After Callback Decorators

You can add callback hooks that get executed before or after an event
(see example above).
If a event is called with parameters, all the before/after callback must be defined with a compatible signature

*Important:* if the *before* event causes an exception or returns
``False``, the state will not change (transition is blocked) and the
Expand All @@ -90,87 +91,25 @@ Blocks invalid state transitions
An *InvalidStateTransition Exception* will be thrown if you try to move
into an invalid state.

ORM support
-----------

We have basic support for `mongoengine`_, and `sqlalchemy`_.

Mongoengine
~~~~~~~~~~~

Just have your object inherit from ``mongoengine.Document`` and
state\_machine will add a StringField for state.

*Note:* You must explicitly call #save to persist the document to the
datastore.

.. code:: python

@acts_as_state_machine
class Person(mongoengine.Document):
name = mongoengine.StringField(default='Billy')

sleeping = State(initial=True)
running = State()
cleaning = State()

run = Event(from_states=sleeping, to_state=running)
cleanup = Event(from_states=running, to_state=cleaning)
sleep = Event(from_states=(running, cleaning), to_state=sleeping)

@before('sleep')
def do_one_thing(self):
print "{} is sleepy".format(self.name)
Name of the state field
~~~~~~~~~~~~~~~~~~~~~~~

@before('sleep')
def do_another_thing(self):
print "{} is REALLY sleepy".format(self.name)

@after('sleep')
def snore(self):
print "Zzzzzzzzzzzz"

@after('sleep')
def snore(self):
print "Zzzzzzzzzzzzzzzzzzzzzz"


person = Person()
person.save()
eq_(person.current_state, Person.sleeping)
assert person.is_sleeping
assert not person.is_running
person.run()
assert person.is_running
person.sleep()
assert person.is_sleeping
person.run()
person.save()

person2 = Person.objects(id=person.id).first()
assert person2.is_running

.. _mongoengine: http://mongoengine.org/
.. _sqlalchemy: http://www.sqlalchemy.org/

Sqlalchemy
~~~~~~~~~~
The default name of the state field is "aasm_state". If you want to chane it, you just have to give it as an argument to the acts_as_state_machine decorator:
@acts_as_state_machine('my_field_name')
class Person():
...

All you need to do is have sqlalchemy manage your object. For example:
ORM support
-----------

.. code:: python
No more support for ORM, just plain old object.
If the state field preexists, it's reused and you must give the default value at the initialization else it's created and initialized to the name of the default State.

from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
@acts_as_state_machine
class Puppy(Base):
...

Issues / Roadmap:
-----------------

- Allow multiple state\_machines per object
- Be able to configure the state field

Questions / Issues
------------------
Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ def get_packages():
required_modules = []

setup(name='state_machine',
version='0.2.9',
version='0.3.0',
description='Python State Machines for Humans',
url='http://github.com/jtushman/state_machine',
url='https://github.com/egillet/state_machine',
author='Jonathan Tushman',
author_email='jonathan@zefr.com',
install_requires=required_modules,
Expand Down
16 changes: 9 additions & 7 deletions state_machine/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import inspect

from state_machine.models import Event, State, InvalidStateTransition
from state_machine.orm import get_adaptor
from state_machine.orm.base import BaseAdaptor

_temp_callback_cache = None

Expand Down Expand Up @@ -41,9 +41,11 @@ def wrapper(func):
return wrapper


def acts_as_state_machine(original_class):
adaptor = get_adaptor(original_class)
global _temp_callback_cache
modified_class = adaptor.modifed_class(original_class, _temp_callback_cache)
_temp_callback_cache = None
return modified_class
def acts_as_state_machine(state_field_name='aasm_state'):
def f(original_class):
adaptor = BaseAdaptor(original_class)
global _temp_callback_cache
adaptor.modifed_class(original_class, _temp_callback_cache, state_field_name)
_temp_callback_cache = None
return original_class
return f
26 changes: 0 additions & 26 deletions state_machine/orm/__init__.py
Original file line number Diff line number Diff line change
@@ -1,26 +0,0 @@
from __future__ import absolute_import

from state_machine.orm.base import BaseAdaptor
from state_machine.orm.mongoengine import get_mongo_adaptor
from state_machine.orm.sqlalchemy import get_sqlalchemy_adaptor

_adaptors = [get_mongo_adaptor, get_sqlalchemy_adaptor]


def get_adaptor(original_class):
# if none, then just keep state in memory
for get_adaptor in _adaptors:
adaptor = get_adaptor(original_class)
if adaptor is not None:
break
else:
adaptor = NullAdaptor(original_class)
return adaptor


class NullAdaptor(BaseAdaptor):
def extra_class_members(self, initial_state):
return {"aasm_state": initial_state.name}

def update(self, document, state_name):
document.aasm_state = state_name
Loading