added cybertools.process - some simple workflow/process management stuff

git-svn-id: svn://svn.cy55.de/Zope3/src/cybertools/trunk@1274 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
helmutm 2006-07-28 11:22:50 +00:00
parent 698df39f4c
commit 9774638a5e
8 changed files with 446 additions and 4 deletions

View file

@ -5,10 +5,7 @@
</script>
<script type="text/javascript" src="ajax.dojo/dojo.js"
tal:attributes="src context/++resource++ajax.dojo/dojo.js" />
<script type="text/javascript">
dojo.require("dojo.io.*");
tal:attributes="src context/++resource++ajax.dojo/dojo.js">
</script>
</metal:def>

54
process/README.txt Normal file
View file

@ -0,0 +1,54 @@
Business Process Management
===========================
($Id$)
We start with the definition of a simple process:
startNode --> n01 --> endNode
>>> from cybertools.process.definition import ProcessDefinition, Node, Transition
>>> process = ProcessDefinition()
>>> n01 = Node()
>>> process.startNode.addTransition(n01)
>>> n01.addTransition(process.endNode)
Now let's execute the process:
>>> from cybertools.process.execution import ProcessInstance
>>> instance = ProcessInstance(process)
>>> execution = instance.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:
>>> execution.currentNode is process.endNode
True
So let's now associate an action handler with the process' nodes:
>>> from zope.component import provideAdapter, adapts
>>> from zope.interface import implements
>>> from cybertools.process.interfaces import INode, IActionHandler
>>> class DummyHandler(object):
... implements(IActionHandler)
... adapts(INode)
... def __init__(self, context): pass
... def handle(self, execution):
... print 'working.'
>>> provideAdapter(DummyHandler)
>>> execution = instance.execute()
working.
>>> execution.currentNode is process.startNode
True
>>> execution.trigger()
working.
>>> execution.currentNode is n01
True
>>> execution.trigger()
working.
>>> execution.currentNode is process.endNode
True

4
process/__init__.py Normal file
View file

@ -0,0 +1,4 @@
"""
$Id$
"""

116
process/definition.py Normal file
View file

@ -0,0 +1,116 @@
#
# 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
#
"""
Process definitions.
$Id$
"""
from zope import component
from zope.interface import implements
from cybertools.process.interfaces import INode, ITransition, IProcessDefinition
from cybertools.process.interfaces import IActionHandler
class Node(object):
implements(INode)
def __init__(self, process=None, name=u'', title=u'', handlerName=''):
self._process = process
self._incoming = set()
self._outgoing = 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 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 execute(self, execution):
execution.currentNode = 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()
class Transition(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)
def __init__(self):
self._instances = set()
self._startNode = Node(self)
self._endNode = Node(self)
@property
def instances(self): return self._instances
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)

74
process/execution.py Normal file
View file

@ -0,0 +1,74 @@
#
# 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
#
"""
Execution of a process.
$Id$
"""
from zope.interface import implements
from cybertools.process.interfaces import IExecution, IProcessInstance
class Execution(object):
implements(IExecution)
def __init__(self, instance):
self._instance = instance
self._currentNode = None
self._workItem = None
@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 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 process(self): return self._process
@property
def executions(self): return self._executions
def execute(self):
execution = Execution(self)
self.executions.add(execution)
self.process.startNode.execute(execution)
return execution

137
process/interfaces.py Normal file
View file

@ -0,0 +1,137 @@
#
# 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
#
"""
Interfaces for process management.
$Id$
"""
from zope.interface import Interface, Attribute
from zope import schema
from zope.i18nmessageid import MessageFactory
_ = MessageFactory('zope')
# process/workflow definitions
class INode(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')
handlerName = Attribute('Name of an adapter that may handle the '
'execution of this node')
def addTransition(destination):
""" Append a transition to the destination node given
to the collection of outgoing transitions.
"""
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.
"""
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):
""" 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')
class IActionHandler(Interface):
""" Will be called for handling process executions. Is typically
implemented as an adapter for INode.
"""
def handle(execution):
""" Handles the execution of a node in the execution context given.
"""
# process execution
class IExecution(Interface):
""" An execution context signifying the current state (or one of the
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')
workItem = Attribute('The work item the process instance is currently '
'waiting for; None if the current node is not in a '
'waiting state')
def trigger(transitionQualifiers=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.
"""
class IWorkItem(Interface):
""" An instance of an activity from a process definition.
"""
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')

28
process/tests.py Executable file
View file

@ -0,0 +1,28 @@
#! /usr/bin/python
"""
Tests for the 'cybertools.process' package.
$Id$
"""
import unittest, doctest
from zope.testing.doctestunit import DocFileSuite
from cybertools.process.definition import ProcessDefinition
class TestProcess(unittest.TestCase):
"Basic tests for the process package."
def testBasicStuff(self):
p = ProcessDefinition()
def test_suite():
flags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
return unittest.TestSuite((
unittest.makeSuite(TestProcess),
DocFileSuite('README.txt', optionflags=flags),
))
if __name__ == '__main__':
unittest.main(defaultTest='test_suite')

32
process/work.py Normal file
View file

@ -0,0 +1,32 @@
#
# 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)