# -*- coding: utf-8 -*-
"""
For now, a "stateful object" is, to this library, anything that as a "state" attribute that can
be read. If it quacks, then it's a duck.
However, as an helper class to demonstrate how a workflow can be bound to an object, we provide
``StatefulObject`` as an example implementation that you can use.
"""
import functools
[docs]class StatefulObject(object):
"""
Example stateful object.
To use it, subclass me and set the workflow attribute to a ``edgy.workflow.Workflow`` instance.
.. attribute:: workflow
A workflow instance, setting the system in which the instances of this object live.
.. attribute:: initial_state
The default initial state of this object.
.. attribute:: current_state
The current state of this object.
"""
workflow = None
initial_state = None
current_state = None
@property
def available_transitions(self):
return self.workflow.get_available_transitions_for(self)
def __new__(cls, *args, **kwargs):
if not cls.workflow:
raise RuntimeError('It is not possible to instanciate a StatefulObject without a workflow.')
state = kwargs.pop('state', None)
instance = super(StatefulObject, cls).__new__(cls)
if state:
instance.current_state = state
return instance
def __repr__(self):
return '<{}.{} object with {} "{}" at {}>'.format(
type(self).__module__,
type(self).__name__,
'state' if self.state in self.workflow.states else 'unknown state',
self.state,
hex(id(self)),
)
def __getattr__(self, attr):
if attr in self.workflow:
return functools.partial(self.workflow[attr], subject=self)
raise AttributeError("%r object has no attribute %r" % (self.__class__, attr))
def _get_state(self):
return self.current_state or self.initial_state
def _set_state(self, state):
self.current_state = state
state = property(fget=_get_state, fset=_set_state, doc='''
Helper for getting the actual state of an object. You should use this instead of
``initial_state`` and ``current_state`` if your only aim is to read or write a new state to
this object.
Beware though, the setter of this property will override the state, without going through the
transitions. If you wanna run the transitions (and in 95% of the cases, you should, otherwise
this library is a pretty bad choice for you), then a proxy attribute exist on the object
for each transition name, and you should just call it (for example, if a transition is named
``wakeup``, you can just call ``instance.wakeup()``).
''')