transmission

Transmission is a utility package that houses decorators and signals pertaining to state transitions and generalized events


Keywords
django, finite, state, machine, rest, signals
License
Other
Install
pip install transmission==1.4.1

Documentation

transmission

Build Status

transmission is the place where we store a finite state machine design pattern built for Django.

It includes a decorator called transition that is used to declaratively state the conditions that the model can transition from and to. In the process of executing a transition a transition_event signal is triggered. Receivers that connect to this signal can execute additional logic after the transition has occured.

Usage

Transitions

transmission.utils.transition

Signature transmission.utils.transition(state, from_state, to_state, [is_mutable=True, **extra_data])(model_method)

transition is a model method decorator that requires the following args:

  • state: [str] The name of the field that holds the model's state information. N.B. The field must be a choice field through use of the choices kwarg on the field declaration.
  • from_state: [value or list of values] This is the declaration of states that the field can be in when the transition begins else an IntegrityError is raised.
  • to_state: [value] This is the state the the model is transitioning to.
  • is_mutable: [bool] Optional flag that indicates whether or not the state field should be updated to the value given by the to_state. Default value is True.
  • extra_data: [kwargs] This is optional keyword args that will be passed to the pre and post transition event handlers.
from django.db import models
from django.utils.translation import ugettext as _

from transmission.utils import transition


class MyModel(models.Model):
    DRAFT = 'draft'
    ACTIVE = 'active'
    WHATEVER = 'whatever'
    REMOVED = 'removed'
    STATES = (
        (DRAFT, 'draft'),
        (ACTIVE, 'active'),
        (WHATEVER, 'whatever'),
        (REMOVED, 'fake deletion')
    )

    state = models.CharField(
        _("the model's current state"), editable=False, max_length=256,
        db_index=True, choices=STATES, default=DRAFT, blank=True
    )

    name = models.CharField(
        _("An arbitrary name field for demo"), max_length=256
    )
    ...

    @transition('state', DRAFT, ACTIVE)
    def activate(self):
        # you can do whatever you want in here
        # e.g.
        print "hello suckers!!!! I'm polluting your terminal!!"

        # perhaps you want a cascading transition
        self.someothermodel.activate()

        # ALSO NOTE WORTHY ... a `transmission.signals.transition_event`
        # signal is emitted when this model method is hit

    @transition('state', [DRAFT, ACTIVE, WHATEVER], REMOVED)
    def remove(self):
        # ALSO NOTE that you don't have to do anything in the transition
        # method AND that you have multiple potential starting states but ONLY
        # one final state
        pass

...

my_instance = MyModel(name='my weird name')
print my_instance.state  # prints default 'draft'
my_instance.activate()  # returns None

instance = MyModel.objects.get(name='my weird name')
print instance.state  # prints 'active'

transmission.utils.property_transition

Signature transmission.utils.transition(state, from_state, [**extra_data])(model_method)

property_transition is a simple wrapper around transition that provides a simple interface for specifying transitions based on model properties.

  • state: [str] The name of the property that holds the model's state information.
  • from_state: [value or list of values] This is the declaration of states that the property can be in when the transition begins else an IntegrityError is raised.
  • extra_data: [kwargs] This is optional keyword args that will be passed to the pre and post transition event handlers.

Transition Events

transmission.signals.pre_transition_event

Signature

The pre_transition_event signal is triggered just before the transition method is attempted.

N.B. This signal will be sent be irrespective of any errors raised during the actual transition. As such this event signal should NOT be used to any logic that assumes the success of the transition.

Receivers that connect to pre_transition_event should expect the following keyword arguments to be passed them:

  • instance: An instance of a Django model
  • state: (str) The name of the field that is used to maintain state
  • from_state: The value associated to the state of the model before the transition was executed. Note that the date type is dependent on the field data type chosen for the "state" field.
  • to_state: The value associated to the state of the model after the transition was executed.
  • transition_name: The name of the transition function that triggered the signal.
  • uuid: A uuid4 str unique to the whole transition. N.B. This value will be the same for the post_transition_event.
  • decorator_kwargs: This contain extra data specified when defining the transition using the decorator.

transmission.signals.post_transition_event

Signature

The post_transition_event signal is triggered after the transition method has successfully completed and the model has been saved.

Receivers that connect to post_transition_event should expect the following keyword arguments to be passed them:

  • instance: An instance of a Django model
  • state: (str) The name of the field that is used to maintain state
  • from_state: The value associated to the state of the model before the transition was executed. Note that the date type is dependent on the field data type chosen for the "state" field.
  • to_state: The value associated to the state of the model after the transition was executed.
  • transition_name: The name of the transition function that triggered the signal.
  • uuid: A uuid4 str unique to the whole transition. N.B. This value will be the same for the pre_transition_event.
  • decorator_kwargs: This contain extra data specified when defining the transition using the decorator.
# in myapp/apps.py
from django.apps import AppConfig
from transmission.signals import post_transition_event


def on_mymodel_activated(
    instance, state, from_state, to_state, transition_name, **kwargs
):
    if to_state == instance.ACTIVE:
        print 'oh well, hello mr. active!!!'


class TestAppConfig(AppConfig):
    name = 'mpapp'
    verbose_name = "My App"

    def ready(self):

        post_transition_event.connect(
            on_mymodel_activated, dispatch_uid='something random'
        )

my_instance = MyModel(name='my weird name')
print my_instance.state  # prints 'draft'
my_instance.activate() # prints 'oh well, hello mr. active!!!'

Development

Installation

git clone git@github.com:hangarunderground/transmission.git
cd transmission
virtualenv venv --no-site-packages --distribute
pip install Setuptools --upgrade
pip install -e .[dev]

Testing

Test the Jeebuz out of it. JJEeeeeeeebbbuzz!

Just write your tests in transmission/tests/testproject/testapp/tests.py then in your terminal...

tox

See it's easy.

Misc

Reason behind the name

In a car the transmission is the place where the car's gear state is managed and as a result it is the place where the car's "power" is controlled.

... Also I was listening to Joy Division at the time. And yes it's funny that the song is about a radio broadcast, much like a signal broadcast...