工作流管理联盟工作流引擎
项目描述
此包提供了一个工作流管理联盟(WFMC)工作流引擎的实现。该引擎提供了一组工作流过程组件。工作流过程可以使用Python或XML过程定义语言(XPDL)定义。
详细文档
工作流管理联盟工作流引擎
此包提供了一个工作流管理联盟(WFMC)工作流引擎的实现。该引擎提供了一组工作流过程组件。工作流过程可以使用Python或XML过程定义语言(XPDL)定义。
在本文档中,我们将查看Python定义的过程定义
>>> from shoobx.wfmc import process >>> pd = process.ProcessDefinition('sample')
过程的参数是过程ID。
一个过程包含多个部分。让我们看看一个示例审查过程
----------- -->| Publish | ---------- ---------- / ----------- | Author |-->| Review |- ---------- ---------- ---------- \-->| Reject | ----------
这里有一个单一的开始活动以及两个结束活动。我们可以使用单个结束活动来建模,但这不是必需的。单一的开始活动是必需的。过程定义包含一系列活动,以及它们之间的转换。让我们为我们的过程定义定义活动
>>> pd.defineActivities( ... author = process.ActivityDefinition(), ... review = process.ActivityDefinition(), ... publish = process.ActivityDefinition(), ... reject = process.ActivityDefinition(), ... )
我们通过关键字参数提供活动。参数名称提供了活动ID,我们在定义转换时将使用这些ID
>>> pd.defineTransitions( ... process.TransitionDefinition('author', 'review'), ... process.TransitionDefinition('review', 'publish'), ... process.TransitionDefinition('review', 'reject'), ... )
每个转换都是通过一个起始活动的标识符和一个结束活动的标识符来构建的。
在使用工作流定义之前,我们必须将其注册为实用工具。这是必要的,以便过程实例可以找到它们的定义。此外,实用工具名称必须与过程ID匹配
>>> import zope.component >>> from shoobx.wfmc.process import StaticProcessDefinitionFactory >>> pdfactory = StaticProcessDefinitionFactory() >>> zope.component.provideUtility(pdfactory) >>> pdfactory.register(pd)
现在,有了这个定义,我们可以执行我们的工作流。我们还没有定义任何工作,但我们可以看到工作流正在执行。我们将通过注册一个记录工作流事件的订阅者来查看工作流的执行
>>> def log_workflow(event): ... print (event)>>> import zope.event >>> zope.event.subscribers.append(log_workflow)
要使用工作流定义,我们需要创建一个实例
>>> proc = pd()
现在,如果我们启动工作流
>>> 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'))
我们看到我们立即过渡到作者活动,然后过渡到审查和发布。通常,我们需要在每个活动中进行一些工作,转换只有在工作完成后才会继续,然而,在这种情况下,我们没有定义任何工作,所以每个活动立即完成。
请注意,我们没有过渡到被拒绝的活动。默认情况下,当活动完成时,将使用条件评估为True的第一个转换。默认情况下,转换具有评估为True的布尔条件,因此使用到出版物的转换,因为它在拒绝转换之前定义。我们想要的转换到出版物的条件是,如果审阅者批准内容发布,则过渡到出版物,如果审阅者拒绝内容发布,则过渡到拒绝。我们可以为此使用条件
>>> pd = process.ProcessDefinition('sample') >>> 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')
一个活动可以使用多个应用程序,因此我们称之为addApplication。在“审查”应用程序的应用程序定义中,我们提供了对应于为应用程序定义的输出参数的工作流程相关数据变量的名称。在活动中使用应用程序时,必须为已标识的应用程序签名中的每个参数提供工作流程相关数据变量名称。当在活动中使用应用程序时,将传递每个输入参数的工作流程相关数据,并由每个输出参数设置。在这个例子中,输出参数将用于向工作流程相关数据添加一个发布属性。
参与者
我们已经声明了一些应用程序,并将它们连接到活动中,但我们还没有指定任何应用程序代码。在我们可以指定应用程序代码之前,我们需要考虑谁将执行应用程序。工作流程应用程序通常由人员或其他外部行为者执行。与应用程序一样,流程定义允许在工作流程中声明和识别参与者。我们声明参与者的方式与声明应用程序的方式相似,只是没有参数
>>> pd.defineParticipants( ... author = process.Participant(), ... reviewer = process.Participant(), ... )
在这种情况下,我们恰巧为其中一个参与者重用了活动名称,但没有为两个参与者都这样做。定义了这些参与者之后,我们可以将它们与活动相关联
>>> pd.activities['author'].definePerformer('author') >>> pd.activities['review'].definePerformer('reviewer')
应用程序集成
为了使用流程定义来控制应用程序逻辑,我们需要将其与一个“集成”对象关联。
当流程需要获取参与者时,它在其集成属性上调用createParticipant,传递流程ID和执行者ID。如果一个活动没有执行者,则使用空执行者ID使用上述过程。
同样,当流程需要工作项时,它在其集成属性上调用createWorkItem,传递流程ID和应用ID。
工作项提供了一个start方法,用于启动工作并传递输入参数。工作项的责任是在某个稍后时间调用活动上的workItemFinished方法,以通知活动工作项已完成。输出参数传递到workItemFinished方法。
创建集成对象的一个简单方法是使用shoobx.wfmc.attributeintegration.AttributeIntegration。
>>> from shoobx.wfmc.attributeintegration import AttributeIntegration >>> integration = AttributeIntegration() >>> pd.integration = integration
我们首先定义一个简单的参与者类
>>> 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, process): ... self.activity = activity
我们为每个参与者设置集成属性
>>> integration.authorParticipant = Participant >>> integration.reviewerParticipant = Participant
我们还为没有执行者的活动定义了参与者的属性
>>> integration.Participant = Participant
现在我们将定义我们的工作项。首先我们将定义一些类
>>> work_list = []>>> @zope.interface.implementer(interfaces.IWorkItem) ... class ApplicationBase: ... zope.component.adapts(interfaces.IParticipant) ... ... def __init__(self, participant, process, activity): ... self.participant = participant ... work_list.append(self) ... ... def start(self, args): ... pass ... ... def finish(self): ... self.participant.activity.workItemFinished(self)>>> class Review(ApplicationBase): ... def finish(self, publish): ... output = {'publish': publish} ... self.participant.activity.workItemFinished(self, output)>>> class Publish(ApplicationBase): ... def start(self, args): ... print ("Published") ... self.finish()>>> class Reject(ApplicationBase): ... def start(self, args): ... print ("Rejected") ... self.finish()
然后我们将它们与集成对象连接起来
>>> integration.authorWorkItem = ApplicationBase >>> integration.reviewWorkItem = Review >>> integration.publishWorkItem = Publish >>> integration.rejectWorkItem = Reject
使用工作流程过程
要使用流程定义,实例化它并调用其start方法以开始执行
>>> proc = pd() >>> proc.start() ... # doctest: +NORMALIZE_WHITESPACE ProcessStarted(Process('sample')) Transition(None, Activity('sample.author')) ActivityStarted(Activity('sample.author')) WorkItemStarting('author') WorkItemStarted('author')
我们过渡到作者活动并等待工作完成。要前进,我们需要获取作者工作项,以便我们可以完成它。我们的工作项将自己添加到工作列表中,因此我们可以从列表中获取项。
>>> item = work_list.pop()
现在我们可以通过调用其finish方法来完成工作项
>>> item.finish() WorkItemFinished('author') ActivityFinished(Activity('sample.author')) Transition(Activity('sample.author'), Activity('sample.review')) ActivityStarted(Activity('sample.review')) WorkItemStarting('review') WorkItemStarted('review')
我们看到我们过渡到了审查活动。请注意,finish方法不是工作流程API的一部分。它是由我们的示例类定义的。其他应用程序可以使用不同的机制。
现在,我们将通过调用审查工作项的 finish 来完成审查流程。我们将传递 False,表示内容不应发布。
>>> 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')
输出转换排序
通常,输出转换按照转换定义的顺序排序,并使用给定活动中的所有转换。
如果转换定义的顺序不合适,则工作流程可能不会按预期工作。例如,让我们通过更改一些转换的定义顺序来修改上述流程。我们将通过将其传递给定义构造函数来重用之前示例中的集成对象
>>> pd = process.ProcessDefinition('sample', 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', 'reject'), ... process.TransitionDefinition( ... 'review', 'publish', ... condition=lambda proc: proc.workflowRelevantData.publish), ... )>>> 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')
并运行我们的流程
>>> proc = pd() >>> proc.start() ... # doctest: +NORMALIZE_WHITESPACE ProcessStarted(Process('sample')) Transition(None, Activity('sample.author')) ActivityStarted(Activity('sample.author')) WorkItemStarting('author') WorkItemStarted('author')>>> work_list.pop().finish() WorkItemFinished('author') ActivityFinished(Activity('sample.author')) Transition(Activity('sample.author'), Activity('sample.review')) ActivityStarted(Activity('sample.review')) WorkItemStarting('review') WorkItemStarted('review')
这次,我们将说应该发布
>>> work_list.pop().finish(True) 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')
但无论如何,我们都进入了拒绝活动。为什么?因为转换是按顺序测试的。因为拒绝活动的转换首先被测试且没有条件,我们没有检查发布活动转换的条件就跟随了它。我们可以通过在审查活动上直接指定输出转换来修复此问题。为此,我们还需要在转换中指定ids。让我们重新定义流程
>>> pd = process.ProcessDefinition('sample', 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', 'reject', id='reject'), ... process.TransitionDefinition( ... 'review', 'publish', id='publish', ... condition=lambda proc: proc.workflowRelevantData.publish), ... )>>> 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')>>> pd.activities['review'].addOutgoing('publish') >>> pd.activities['review'].addOutgoing('reject')
现在,当我们运行流程时,我们将像预期的那样进入发布活动
>>> proc = pd() >>> proc.start() ... # doctest: +NORMALIZE_WHITESPACE ProcessStarted(Process('sample')) Transition(None, Activity('sample.author')) ActivityStarted(Activity('sample.author')) WorkItemStarting('author') WorkItemStarted('author')>>> work_list.pop().finish() WorkItemFinished('author') ActivityFinished(Activity('sample.author')) Transition(Activity('sample.author'), Activity('sample.review')) ActivityStarted(Activity('sample.review')) WorkItemStarting('review') WorkItemStarted('review')>>> work_list.pop().finish(True) WorkItemFinished('review') ActivityFinished(Activity('sample.review')) Transition(Activity('sample.review'), Activity('sample.publish')) ActivityStarted(Activity('sample.publish')) WorkItemStarting('publish') Published WorkItemFinished('publish') ActivityFinished(Activity('sample.publish')) ProcessFinished(Process('sample')) WorkItemStarted('publish')
让我们也看看另一种情况,我们应该转换到拒绝
>>> proc = pd() >>> proc.start() ... # doctest: +NORMALIZE_WHITESPACE ProcessStarted(Process('sample')) Transition(None, Activity('sample.author')) ActivityStarted(Activity('sample.author')) WorkItemStarting('author') WorkItemStarted('author')>>> work_list.pop().finish() WorkItemFinished('author') ActivityFinished(Activity('sample.author')) Transition(Activity('sample.author'), Activity('sample.review')) ActivityStarted(Activity('sample.review')) WorkItemStarting('review') WorkItemStarted('review')>>> 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')
复杂流程
让我们看看一个更复杂的例子。在这个例子中,我们将扩展流程以支持多个审查人。我们还将使工作列表处理更加复杂。我们还将引入一些新概念
分割和连接
流程参数
考虑以下所示的发布流程
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 | ------------------------------------| | ----------- ----------
在这里,我们将流程图排列成列,每列包含每个参与者的活动。我们有四个参与者,作者、两位技术审查人和一位编辑审查人。作者准备草稿。作者将草稿发送给 两位 技术审查人进行审查。当技术审查完成后,编辑审查进行初步编辑审查。根据技术审查结果,编辑可以选择
拒绝文档
按原样发布文档
请求技术修改(基于技术审查人的评论),或
请求编辑修改。
如果需要技术修改,则工作流程将返回到“准备”活动。如果需要编辑修改,则工作流程将流向“准备最终版”活动。当作者完成编辑修改后,工作流程将流向“最终审查”。编辑可能会要求额外的修改,在这种情况下,工作流程将返回到“准备最终版”,否则,工作流程将流向“发布”。
此示例说明了不同类型的“连接”和“分割”。术语“连接”指的是如何处理活动进入的转换。有两种类型的连接:“与”和“或”。在“与”连接中,活动等待每个进入的转换。在这个例子中,“编辑审查”活动的输入形成了一个“与”连接。编辑审查等待每个技术审查都完成。这个例子中的其余连接都是“或”连接。活动在进入活动的任何转换上启动。
术语“分割”指的是处理活动输出转换的方式。通常,活动使用正好一个输出转换。这称为“或”分割。在“与”分割中,所有布尔条件评估为 True 的转换都被使用。在这个例子中,“准备”活动有一个“与”分割。工作流程同时流向两个技术审查活动。这个例子中的其余分割都是“或”分割。
让我们创建我们的新工作流程过程。我们将重用现有的集成对象
>>> Publication = process.ProcessDefinition('Publication') >>> Publication.integration = integration >>> pdfactory.register(Publication)>>> 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"), ... )
在这里,我们向活动定义传递了字符串以提供名称。名称必须是 Unicode 或 ASCII 字符串。
我们定义我们的转换
>>> 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 proc: not proc.workflowRelevantData.publish ... ), ... process.TransitionDefinition( ... 'review', 'prepare', ... condition=lambda proc: proc.workflowRelevantData.tech_changes ... ), ... process.TransitionDefinition( ... 'review', 'final', ... condition=lambda proc: proc.workflowRelevantData.ed_changes ... ), ... process.TransitionDefinition('review', 'publish'), ... ... process.TransitionDefinition('final', 'rfinal'), ... process.TransitionDefinition( ... 'rfinal', 'final', ... condition=lambda proc: proc.workflowRelevantData.ed_changes ... ), ... process.TransitionDefinition('rfinal', 'publish'), ... )
我们指定我们的“与”分割和连接
>>> Publication.activities['prepare'].andSplit(True) >>> Publication.activities['review'].andJoin(True)
我们定义我们的参与者和应用程序
>>> 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', ['publish1', 'tech_changes1'])>>> Publication.activities['tech2'].definePerformer('tech2') >>> Publication.activities['tech2'].addApplication( ... 'tech_review', ['publish2', 'tech_changes2'])>>> Publication.activities['review'].definePerformer('reviewer') >>> Publication.activities['review'].addApplication( ... 'ed_review', ... ['publish1', 'tech_changes1', 'publish2', 'tech_changes2', ... 'publish', 'tech_changes', 'ed_changes'], ... )>>> Publication.activities['final'].definePerformer('author') >>> Publication.activities['final'].addApplication('final')>>> Publication.activities['rfinal'].definePerformer('reviewer') >>> Publication.activities['rfinal'].addApplication( ... 'rfinal', ['ed_changes'], ... )>>> Publication.activities['publish'].addApplication('publish') >>> Publication.activities['reject'].addApplication('reject')
在开始流程时,我们希望能够指定一个作者。我们还希望被告知流程的最终处理结果。为了实现这一点,我们将为我们的流程定义参数。
>>> Publication.defineParameters( ... process.InputParameter('author'), ... process.OutputParameter('publish'), ... )
现在我们已经定义了流程,我们需要提供参与者和应用程序组件。让我们从我们的参与者开始。我们不会共享单一的工作列表,而是为每个用户提供一个自己的工作列表。我们还将创建现有的参与者并将它们返回。最后,我们将创建多个作者并使用选定的一个。
>>> 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, process): ... Participant.__init__(self, activity, process) ... author_name = activity.process.workflowRelevantData.author ... print(("Author `%s` selected" % author_name)) ... self.user = authors[author_name]
在这个例子中,我们需要为每个参与者定义一个单独的属性。
>>> integration.authorParticipant = Author
当流程创建时,作者名称将被传递并分配给与工作流相关的数据。我们的作者类使用这些信息来选择命名的用户。
>>> class Reviewer(Participant): ... user = reviewer >>> integration.reviewerParticipant = Reviewer>>> class Tech1(Participant): ... user = tech1 >>> integration.tech1Participant = Tech1>>> class Tech2(Participant): ... user = tech2 >>> integration.tech2Participant = Tech2
我们将使用原始的参与类来处理没有表演者的活动。
>>> integration.Participant = Participant
现在我们来创建我们的应用程序。让我们从作者开始。
>>> @zope.interface.implementer(interfaces.IWorkItem) ... class ApplicationBase(object): ... zope.component.adapts(interfaces.IParticipant) ... ... def __init__(self, participant, process, activity): ... self.participant = participant ... self.activity = participant.activity ... participant.user.work_list.append(self) ... ... def start(self, args): ... 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 ('Changes 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()>>> integration.prepareWorkItem = Prepare
由于我们既使用了准备应用程序进行修订,也用于初始准备,我们提供了一个汇总方法来显示我们必须做什么。
在这里,我们从作者传递给完成方法的参数中获取作者创建的文档。在更现实的实现中,作者任务将在任务开始时创建文档,并为用户提供一个用户界面来编辑它。我们将文档存储为应用程序相关的数据,因为我们希望审稿人能够访问它,但我们不需要它直接用于工作流控制。
>>> class TechReview(ApplicationBase): ... ... def getDoc(self): ... return self.activity.process.applicationRelevantData.doc ... ... def finish(self, decision, changes): ... output = {'publish': decision, 'tech_changes': changes} ... self.activity.workItemFinished(self, output)>>> integration.tech_reviewWorkItem = TechReview
在这里,我们提供了一个访问原始文档的方法。
>>> class Review(TechReview): ... ... def start(self, args): ... publish1 = args['publish1'] ... publish2 = args['publish2'] ... changes1 = args['tech_changes1'] ... changes2 = args['tech_changes2'] ... if not (publish1 and publish2): ... output = {'publish': False, ... 'tech_changes': changes1 + changes2, ... 'ed_changes': ()} ... # Reject if either tech reviewer rejects ... self.activity.workItemFinished( ... self, output) ... ... if changes1 or changes2: ... output = {'publish': True, ... 'tech_changes': changes1 + changes2, ... 'ed_changes': ()} ... # we won't do anything if there are tech changes ... self.activity.workItemFinished( ... self, output) ... ... def finish(self, ed_changes): ... output = {'publish': True, 'tech_changes': (), 'ed_changes': ed_changes} ... self.activity.workItemFinished(self, output)>>> integration.ed_reviewWorkItem = Review
在这个实现中,我们决定如果任何技术编辑建议拒绝,就明确拒绝,如果有任何技术更改,就将工作发送回准备。我们还从
我们将重用前一个示例中的
>>> 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 ('Changes 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()>>> integration.finalWorkItem = Final
在我们的这个应用程序中,我们只是更新文档以反映更改。
>>> class ReviewFinal(TechReview): ... ... def finish(self, ed_changes): ... output = {'ed_changes': ed_changes} ... self.activity.workItemFinished(self, output)>>> integration.rfinalWorkItem = ReviewFinal
我们的流程现在返回数据。当我们创建一个流程时,我们需要提供一个它可以回调的对象。
>>> @zope.interface.implementer(interfaces.IProcessContext) ... class PublicationContext: ... ... def processFinished(self, process, decision): ... self.decision = decision
现在,让我们尝试我们的流程。
>>> context = PublicationContext() >>> proc = Publication(context) >>> proc.start('bob') ProcessStarted(Process('Publication')) Transition(None, Activity('Publication.start')) ActivityStarted(Activity('Publication.start')) ActivityFinished(Activity('Publication.start')) Author `bob` selected Transition(Activity('Publication.start'), Activity('Publication.prepare')) ActivityStarted(Activity('Publication.prepare')) WorkItemStarting('prepare') WorkItemStarted('prepare')
我们应该已经在bob的工作列表中添加了一个条目。让我们取回它并完成它,提交一个文档。
>>> 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')) WorkItemStarting('tech_review') WorkItemStarted('tech_review') Transition(Activity('Publication.prepare'), Activity('Publication.tech2')) ActivityStarted(Activity('Publication.tech2')) WorkItemStarting('tech_review') WorkItemStarted('tech_review')
请注意,我们已经过渡到了两个活动,即
>>> 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.
让我们告诉作者将“American”改为“Earthling”。
>>> item.finish(True, ['Change "American" to "Earthling"']) WorkItemFinished('tech_review') ActivityFinished(Activity('Publication.tech1')) Transition(Activity('Publication.tech1'), Activity('Publication.review'))
在这里,我们已经过渡到了编辑审查活动,但我们没有启动它。这是因为编辑审查活动有一个“and”连接,意味着它不会在两个过渡都发生之前开始。
现在我们将进行另一个技术审查。
>>> 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')) WorkItemStarting('ed_review') WorkItemFinished('ed_review') ActivityFinished(Activity('Publication.review')) Author `bob` selected Transition(Activity('Publication.review'), Activity('Publication.prepare')) ActivityStarted(Activity('Publication.prepare')) WorkItemStarting('prepare') WorkItemStarted('prepare') WorkItemStarted('ed_review')
现在当我们过渡到编辑审查活动时,我们启动了它,因为每个输入过渡都发生了。我们的编辑审查应用程序自动将工作发送回准备,因为有一些技术评论。当然,作者仍然是
>>> 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. Changes we need to make: Change "American" to "Earthling" Change "Country" to "planet">>> item.finish("I give my pledge, as an Earthling\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')) WorkItemStarting('tech_review') WorkItemStarted('tech_review') Transition(Activity('Publication.prepare'), Activity('Publication.tech2')) ActivityStarted(Activity('Publication.tech2')) WorkItemStarting('tech_review') WorkItemStarted('tech_review')
像以前一样,在完成初始编辑后,我们再次开始技术审查活动。我们将再次审查它。这次,我们没有评论,因为作者应用了我们的请求更改。
>>> 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')) WorkItemStarting('ed_review') WorkItemStarted('ed_review')
这次,我们留在了技术审查活动中,因为没有技术更改。我们准备好进行我们的编辑审查。我们将请求编辑更改。
>>> item = reviewer.work_list.pop() >>> print((item.getDoc())) I give my pledge, as an Earthling 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')) Author `bob` selected Transition(Activity('Publication.review'), Activity('Publication.final')) ActivityStarted(Activity('Publication.final')) WorkItemStarting('final') WorkItemStarted('final')
由于我们请求了编辑更改,我们过渡到了最终编辑活动,以便作者(仍然是bob)可以进行更改。
>>> item = authors['bob'].work_list.pop() >>> item.summary() Previous draft: I give my pledge, as an Earthling to save, and faithfully to defend from waste the natural resources of my planet. Changes we need to make: change "an" to "a">>> item.finish("I give my pledge, as a Earthling\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')) WorkItemStarting('rfinal') WorkItemStarted('rfinal')
我们过渡到审查最终编辑的活动。我们审查文档并批准其出版。
>>> item = reviewer.work_list.pop() >>> print((item.getDoc())) I give my pledge, as a Earthling 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')) WorkItemStarting('publish') Published WorkItemFinished('publish') ActivityFinished(Activity('Publication.publish')) ProcessFinished(Process('Publication')) WorkItemStarted('publish')
在这个时候,其余的流程自动完成。此外,决策被记录在流程上下文对象中。
>>> proc.workflowRelevantData.publish True
即将推出
超时/异常
另请参阅
http://www.wfmc.org http://www.wfmc.org/standards/standards.htm
XPDL导入
我们可以从XML流程定义语言(XPDL)格式的文件中导入流程定义。一个XPDL文件包含多个流程定义,这些定义被组织在一个包中。当我们加载文件时,我们得到一个包含一定数量流程定义的包。
让我们来看一个例子。文件publication.xpdl包含了在“README.txt”文件中开发的发布示例的定义。我们可以使用xpdl模块读取它
>>> from shoobx.wfmc import xpdl >>> import os >>> package = xpdl.read(open(os.path.join(this_directory, ... 'publication-1.0.xpdl')))
这个包包含一个单一的定义
>>> package {'Publication': ProcessDefinition('Publication')}>>> pd = package['Publication'] >>> from shoobx.wfmc.attributeintegration import AttributeIntegration >>> integration = AttributeIntegration() >>> pd.integration = integration
现在,我们已经读取了流程定义,我们可以像之前(在“README.txt”中)那样使用它。像之前一样,我们将创建一个事件订阅者,以便我们可以看到发生了什么
>>> def log_workflow(event): ... print(event)>>> import zope.event >>> zope.event.subscribers.append(log_workflow)
并将流程定义注册为实用工具
>>> import zope.component >>> from shoobx.wfmc.process import StaticProcessDefinitionFactory >>> pdfactory = StaticProcessDefinitionFactory() >>> zope.component.provideUtility(pdfactory) >>> pdfactory.register(pd)
定义并注册参与者和应用程序适配器
>>> 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, process): ... self.activity = activity>>> 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, process): ... Participant.__init__(self, activity, process) ... author_name = activity.process.workflowRelevantData.author ... print("Author `%s` selected" % author_name) ... self.user = authors[author_name]>>> integration.authorParticipant = Author>>> class Reviewer(Participant): ... user = reviewer >>> integration.reviewerParticipant = Reviewer>>> class Tech1(Participant): ... user = tech1 >>> integration.tech1Participant = Tech1>>> class Tech2(Participant): ... user = tech2 >>> integration.tech2Participant = Tech2>>> integration.SystemParticipant = Participant>>> @zope.interface.implementer(interfaces.IWorkItem) ... class ApplicationBase(object): ... zope.component.adapts(interfaces.IParticipant) ... ... def __init__(self, participant, process, activity): ... self.participant = participant ... self.activity = participant.activity ... participant.user.work_list.append(self) ... ... def start(self, args): ... 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('Changes 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()>>> integration.prepareWorkItem = Prepare>>> class TechReview(ApplicationBase): ... ... def getDoc(self): ... return self.activity.process.applicationRelevantData.doc ... ... def finish(self, decision, changes): ... output = {'publish': decision, 'tech_changes': changes} ... self.activity.workItemFinished(self, output)>>> integration.tech_reviewWorkItem = TechReview>>> class Review(TechReview): ... ... def start(self, args): ... publish1 = args['publish1'] ... publish2 = args['publish2'] ... changes1 = args['tech_changes1'] ... changes2 = args['tech_changes2'] ... if not (publish1 and publish2): ... output = {'publish': False, ... 'tech_changes': changes1 + changes2, ... 'ed_changes': ()} ... # Reject if either tech reviewer rejects ... self.activity.workItemFinished( ... self, output) ... ... if changes1 or changes2: ... output = {'publish': True, ... 'tech_changes': changes1 + changes2, ... 'ed_changes': ()} ... # we won't do anyting if there are tech changes ... self.activity.workItemFinished( ... self, output) ... ... def finish(self, ed_changes): ... output = {'publish': True, 'tech_changes': (), 'ed_changes': ed_changes} ... self.activity.workItemFinished(self, output)>>> integration.ed_reviewWorkItem = Review>>> 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('Changes 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()>>> integration.finalWorkItem = Final>>> class ReviewFinal(TechReview): ... ... def finish(self, ed_changes): ... output = {'publish': True, 'tech_changes': (), 'ed_changes': ed_changes} ... self.activity.workItemFinished(self, output)>>> integration.rfinalWorkItem = ReviewFinal>>> @zope.interface.implementer(interfaces.IWorkItem) ... class Publish: ... zope.component.adapts(interfaces.IParticipant) ... ... def __init__(self, participant, process, activity): ... self.participant = participant ... ... def start(self, args): ... print("Published") ... self.finish() ... ... def finish(self): ... self.participant.activity.workItemFinished(self)>>> integration.publishWorkItem = Publish>>> class Reject(Publish): ... def start(self, args): ... print("Rejected") ... self.finish()>>> integration.rejectWorkItem = Reject
以及流程上下文,以便我们可以传递参数
>>> @zope.interface.implementer(interfaces.IProcessContext) ... class PublicationContext: ... ... def processFinished(self, process, decision): ... self.decision = decision
现在,让我们尝试我们的流程。我们将遵循在“README.txt”中相同的步骤,得到相同的结果
>>> context = PublicationContext() >>> proc = pd(context) >>> proc.start('bob') ProcessStarted(Process('Publication')) Transition(None, Activity('Publication.start')) ActivityStarted(Activity('Publication.start')) ActivityFinished(Activity('Publication.start')) Author `bob` selected Transition(Activity('Publication.start'), Activity('Publication.prepare')) ActivityStarted(Activity('Publication.prepare')) WorkItemStarting('prepare') WorkItemStarted('prepare')>>> 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')) WorkItemStarting('tech_review') WorkItemStarted('tech_review') Transition(Activity('Publication.prepare'), Activity('Publication.tech2')) ActivityStarted(Activity('Publication.tech2')) WorkItemStarting('tech_review') WorkItemStarted('tech_review')>>> 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.>>> item.finish(True, ['Change "American" to "human"']) WorkItemFinished('tech_review') ActivityFinished(Activity('Publication.tech1')) Transition(Activity('Publication.tech1'), Activity('Publication.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')) WorkItemStarting('ed_review') WorkItemFinished('ed_review') ActivityFinished(Activity('Publication.review')) Author `bob` selected Transition(Activity('Publication.review'), Activity('Publication.prepare')) ActivityStarted(Activity('Publication.prepare')) WorkItemStarting('prepare') WorkItemStarted('prepare') WorkItemStarted('ed_review')>>> 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. Changes 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')) WorkItemStarting('tech_review') WorkItemStarted('tech_review') Transition(Activity('Publication.prepare'), Activity('Publication.tech2')) ActivityStarted(Activity('Publication.tech2')) WorkItemStarting('tech_review') WorkItemStarted('tech_review')>>> 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')) WorkItemStarting('ed_review') WorkItemStarted('ed_review')>>> 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')) Author `bob` selected Transition(Activity('Publication.review'), Activity('Publication.final')) ActivityStarted(Activity('Publication.final')) WorkItemStarting('final') WorkItemStarted('final')>>> 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. Changes 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')) WorkItemStarting('rfinal') WorkItemStarted('rfinal')>>> 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')) WorkItemStarting('publish') Published WorkItemFinished('publish') ActivityFinished(Activity('Publication.publish')) ProcessFinished(Process('Publication')) WorkItemStarted('publish')>>> proc.workflowRelevantData.publish True
描述
大多数流程元素都可以有名称和描述。
>>> pd.__name__ 'Publication'>>> pd.description 'This is the sample process'>>> pd.applications['prepare'].__name__ 'Prepare'>>> pd.applications['prepare'].description 'Prepare the initial draft'>>> pd.activities['tech1'].__name__ 'Technical Review 1'>>> pd.activities['tech1'].description 'This is the first Technical Review.'>>> pd.participants['tech1'].__name__ 'Technical Reviewer 1'>>> pd.participants['tech1'].description 'He is a smart guy.'>>> sorted([item.__name__ for item in pd.transitions]) ['Transition', 'Transition', 'Transition', 'Transition', 'Transition', 'Transition', 'Transition', 'Transition', 'Transition', 'Transition', 'Transition to Tech Review 1', 'Transition to Tech Review 2']>>> descriptions = [item.description for item in pd.transitions if item.description] >>> 'Use this transition if there are editorial changes required.' in descriptions True
变更
4.3.1 (2023-04-04)
将CI迁移到github actions
移除了buildout支持
增加了Python 3.10和3.11支持。
移除了Python 2支持。
修复了Deadline / ExceptionName解析。
4.3.0 (2022-04-07)
evaluateInputs:重构以处理最常见的异常。
getInitialDataFieldsValues:新的函数用于解析数据字段的初始值。
停止对Python 2的支持。
4.2.2 (2019-09-06)
将截止日期解析作为Activity.digestDeadlineDefinition暴露。
4.2.0 (2018-11-12)
增加了Python 3.7支持。
移除了Python 3.5支持。
移除了所有弃用和资源警告。
4.1.1 (2018-02-08)
增加了Python 3兼容性。
4.1.0 (2018-02-06)
Python 3支持。
4.0.4 (2017-11-01)
使now函数可插拔。
4.0.3 (2017-06-20)
尚未有任何变化。
4.0.2 (2017-05-25)
更新并改进Trove分类器。
4.0.1 (2017-05-25)
修复了小的ReST问题,以便PyPI描述可以渲染。
4.0.0 (2017-05-25)
从zope.wfmc重命名为shoobx.wfmc。
增加了对社区CI和覆盖率工具的支持。
在单个ExtendedAttributes容器中检测到重复的ExtendedAttribute Name时抛出异常
使用IProcessDefinitionFactory检索流程定义,而不是命名实用工具。这一额外的间接层允许动态生成定义。
支持同步和异步执行WFMC子流程。子流程作为主流程的一部分执行,但是具有它们自己的独立状态(工作流变量)。
简单的Python evaluate(expr, locals)函数已被替换为PythonExpressionEvaluator组件,它是从IProcess到IPythonExpressionEvaluator的适配器。评估本地命名空间会自动填充与工作流和应用程序相关的数据属性、流程上下文以及传入的本地变量。
所有对evaluate()的调用都已更新为使用适配器。
此更改允许轻松替换评估引擎,以连接到安全的Python引擎(例如,RestrictedPython)并提供更多的命名空间条目。
现在可以在更大的流程上下文中评估转换条件,而不仅仅是工作流相关的数据。因此,它们的调用签名已从condition(data)更改为condition(process, data)。
TextCondition已被更改为使用PythonExpressionEvaluator组件。此外,编译优化已被删除,因为表达式evalautor可以更有效地完成此操作。
支持中止流程和活动。
工作项可以通过实现IAbortWorkItem来中止。
如果工作项实现了ICleanupWorkItem,则可以清理工作项。
活动跟踪已完成的工作项。
活动可以通过清理工作项来自行清理。
流程跟踪已完成的活动。
当流程被中止时,将执行以下操作
中止所有活动。
清理所有已完成的活动。
在流程上设置isAborted标志。
增加了对读取 XPDL-2.1 的支持
增加了从 XPDL 读取池和通道的功能
3.5.0 (2009-07-24)
更新测试到最新包版本。
3.4.0 (2007-11-02)
首次独立发布,不依赖于主 Zope 树。
项目详情
shoobx.wfmc-4.3.1.tar.gz 的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | ce160cb6ef217fa9692c36557d786ff241c29c599bb69a56e0311edf6585ffa5 |
|
MD5 | cb03531db6b0180190f4122fd3875ec6 |
|
BLAKE2b-256 | 66865a244e00342ff680d0b18e4a8d2e4b8836414b8752b6358adf2b244af4b1 |