reorganize cybertools.process; add simple action handler and work item class
git-svn-id: svn://svn.cy55.de/Zope3/src/cybertools/trunk@1280 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
parent
9774638a5e
commit
b978abe473
6 changed files with 174 additions and 205 deletions
|
@ -5,50 +5,79 @@ Business Process Management
|
||||||
|
|
||||||
We start with the definition of a simple process:
|
We start with the definition of a simple process:
|
||||||
|
|
||||||
startNode --> n01 --> endNode
|
startActivity --> n01 --> endActivity
|
||||||
|
|
||||||
>>> from cybertools.process.definition import ProcessDefinition, Node, Transition
|
>>> from cybertools.process.definition import Process, Activity
|
||||||
>>> process = ProcessDefinition()
|
>>> process = Process()
|
||||||
>>> n01 = Node()
|
>>> n01 = Activity()
|
||||||
>>> process.startNode.addTransition(n01)
|
>>> process.startActivity.add(n01)
|
||||||
>>> n01.addTransition(process.endNode)
|
>>> n02 = Activity()
|
||||||
|
>>> n01.add(n02)
|
||||||
|
|
||||||
Now let's execute the process:
|
Now let's execute the process:
|
||||||
|
|
||||||
>>> from cybertools.process.execution import ProcessInstance
|
>>> execution = process.execute()
|
||||||
>>> instance = ProcessInstance(process)
|
|
||||||
>>> execution = instance.execute()
|
|
||||||
|
|
||||||
As there aren't any interactions with the outside world in our process we
|
As there aren't any interactions with the outside world in our process we
|
||||||
don't see anything. But we can check if the process instance has reached the
|
don't see anything. But we can check if the process instance has reached the
|
||||||
process' end node:
|
process' end activity:
|
||||||
|
|
||||||
>>> execution.currentNode is process.endNode
|
>>> execution.currentActivity is n02
|
||||||
True
|
True
|
||||||
|
|
||||||
So let's now associate an action handler with the process' nodes:
|
So let's now associate an action handler with the process' activitys:
|
||||||
|
|
||||||
>>> from zope.component import provideAdapter, adapts
|
>>> from zope.component import provideAdapter, adapts
|
||||||
>>> from zope.interface import implements
|
>>> from zope.interface import implements
|
||||||
>>> from cybertools.process.interfaces import INode, IActionHandler
|
>>> from cybertools.process.interfaces import IActivity, IActionHandler
|
||||||
|
|
||||||
>>> class DummyHandler(object):
|
>>> class DummyHandler(object):
|
||||||
... implements(IActionHandler)
|
... implements(IActionHandler)
|
||||||
... adapts(INode)
|
... adapts(IActivity)
|
||||||
... def __init__(self, context): pass
|
... def __init__(self, context): pass
|
||||||
... def handle(self, execution):
|
... def handle(self, execution):
|
||||||
... print 'working.'
|
... print 'working.'
|
||||||
|
|
||||||
>>> provideAdapter(DummyHandler)
|
>>> provideAdapter(DummyHandler)
|
||||||
>>> execution = instance.execute()
|
>>> execution = process.execute()
|
||||||
working.
|
working.
|
||||||
>>> execution.currentNode is process.startNode
|
>>> execution.currentActivity is process.startActivity
|
||||||
True
|
True
|
||||||
>>> execution.trigger()
|
>>> execution.trigger()
|
||||||
working.
|
working.
|
||||||
>>> execution.currentNode is n01
|
>>> execution.currentActivity is n01
|
||||||
True
|
True
|
||||||
>>> execution.trigger()
|
>>> execution.trigger()
|
||||||
working.
|
working.
|
||||||
>>> execution.currentNode is process.endNode
|
>>> execution.currentActivity is n02
|
||||||
True
|
True
|
||||||
|
|
||||||
|
Next we'll use a predefined action handler that creates a work item. As this
|
||||||
|
makes only sense if the action handler can give the outside world access
|
||||||
|
to the work item somehow, we have to subclass this generic, abstract class:
|
||||||
|
|
||||||
|
>>> workItems = []
|
||||||
|
>>> from cybertools.process.execution import WorkActionHandler
|
||||||
|
>>> class MyActionHandler(WorkActionHandler):
|
||||||
|
... def handle(self, execution):
|
||||||
|
... super(MyActionHandler, self).handle(execution)
|
||||||
|
... workItems.append(self.workItem)
|
||||||
|
>>> provideAdapter(MyActionHandler)
|
||||||
|
|
||||||
|
>>> execution = process.execute()
|
||||||
|
|
||||||
|
Now the process is waiting for somebody to pick up the work item and
|
||||||
|
submit it:
|
||||||
|
|
||||||
|
>>> execution.currentActivity is process.startActivity
|
||||||
|
True
|
||||||
|
>>> workItem = workItems[0]
|
||||||
|
>>> workItem.done
|
||||||
|
False
|
||||||
|
>>> workItem.submit()
|
||||||
|
>>> execution.currentActivity is n01
|
||||||
|
True
|
||||||
|
>>> workItem.done
|
||||||
|
False
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -24,93 +24,52 @@ $Id$
|
||||||
|
|
||||||
from zope import component
|
from zope import component
|
||||||
from zope.interface import implements
|
from zope.interface import implements
|
||||||
from cybertools.process.interfaces import INode, ITransition, IProcessDefinition
|
from cybertools.process.interfaces import IActivity, IProcess
|
||||||
from cybertools.process.interfaces import IActionHandler
|
from cybertools.process.interfaces import IActionHandler
|
||||||
|
from cybertools.process.execution import Execution
|
||||||
|
|
||||||
|
|
||||||
class Node(object):
|
class Activity(object):
|
||||||
|
|
||||||
implements(INode)
|
implements(IActivity)
|
||||||
|
|
||||||
def __init__(self, process=None, name=u'', title=u'', handlerName=''):
|
def __init__(self, name=u'', title=u'', handlerName=''):
|
||||||
self._process = process
|
self._successors = set()
|
||||||
self._incoming = set()
|
|
||||||
self._outgoing = set()
|
|
||||||
self._handlerName = handlerName
|
self._handlerName = handlerName
|
||||||
self.__name__ = name
|
self.__name__ = name
|
||||||
self.title = title
|
self.title = title
|
||||||
|
|
||||||
def getProcess(self): return self._process
|
|
||||||
def setProcess(self, prc): self._process = prc
|
|
||||||
process = property(getProcess, setProcess)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def outgoing(self): return self._outgoing
|
def successors(self): return self._successors
|
||||||
|
|
||||||
@property
|
|
||||||
def incoming(self): return self._incoming
|
|
||||||
|
|
||||||
def getHandlerName(self): return self._handlerName
|
def getHandlerName(self): return self._handlerName
|
||||||
def setHandlerName(self, name): self._handlerName = name
|
def setHandlerName(self, name): self._handlerName = name
|
||||||
handlerName = property(getHandlerName, setHandlerName)
|
handlerName = property(getHandlerName, setHandlerName)
|
||||||
|
|
||||||
def addTransition(self, destination):
|
def add(self, activity):
|
||||||
transition = Transition(destination)
|
self.successors.add(activity)
|
||||||
self.outgoing.add(transition)
|
|
||||||
transition.source = self
|
|
||||||
if transition.destination.process is None:
|
|
||||||
transition.destination.process = self.process
|
|
||||||
|
|
||||||
def execute(self, execution):
|
def execute(self, execution=None):
|
||||||
execution.currentNode = self
|
if execution is None:
|
||||||
|
execution = Execution()
|
||||||
|
execution.currentActivity = self
|
||||||
handler = component.queryAdapter(self, IActionHandler, name=self.handlerName)
|
handler = component.queryAdapter(self, IActionHandler, name=self.handlerName)
|
||||||
if handler is not None:
|
if handler is not None:
|
||||||
handler.handle(execution) # creates work item; work item triggers execution
|
handler.handle(execution) # creates work item; work item triggers execution
|
||||||
else:
|
else:
|
||||||
execution.trigger()
|
execution.trigger()
|
||||||
|
return execution
|
||||||
|
|
||||||
|
|
||||||
class Transition(object):
|
class Process(object):
|
||||||
|
|
||||||
implements(ITransition)
|
implements(IProcess)
|
||||||
|
|
||||||
def __init__(self, destination, qualifier=u''):
|
|
||||||
self._source = None
|
|
||||||
self._destination = destination
|
|
||||||
self._qualifier = qualifier
|
|
||||||
|
|
||||||
def getSource(self): return self._source
|
|
||||||
def setSource(self, node): self._source = node
|
|
||||||
source = property(getSource, setSource)
|
|
||||||
|
|
||||||
def getDestination(self): return self._destination
|
|
||||||
def setDestination(self, node): self._destination = node
|
|
||||||
destination = property(getDestination, setDestination)
|
|
||||||
|
|
||||||
def getQualifier(self): return self._qualifier
|
|
||||||
def setQualifier(self, node): self._qualifier = node
|
|
||||||
qualifier = property(getQualifier, setQualifier)
|
|
||||||
|
|
||||||
def take(self, execution):
|
|
||||||
self.destination.execute(execution)
|
|
||||||
|
|
||||||
|
|
||||||
class ProcessDefinition(object):
|
|
||||||
|
|
||||||
implements(IProcessDefinition)
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._instances = set()
|
self._startActivity = Activity()
|
||||||
self._startNode = Node(self)
|
|
||||||
self._endNode = Node(self)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def instances(self): return self._instances
|
def startActivity(self): return self._startActivity
|
||||||
|
|
||||||
def getStartNode(self): return self._startNode
|
def execute(self):
|
||||||
def setStartNode(self, node): self._startNode = node
|
return self.startActivity.execute()
|
||||||
startNode = property(getStartNode, setStartNode)
|
|
||||||
|
|
||||||
def getEndNode(self): return self._endNode
|
|
||||||
def setEndNode(self, node): self._endNode = node
|
|
||||||
endNode = property(getEndNode, setEndNode)
|
|
||||||
|
|
|
@ -23,52 +23,81 @@ $Id$
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from zope.interface import implements
|
from zope.interface import implements
|
||||||
from cybertools.process.interfaces import IExecution, IProcessInstance
|
from zope.component import adapts
|
||||||
|
from cybertools.process.interfaces import IActivity, IExecution
|
||||||
|
from cybertools.process.interfaces import IWorkItem, IActionHandler
|
||||||
|
|
||||||
|
|
||||||
class Execution(object):
|
class Execution(object):
|
||||||
|
|
||||||
implements(IExecution)
|
implements(IExecution)
|
||||||
|
|
||||||
def __init__(self, instance):
|
def __init__(self, parent=None):
|
||||||
self._instance = instance
|
self._currentActivity = None
|
||||||
self._currentNode = None
|
|
||||||
self._workItem = None
|
self._workItem = None
|
||||||
|
self._parent = parent
|
||||||
|
self._children = set()
|
||||||
|
|
||||||
@property
|
def getCurrentActivity(self): return self._currentActivity
|
||||||
def instance(self): return self._instance
|
def setCurrentActivity(self, activity): self._currentActivity = activity
|
||||||
|
currentActivity = property(getCurrentActivity, setCurrentActivity)
|
||||||
def getCurrentNode(self): return self._currentNode
|
|
||||||
def setCurrentNode(self, node): self._currentNode = node
|
|
||||||
currentNode = property(getCurrentNode, setCurrentNode)
|
|
||||||
|
|
||||||
def getWorkItem(self): return self._workItem
|
def getWorkItem(self): return self._workItem
|
||||||
def setWorkItem(self, item): self._workItem = item
|
def setWorkItem(self, item): self._workItem = item
|
||||||
workItem = property(getWorkItem, setWorkItem)
|
workItem = property(getWorkItem, setWorkItem)
|
||||||
|
|
||||||
def trigger(self, transitionPattern=None):
|
@property
|
||||||
outgoing = self.currentNode.outgoing
|
def parent(self): return self._parent
|
||||||
if outgoing:
|
|
||||||
transition = iter(outgoing).next()
|
|
||||||
transition.take(self)
|
|
||||||
|
|
||||||
|
|
||||||
class ProcessInstance(object):
|
|
||||||
|
|
||||||
implements(IProcessInstance)
|
|
||||||
|
|
||||||
def __init__(self, process):
|
|
||||||
self._process = process
|
|
||||||
self._executions = set()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def process(self): return self._process
|
def children(self): return self._children
|
||||||
|
|
||||||
|
def trigger(self, qualifiers=set()):
|
||||||
|
successors = [s for s in self.currentActivity.successors
|
||||||
|
if not qualifiers
|
||||||
|
or qualifiers.union(successor.qualifiers)]
|
||||||
|
for successor in successors:
|
||||||
|
if len(successors) == 1:
|
||||||
|
execution = self
|
||||||
|
else:
|
||||||
|
execution = Execution(self)
|
||||||
|
self.children.add(execution)
|
||||||
|
successor.execute(execution)
|
||||||
|
|
||||||
|
|
||||||
|
class WorkItem(object):
|
||||||
|
|
||||||
|
implements(IWorkItem)
|
||||||
|
|
||||||
|
def __init__(self, execution):
|
||||||
|
self._execution = execution
|
||||||
|
self._activity = execution.currentActivity
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def executions(self): return self._executions
|
def execution(self): return self._execution
|
||||||
|
|
||||||
|
@property
|
||||||
|
def activity(self): return self._activity
|
||||||
|
|
||||||
|
_done = False
|
||||||
|
@property
|
||||||
|
def done(self): return self._done
|
||||||
|
|
||||||
|
def submit(self, data={}):
|
||||||
|
_done = True
|
||||||
|
self.execution.trigger()
|
||||||
|
|
||||||
|
|
||||||
|
class WorkActionHandler(object):
|
||||||
|
""" A simple action handler that creates a work item.
|
||||||
|
"""
|
||||||
|
|
||||||
|
implements(IActionHandler)
|
||||||
|
adapts(IActivity)
|
||||||
|
|
||||||
|
def __init__(self, context):
|
||||||
|
self.context = context
|
||||||
|
|
||||||
|
def handle(self, execution):
|
||||||
|
self.workItem = WorkItem(execution)
|
||||||
|
|
||||||
def execute(self):
|
|
||||||
execution = Execution(self)
|
|
||||||
self.executions.add(execution)
|
|
||||||
self.process.startNode.execute(execution)
|
|
||||||
return execution
|
|
||||||
|
|
|
@ -31,64 +31,53 @@ _ = MessageFactory('zope')
|
||||||
|
|
||||||
# process/workflow definitions
|
# process/workflow definitions
|
||||||
|
|
||||||
class INode(Interface):
|
class IActivity(Interface):
|
||||||
""" A step of a process - typically a state that lets the process wait
|
""" A step of a process - typically a state that lets the process wait
|
||||||
for a user interaction or an action that is executed automatically.
|
for a user interaction or an action that is executed automatically.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
process = Attribute('The process this node belongs to')
|
successors = Attribute('A set of activities following this activity')
|
||||||
incoming = Attribute('Transitions that lead to this node')
|
|
||||||
outgoing = Attribute('Transitions that lead to the next nodes')
|
|
||||||
handlerName = Attribute('Name of an adapter that may handle the '
|
handlerName = Attribute('Name of an adapter that may handle the '
|
||||||
'execution of this node')
|
'execution of this activity')
|
||||||
|
qualifier = Attribute('A collection of strings giving additional '
|
||||||
|
'information about an activity. '
|
||||||
|
'May be used by an execution context '
|
||||||
|
'for deciding which activity it will execute next')
|
||||||
|
|
||||||
def addTransition(destination):
|
def add(successor):
|
||||||
""" Append a transition to the destination node given
|
""" Append an activity to the collection of following activities.
|
||||||
to the collection of outgoing transitions.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def execute(execution):
|
def execute(execution=None):
|
||||||
""" Execute a node in an execution context of a process instance;
|
""" Execute a activity in an execution context of a process instance;
|
||||||
if this node signifies a wait state this will create a work item.
|
if this activity signifies a wait state this will create a work item.
|
||||||
|
If the execution argument is None a new execution context will
|
||||||
|
be created. The execution context is returned.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class ITransition(Interface):
|
class IProcess(Interface):
|
||||||
""" A transition leading from one node (activity, state, action) to
|
|
||||||
the next.
|
|
||||||
"""
|
|
||||||
source = Attribute('The node that triggered this transition')
|
|
||||||
destination = Attribute('The destination node of this transition')
|
|
||||||
qualifier = Attribute('A string giving a hint for the meaning of the '
|
|
||||||
'transition. May be used by an execution context '
|
|
||||||
'for deciding which transition it will transfer '
|
|
||||||
'control to')
|
|
||||||
|
|
||||||
def take(execution):
|
|
||||||
""" Pass over the execution context from the source node to the
|
|
||||||
destination node.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class IProcessDefinition(Interface):
|
|
||||||
""" The definition of a process or workflow.
|
""" The definition of a process or workflow.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
instances = Attribute('A collection of process instances created from '
|
startActivity = Attribute('The start activity of this process')
|
||||||
'this process definition')
|
|
||||||
startNode = Attribute('The start node of this process')
|
def execute():
|
||||||
endNode = Attribute('The end node of this process')
|
""" Start the process (typically by executing its start activity).
|
||||||
|
Return the execution context.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class IActionHandler(Interface):
|
class IActionHandler(Interface):
|
||||||
""" Will be called for handling process executions. Is typically
|
""" Will be called for handling process executions. Is typically
|
||||||
implemented as an adapter for INode.
|
implemented as an adapter for IActivity.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def handle(execution):
|
def handle(execution):
|
||||||
""" Handles the execution of a node in the execution context given.
|
""" Handles the execution of a activity in the execution context given.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
# process execution
|
# process execution
|
||||||
|
|
||||||
|
|
||||||
|
@ -97,41 +86,36 @@ class IExecution(Interface):
|
||||||
current states) of a process instance.
|
current states) of a process instance.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
instance = Attribute('The process instance this execution context belongs to')
|
currentActivity = Attribute('The activity this execution is currently in')
|
||||||
currentNode = Attribute('The node the process instance is currently in')
|
|
||||||
workItem = Attribute('The work item the process instance is currently '
|
workItem = Attribute('The work item the process instance is currently '
|
||||||
'waiting for; None if the current node is not in a '
|
'waiting for; None if the current activity is not in a '
|
||||||
'waiting state')
|
'waiting state')
|
||||||
|
parent = Attribute('The execution context that has created this one '
|
||||||
|
'e.g. because of a forking operation')
|
||||||
|
children = Attribute('A collection of execution contexts that have been '
|
||||||
|
'created by this one')
|
||||||
|
|
||||||
def trigger(transitionQualifiers=None):
|
def trigger(qualifiers=None):
|
||||||
""" A callback (handler) that will may be called by an action handler.
|
""" A callback (handler) that will may be called by an action handler.
|
||||||
This will typically lead to moving on the execution context
|
This will typically lead to moving on the execution context
|
||||||
to an outgoing transition of the current node. The execution
|
to a successor activity of the current activity. The execution
|
||||||
context will use the transitionQualifiers to decide which outgoing
|
context will use the qualifiers argument to decide which
|
||||||
transition(s) to transfer control to.
|
activities to transfer control to.
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class IProcessInstance(Interface):
|
|
||||||
""" An executing process, i.e. an execution context that keeps track of
|
|
||||||
the currently active node(s) of the process.
|
|
||||||
"""
|
|
||||||
|
|
||||||
process = Attribute('The process definition this instance is created from')
|
|
||||||
executions = Attribute('A collection of currently active execution contexts')
|
|
||||||
|
|
||||||
def execute():
|
|
||||||
""" Start the execution of the process with its start node;
|
|
||||||
return the execution context.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class IWorkItem(Interface):
|
class IWorkItem(Interface):
|
||||||
""" An instance of an activity from a process definition.
|
""" A work item tells some external entity - typically a user - to
|
||||||
|
do something in order to let the process proceed.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
node = Attribute('The node this work item has been created from')
|
activity = Attribute('The activity this work item has been created from')
|
||||||
execution = Attribute('The execution context (and thus the process '
|
execution = Attribute('The execution context that has created this work item')
|
||||||
'instance) that has create this work item')
|
done = Attribute('A Boolean attribute that is true if the work '
|
||||||
|
'item has been submitted')
|
||||||
|
|
||||||
|
def submit(data={}):
|
||||||
|
""" Provide the work item with some data (optional) and have it
|
||||||
|
continue the process by triggering the execution context.
|
||||||
|
This should also set the `done` attribute to True.
|
||||||
|
"""
|
||||||
|
|
|
@ -8,13 +8,13 @@ $Id$
|
||||||
|
|
||||||
import unittest, doctest
|
import unittest, doctest
|
||||||
from zope.testing.doctestunit import DocFileSuite
|
from zope.testing.doctestunit import DocFileSuite
|
||||||
from cybertools.process.definition import ProcessDefinition
|
from cybertools.process.definition import Process
|
||||||
|
|
||||||
class TestProcess(unittest.TestCase):
|
class TestProcess(unittest.TestCase):
|
||||||
"Basic tests for the process package."
|
"Basic tests for the process package."
|
||||||
|
|
||||||
def testBasicStuff(self):
|
def testBasicStuff(self):
|
||||||
p = ProcessDefinition()
|
p = Process()
|
||||||
|
|
||||||
|
|
||||||
def test_suite():
|
def test_suite():
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
#
|
|
||||||
# Copyright (c) 2006 Helmut Merz helmutm@cy55.de
|
|
||||||
#
|
|
||||||
# This program is free software; you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation; either version 2 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program; if not, write to the Free Software
|
|
||||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
||||||
#
|
|
||||||
|
|
||||||
"""
|
|
||||||
Work items (tasks) and similar stuff.
|
|
||||||
|
|
||||||
$Id$
|
|
||||||
"""
|
|
||||||
|
|
||||||
from zope.interface import implements
|
|
||||||
from cybertools.process.interfaces import IWorkItem
|
|
||||||
|
|
||||||
|
|
||||||
class WorkItem(object):
|
|
||||||
|
|
||||||
implements(IWorkItem)
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue