[Zope3-checkins] SVN: Zope3/trunk/src/zope/wfmc/ Created a
first-cut wfmc workflow engine implementation.
Jim Fulton
jim at zope.com
Wed Dec 22 15:47:06 EST 2004
Log message for revision 28689:
Created a first-cut wfmc workflow engine implementation.
Changed:
A Zope3/trunk/src/zope/wfmc/
A Zope3/trunk/src/zope/wfmc/README.txt
A Zope3/trunk/src/zope/wfmc/__init__.py
A Zope3/trunk/src/zope/wfmc/interfaces.py
A Zope3/trunk/src/zope/wfmc/process.py
A Zope3/trunk/src/zope/wfmc/tests.py
-=-
Added: Zope3/trunk/src/zope/wfmc/README.txt
===================================================================
--- Zope3/trunk/src/zope/wfmc/README.txt 2004-12-22 18:12:54 UTC (rev 28688)
+++ Zope3/trunk/src/zope/wfmc/README.txt 2004-12-22 20:47:04 UTC (rev 28689)
@@ -0,0 +1,876 @@
+Workflow-Management Coalition Workflow Engine
+=============================================
+
+This package provides an implementation of a Workflow-Management
+Coalition (WFMC) workflow engine. The engine is provided as a
+collection of workflow process components. Workflow processes can be
+defined in Python or via the XML Process-Definition Language, XPDL.
+
+In this document, we'll look at Python-defined process definitions:
+
+ >>> from zope.wfmc import process
+ >>> pd = process.ProcessDefinition('sample')
+
+The argument to the process is a process id.
+
+A process has a number of parts. Let's look at a sample review
+process::
+
+ -----------
+ -->| Publish |
+ ---------- ---------- / -----------
+ | Author |-->| Review |- ----------
+ ---------- ---------- \-->| Reject |
+ ----------
+
+Here we have a single start activity and 2 end activities. We could
+have modeled this with a single end activity, but that is not
+required. A single start activity *is* required. A process definition
+has a set of activities, with transitions between them. Let's define
+the activities for our process definition:
+
+ >>> pd.defineActivities(
+ ... author = process.ActivityDefinition(),
+ ... review = process.ActivityDefinition(),
+ ... publish = process.ActivityDefinition(),
+ ... reject = process.ActivityDefinition(),
+ ... )
+
+We supply activities as keyword arguments. The argument names provide
+activity ids that we'll use when defining transitions:
+
+ >>> pd.defineTransitions(
+ ... process.TransitionDefinition('author', 'review'),
+ ... process.TransitionDefinition('review', 'publish'),
+ ... process.TransitionDefinition('review', 'reject'),
+ ... )
+
+Each transition is constructed with an identifier for a starting
+activity, and an identifier for an ending activity.
+
+Before we can use a workflow definition, we have to register it as a
+utility. This is necessary so that process instances can find their
+definitions. In addition, the utility name must match the process id:
+
+ >>> import zope.component
+ >>> zope.component.provideUtility(pd, name=pd.id)
+
+Now, with this definition, we can execute our workflow. We haven't
+defined any work yet, but we can see the workflow execute. We'll see
+the workflow executing by registering a subscriber that logs workflow
+events:
+
+ >>> def log_workflow(event):
+ ... print event
+
+ >>> import zope.event
+ >>> zope.event.subscribers.append(log_workflow)
+
+To use the workflow definition, we need to create an instance:
+
+ >>> proc = pd()
+
+Now, if we start the workflow:
+
+ >>> proc.start()
+ ProcessStarted(Process('sample'))
+ Transition(None, Activity('sample.author'))
+ ActivityStarted(Activity('sample.author'))
+ ActivityFinished(Activity('sample.author'))
+ Transition(Activity('sample.author'), Activity('sample.review'))
+ ActivityStarted(Activity('sample.review'))
+ ActivityFinished(Activity('sample.review'))
+ Transition(Activity('sample.review'), Activity('sample.publish'))
+ ActivityStarted(Activity('sample.publish'))
+ ActivityFinished(Activity('sample.publish'))
+ ProcessFinished(Process('sample'))
+
+we see that we transition immediately into the author activity, then
+into review and publish. Normally, we'd need to do some work in each
+activity, and transitions would continue only after work had been
+done, however, in this case, we didn't define any work, so each
+activity completed immediately.
+
+Note that we didn't transition into the rejected activity. By
+default, when an activity is completed, the first transition for which
+it's condition evaluates to `True` is used. By default, transitions
+have boolean conditions [1]_ that evaluate to `True`, so the transition
+to `publish` is used because it was defined before the transition to
+`reject`. What we want is to transition to `publish` if a reviewer
+approves the content for publication, but to `reject` if the reviewer
+rejects the content for publication. We can use a condition for this:
+
+ >>> pd = process.ProcessDefinition('sample')
+ >>> zope.component.provideUtility(pd, name=pd.id)
+ >>> pd.defineActivities(
+ ... author = process.ActivityDefinition(),
+ ... review = process.ActivityDefinition(),
+ ... publish = process.ActivityDefinition(),
+ ... reject = process.ActivityDefinition(),
+ ... )
+ >>> pd.defineTransitions(
+ ... process.TransitionDefinition('author', 'review'),
+ ... process.TransitionDefinition(
+ ... 'review', 'publish', condition=lambda data: data.publish),
+ ... process.TransitionDefinition('review', 'reject'),
+ ... )
+
+We redefined the workflow process, specifying a condition for the
+transition to `publish`. Boolean conditions are just callable objects that
+take a data object and return a boolean value. The data object is
+called "workflow-relevant data". A process instance has a data object
+containing this data. In the example, above, the condition simply
+returned the value of the `publish` attribute. How does this attribute
+get set? It needs to be set by the review activity. Do to that, we
+need to arrange for the activity to set the data. This brings us to
+applications.
+
+Process definitions are meant to be used with different
+applications. For this reason, process definitions don't include
+application logic. What they do include is a specifications of the
+applications to be invoked and the flow of work-flow-relevant data to
+and from the application. Now, we can define our applications:
+
+ >>> pd.defineApplications(
+ ... author = process.Application(),
+ ... review = process.Application(
+ ... process.OutputParameter('publish')),
+ ... publish = process.Application(),
+ ... reject = process.Application(),
+ ... )
+
+We used the same names for the applications that we used for our
+activities. This isn't required, but is a common practice. Note that
+the `review` application includes a specification of an output
+parameter. Now that we've defined our applications, we need to modify
+our activites to use them:
+
+ >>> pd.activities['author'].addApplication('author')
+ >>> pd.activities['review'].addApplication('review',
+ ... process.OutputParameter('publish'))
+ >>> pd.activities['publish'].addApplication('publish')
+ >>> pd.activities['reject'].addApplication('reject')
+
+An activity can use many applications, so we call `addApplication`. In
+the application definition for the 'review' application, we again
+specified an output arguments definition. Why do we specify the output
+parameter for both the application definition and the application
+assignment? The parameters in the application definition are "formal
+paramerters". The paramerters in the application assignment are
+"actual parameters". Parameters are posistional. The names (but not
+the direction) are allowed to differ. The names of the actual
+parameters are used to access or update workflow-relevant data. In
+this example, the output parameter, will be used to add a `publish`
+attribute to the workflow relevant data.
+
+We've declared some applications, and we've wired them up to
+activitis, but we still haven't specified any application code. Before
+we can specify application code, we need to consider who will be
+performing the application. Workflow applications are normally
+executed by people, or other external actors. As with applications,
+process definitions allow participants in the workflow to be declared
+and identified with activities. We declare participants much as we
+declare applications, except without parameters:
+
+ >>> pd.defineParticipants(
+ ... author = process.Participant(),
+ ... reviewer = process.Participant(),
+ ... )
+
+In this case, we happened to reuse an activity name for one, but
+not both of the participants. Having defined these participants, we
+can associate them with activities:
+
+ >>> pd.activities['author'].definePerformer('author')
+ >>> pd.activities['review'].definePerformer('reviewer')
+
+For each of the participants provided, we need to provide a named
+adapter from an activity instance
+(`zope.wfmc.interfaces.IActivity`) to
+`zope.wfmc.interfaces.IParticipant`. When a process needs to get a
+participant, it adapts the activity instance to `IParticipant` using a
+qualified name, consisting of the process-definition identifier, a
+dot, and the performer's participant identifier. If an adapter can't
+be found with that name, it tries again using just a dot followed by
+the participant identifier. This way, participant adapters can be
+shared accross multiple process definitions, or provided for a
+specific definition. If an activity doesn't have a performer, then
+procedure above is used with an empty participant id. We first look
+for an adapter with a name consisting of the process id followed by a
+dot, and then we look for an adapter with a single dot as it's name.
+
+Application implementations are provided as named adapters from
+participants to `zope.wfmc.interfaces.IWorkItem`. As when looking up
+applications, we first look for an adapter with a name consisting of
+the process-definition id, a dot, and the application id. If that
+fails, we look for an adapter consisting of a dot followed by the
+application id. Workflow items provide a `start` method, which is
+used to start the work and pass input arguments. It is the
+responsibility of the work item, at some later time, to call the
+`workitemFinished` method on the activity, to notify the activity that
+the work item was completed. Output parameters are passed to the
+`workitemFinished` method.
+
+Let's implement participants for our process. We'll start with a
+generic participant:
+
+ >>> import zope.interface
+ >>> from zope.wfmc import interfaces
+
+ >>> class Participant(object):
+ ... zope.component.adapts(interfaces.IActivity)
+ ... zope.interface.implements(interfaces.IParticipant)
+ ...
+ ... def __init__(self, activity):
+ ... self.activity = activity
+
+ >>> zope.component.provideAdapter(Participant, name=".author")
+ >>> zope.component.provideAdapter(Participant, name=".reviewer")
+ >>> zope.component.provideAdapter(Participant, name=".")
+
+And finally, we can define adapters that implement our application:
+
+ >>> work_list = []
+
+ >>> class ApplicationBase:
+ ... zope.component.adapts(interfaces.IParticipant)
+ ... zope.interface.implements(interfaces.IWorkItem)
+ ...
+ ... def __init__(self, participant):
+ ... self.participant = participant
+ ... work_list.append(self)
+ ...
+ ... def start(self):
+ ... pass
+ ...
+ ... def finish(self):
+ ... self.participant.activity.workItemFinished(self)
+
+ >>> class Author(ApplicationBase):
+ ... pass
+
+ >>> zope.component.provideAdapter(Author, name=".author")
+
+ >>> class Review(ApplicationBase):
+ ... def finish(self, publish):
+ ... self.participant.activity.workItemFinished(self, publish)
+
+ >>> zope.component.provideAdapter(Review, name=".review")
+
+ >>> class Publish(ApplicationBase):
+ ... def start(self):
+ ... print "Published"
+ ... self.finish()
+
+ >>> zope.component.provideAdapter(Publish, name=".publish")
+
+ >>> class Reject(ApplicationBase):
+ ... def start(self):
+ ... print "Rejected"
+ ... self.finish()
+
+ >>> zope.component.provideAdapter(Reject, name=".reject")
+
+Now, when we instantiate and start our workflow:
+
+ >>> proc = pd()
+ >>> proc.start()
+ ... # doctest: +NORMALIZE_WHITESPACE
+ ProcessStarted(Process('sample'))
+ Transition(None, Activity('sample.author'))
+ ActivityStarted(Activity('sample.author'))
+
+We transition into the author activity and wait for work to get done.
+To move forward, we need to get at the authoring work item, so we can
+finish it. Our work items add themselves to a work list, so we can
+get the item from the list.
+
+ >>> item = work_list.pop()
+
+Now we can finish the work item, by calling it's finish method:
+
+ >>> item.finish()
+ WorkItemFinished('author')
+ ActivityFinished(Activity('sample.author'))
+ Transition(Activity('sample.author'), Activity('sample.review'))
+ ActivityStarted(Activity('sample.review'))
+
+We see that we transitioned to the review activity. Note that the
+`finish` method isn't a part of the workflow APIs. It was defined by
+our sample classes. Other applications could use fifferent mechanisms.
+
+Now, we'll finish the review process by calling the review work item's
+`finish`. We'll pass `False`, indicating that the content should not
+be published:
+
+ >>> work_list.pop().finish(False)
+ WorkItemFinished('review')
+ ActivityFinished(Activity('sample.review'))
+ Transition(Activity('sample.review'), Activity('sample.reject'))
+ ActivityStarted(Activity('sample.reject'))
+ Rejected
+ WorkItemFinished('reject')
+ ActivityFinished(Activity('sample.reject'))
+ ProcessFinished(Process('sample'))
+
+Lets look at a more complex example. In this example, we'll extend
+the process to work with multiple reviewers. We'll also make the
+work-list handling a bit more sophisticated. We'll also introduce
+some new concepts:
+
+- splits and joins
+
+- process arguments
+
+Consider the publication
+process shown below::
+
+
+ Author: Tech Tech Editorial
+ Reviewer 1: Reviewer 2: Reviewer:
+ =========== =========== =========== ==============
+ ---------
+ ----------------------------------------------------| Start |
+ / ---------
+ |
+ V
+ -----------
+ | Prepare |<------------------------------\
+ ----------- \
+ | ------------ \
+ | | Tech |--------------- \ \
+ |------->| Review 1 | V |
+ | ------------ ---------- -------------
+ \ | Tech | | Editorial | ----------
+ ------------------->| Review |--->| Review |-->| Reject |
+ | 2 | ------------- ----------
+ ---------- | |
+ ----------- / \
+ | Prepare | / \--------\
+ | Final |<----------------------------/ |
+ ----------- |
+ ^ | ---------- V
+ | \------------------------------->| Review | -----------
+ \ | Final |----->| Publish |
+ ------------------------------------| | -----------
+ ----------
+
+Here we've arranged the process diagram into columns, with the
+activities for each participant. We have four participants, the
+author, twp technical reviewers, and an editorial reviewer. The
+author prepares a draft. The author sends the draft to *both*
+technical reviewers for review. When the technical reviews have
+completed, the editorial review does an initial editorial
+review. Based on the technical reviews, the editor may choose to:
+
+- Reject the document
+
+- Publish the document as is
+
+- Request technical changes (based on the technical reviewers'
+ commonents), or
+
+- Request editorial changes.
+
+If technical changes are required, the work flows back to the
+"Prepare" activity. If editorial changes are necessary, then work
+flows to the "Prepare Final" activity. When the author has made the
+editorial changes, work flows to "Review Final". The editor may
+request additional changes, in which case, work flows back to "Prepare
+Final", otherwise, the work flows to "Publish".
+
+This example illustrates different kinds of "joins" and "splits". The
+term "join" refers to the way incoming transitions to an activity are
+handled. There are two kinds of joins: "and" and "xor". With an "and"
+join, the activity waits for each of the incoming transitions. In
+this example, the inputs to the "Editorial Review" activity form an
+"and" join. Editorial review waits until each of the technical
+reviews are completed. The rest of the joins in this example are
+"xor" joins. The activity starts on any transition into the activity.
+
+The term "split" refers to way outgoing transitions from an activity
+are handled. Normally, exactly one transition out of an activity is
+used. This is called an "xor" split. With an "and" split, all
+transitions with boolean conditions that evaluate to `True` are used.
+In this example, the "Prepare" activity has an "and" split. Work
+flows simultaneously to the two technical review activities. The rest
+of the splits in this example are "xor" splits.
+
+Lets create our new workflow process:
+
+ >>> Publication = process.ProcessDefinition('Publication')
+ >>> zope.component.provideUtility(Publication, name=Publication.id)
+
+ >>> Publication.defineActivities(
+ ... start = process.ActivityDefinition("Start"),
+ ... prepare = process.ActivityDefinition("Prepare"),
+ ... tech1 = process.ActivityDefinition("Technical Review 1"),
+ ... tech2 = process.ActivityDefinition("Technical Review 2"),
+ ... review = process.ActivityDefinition("Editorial Review"),
+ ... final = process.ActivityDefinition("Final Preparation"),
+ ... rfinal = process.ActivityDefinition("Review Final"),
+ ... publish = process.ActivityDefinition("Publish"),
+ ... reject = process.ActivityDefinition("Reject"),
+ ... )
+
+Here, we've passed strings to the activity definitions providing
+names. Names must be either unicode or ASCII strings.
+
+We define our transitions:
+
+ >>> Publication.defineTransitions(
+ ... process.TransitionDefinition('start', 'prepare'),
+ ... process.TransitionDefinition('prepare', 'tech1'),
+ ... process.TransitionDefinition('prepare', 'tech2'),
+ ... process.TransitionDefinition('tech1', 'review'),
+ ... process.TransitionDefinition('tech2', 'review'),
+ ...
+ ... process.TransitionDefinition(
+ ... 'review', 'reject',
+ ... condition=lambda data: not data.publish
+ ... ),
+ ... process.TransitionDefinition(
+ ... 'review', 'prepare',
+ ... condition=lambda data: data.tech_changes
+ ... ),
+ ... process.TransitionDefinition(
+ ... 'review', 'final',
+ ... condition=lambda data: data.ed_changes
+ ... ),
+ ... process.TransitionDefinition('review', 'publish'),
+ ...
+ ... process.TransitionDefinition('final', 'rfinal'),
+ ... process.TransitionDefinition(
+ ... 'rfinal', 'final',
+ ... condition=lambda data: data.ed_changes
+ ... ),
+ ... process.TransitionDefinition('rfinal', 'publish'),
+ ... )
+
+We specify our "and" split and join:
+
+ >>> Publication.activities['prepare'].andSplit(True)
+ >>> Publication.activities['review'].andJoin(True)
+
+We define our participants and applications:
+
+ >>> Publication.defineParticipants(
+ ... author = process.Participant("Author"),
+ ... tech1 = process.Participant("Technical Reviewer 1"),
+ ... tech2 = process.Participant("Technical Reviewer 2"),
+ ... reviewer = process.Participant("Editorial Reviewer"),
+ ... )
+
+ >>> Publication.defineApplications(
+ ... prepare = process.Application(),
+ ... tech_review = process.Application(
+ ... process.OutputParameter('publish'),
+ ... process.OutputParameter('tech_changes'),
+ ... ),
+ ... ed_review = process.Application(
+ ... process.InputParameter('publish1'),
+ ... process.InputParameter('tech_changes1'),
+ ... process.InputParameter('publish2'),
+ ... process.InputParameter('tech_changes2'),
+ ... process.OutputParameter('publish'),
+ ... process.OutputParameter('tech_changes'),
+ ... process.OutputParameter('ed_changes'),
+ ... ),
+ ... publish = process.Application(),
+ ... reject = process.Application(),
+ ... final = process.Application(),
+ ... rfinal = process.Application(
+ ... process.OutputParameter('ed_changes'),
+ ... ),
+ ... )
+
+ >>> Publication.activities['prepare'].definePerformer('author')
+ >>> Publication.activities['prepare'].addApplication('prepare')
+
+ >>> Publication.activities['tech1'].definePerformer('tech1')
+ >>> Publication.activities['tech1'].addApplication(
+ ... 'tech_review',
+ ... process.OutputParameter('publish1'),
+ ... process.OutputParameter('tech_changes1'),
+ ... )
+
+ >>> Publication.activities['tech2'].definePerformer('tech2')
+ >>> Publication.activities['tech2'].addApplication(
+ ... 'tech_review',
+ ... process.OutputParameter('publish2'),
+ ... process.OutputParameter('tech_changes2'),
+ ... )
+
+ >>> Publication.activities['review'].definePerformer('reviewer')
+ >>> Publication.activities['review'].addApplication(
+ ... 'ed_review',
+ ... process.InputParameter('publish1'),
+ ... process.InputParameter('tech_changes1'),
+ ... process.InputParameter('publish2'),
+ ... process.InputParameter('tech_changes2'),
+ ... process.OutputParameter('publish'),
+ ... process.OutputParameter('tech_changes'),
+ ... process.OutputParameter('ed_changes'),
+ ... )
+
+ >>> Publication.activities['final'].definePerformer('author')
+ >>> Publication.activities['final'].addApplication('final')
+
+ >>> Publication.activities['rfinal'].definePerformer('reviewer')
+ >>> Publication.activities['rfinal'].addApplication(
+ ... 'rfinal',
+ ... process.OutputParameter('ed_changes'),
+ ... )
+
+ >>> Publication.activities['publish'].addApplication('publish')
+ >>> Publication.activities['reject'].addApplication('reject')
+
+We want to be able to specify an author when we start the process.
+We'd also like to be told the final disposition of the process. To
+accomplish this, we'll define parameters for our process:
+
+ >>> Publication.defineParameters(
+ ... process.InputParameter('author'),
+ ... process.OutputParameter('publish'),
+ ... )
+
+Now that we've defined the process, we need to provide participant and
+application components. Let's start with our participants. Rather
+than sharing a single work list, we'll give each user their own
+work list. We'll also create preexisting participants and return
+them. Finally, we'll create multiple authors and use the selected one:
+
+
+ >>> class User:
+ ... def __init__(self):
+ ... self.work_list = []
+
+ >>> authors = {'bob': User(), 'ted': User(), 'sally': User()}
+
+ >>> reviewer = User()
+ >>> tech1 = User()
+ >>> tech2 = User()
+
+ >>> class Author(Participant):
+ ... def __init__(self, activity):
+ ... Participant.__init__(self, activity)
+ ... author_name = activity.process.workflowRelevantData.author
+ ... self.user = authors[author_name]
+
+ >>> zope.component.provideAdapter(Author, name="Publication.author")
+
+When the process is created, the author name will be passed in and
+assigned to the workflow-relevent data. Our author class uses this
+information to select the named user.
+
+ >>> class Reviewer(Participant):
+ ... user = reviewer
+ >>> zope.component.provideAdapter(Reviewer, name="Publication.reviewer")
+
+ >>> class Tech1(Participant):
+ ... user = tech1
+ >>> zope.component.provideAdapter(Tech1, name="Publication.tech1")
+
+ >>> class Tech2(Participant):
+ ... user = tech2
+ >>> zope.component.provideAdapter(Tech2, name="Publication.tech2")
+
+Now we'll create our applications. Let's start with our author:
+
+ >>> class ApplicationBase(object):
+ ... zope.component.adapts(interfaces.IParticipant)
+ ... zope.interface.implements(interfaces.IWorkItem)
+ ...
+ ... def __init__(self, participant):
+ ... self.participant = participant
+ ... self.activity = participant.activity
+ ... participant.user.work_list.append(self)
+ ...
+ ... def start(self):
+ ... pass
+ ...
+ ... def finish(self):
+ ... self.participant.activity.workItemFinished(self)
+
+ >>> class Prepare(ApplicationBase):
+ ...
+ ... def summary(self):
+ ... process = self.activity.process
+ ... doc = getattr(process.applicationRelevantData, 'doc', '')
+ ... if doc:
+ ... print 'Previous draft:'
+ ... print doc
+ ... print 'Changed we need to make:'
+ ... for change in process.workflowRelevantData.tech_changes:
+ ... print change
+ ... else:
+ ... print 'Please write the initial draft'
+ ...
+ ... def finish(self, doc):
+ ... self.activity.process.applicationRelevantData.doc = doc
+ ... super(Prepare, self).finish()
+
+ >>> zope.component.provideAdapter(Prepare, name="Publication.prepare")
+
+Since we used the prepare application for revisions as well as initial
+preparation, we provide a summary method to show us what we have to do.
+
+Here we get the document created by the author passed in as an
+argument to the finish method. In a more realistic implementation,
+the author task would create the document at the start of the task and
+provide a user inyerface for the user to edit it. We store the
+document as application-relevant data, since we'll want reviewers to
+be able to access it, but we don't need it directly for workflow
+control.
+
+ >>> class TechReview(ApplicationBase):
+ ...
+ ... def getDoc(self):
+ ... return self.activity.process.applicationRelevantData.doc
+ ...
+ ... def finish(self, decision, changes):
+ ... self.activity.workItemFinished(self, decision, changes)
+
+ >>> zope.component.provideAdapter(TechReview,
+ ... name="Publication.tech_review")
+
+Here, we provided a method to access the original document.
+
+ >>> class Review(TechReview):
+ ...
+ ... def start(self, publish1, changes1, publish2, changes2):
+ ... if not (publish1 and publish2):
+ ... # Reject if either tech reviewer rejects
+ ... self.activity.workItemFinished(
+ ... self, False, changes1 + changes2, ())
+ ...
+ ... if changes1 or changes2:
+ ... # we won't do anyting if there are tech changes
+ ... self.activity.workItemFinished(
+ ... self, True, changes1 + changes2, ())
+ ...
+ ... def finish(self, ed_changes):
+ ... self.activity.workItemFinished(self, True, (), ed_changes)
+
+ >>> zope.component.provideAdapter(Review, name="Publication.ed_review")
+
+In this implementation, we decided to reject outright if either
+technical editor recommended rejection and to send work back to
+preparation if there are any technical changes. We also subclassed
+`TechEdit` to get the `getDoc` method.
+
+We'll reuse the `publish` and `reject` application from the previous
+example.
+
+ >>> class Final(ApplicationBase):
+ ...
+ ... def summary(self):
+ ... process = self.activity.process
+ ... doc = getattr(process.applicationRelevantData, 'doc', '')
+ ... print 'Previous draft:'
+ ... print self.activity.process.applicationRelevantData.doc
+ ... print 'Changed we need to make:'
+ ... for change in process.workflowRelevantData.ed_changes:
+ ... print change
+ ...
+ ... def finish(self, doc):
+ ... self.activity.process.applicationRelevantData.doc = doc
+ ... super(Final, self).finish()
+
+ >>> zope.component.provideAdapter(Final, name="Publication.final")
+
+In our this application, we simply update the document to reflect
+changes.
+
+ >>> class ReviewFinal(TechReview):
+ ...
+ ... def finish(self, ed_changes):
+ ... self.activity.workItemFinished(self, ed_changes)
+
+ >>> zope.component.provideAdapter(ReviewFinal, name="Publication.rfinal")
+
+Our process now returns data. When we create a process, we need to
+supply an object that it can call back to:
+
+ >>> class PublicationContext:
+ ... zope.interface.implements(interfaces.IProcessContext)
+ ...
+ ... def processFinished(self, process, decision):
+ ... self.decision = decision
+
+Now, let's try out our process:
+
+ >>> context = PublicationContext()
+ >>> proc = Publication(context)
+ >>> proc.start('bob')
+ ProcessStarted(Process('Publication'))
+ Transition(None, Activity('Publication.start'))
+ ActivityStarted(Activity('Publication.start'))
+ ActivityFinished(Activity('Publication.start'))
+ Transition(Activity('Publication.start'), Activity('Publication.prepare'))
+ ActivityStarted(Activity('Publication.prepare'))
+
+We should have added an item to bob's work list. Let's get it and
+finish it, submitting a document:
+
+ >>> item = authors['bob'].work_list.pop()
+ >>> item.finish("I give my pledge, as an American\n"
+ ... "to save, and faithfully to defend from waste\n"
+ ... "the natural resources of my Country.")
+ WorkItemFinished('prepare')
+ ActivityFinished(Activity('Publication.prepare'))
+ Transition(Activity('Publication.prepare'), Activity('Publication.tech1'))
+ ActivityStarted(Activity('Publication.tech1'))
+ Transition(Activity('Publication.prepare'), Activity('Publication.tech2'))
+ ActivityStarted(Activity('Publication.tech2'))
+
+Notice that we transitioned to *two* activities, `tech1' and
+`tech2'. This is because the prepare activity has an "and" split.
+Now we'll do a tech review. Let's see what tech1 has:
+
+ >>> item = tech1.work_list.pop()
+ >>> print item.getDoc()
+ I give my pledge, as an American
+ to save, and faithfully to defend from waste
+ the natural resources of my Country.
+
+Let's tell the author to change "dogs" to "men":
+
+ >>> item.finish(True, ['Change "American" to "human"'])
+ WorkItemFinished('tech_review')
+ ActivityFinished(Activity('Publication.tech1'))
+ Transition(Activity('Publication.tech1'), Activity('Publication.review'))
+
+Here we transitioned to the editorial review activity, but we didn't
+start it. This is because the editorial review activity has an "and"
+join, meaning that it won't start until both transitions have
+occurred.
+
+Now we'll do the other tectnical review:
+
+ >>> item = tech2.work_list.pop()
+ >>> item.finish(True, ['Change "Country" to "planet"'])
+ WorkItemFinished('tech_review')
+ ActivityFinished(Activity('Publication.tech2'))
+ Transition(Activity('Publication.tech2'), Activity('Publication.review'))
+ ActivityStarted(Activity('Publication.review'))
+ WorkItemFinished('ed_review')
+ ActivityFinished(Activity('Publication.review'))
+ Transition(Activity('Publication.review'), Activity('Publication.prepare'))
+ ActivityStarted(Activity('Publication.prepare'))
+
+Now when we transitioned to the editorial review activity, we started
+it, because each of the input transitions had happened. Our editorial
+review application automatically sent the work back to preparation,
+because there were technical commments. Let's address the comments:
+
+ >>> item = authors['bob'].work_list.pop()
+ >>> item.summary()
+ Previous draft:
+ I give my pledge, as an American
+ to save, and faithfully to defend from waste
+ the natural resources of my Country.
+ Changed we need to make:
+ Change "American" to "human"
+ Change "Country" to "planet"
+
+ >>> item.finish("I give my pledge, as an human\n"
+ ... "to save, and faithfully to defend from waste\n"
+ ... "the natural resources of my planet.")
+ WorkItemFinished('prepare')
+ ActivityFinished(Activity('Publication.prepare'))
+ Transition(Activity('Publication.prepare'), Activity('Publication.tech1'))
+ ActivityStarted(Activity('Publication.tech1'))
+ Transition(Activity('Publication.prepare'), Activity('Publication.tech2'))
+ ActivityStarted(Activity('Publication.tech2'))
+
+As before, after completing the initial edits, we start the technical
+review activities again. We'll review it again. This time, we have no
+comments, because the author applied our requested changes:
+
+ >>> item = tech1.work_list.pop()
+ >>> item.finish(True, [])
+ WorkItemFinished('tech_review')
+ ActivityFinished(Activity('Publication.tech1'))
+ Transition(Activity('Publication.tech1'), Activity('Publication.review'))
+
+ >>> item = tech2.work_list.pop()
+ >>> item.finish(True, [])
+ WorkItemFinished('tech_review')
+ ActivityFinished(Activity('Publication.tech2'))
+ Transition(Activity('Publication.tech2'), Activity('Publication.review'))
+ ActivityStarted(Activity('Publication.review'))
+
+This time, we are left in the technical review activity because there
+weren't any technical changes. We're ready to do our editorial review.
+Whe'll request an editorial change:
+
+ >>> item = reviewer.work_list.pop()
+ >>> print item.getDoc()
+ I give my pledge, as an human
+ to save, and faithfully to defend from waste
+ the natural resources of my planet.
+
+ >>> item.finish(['change "an" to "a"'])
+ WorkItemFinished('ed_review')
+ ActivityFinished(Activity('Publication.review'))
+ Transition(Activity('Publication.review'), Activity('Publication.final'))
+ ActivityStarted(Activity('Publication.final'))
+
+Because we requested editorial changes, we transitioned to the final
+editing activity, so that the author can make the changes:
+
+ >>> item = authors['bob'].work_list.pop()
+ >>> item.summary()
+ Previous draft:
+ I give my pledge, as an human
+ to save, and faithfully to defend from waste
+ the natural resources of my planet.
+ Changed we need to make:
+ change "an" to "a"
+
+ >>> item.finish("I give my pledge, as a human\n"
+ ... "to save, and faithfully to defend from waste\n"
+ ... "the natural resources of my planet.")
+ WorkItemFinished('final')
+ ActivityFinished(Activity('Publication.final'))
+ Transition(Activity('Publication.final'), Activity('Publication.rfinal'))
+ ActivityStarted(Activity('Publication.rfinal'))
+
+We transition to the activity for reviewing the final edits. We
+review the document and approve it for publication:
+
+ >>> item = reviewer.work_list.pop()
+ >>> print item.getDoc()
+ I give my pledge, as a human
+ to save, and faithfully to defend from waste
+ the natural resources of my planet.
+
+ >>> item.finish([])
+ WorkItemFinished('rfinal')
+ ActivityFinished(Activity('Publication.rfinal'))
+ Transition(Activity('Publication.rfinal'), Activity('Publication.publish'))
+ ActivityStarted(Activity('Publication.publish'))
+ Published
+ WorkItemFinished('publish')
+ ActivityFinished(Activity('Publication.publish'))
+ ProcessFinished(Process('Publication'))
+
+At this point, the rest of the process finished automatically. In
+addition, the decision was recorded in the process context object:
+
+ >>> context.decision
+ True
+
+Comming Soon
+------------
+
+- XPDF support
+
+- Timeouts/exceptions
+
+- "otherwise" conditions
+
+
+.. [1] There are other kinds of conditions, namely "otherwise" and
+ "exception" conditions.
Property changes on: Zope3/trunk/src/zope/wfmc/README.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: Zope3/trunk/src/zope/wfmc/__init__.py
===================================================================
--- Zope3/trunk/src/zope/wfmc/__init__.py 2004-12-22 18:12:54 UTC (rev 28688)
+++ Zope3/trunk/src/zope/wfmc/__init__.py 2004-12-22 20:47:04 UTC (rev 28689)
@@ -0,0 +1 @@
+#
Property changes on: Zope3/trunk/src/zope/wfmc/__init__.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: Zope3/trunk/src/zope/wfmc/interfaces.py
===================================================================
--- Zope3/trunk/src/zope/wfmc/interfaces.py 2004-12-22 18:12:54 UTC (rev 28688)
+++ Zope3/trunk/src/zope/wfmc/interfaces.py 2004-12-22 20:47:04 UTC (rev 28689)
@@ -0,0 +1,195 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Workflow-integration interfaces
+
+$Id$
+"""
+
+import zope.interface
+
+class IProcessDefinition(zope.interface.Interface):
+ """Process definition
+ """
+
+ id = zope.interface.Attribute("Process-definition identifier")
+
+ def defineActivities(**activities):
+ """Add activity definitions to the collection of defined activities
+
+ Activity definitions are supplied as keyword arguments. The
+ keywords provide activity identifiers. The values are
+ IActivityDefinition objects.
+
+ """
+
+ def defineTransitions(*transitions):
+ """Add transition definitions
+
+ The transitions are ITransition objects.
+ """
+
+ def defineParticipants(**participants):
+ """Declare participants
+
+ The participants are provided as keyword arguments.
+ Participant identifiers are supplied as the keywords and the
+ definitions are supplied as values. The definitions are
+ IParticipantDefinition objects.
+ """
+
+ def defineApplications(**applications):
+ """Declare applications
+
+ The applications are provided as keyword arguments.
+ Application identifiers are supplied as the keywords and the
+ definitions are supplied as values. The definitions are
+ IApplicationDefinition objects.
+ """
+
+ def defineParameters(*parameters):
+ """Declate process parameters
+
+ Input parameters are set as workflow-relevent data. Output
+ parameters are passed from workflow-relevant data to the
+ processFinished method of process-instances process contexts.
+
+ """
+
+class IActivityDefinition(zope.interface.Interface):
+ """Activity definition
+ """
+
+ id = zope.interface.Attribute("Activity identifier")
+
+ def addApplication(id, *parameters):
+ """Declare that the activity uses the identified activity
+
+ The application identifier must match an application declared
+ for the process.
+
+ Parameter definitions can be given as positional arguments.
+ The parameter definition directions must match those given in
+ the application definition.
+ """
+
+ def definePerformer(performer):
+ """Set the activity performer
+
+ The argument must be the identifier of a participant defined
+ for the enclosing process.
+ """
+
+ def setAndSplit(setting):
+ """Provide an and-split setting
+
+ If the setting is true, then the activity will use an "and" split.
+ """
+
+ def setAndJoin(setting):
+ """Provide an and-join setting
+
+ If the setting is true, then the activity will use an "and" join.
+ """
+
+class ITransitionDefinition(zope.interface.Interface):
+ """Activity definition
+ """
+
+class IProcess(zope.interface.Interface):
+ """Process instance
+ """
+
+ definition = zope.interface.Attribute("Process definition")
+
+ workflowRelevantData = zope.interface.Attribute(
+ """Workflow-relevant data
+
+ Object with attributes containing data used in conditions and
+ to pass data as parameters between applications
+ """
+ )
+
+ applicationRelevantData = zope.interface.Attribute(
+ """Application-relevant data
+
+ Object with attributes containing data used to pass data as
+ shared data for applications
+
+ """
+ )
+
+class IProcessContext(zope.interface.Interface):
+ """Object that can recieve process results.
+ """
+
+ def processFinished(process, *results):
+ """Recieve notification of process completion, with results
+ """
+
+class IActivity(zope.interface.Interface):
+ """Activity instance
+ """
+
+ id = zope.interface.Attribute(
+ """Activity identifier
+
+ This identifier is set by the process instance
+
+ """)
+
+ definition = zope.interface.Attribute("Activity definition")
+
+ def workItemFinished(work_item, *results):
+ """Notify the activity that the work item has been completed.
+ """
+
+class IApplicationDefinition(zope.interface.Interface):
+ """Application definition
+ """
+
+ parameters = zope.interface.Attribute(
+ "A sequence of parameter definitions")
+
+class IParameterDefinition(zope.interface.Interface):
+ """Parameter definition
+ """
+
+ name = zope.interface.Attribute("Parameter name")
+
+ input = zope.interface.Attribute("Is this an input parameter?")
+
+ output = zope.interface.Attribute("Is this an output parameter?")
+
+class IParticipantDefinition(zope.interface.Interface):
+ """Participant definition
+ """
+
+class IParticipant(zope.interface.Interface):
+ """Workflow participant
+ """
+
+class IWorkItem(zope.interface.Interface):
+ """Work items
+ """
+
+ id = zope.interface.Attribute(
+ """Item identifier
+
+ This identifier is set by the activity instance
+
+ """)
+
+ def start(*arguments):
+ """Start the work
+ """
Property changes on: Zope3/trunk/src/zope/wfmc/interfaces.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: Zope3/trunk/src/zope/wfmc/process.py
===================================================================
--- Zope3/trunk/src/zope/wfmc/process.py 2004-12-22 18:12:54 UTC (rev 28688)
+++ Zope3/trunk/src/zope/wfmc/process.py 2004-12-22 20:47:04 UTC (rev 28689)
@@ -0,0 +1,453 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Processes
+
+$Id$
+"""
+
+import sets
+
+import persistent
+import zope.cachedescriptors.property
+import zope.component
+import zope.event
+import zope.interface
+
+from zope.wfmc import interfaces
+
+class InvalidProcessDefinition(Exception):
+ """A process definition isn't valid in some way.
+ """
+
+class ProcessError(Exception):
+ """An error occured in execution of a process
+ """
+
+class ProcessDefinition:
+
+ zope.interface.implements(interfaces.IProcessDefinition)
+
+ def __init__(self, id):
+ self.id = id
+ self.activities = {}
+ self.transitions = []
+ self.applications = {}
+ self.participants = {}
+ self.parameters = ()
+
+ def defineActivities(self, **activities):
+ self._dirty()
+ for id, activity in activities.items():
+ activity.id = id
+ if activity.__name__ is None:
+ activity.__name__ = self.id + '.' + id
+ activity.process = self
+ self.activities[id] = activity
+
+ def defineTransitions(self, *transitions):
+ self._dirty()
+ self.transitions.extend(transitions)
+
+ def defineApplications(self, **applications):
+ for id, application in applications.items():
+ application.id = id
+ self.applications[id] = application
+
+ def defineParticipants(self, **participants):
+ for id, participant in participants.items():
+ participant.id = id
+ self.participants[id] = participant
+
+ def defineParameters(self, *parameters):
+ self.parameters += parameters
+
+ def _start(self):
+ # Compute activity incoming and outgoing transitions
+ # Return an initial transition
+
+ activities = self.activities
+
+ # Reinitialize activity transitions
+ for activity in activities.values():
+ activity.incoming = activity.outgoing = ()
+
+ # Recompute activity transitions based on transition data:
+ for transition in self.transitions:
+ activities[transition.from_].outgoing += (transition, )
+ activities[transition.to].incoming += (transition, )
+
+ # Find the start, making sure that there is one and that there
+ # aren't any activities w no transitions:
+ start = ()
+ for aid, activity in activities.items():
+ if not activity.incoming:
+ start += ((aid, activity), )
+ if not activity.outgoing:
+ raise InvalidProcessDefinition(
+ "Activity %s has no transitions",
+ aid)
+
+ if len(start) != 1:
+ if start:
+ raise InvalidProcessDefinition("Multiple start activities",
+ [id for (id, a) in start])
+ else:
+ raise InvalidProcessDefinition("No start activities")
+
+ return TransitionDefinition(None, start[0][0])
+
+ _start = zope.cachedescriptors.property.Lazy(_start)
+
+ def __call__(self, context=None):
+ return Process(self, self._start, context)
+
+ def _dirty(self):
+ try:
+ del self._start
+ except AttributeError:
+ pass
+
+class ActivityDefinition:
+
+ zope.interface.implements(interfaces.IActivityDefinition)
+
+ performer = ''
+ process = None
+
+ def __init__(self, __name__=None):
+ self.__name__ = __name__
+ self.incoming = self.outgoing = ()
+ self.applications = ()
+ self.andJoinSetting = self.andSplitSetting = False
+
+ def andSplit(self, setting):
+ self.andSplitSetting = setting
+
+ def andJoin(self, setting):
+ self.andJoinSetting = setting
+
+ def addApplication(self, application, *parameters):
+ formal = self.process.applications[application].parameters
+ if len(formal) != len(parameters):
+ raise TypeError("Wrong number of parameters")
+
+ for formal, parameter in zip(formal, parameters):
+ if (formal.input != parameter.input
+ or formal.output != parameter.output
+ ):
+ raise TypeError("Parameter type missmatch")
+
+ self.applications += ((application, parameters), )
+
+ def definePerformer(self, performer):
+ self.performer = performer
+
+class TransitionDefinition:
+
+ zope.interface.implements(interfaces.ITransitionDefinition)
+
+ def __init__(self, from_, to, condition=lambda data: True):
+ self.from_ = from_
+ self.to = to
+ self.condition = condition
+
+
+class Process(persistent.Persistent):
+
+ zope.interface.implements(interfaces.IProcess)
+
+ def __init__(self, definition, start, context=None):
+ self.process_definition_identifier = definition.id
+ self.startTransition = start
+ self.context = context
+ self.activities = {}
+ self.nextActivityId = 0
+ self.workflowRelevantData = WorkflowData()
+ self.applicationRelevantData = WorkflowData()
+
+ def definition(self):
+ return zope.component.getUtility(
+ interfaces.IProcessDefinition,
+ self.process_definition_identifier,
+ )
+ definition = property(definition)
+
+ def start(self, *arguments):
+ if self.activities:
+ raise TypeError("Already started")
+
+ definition = self.definition
+ data = self.workflowRelevantData
+ args = arguments
+ for parameter in definition.parameters:
+ if parameter.input:
+ arg, args = arguments[0], args[1:]
+ setattr(data, parameter.__name__, arg)
+ if args:
+ raise TypeError("Too many arguments. Expected %s. got %s",
+ len(process_definition.parameter), len(arguments))
+
+ zope.event.notify(ProcessStarted(self))
+ self.transition(None, (self.startTransition, ))
+
+ def _finish(self):
+ if self.context is not None:
+ args = []
+ for parameter in self.definition.parameters:
+ if parameter.output:
+ args.append(
+ getattr(self.workflowRelevantData,
+ parameter.__name__))
+ self.context.processFinished(self, *args)
+
+ zope.event.notify(ProcessFinished(self))
+
+
+ def transition(self, activity, transitions):
+ if transitions:
+ definition = self.definition
+
+ for transition in transitions:
+ activity_definition = definition.activities[transition.to]
+ next = None
+ if activity_definition.andJoinSetting:
+ # If it's an and-join, we want only one.
+ for i, a in self.activities.items():
+ if a.activity_definition_identifier == transition.to:
+ # we already have the activity -- use it
+ next = a
+ break
+
+ if next is None:
+ next = Activity(self, activity_definition)
+ self.nextActivityId += 1
+ next.id = self.nextActivityId
+
+ zope.event.notify(Transition(activity, next))
+ self.activities[next.id] = next
+ next.start(transition)
+
+ if activity is not None:
+ del self.activities[activity.id]
+ if not self.activities:
+ self._finish()
+
+ self._p_changed = True
+
+ def __repr__(self):
+ return "Process(%r)" % self.process_definition_identifier
+
+class WorkflowData(persistent.Persistent):
+ """Container for workflow-relevant and application-relevant data
+ """
+
+class ProcessStarted:
+
+ def __init__(self, process):
+ self.process = process
+
+ def __repr__(self):
+ return "ProcessStarted(%r)" % self.process
+
+class ProcessFinished:
+
+ def __init__(self, process):
+ self.process = process
+
+ def __repr__(self):
+ return "ProcessFinished(%r)" % self.process
+
+
+class Activity(persistent.Persistent):
+
+ zope.interface.implements(interfaces.IActivity)
+
+ def __init__(self, process, definition):
+ self.process = process
+ self.activity_definition_identifier = definition.id
+
+ workitems = {}
+ if definition.applications:
+
+ performer = zope.component.queryAdapter(
+ self, interfaces.IParticipant,
+ process.process_definition_identifier
+ + '.' + definition.performer)
+
+ if performer is None:
+ performer = zope.component.getAdapter(
+ self, interfaces.IParticipant,
+ '.' + definition.performer)
+
+ i = 0
+ for application, parameters in definition.applications:
+ workitem = zope.component.queryAdapter(
+ performer, interfaces.IWorkItem,
+ process.process_definition_identifier + '.' + application)
+ if workitem is None:
+ workitem = zope.component.getAdapter(
+ performer, interfaces.IWorkItem,
+ '.' + application)
+ i += 1
+ workitem.id = i
+ workitems[i] = workitem, application, parameters
+
+ self.workitems = workitems
+
+ def definition(self):
+ return self.process.definition.activities[
+ self.activity_definition_identifier]
+ definition = property(definition)
+
+ incoming = ()
+ def start(self, transition):
+ # Start the activity, if we've had enough incoming transitions
+
+ definition = self.definition
+
+ if definition.andJoinSetting:
+ if transition in self.incoming:
+ raise ProcessError(
+ "Repeated incoming transition while waiting for and "
+ "completion")
+ self.incoming += (transition, )
+
+ if len(self.incoming) < len(definition.incoming):
+ return # not enough incoming yet
+
+ zope.event.notify(ActivityStarted(self))
+
+ if self.workitems:
+ for workitem, application, parameters in self.workitems.values():
+ args = []
+ for parameter in parameters:
+ if parameter.input:
+ args.append(
+ getattr(self.process.workflowRelevantData,
+ parameter.__name__))
+ workitem.start(*args)
+ else:
+ # Since we don't have any work items, we're done
+ self.finish()
+
+ def workItemFinished(self, work_item, *results):
+ unused, application, parameters = self.workitems.pop(work_item.id)
+ self._p_changed = True
+ res = results
+ for parameter in parameters:
+ if parameter.output:
+ v = res[0]
+ res = res[1:]
+ setattr(self.process.workflowRelevantData,
+ parameter.__name__, v)
+
+ if res:
+ raise TypeError("Too many results")
+
+ zope.event.notify(WorkItemFinished(
+ work_item, application, parameters, results))
+
+ if not self.workitems:
+ self.finish()
+
+ def finish(self):
+ zope.event.notify(ActivityFinished(self))
+
+ definition = self.definition
+
+ transitions = []
+ for transition in definition.outgoing:
+ if transition.condition(self.process.workflowRelevantData):
+ transitions.append(transition)
+ if not definition.andSplitSetting:
+ break # xor split, want first one
+
+ self.process.transition(self, transitions)
+
+ def __repr__(self):
+ return "Activity(%r)" % (
+ self.process.process_definition_identifier + '.' +
+ self.activity_definition_identifier
+ )
+
+class WorkItemFinished:
+
+ def __init__(self, workitem, application, parameters, results):
+ self.workitem = workitem
+ self.application = application
+ self.parameters = parameters
+ self.results = results
+
+ def __repr__(self):
+ return "WorkItemFinished(%r)" % self.application
+
+class Transition:
+
+ def __init__(self, from_, to):
+ self.from_ = from_
+ self.to = to
+
+ def __repr__(self):
+ return "Transition(%r, %r)" % (self.from_, self.to)
+
+class ActivityFinished:
+
+ def __init__(self, activity):
+ self.activity = activity
+
+ def __repr__(self):
+ return "ActivityFinished(%r)" % self.activity
+
+class ActivityStarted:
+
+ def __init__(self, activity):
+ self.activity = activity
+
+ def __repr__(self):
+ return "ActivityStarted(%r)" % self.activity
+
+class Parameter:
+
+ zope.interface.implements(interfaces.IParameterDefinition)
+
+ input = output = False
+
+ def __init__(self, name):
+ self.__name__ = name
+
+class OutputParameter(Parameter):
+
+ output = True
+
+class InputParameter(Parameter):
+
+ input = True
+
+class InputOutputParameter(InputParameter, OutputParameter):
+
+ pass
+
+class Application:
+
+ zope.interface.implements(interfaces.IApplicationDefinition)
+
+ def __init__(self, *parameters):
+ self.parameters = parameters
+
+class Participant:
+
+ zope.interface.implements(interfaces.IParticipantDefinition)
+
+ def __init__(self, name=None):
+ self.__name__ = name
Property changes on: Zope3/trunk/src/zope/wfmc/process.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: Zope3/trunk/src/zope/wfmc/tests.py
===================================================================
--- Zope3/trunk/src/zope/wfmc/tests.py 2004-12-22 18:12:54 UTC (rev 28688)
+++ Zope3/trunk/src/zope/wfmc/tests.py 2004-12-22 20:47:04 UTC (rev 28689)
@@ -0,0 +1,36 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Test hookup
+
+$Id$
+"""
+import unittest
+import zope.event
+from zope.component.tests import placelesssetup
+
+def tearDown(test):
+ placelesssetup.tearDown(test)
+ zope.event.subscribers.pop()
+
+def test_suite():
+ from zope.testing import doctest
+ suite = unittest.TestSuite()
+ # suite.addTest(doctest.DocTestSuite())
+ suite.addTest(doctest.DocFileSuite('README.txt', tearDown=tearDown,
+ setUp=placelesssetup.setUp))
+ return suite
+
+if __name__ == '__main__':
+ unittest.main(defaultTest='test_suite')
+
Property changes on: Zope3/trunk/src/zope/wfmc/tests.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
More information about the Zope3-Checkins
mailing list