Squashed 'SpiffWorkflow/' changes from 9d597627..d9fcd45a
d9fcd45a Updating release numbers e0d04877 Merge pull request #251 from sartography/bugfix/make-data-objects-available-to-prescripts 7d260c36 Merge pull request #252 from sartography/bug/data_object_serializer_fix 3de54d97 Fixing a failing test. 27c5d3e1 A minor fix during deserialization to avoid issues for some users that upgraded between official releases. 250311d6 copy data objects before prescript execution git-subtree-dir: SpiffWorkflow git-subtree-split: d9fcd45a384f8376a669cf58677564289d2c661c
This commit is contained in:
parent
a79d0962a9
commit
7515519bc9
|
@ -25,13 +25,12 @@ from ..specs.ScriptTask import ScriptTask
|
||||||
from ..specs.UserTask import UserTask
|
from ..specs.UserTask import UserTask
|
||||||
from ..specs.events import _BoundaryEventParent, CancelEventDefinition
|
from ..specs.events import _BoundaryEventParent, CancelEventDefinition
|
||||||
from ..specs.MultiInstanceTask import getDynamicMIClass
|
from ..specs.MultiInstanceTask import getDynamicMIClass
|
||||||
from ..specs.SubWorkflowTask import CallActivity, TransactionSubprocess
|
from ..specs.SubWorkflowTask import CallActivity, TransactionSubprocess, SubWorkflowTask
|
||||||
from ..specs.ExclusiveGateway import ExclusiveGateway
|
from ..specs.ExclusiveGateway import ExclusiveGateway
|
||||||
from ...dmn.specs.BusinessRuleTask import BusinessRuleTask
|
from ...dmn.specs.BusinessRuleTask import BusinessRuleTask
|
||||||
from ...operators import Attrib, PathAttrib
|
from ...operators import Attrib, PathAttrib
|
||||||
from .util import one, first
|
from .util import one, first
|
||||||
from .node_parser import NodeParser
|
from .node_parser import NodeParser
|
||||||
from ...specs.SubWorkflow import SubWorkflow
|
|
||||||
|
|
||||||
STANDARDLOOPCOUNT = '25'
|
STANDARDLOOPCOUNT = '25'
|
||||||
|
|
||||||
|
@ -68,7 +67,7 @@ class TaskParser(NodeParser):
|
||||||
# the current parser achitecture). We should also consider separate classes for loop vs
|
# the current parser achitecture). We should also consider separate classes for loop vs
|
||||||
# multiinstance because having all these optional attributes is a nightmare
|
# multiinstance because having all these optional attributes is a nightmare
|
||||||
|
|
||||||
if not isinstance(self.task, (NoneTask,UserTask,BusinessRuleTask,ScriptTask,CallActivity,SubWorkflow)):
|
if not isinstance(self.task, (NoneTask, UserTask, BusinessRuleTask, ScriptTask, CallActivity, SubWorkflowTask)):
|
||||||
raise ValidationException(
|
raise ValidationException(
|
||||||
f'Unsupported MultiInstance Task: {self.task.__class__}',
|
f'Unsupported MultiInstance Task: {self.task.__class__}',
|
||||||
node=self.node,
|
node=self.node,
|
||||||
|
|
|
@ -141,7 +141,12 @@ class BpmnProcessSpecConverter(BpmnWorkflowSpecConverter):
|
||||||
# Add the data specs
|
# Add the data specs
|
||||||
spec.data_inputs = [ self.restore(obj_dct) for obj_dct in dct.pop('data_inputs', []) ]
|
spec.data_inputs = [ self.restore(obj_dct) for obj_dct in dct.pop('data_inputs', []) ]
|
||||||
spec.data_outputs = [ self.restore(obj_dct) for obj_dct in dct.pop('data_outputs', []) ]
|
spec.data_outputs = [ self.restore(obj_dct) for obj_dct in dct.pop('data_outputs', []) ]
|
||||||
spec.data_objects = dict([ (name, self.restore(obj_dct)) for name, obj_dct in dct.pop('data_objects', {}).items() ])
|
# fixme: This conditional can be removed in the next release, just avoiding invalid a potential
|
||||||
|
# serialization issue for some users caught between official releases.
|
||||||
|
if isinstance(dct.get('data_objects', {}), dict):
|
||||||
|
spec.data_objects = dict([ (name, self.restore(obj_dct)) for name, obj_dct in dct.pop('data_objects', {}).items() ])
|
||||||
|
else:
|
||||||
|
spec.data_objects = {}
|
||||||
|
|
||||||
# Add messaging related stuff
|
# Add messaging related stuff
|
||||||
spec.correlation_keys = dct.pop('correlation_keys', {})
|
spec.correlation_keys = dct.pop('correlation_keys', {})
|
||||||
|
|
|
@ -3,11 +3,10 @@ from copy import deepcopy
|
||||||
|
|
||||||
from SpiffWorkflow.task import TaskState
|
from SpiffWorkflow.task import TaskState
|
||||||
from .BpmnSpecMixin import BpmnSpecMixin
|
from .BpmnSpecMixin import BpmnSpecMixin
|
||||||
from ...specs.SubWorkflow import SubWorkflow
|
|
||||||
from ...specs import TaskSpec
|
from ...specs import TaskSpec
|
||||||
|
|
||||||
|
|
||||||
class SubWorkflowTask(SubWorkflow, BpmnSpecMixin):
|
class SubWorkflowTask(BpmnSpecMixin):
|
||||||
"""
|
"""
|
||||||
Task Spec for a bpmn node containing a subworkflow.
|
Task Spec for a bpmn node containing a subworkflow.
|
||||||
"""
|
"""
|
||||||
|
@ -18,7 +17,7 @@ class SubWorkflowTask(SubWorkflow, BpmnSpecMixin):
|
||||||
:param bpmn_wf_spec: the BpmnProcessSpec for the sub process.
|
:param bpmn_wf_spec: the BpmnProcessSpec for the sub process.
|
||||||
:param bpmn_wf_class: the BpmnWorkflow class to instantiate
|
:param bpmn_wf_class: the BpmnWorkflow class to instantiate
|
||||||
"""
|
"""
|
||||||
super(SubWorkflowTask, self).__init__(wf_spec, name, None, **kwargs)
|
super(SubWorkflowTask, self).__init__(wf_spec, name, **kwargs)
|
||||||
self.spec = subworkflow_spec
|
self.spec = subworkflow_spec
|
||||||
self.transaction = transaction
|
self.transaction = transaction
|
||||||
|
|
||||||
|
@ -36,25 +35,8 @@ class SubWorkflowTask(SubWorkflow, BpmnSpecMixin):
|
||||||
|
|
||||||
def _on_ready_hook(self, my_task):
|
def _on_ready_hook(self, my_task):
|
||||||
|
|
||||||
for obj in self.data_input_associations:
|
super()._on_ready_hook(my_task)
|
||||||
obj.get(my_task)
|
self.start_workflow(my_task)
|
||||||
|
|
||||||
subworkflow = my_task.workflow.get_subprocess(my_task)
|
|
||||||
start = subworkflow.get_tasks_from_spec_name('Start', workflow=subworkflow)
|
|
||||||
|
|
||||||
if len(subworkflow.spec.data_inputs) == 0:
|
|
||||||
# Copy all task data into start task if no inputs specified
|
|
||||||
start[0].set_data(**my_task.data)
|
|
||||||
else:
|
|
||||||
# Otherwise copy only task data with the specified names
|
|
||||||
for var in subworkflow.spec.data_inputs:
|
|
||||||
var.copy(my_task, start[0], data_input=True)
|
|
||||||
|
|
||||||
self._predict(my_task)
|
|
||||||
for child in subworkflow.task_tree.children:
|
|
||||||
child.task_spec._update(child)
|
|
||||||
|
|
||||||
my_task._set_state(TaskState.WAITING)
|
|
||||||
|
|
||||||
def _on_subworkflow_completed(self, subworkflow, my_task):
|
def _on_subworkflow_completed(self, subworkflow, my_task):
|
||||||
|
|
||||||
|
@ -92,6 +74,24 @@ class SubWorkflowTask(SubWorkflow, BpmnSpecMixin):
|
||||||
if subworkflow is not None:
|
if subworkflow is not None:
|
||||||
subworkflow.cancel()
|
subworkflow.cancel()
|
||||||
|
|
||||||
|
def start_workflow(self, my_task):
|
||||||
|
|
||||||
|
subworkflow = my_task.workflow.get_subprocess(my_task)
|
||||||
|
start = subworkflow.get_tasks_from_spec_name('Start', workflow=subworkflow)
|
||||||
|
|
||||||
|
if len(subworkflow.spec.data_inputs) == 0:
|
||||||
|
# Copy all task data into start task if no inputs specified
|
||||||
|
start[0].set_data(**my_task.data)
|
||||||
|
else:
|
||||||
|
# Otherwise copy only task data with the specified names
|
||||||
|
for var in subworkflow.spec.data_inputs:
|
||||||
|
var.copy(my_task, start[0], data_input=True)
|
||||||
|
|
||||||
|
for child in subworkflow.task_tree.children:
|
||||||
|
child.task_spec._update(child)
|
||||||
|
|
||||||
|
my_task._set_state(TaskState.WAITING)
|
||||||
|
|
||||||
def serialize(self, serializer):
|
def serialize(self, serializer):
|
||||||
return serializer.serialize_subworkflow_task(self)
|
return serializer.serialize_subworkflow_task(self)
|
||||||
|
|
||||||
|
|
|
@ -34,9 +34,9 @@ class SpiffBpmnTask(BpmnSpecMixin):
|
||||||
raise exc
|
raise exc
|
||||||
|
|
||||||
def _on_ready_hook(self, my_task):
|
def _on_ready_hook(self, my_task):
|
||||||
|
super()._on_ready_hook(my_task)
|
||||||
if self.prescript is not None:
|
if self.prescript is not None:
|
||||||
self.execute_script(my_task, self.prescript)
|
self.execute_script(my_task, self.prescript)
|
||||||
super()._on_ready_hook(my_task)
|
|
||||||
|
|
||||||
def _on_complete_hook(self, my_task):
|
def _on_complete_hook(self, my_task):
|
||||||
if self.postscript is not None:
|
if self.postscript is not None:
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from SpiffWorkflow.bpmn.specs.SubWorkflowTask import SubWorkflowTask, TransactionSubprocess, CallActivity
|
from SpiffWorkflow.bpmn.specs.SubWorkflowTask import SubWorkflowTask, TransactionSubprocess, CallActivity
|
||||||
from SpiffWorkflow.spiff.specs.spiff_task import SpiffBpmnTask
|
from SpiffWorkflow.spiff.specs.spiff_task import SpiffBpmnTask
|
||||||
|
|
||||||
class SubWorkflowTask(SpiffBpmnTask, SubWorkflowTask):
|
class SubWorkflowTask(SubWorkflowTask, SpiffBpmnTask):
|
||||||
|
|
||||||
def __init__(self, wf_spec, name, subworkflow_spec, transaction=False, **kwargs):
|
def __init__(self, wf_spec, name, subworkflow_spec, transaction=False, **kwargs):
|
||||||
|
|
||||||
|
@ -13,11 +13,19 @@ class SubWorkflowTask(SpiffBpmnTask, SubWorkflowTask):
|
||||||
self.in_assign = []
|
self.in_assign = []
|
||||||
self.out_assign = []
|
self.out_assign = []
|
||||||
|
|
||||||
|
def _on_ready_hook(self, my_task):
|
||||||
|
SpiffBpmnTask._on_ready_hook(self, my_task)
|
||||||
|
self.start_workflow(my_task)
|
||||||
|
|
||||||
|
def _on_complete_hook(self, my_task):
|
||||||
|
SpiffBpmnTask._on_complete_hook(self, my_task)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def spec_type(self):
|
def spec_type(self):
|
||||||
return 'Subprocess'
|
return 'Subprocess'
|
||||||
|
|
||||||
class TransactionSubprocess(SpiffBpmnTask, TransactionSubprocess):
|
|
||||||
|
class TransactionSubprocess(SubWorkflowTask, TransactionSubprocess):
|
||||||
|
|
||||||
def __init__(self, wf_spec, name, subworkflow_spec, transaction=True, **kwargs):
|
def __init__(self, wf_spec, name, subworkflow_spec, transaction=True, **kwargs):
|
||||||
|
|
||||||
|
@ -31,7 +39,8 @@ class TransactionSubprocess(SpiffBpmnTask, TransactionSubprocess):
|
||||||
def spec_type(self):
|
def spec_type(self):
|
||||||
return 'Transactional Subprocess'
|
return 'Transactional Subprocess'
|
||||||
|
|
||||||
class CallActivity(SpiffBpmnTask, CallActivity):
|
|
||||||
|
class CallActivity(SubWorkflowTask, CallActivity):
|
||||||
|
|
||||||
def __init__(self, wf_spec, name, subworkflow_spec, **kwargs):
|
def __init__(self, wf_spec, name, subworkflow_spec, **kwargs):
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ copyright = '2022, Sartography'
|
||||||
author = 'Sartography'
|
author = 'Sartography'
|
||||||
|
|
||||||
# The full version, including alpha/beta/rc tags
|
# The full version, including alpha/beta/rc tags
|
||||||
|
release = '1.2.0'
|
||||||
|
|
||||||
# -- General configuration ---------------------------------------------------
|
# -- General configuration ---------------------------------------------------
|
||||||
|
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -13,7 +13,7 @@ HERE = pathlib.Path(__file__).parent
|
||||||
README = (HERE / "README.md").read_text()
|
README = (HERE / "README.md").read_text()
|
||||||
|
|
||||||
setup(name='SpiffWorkflow',
|
setup(name='SpiffWorkflow',
|
||||||
version='1.1.7',
|
version='1.2.0',
|
||||||
description='A workflow framework and BPMN/DMN Processor',
|
description='A workflow framework and BPMN/DMN Processor',
|
||||||
long_description=README,
|
long_description=README,
|
||||||
long_description_content_type="text/markdown",
|
long_description_content_type="text/markdown",
|
||||||
|
|
|
@ -17,6 +17,19 @@ class PrescriptPostsciptTest(BaseTestCase):
|
||||||
def testCallActivitySaveRestore(self):
|
def testCallActivitySaveRestore(self):
|
||||||
self.call_activity_test(True)
|
self.call_activity_test(True)
|
||||||
|
|
||||||
|
def testDataObject(self):
|
||||||
|
|
||||||
|
spec, subprocesses = self.load_workflow_spec('prescript_postscript_data_object.bpmn', 'Process_1')
|
||||||
|
self.workflow = BpmnWorkflow(spec, subprocesses)
|
||||||
|
# Set a on the workflow and b in the first task.
|
||||||
|
self.workflow.data['a'] = 1
|
||||||
|
self.set_process_data({'b': 2})
|
||||||
|
ready_tasks = self.workflow.get_tasks(TaskState.READY)
|
||||||
|
# This execute the same script as task_test
|
||||||
|
ready_tasks[0].complete()
|
||||||
|
# a should be removed, b should be unchanged, and c and z should be present (but not x & y)
|
||||||
|
self.assertDictEqual({'b': 2, 'c': 12, 'z': 6}, ready_tasks[0].data)
|
||||||
|
|
||||||
def task_test(self, save_restore=False):
|
def task_test(self, save_restore=False):
|
||||||
|
|
||||||
spec, subprocesses = self.load_workflow_spec('prescript_postscript.bpmn', 'Process_1')
|
spec, subprocesses = self.load_workflow_spec('prescript_postscript.bpmn', 'Process_1')
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:spiffworkflow="http://spiffworkflow.org/bpmn/schema/1.0/core" xmlns:modeler="http://camunda.org/schema/modeler/1.0" id="Definitions_19o7vxg" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="5.0.0" modeler:executionPlatform="Camunda Platform" modeler:executionPlatformVersion="7.17.0">
|
||||||
|
<bpmn:process id="Process_1" isExecutable="true">
|
||||||
|
<bpmn:ioSpecification />
|
||||||
|
<bpmn:startEvent id="Event_1ftsuzw">
|
||||||
|
<bpmn:outgoing>Flow_1hjrex4</bpmn:outgoing>
|
||||||
|
</bpmn:startEvent>
|
||||||
|
<bpmn:task id="Activity_1iqs4li" name="Any Task">
|
||||||
|
<bpmn:extensionElements>
|
||||||
|
<spiffworkflow:preScript>x = a * 2
|
||||||
|
y = b * 2
|
||||||
|
z = x + y</spiffworkflow:preScript>
|
||||||
|
<spiffworkflow:postScript>c = z * 2
|
||||||
|
del x
|
||||||
|
del y</spiffworkflow:postScript>
|
||||||
|
</bpmn:extensionElements>
|
||||||
|
<bpmn:incoming>Flow_1hjrex4</bpmn:incoming>
|
||||||
|
<bpmn:outgoing>Flow_1xndbxy</bpmn:outgoing>
|
||||||
|
<bpmn:property id="Property_1bj3qrk" name="__targetRef_placeholder" />
|
||||||
|
<bpmn:dataInputAssociation id="DataInputAssociation_0qit1wt">
|
||||||
|
<bpmn:sourceRef>DataObjectReference_15lldo2</bpmn:sourceRef>
|
||||||
|
<bpmn:targetRef>Property_1bj3qrk</bpmn:targetRef>
|
||||||
|
</bpmn:dataInputAssociation>
|
||||||
|
</bpmn:task>
|
||||||
|
<bpmn:sequenceFlow id="Flow_1hjrex4" sourceRef="Event_1ftsuzw" targetRef="Activity_1iqs4li" />
|
||||||
|
<bpmn:endEvent id="Event_0c80924">
|
||||||
|
<bpmn:incoming>Flow_1xndbxy</bpmn:incoming>
|
||||||
|
</bpmn:endEvent>
|
||||||
|
<bpmn:sequenceFlow id="Flow_1xndbxy" sourceRef="Activity_1iqs4li" targetRef="Event_0c80924" />
|
||||||
|
<bpmn:dataObjectReference id="DataObjectReference_15lldo2" name="a" dataObjectRef="a" />
|
||||||
|
<bpmn:dataObject id="a" />
|
||||||
|
</bpmn:process>
|
||||||
|
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||||
|
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1">
|
||||||
|
<bpmndi:BPMNEdge id="Flow_1xndbxy_di" bpmnElement="Flow_1xndbxy">
|
||||||
|
<di:waypoint x="380" y="220" />
|
||||||
|
<di:waypoint x="432" y="220" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNEdge id="Flow_1hjrex4_di" bpmnElement="Flow_1hjrex4">
|
||||||
|
<di:waypoint x="228" y="220" />
|
||||||
|
<di:waypoint x="280" y="220" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNShape id="Event_1ftsuzw_di" bpmnElement="Event_1ftsuzw">
|
||||||
|
<dc:Bounds x="192" y="202" width="36" height="36" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="Activity_1iqs4li_di" bpmnElement="Activity_1iqs4li">
|
||||||
|
<dc:Bounds x="280" y="180" width="100" height="80" />
|
||||||
|
<bpmndi:BPMNLabel />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="Event_0c80924_di" bpmnElement="Event_0c80924">
|
||||||
|
<dc:Bounds x="432" y="202" width="36" height="36" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="DataObjectReference_15lldo2_di" bpmnElement="DataObjectReference_15lldo2">
|
||||||
|
<dc:Bounds x="312" y="75" width="36" height="50" />
|
||||||
|
<bpmndi:BPMNLabel>
|
||||||
|
<dc:Bounds x="328" y="38" width="7" height="14" />
|
||||||
|
</bpmndi:BPMNLabel>
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNEdge id="DataInputAssociation_0qit1wt_di" bpmnElement="DataInputAssociation_0qit1wt">
|
||||||
|
<di:waypoint x="330" y="125" />
|
||||||
|
<di:waypoint x="330" y="180" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
</bpmndi:BPMNPlane>
|
||||||
|
</bpmndi:BPMNDiagram>
|
||||||
|
</bpmn:definitions>
|
Loading…
Reference in New Issue