diff --git a/SpiffWorkflow/bpmn/parser/TaskParser.py b/SpiffWorkflow/bpmn/parser/TaskParser.py index 16a875e24..bbfbae450 100644 --- a/SpiffWorkflow/bpmn/parser/TaskParser.py +++ b/SpiffWorkflow/bpmn/parser/TaskParser.py @@ -25,13 +25,12 @@ from ..specs.ScriptTask import ScriptTask from ..specs.UserTask import UserTask from ..specs.events import _BoundaryEventParent, CancelEventDefinition from ..specs.MultiInstanceTask import getDynamicMIClass -from ..specs.SubWorkflowTask import CallActivity, TransactionSubprocess +from ..specs.SubWorkflowTask import CallActivity, TransactionSubprocess, SubWorkflowTask from ..specs.ExclusiveGateway import ExclusiveGateway from ...dmn.specs.BusinessRuleTask import BusinessRuleTask from ...operators import Attrib, PathAttrib from .util import one, first from .node_parser import NodeParser -from ...specs.SubWorkflow import SubWorkflow STANDARDLOOPCOUNT = '25' @@ -68,7 +67,7 @@ class TaskParser(NodeParser): # the current parser achitecture). We should also consider separate classes for loop vs # 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( f'Unsupported MultiInstance Task: {self.task.__class__}', node=self.node, diff --git a/SpiffWorkflow/bpmn/serializer/workflow_spec_converter.py b/SpiffWorkflow/bpmn/serializer/workflow_spec_converter.py index c42f06486..566fe7c8e 100644 --- a/SpiffWorkflow/bpmn/serializer/workflow_spec_converter.py +++ b/SpiffWorkflow/bpmn/serializer/workflow_spec_converter.py @@ -141,7 +141,12 @@ class BpmnProcessSpecConverter(BpmnWorkflowSpecConverter): # Add the data specs 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_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 spec.correlation_keys = dct.pop('correlation_keys', {}) diff --git a/SpiffWorkflow/bpmn/specs/SubWorkflowTask.py b/SpiffWorkflow/bpmn/specs/SubWorkflowTask.py index 5d7c3e85a..5411a265b 100644 --- a/SpiffWorkflow/bpmn/specs/SubWorkflowTask.py +++ b/SpiffWorkflow/bpmn/specs/SubWorkflowTask.py @@ -3,11 +3,10 @@ from copy import deepcopy from SpiffWorkflow.task import TaskState from .BpmnSpecMixin import BpmnSpecMixin -from ...specs.SubWorkflow import SubWorkflow from ...specs import TaskSpec -class SubWorkflowTask(SubWorkflow, BpmnSpecMixin): +class SubWorkflowTask(BpmnSpecMixin): """ 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_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.transaction = transaction @@ -36,25 +35,8 @@ class SubWorkflowTask(SubWorkflow, BpmnSpecMixin): def _on_ready_hook(self, my_task): - for obj in self.data_input_associations: - obj.get(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) + super()._on_ready_hook(my_task) + self.start_workflow(my_task) def _on_subworkflow_completed(self, subworkflow, my_task): @@ -92,6 +74,24 @@ class SubWorkflowTask(SubWorkflow, BpmnSpecMixin): if subworkflow is not None: 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): return serializer.serialize_subworkflow_task(self) diff --git a/SpiffWorkflow/spiff/specs/spiff_task.py b/SpiffWorkflow/spiff/specs/spiff_task.py index 40ca8fa9d..fe497d138 100644 --- a/SpiffWorkflow/spiff/specs/spiff_task.py +++ b/SpiffWorkflow/spiff/specs/spiff_task.py @@ -34,9 +34,9 @@ class SpiffBpmnTask(BpmnSpecMixin): raise exc def _on_ready_hook(self, my_task): + super()._on_ready_hook(my_task) if self.prescript is not None: self.execute_script(my_task, self.prescript) - super()._on_ready_hook(my_task) def _on_complete_hook(self, my_task): if self.postscript is not None: diff --git a/SpiffWorkflow/spiff/specs/subworkflow_task.py b/SpiffWorkflow/spiff/specs/subworkflow_task.py index 78dfebb96..22587aff3 100644 --- a/SpiffWorkflow/spiff/specs/subworkflow_task.py +++ b/SpiffWorkflow/spiff/specs/subworkflow_task.py @@ -1,7 +1,7 @@ from SpiffWorkflow.bpmn.specs.SubWorkflowTask import SubWorkflowTask, TransactionSubprocess, CallActivity 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): @@ -13,11 +13,19 @@ class SubWorkflowTask(SpiffBpmnTask, SubWorkflowTask): self.in_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 def spec_type(self): return 'Subprocess' -class TransactionSubprocess(SpiffBpmnTask, TransactionSubprocess): + +class TransactionSubprocess(SubWorkflowTask, TransactionSubprocess): def __init__(self, wf_spec, name, subworkflow_spec, transaction=True, **kwargs): @@ -31,7 +39,8 @@ class TransactionSubprocess(SpiffBpmnTask, TransactionSubprocess): def spec_type(self): return 'Transactional Subprocess' -class CallActivity(SpiffBpmnTask, CallActivity): + +class CallActivity(SubWorkflowTask, CallActivity): def __init__(self, wf_spec, name, subworkflow_spec, **kwargs): diff --git a/doc/conf.py b/doc/conf.py index ae9037ba0..ca10c199a 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -22,7 +22,7 @@ copyright = '2022, Sartography' author = 'Sartography' # The full version, including alpha/beta/rc tags - +release = '1.2.0' # -- General configuration --------------------------------------------------- diff --git a/setup.py b/setup.py index 03a0e4eeb..30adfa29b 100644 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ HERE = pathlib.Path(__file__).parent README = (HERE / "README.md").read_text() setup(name='SpiffWorkflow', - version='1.1.7', + version='1.2.0', description='A workflow framework and BPMN/DMN Processor', long_description=README, long_description_content_type="text/markdown", diff --git a/tests/SpiffWorkflow/spiff/PrescriptPostscriptTest.py b/tests/SpiffWorkflow/spiff/PrescriptPostscriptTest.py index a07341096..52077059a 100644 --- a/tests/SpiffWorkflow/spiff/PrescriptPostscriptTest.py +++ b/tests/SpiffWorkflow/spiff/PrescriptPostscriptTest.py @@ -17,6 +17,19 @@ class PrescriptPostsciptTest(BaseTestCase): def testCallActivitySaveRestore(self): 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): spec, subprocesses = self.load_workflow_spec('prescript_postscript.bpmn', 'Process_1') diff --git a/tests/SpiffWorkflow/spiff/data/prescript_postscript_data_object.bpmn b/tests/SpiffWorkflow/spiff/data/prescript_postscript_data_object.bpmn new file mode 100644 index 000000000..8dce85b1f --- /dev/null +++ b/tests/SpiffWorkflow/spiff/data/prescript_postscript_data_object.bpmn @@ -0,0 +1,65 @@ + + + + + + Flow_1hjrex4 + + + + x = a * 2 +y = b * 2 +z = x + y + c = z * 2 +del x +del y + + Flow_1hjrex4 + Flow_1xndbxy + + + DataObjectReference_15lldo2 + Property_1bj3qrk + + + + + Flow_1xndbxy + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +