extend 'stateful' implementation (sort of a very simple workflow system)

git-svn-id: svn://svn.cy55.de/Zope3/src/cybertools/trunk@1933 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
helmutm 2007-08-19 14:41:23 +00:00
parent 6bd2c94f05
commit c0c772ff30
4 changed files with 199 additions and 36 deletions

View file

@ -1,16 +1,99 @@
================
Stateful objects
Stateful Objects
================
($Id$)
>>> from cybertools.stateful.definition import StatesDefinition
>>> from cybertools.stateful.definition import State, Transition
>>> from cybertools.stateful.definition import registerStatesDefinition
>>> from cybertools.stateful.base import Stateful
We start with a simple demonstration class that provides stateful
behaviour directly.
>>> class Demo(Stateful):
... pass
>>> demo = Demo()
The default states definition has the `started` state has its initial
state.
>>> demo.getState()
'started'
>>> demo.getStateObject().title
'Started'
We can now execute the `finish` Transition.
>>> demo.doTransition('finish')
>>> demo.getState()
'finished'
More complex states definitions
-------------------------------
>>> registerStatesDefinition(
... StatesDefinition('publishing',
... State('private', 'private', ('show',)),
... State('visible', 'visible', ('publish', 'hide',)),
... State('published', 'published', ('retract',)),
... Transition('show', 'show', 'visible'),
... Transition('hide', 'hide', 'private'),
... Transition('publish', 'publish', 'published'),
... Transition('retract', 'retract', 'visible'),
... initialState='visible'))
>>> demo = Demo()
>>> demo.statesDefinition = 'publishing'
>>> demo.getState()
'visible'
If we try to execute a transition that is not an outgoing transition
of the current state we get an error.
>>> demo.doTransition('retract')
Traceback (most recent call last):
...
ValueError: Transition 'retract' is not reachable from state 'visible'.
>>> demo.getState()
'visible'
Stateful Adapters
=================
Objects that show stateful behaviour need not be derived from the Stateful
class, for persistent objects one can also provide a stateful adapter.
>>> from persistent import Persistent
>>> class Demo(Persistent):
... pass
>>> demo = Demo()
>>> from zope import component
>>> from cybertools.stateful.base import StatefulAdapter
>>> component.provideAdapter(StatefulAdapter)
We can now retrieve a stateful adapter using the IStateful interface.
>>> from cybertools.stateful.interfaces import IStateful
>>> statefulDemo = IStateful(demo)
>>> statefulDemo.getState()
'started'
>>> statefulDemo.getStateObject().title
'Started'
>>> statefulDemo.doTransition('finish')
>>> statefulDemo.getState()
'finished'
If we make a new adapter for the same persistent object we get
back the state that is stored with the object.
>>> statefulDemo = IStateful(demo)
>>> statefulDemo.getState()
'finished'

View file

