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:
parent
6bd2c94f05
commit
c0c772ff30
4 changed files with 199 additions and 36 deletions
|
@ -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'
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
Loading…
Add table
Reference in a new issue