Adapter-based workflow application integration ============================================== This package provides an adapter-based workflow integration component. The component is implemented by the integration module: >>> from zope.wfmc import process, adapter >>> import zope.component To demonstrate how this works, we'll use the simple wfmc example process: >>> pd = process.ProcessDefinition('sample', adapter.integration) >>> 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'), ... ) >>> pd.defineApplications( ... author = process.Application(), ... review = process.Application( ... process.OutputParameter('publish')), ... publish = process.Application(), ... reject = process.Application(), ... ) >>> pd.activities['author'].addApplication('author') >>> pd.activities['review'].addApplication('review', ['publish']) >>> pd.activities['publish'].addApplication('publish') >>> pd.activities['reject'].addApplication('reject') >>> pd.defineParticipants( ... author = process.Participant(), ... reviewer = process.Participant(), ... ) >>> 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 across 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") 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) 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 different 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'))