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:
parent
698df39f4c
commit
9774638a5e
8 changed files with 446 additions and 4 deletions
|
@ -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
54
process/README.txt
Normal 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
4
process/__init__.py
Normal file
|
@ -0,0 +1,4 @@
|
|||
"""
|
||||
$Id$
|
||||
"""
|
||||
|
116
process/definition.py
Normal file
116
process/definition.py
Normal 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
74
process/execution.py
Normal 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
137
process/interfaces.py
Normal 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
28
process/tests.py
Executable 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
32
process/work.py
Normal 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)
|
||||
|
Loading…
Add table
Reference in a new issue