Shoobx/shoobx.wfmc

View on GitHub
src/shoobx/wfmc/adapter/integration.txt

Summary

Maintainability
Test Coverage
Adapter-based workflow application integration
==============================================

This package provides an adapter-based workflow integration component. The
component is implemented by the integration module:

    >>> from shoobx.wfmc import process, adapter
    >>> import zope.component

To demonstrate how this works, we'll use the simple wfmc example process:

    >>> from shoobx.wfmc.process import StaticProcessDefinitionFactory
    >>> pdfactory = StaticProcessDefinitionFactory()
    >>> zope.component.provideUtility(pdfactory)

    >>> pd = process.ProcessDefinition('sample', adapter.integration)
    >>> pdfactory.register(pd)

    >>> 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 proc: proc.workflowRelevantData.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 (`shoobx.wfmc.interfaces.IActivity`) to
`shoobx.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 `shoobx.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 shoobx.wfmc import interfaces

    >>> @zope.interface.implementer(interfaces.IParticipant)
    ... class Participant(object):
    ...     zope.component.adapts(interfaces.IActivity)
    ... 
    ...     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 = []

    >>> @zope.interface.implementer(interfaces.IWorkItem)
    ... class ApplicationBase:
    ...     zope.component.adapts(interfaces.IParticipant)
    ... 
    ...     def __init__(self, participant):
    ...         self.participant = participant
    ...         work_list.append(self)
    ... 
    ...     def start(self, args):
    ...         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, args):
    ...         print("Published")
    ...         self.finish()

    >>> zope.component.provideAdapter(Publish, name=".publish")

    >>> class Reject(ApplicationBase):
    ...     def start(self, args):
    ...         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'))
    WorkItemStarting('author')
    WorkItemStarted('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'))
    WorkItemStarting('review')
    WorkItemStarted('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'))
    WorkItemStarting('reject')
    Rejected
    WorkItemFinished('reject')
    ActivityFinished(Activity('sample.reject'))
    ProcessFinished(Process('sample'))
    WorkItemStarted('reject')