@ -17,28 +17,35 @@
#
"""
State definition implementation.
Basic implementations for stateful objects and adapters.
$Id$
"""
from persistent.interfaces import IPersistent
from persistent.mapping import PersistentMapping
from zope.component import adapts
from zope.interface import implements
from cybertools.stateful.interfaces import IStateful
from cybertools.stateful.definition import statesDefinitions
class Stateful:
class Stateful(object):
implements(IStateful)
_statesDefinition = 'default'
_state = None
statesDefinition = 'default'
state = None
def getState(self):
if self._state is None:
self._state = self.getStatesDefinition()._initialState
return self._state
if self.state is None:
self.state = self.getStatesDefinition().initialState
return self.state
def getStateObject(self):
state = self.getState()
return self.getStatesDefinition().states[state]
def doTransition(self, transition):
""" execute transition.
@ -51,6 +58,29 @@ class Stateful:
return sd.getAvailableTransitionsFor(self)
def getStatesDefinition(self):
return statesDefinitions.get(self._statesDefinition, None)
return statesDefinitions.get(self.statesDefinition, None)
class StatefulAdapter(Stateful):
""" An adapter for persistent objects to make the stateful.
"""
adapts(IPersistent)
statesAttributeName = '__states__'
def __init__(self, context):
self.context = context
def getState(self):
statesAttr = getattr(self.context, self.statesAttributeName, {})
return statesAttr.get(self.statesDefinition,
self.getStatesDefinition().initialState)
def setState(self, value):
statesAttr = getattr(self.context, self.statesAttributeName, None)
if statesAttr is None:
statesAttr = PersistentMapping()
setattr(self.context, self.statesAttributeName, statesAttr)
statesAttr[self.statesDefinition] = value
state = property(getState, setState)

View file

@ -23,22 +23,28 @@ $Id$
"""
from zope.interface import implements
from cybertools.util.jeep import Jeep
from cybertools.stateful.interfaces import IState, ITransition
from cybertools.stateful.interfaces import IStatesDefinition
class State(object):
def __init__(self, id, title, transitions):
self.id = id
implements(IState)
def __init__(self, name, title, transitions):
self.name = self.__name__ = name
self.title = title
self.transitions = transitions
class Transition(object):
def __init__(self, id, title, targetState):
self.id = id
implements(ITransition)
def __init__(self, name, title, targetState):
self.name = self.__name__ = name
self.title = title
self.targetState = targetState
@ -47,27 +53,49 @@ class StatesDefinition(object):
implements(IStatesDefinition)
# Basic/example states definition:
_states = {
'started': State('started', 'Started', ('finish',)),
'finished': State('finished', 'Finished', ()),
}
_transitions = {
'finish': Transition('finish', 'Finish', 'finished')
}
_initialState = 'started'
initialState = 'started'
def doTransitionFor(self, object, transition):
object._state = self._transitions[transition].targetState
def __init__(self, name, *details, **kw):
self.name = self.__name__ = name
self.states = Jeep()
self.transitions = Jeep()
for d in details:
if ITransition.providedBy(d):
self.transitions.append(d)
elif IState.providedBy(d):
self.states.append(d)
else:
raise TypeError('Only states or transitions are allowed here, '
'got %s instead.' % repr(d))
for k, v in kw.items():
setattr(self, k, v)
def getAvailableTransitionsFor(self, object):
state = object.getState()
return [ self._transitions[t] for t in self._states[state].transitions ]
def doTransitionFor(self, obj, transition):
if transition not in self.transitions:
raise ValueError('Transition %s is not available.' % transition)
if transition not in [t.name for t in self.getAvailableTransitionsFor(obj)]:
raise ValueError("Transition '%s' is not reachable from state '%s'."
% (transition, obj.getState()))
obj.state = self.transitions[transition].targetState
def getAvailableTransitionsFor(self, obj):
state = obj.getState()
return [self.transitions[t] for t in self.states[state].transitions]
# dummy default states definition
defaultSD = StatesDefinition('default',
State('started', 'Started', ('finish',)),
State('finished', 'Finished', ()),
Transition('finish', 'Finish', 'finished'),
)
def registerStatesDefinition(id, definition):
statesDefinitions[id] = definition
# states definitions registry
statesDefinitions = {
'default': StatesDefinition(),
}
statesDefinitions = dict()
def registerStatesDefinition(definition):
statesDefinitions[definition.name] = definition
registerStatesDefinition(defaultSD)

View file

@ -22,7 +22,23 @@ Interfaces for the `stateful` package.
$Id$
"""
from zope.interface import Interface
from zope.interface import Interface, Attribute
class IState(Interface):
name = Attribute('The name or identifier of the state')
title = Attribute('A user-readable name or title of the state')
transitions = Attribute('A sequence of strings naming the transitions '
'that can be executed from this state')
class ITransition(Interface):
name = Attribute('The name or identifier of the transition')
title = Attribute('A user-readable name or title of the transition')
targetState = Attribute('A string naming the state that will be the '
'result of executing this transition')
class IStateful(Interface):
@ -30,11 +46,15 @@ class IStateful(Interface):
"""
def getState():
""" Return the workflow state of the object.
""" Return the name of the state of the object.
"""
def getStateObject():
""" Return the state (an IState implementation) of the object.
"""
def doTransition(transition):
""" Execute a transition; the transition is specified by its id.
""" Execute a transition; the transition is specified by its name.
"""
def getAvailableTransitions():
@ -50,9 +70,11 @@ class IStatesDefinition(Interface):
Similar to an entity-based workflow definition.
"""
name = Attribute('The name or identifier of the states definition')
def doTransitionFor(object, transition):
""" Execute a transition for the object given;
the transition is specified by its id.
the transition is specified by its name.
"""
def getAvailableTransitionsFor(object):