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:
helmutm 2006-08-02 19:21:30 +00:00
parent 9774638a5e
commit b978abe473
6 changed files with 174 additions and 205 deletions

View file

@ -5,50 +5,79 @@ Business Process Management
We start with the definition of a simple process:
startNode --> n01 --> endNode
startActivity --> n01 --> endActivity
>>> from cybertools.process.definition import ProcessDefinition, Node, Transition
>>> process = ProcessDefinition()
>>> n01 = Node()
>>> process.startNode.addTransition(n01)
>>> n01.addTransition(process.endNode)
>>> from cybertools.process.definition import Process, Activity
>>> process = Process()
>>> n01 = Activity()
>>> process.startActivity.add(n01)
>>> n02 = Activity()
>>> n01.add(n02)
Now let's execute the process:
>>> from cybertools.process.execution import ProcessInstance
>>> instance = ProcessInstance(process)
>>> execution = instance.execute()
>>> execution = process.execute()
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
process' end node:
process' end activity:
>>> execution.currentNode is process.endNode
>>> execution.currentActivity is n02
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.interface import implements
>>> from cybertools.process.interfaces import INode, IActionHandler
>>> from cybertools.process.interfaces import IActivity, IActionHandler
>>> class DummyHandler(object):
... implements(IActionHandler)
... adapts(INode)
... adapts(IActivity)
... def __init__(self, context): pass
... def handle(self, execution):
... print 'working.'
>>> provideAdapter(DummyHandler)
>>> execution = instance.execute()
>>> execution = process.execute()
working.
>>> execution.currentNode is process.startNode
>>> execution.currentActivity is process.startActivity
True
>>> execution.trigger()
working.
>>> execution.currentNode is n01
>>> execution.currentActivity is n01
True
>>> execution.trigger()
working.
>>> execution.currentNode is process.endNode
>>> execution.currentActivity is n02
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

View file

@ -24,93 +24,52 @@ $Id$
from zope import component
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.execution import Execution
class Node(object):
class Activity(object):
implements(INode)
implements(IActivity)
def __init__(self, process=None, name=u'', title=u'', handlerName=''):
self._process = process
self._incoming = set()
self._outgoing = set()
def __init__(self, name=u'', title=u'', handlerName=''):
self._successors = set()
self._handlerName = handlerName
self.__name__ = name
self.title = title
def getProcess(self): return self._process
def setProcess(self, prc): self._process = prc
process = property(getProcess, setProcess)
@property
def outgoing(self): return self._outgoing
@property
def incoming(self): return self._incoming
def successors(self): return self._successors
def getHandlerName(self): return self._handlerName
def setHandlerName(self, name): self._handlerName = name
handlerName = property(getHandlerName, setHandlerName)
def addTransition(self, destination):
transition = Transition(destination)
self.outgoing.add(transition)
transition.source = self
if transition.destination.process is None:
transition.destination.process = self.process
def add(self, activity):
self.successors.add(activity)
def execute(self, execution):
execution.currentNode = self
def execute(self, execution=None):
if execution is None:
execution = Execution()
execution.currentActivity = self
handler = component.queryAdapter(self, IActionHandler, name=self.handlerName)
if handler is not None:
handler.handle(execution) # creates work item; work item triggers execution
else:
execution.trigger()
return execution
class Transition(object):
class Process(object):
implements(ITransition)
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)
implements(IProcess)
def __init__(self):
self._instances = set()
self._startNode = Node(self)
self._endNode = Node(self)
self._startActivity = Activity()
@property
def instances(self): return self._instances
def startActivity(self): return self._startActivity
def getStartNode(self): return self._startNode
def setStartNode(self, node): self._startNode = node
startNode = property(getStartNode, setStartNode)
def getEndNode(self): return self._endNode
def setEndNode(self, node): self._endNode = node
endNode = property(getEndNode, setEndNode)
def execute(self):
return self.startActivity.execute()

View file

@ -23,52 +23,81 @@ $Id$
"""
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):
implements(IExecution)
def __init__(self, instance):
self._instance = instance
self._currentNode = None
def __init__(self, parent=None):
self._currentActivity = None
self._workItem = None
self._parent = parent
self._children = set()
@property
def instance(self): return self._instance
def getCurrentNode(self): return self._currentNode
def setCurrentNode(self, node): self._currentNode = node
currentNode = property(getCurrentNode, setCurrentNode)
def getCurrentActivity(self): return self._currentActivity
def setCurrentActivity(self, activity): self._currentActivity = activity
currentActivity = property(getCurrentActivity, setCurrentActivity)
def getWorkItem(self): return self._workItem
def setWorkItem(self, item): self._workItem = item
workItem = property(getWorkItem, setWorkItem)
def trigger(self, transitionPattern=None):
outgoing = self.currentNode.outgoing
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
def parent(self): return self._parent
@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
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

View file

@ -31,64 +31,53 @@ _ = MessageFactory('zope')
# process/workflow definitions
class INode(Interface):
class IActivity(Interface):
""" A step of a process - typically a state that lets the process wait
for a user interaction or an action that is executed automatically.
"""
process = Attribute('The process this node belongs to')
incoming = Attribute('Transitions that lead to this node')
outgoing = Attribute('Transitions that lead to the next nodes')
successors = Attribute('A set of activities following this activity')
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):
""" Append a transition to the destination node given
to the collection of outgoing transitions.
def add(successor):
""" Append an activity to the collection of following activities.
"""
def execute(execution):
""" Execute a node in an execution context of a process instance;
if this node signifies a wait state this will create a work item.
def execute(execution=None):
""" Execute a activity in an execution context of a process instance;
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):
""" 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):
class IProcess(Interface):
""" The definition of a process or workflow.
"""
instances = Attribute('A collection of process instances created from '
'this process definition')
startNode = Attribute('The start node of this process')
endNode = Attribute('The end node of this process')
startActivity = Attribute('The start activity of this process')
def execute():
""" Start the process (typically by executing its start activity).
Return the execution context.
"""
class IActionHandler(Interface):
""" Will be called for handling process executions. Is typically
implemented as an adapter for INode.
implemented as an adapter for IActivity.
"""
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
@ -97,41 +86,36 @@ class IExecution(Interface):
current states) of a process instance.
"""
instance = Attribute('The process instance this execution context belongs to')
currentNode = Attribute('The node the process instance is currently in')
currentActivity = Attribute('The activity this execution is currently in')
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')
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.
This will typically lead to moving on the execution context
to an outgoing transition of the current node. The execution
context will use the transitionQualifiers to decide which outgoing
transition(s) 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.
to a successor activity of the current activity. The execution
context will use the qualifiers argument to decide which
activities to transfer control to.
"""
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')
execution = Attribute('The execution context (and thus the process '
'instance) that has create this work item')
activity = Attribute('The activity this work item has been created from')
execution = Attribute('The execution context that has created 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.
"""

View file

@ -8,13 +8,13 @@ $Id$
import unittest, doctest
from zope.testing.doctestunit import DocFileSuite
from cybertools.process.definition import ProcessDefinition
from cybertools.process.definition import Process
class TestProcess(unittest.TestCase):
"Basic tests for the process package."
def testBasicStuff(self):
p = ProcessDefinition()
p = Process()
def test_suite():

View file

@ -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)