From 15ea564a3393b9036f3dc59b0cb0ccc9e64c740b Mon Sep 17 00:00:00 2001 From: Elizabeth Esswein Date: Tue, 12 Sep 2023 17:26:16 -0400 Subject: [PATCH 1/6] move spec converters into one package --- .../serializer/{ => default}/data_spec.py | 7 ++- .../{ => default}/event_definition.py | 2 +- .../serializer/{ => default}/process_spec.py | 4 +- .../serializer/{ => default}/task_spec.py | 2 +- .../bpmn/serializer/helpers/registry.py | 60 +++++++++++++++--- SpiffWorkflow/bpmn/serializer/helpers/spec.py | 61 +++---------------- SpiffWorkflow/bpmn/serializer/workflow.py | 8 +-- SpiffWorkflow/camunda/serializer/config.py | 4 +- SpiffWorkflow/camunda/serializer/task_spec.py | 4 +- SpiffWorkflow/spiff/serializer/config.py | 4 +- SpiffWorkflow/spiff/serializer/task_spec.py | 4 +- .../SpiffWorkflow/bpmn/BpmnLoaderForTests.py | 5 +- 12 files changed, 84 insertions(+), 81 deletions(-) rename SpiffWorkflow/bpmn/serializer/{ => default}/data_spec.py (87%) rename SpiffWorkflow/bpmn/serializer/{ => default}/event_definition.py (99%) rename SpiffWorkflow/bpmn/serializer/{ => default}/process_spec.py (96%) rename SpiffWorkflow/bpmn/serializer/{ => default}/task_spec.py (99%) diff --git a/SpiffWorkflow/bpmn/serializer/data_spec.py b/SpiffWorkflow/bpmn/serializer/default/data_spec.py similarity index 87% rename from SpiffWorkflow/bpmn/serializer/data_spec.py rename to SpiffWorkflow/bpmn/serializer/default/data_spec.py index 0a9574e3..35bd9ef5 100644 --- a/SpiffWorkflow/bpmn/serializer/data_spec.py +++ b/SpiffWorkflow/bpmn/serializer/default/data_spec.py @@ -17,8 +17,9 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA -from ..specs.data_spec import DataObject, TaskDataReference, BpmnIoSpecification -from .helpers.spec import BpmnSpecConverter, BpmnDataSpecificationConverter +from SpiffWorkflow.bpmn.specs.data_spec import DataObject, TaskDataReference, BpmnIoSpecification +from ..helpers.registry import BpmnConverter +from ..helpers.spec import BpmnDataSpecificationConverter class BpmnDataObjectConverter(BpmnDataSpecificationConverter): @@ -31,7 +32,7 @@ def __init__(self, registry): super().__init__(TaskDataReference, registry) -class IOSpecificationConverter(BpmnSpecConverter): +class IOSpecificationConverter(BpmnConverter): def __init__(self, registry): super().__init__(BpmnIoSpecification, registry) diff --git a/SpiffWorkflow/bpmn/serializer/event_definition.py b/SpiffWorkflow/bpmn/serializer/default/event_definition.py similarity index 99% rename from SpiffWorkflow/bpmn/serializer/event_definition.py rename to SpiffWorkflow/bpmn/serializer/default/event_definition.py index cdbb50b8..a021f7e6 100644 --- a/SpiffWorkflow/bpmn/serializer/event_definition.py +++ b/SpiffWorkflow/bpmn/serializer/default/event_definition.py @@ -35,7 +35,7 @@ from SpiffWorkflow.bpmn.specs.event_definitions.message import MessageEventDefinition from SpiffWorkflow.bpmn.specs.event_definitions.multiple import MultipleEventDefinition -from .helpers.spec import EventDefinitionConverter +from ..helpers.spec import EventDefinitionConverter class CancelEventDefinitionConverter(EventDefinitionConverter): def __init__(self, registry): diff --git a/SpiffWorkflow/bpmn/serializer/process_spec.py b/SpiffWorkflow/bpmn/serializer/default/process_spec.py similarity index 96% rename from SpiffWorkflow/bpmn/serializer/process_spec.py rename to SpiffWorkflow/bpmn/serializer/default/process_spec.py index c4bd9bab..30d27b28 100644 --- a/SpiffWorkflow/bpmn/serializer/process_spec.py +++ b/SpiffWorkflow/bpmn/serializer/default/process_spec.py @@ -19,7 +19,7 @@ from SpiffWorkflow.bpmn.specs.bpmn_process_spec import BpmnProcessSpec -from .helpers.spec import WorkflowSpecConverter +from ..helpers.spec import WorkflowSpecConverter class BpmnProcessSpecConverter(WorkflowSpecConverter): @@ -56,7 +56,7 @@ def to_dict(self, spec): def from_dict(self, dct): - spec = self.spec_class(name=dct['name'], description=dct['description'], filename=dct['file']) + spec = self.target_class(name=dct['name'], description=dct['description'], filename=dct['file']) # These are automatically created with a workflow and should be replaced del spec.task_specs['Start'] spec.start = None diff --git a/SpiffWorkflow/bpmn/serializer/task_spec.py b/SpiffWorkflow/bpmn/serializer/default/task_spec.py similarity index 99% rename from SpiffWorkflow/bpmn/serializer/task_spec.py rename to SpiffWorkflow/bpmn/serializer/default/task_spec.py index 6e1d5386..6c995599 100644 --- a/SpiffWorkflow/bpmn/serializer/task_spec.py +++ b/SpiffWorkflow/bpmn/serializer/default/task_spec.py @@ -50,7 +50,7 @@ ReceiveTask, ) -from .helpers.spec import TaskSpecConverter +from ..helpers.spec import TaskSpecConverter class BpmnTaskSpecConverter(TaskSpecConverter): diff --git a/SpiffWorkflow/bpmn/serializer/helpers/registry.py b/SpiffWorkflow/bpmn/serializer/helpers/registry.py index e2998cf5..2acbedaf 100644 --- a/SpiffWorkflow/bpmn/serializer/helpers/registry.py +++ b/SpiffWorkflow/bpmn/serializer/helpers/registry.py @@ -39,14 +39,58 @@ def __init__(self): self.register(timedelta, lambda v: { 'days': v.days, 'seconds': v.seconds }, lambda v: timedelta(**v)) def convert(self, obj): - self.clean(obj) - return super().convert(obj) + cleaned = self.clean(obj) + return super().convert(cleaned) def clean(self, obj): - # This removes functions and other callables from task data. - # By default we don't want to serialize these + # This can be used to remove functions and other callables; by default we remove these from task data if isinstance(obj, dict): - items = [ (k, v) for k, v in obj.items() ] - for key, value in items: - if callable(value): - del obj[key] \ No newline at end of file + return dict((k, v) for k, v in obj.items() if not callable(v)) + else: + return obj + + +class BpmnConverter: + """The base class for conversion of BPMN classes. + + In general, most classes that extend this would simply take an existing registry as an + argument and automatically supply the class along with the implementations of the + conversion functions `to_dict` and `from_dict`. + + The operation of the converter is a little opaque, but hopefully makes sense with a little + explanation. + + The registry is a `DictionaryConverter` that registers conversion methods by class. It can be + pre-populated with methods for custom data (though this is not required) and is passed into + each of these sublclasses. When a subclass of this one gets instantiated, it adds the spec it + is intended to operate on to this registry. + + There is a lot of interdependence across the classes in spiff -- most of them need to know about + many of the other classes. Subclassing this is intended to consolidate the boiler plate required + to set up a global registry that is usable by any other registered class. + + The goal is to be able to replace the conversion mechanism for a particular entity without + delving into the details of other things spiff knows about. + + So for example, it is not necessary to re-implemnent any of the event-based task spec conversions + because, eg, the `MessageEventDefintion` was modified; the existing `MessageEventDefinitionConverter` + can be replaced with a customized one and it will automatically be used when the event specs are + transformed. + """ + def __init__(self, target_class, registry, typename=None): + """Constructor for a BPMN class. + + :param spec_class: the class of the spec the subclass provides conversions for + :param registry: a registry of conversions to which this one should be added + :param typename: the name of the class as it will appear in the serialization + """ + self.target_class = target_class + self.registry = registry + self.typename = typename if typename is not None else target_class.__name__ + self.registry.register(target_class, self.to_dict, self.from_dict, self.typename) + + def to_dict(self, spec): + raise NotImplementedError + + def from_dict(self, dct): + raise NotImplementedError \ No newline at end of file diff --git a/SpiffWorkflow/bpmn/serializer/helpers/spec.py b/SpiffWorkflow/bpmn/serializer/helpers/spec.py index 68f47672..54f9a628 100644 --- a/SpiffWorkflow/bpmn/serializer/helpers/spec.py +++ b/SpiffWorkflow/bpmn/serializer/helpers/spec.py @@ -23,53 +23,10 @@ from SpiffWorkflow.bpmn.specs.mixins.bpmn_spec_mixin import BpmnSpecMixin from SpiffWorkflow.bpmn.specs.event_definitions.message import CorrelationProperty +from .registry import BpmnConverter -class BpmnSpecConverter: - """The base class for conversion of BPMN spec classes. - - In general, most classes that extend this would simply take an existing registry as an - argument and automatically supply the class along with the implementations of the - conversion functions `to_dict` and `from_dict`. - - The operation of the spec converter is a little opaque, but hopefully makes sense with a - little explanation. - - The registry is a `DictionaryConverter` that registers conversion methods by class. It can be - pre-populated with methods for custom data (though this is not required) and is passed into - each of these sublclasses. When a subclass of this one gets instantiated, it adds itself - to this registry. - - This seems a little bit backwards -- the registry is using the subclass, so it seems like we - ought to pass the subclass to the registry. However, there is a lot of interdependence across - the spec classes, so this doesn't work that well in practice -- most classes need to know about - all the other classes, and this was the most concise way I could think of to make that happen. - - The goal is to be able to replace almost any spec class at the top level without classes that - use it to reimplement conversion mechanisms. So for example, it is not necessary to - re-implemnent all event-based task spec conversions because, eg, the - `MessageEventDefintion` was modified. - """ - def __init__(self, spec_class, registry, typename=None): - """Constructor for a BPMN spec. - - :param spec_class: the class of the spec the subclass provides conversions for - :param registry: a registry of conversions to which this one should be added - :param typename: the name of the class as it will appear in the serialization - """ - self.spec_class = spec_class - self.registry = registry - self.typename = typename if typename is not None else spec_class.__name__ - self.registry.register(spec_class, self.to_dict, self.from_dict, self.typename) - - def to_dict(self, spec): - raise NotImplementedError - - def from_dict(self, dct): - raise NotImplementedError - - -class BpmnDataSpecificationConverter(BpmnSpecConverter): +class BpmnDataSpecificationConverter(BpmnConverter): """This is the base Data Spec converter. Currently the only use is Data Objects. @@ -79,10 +36,10 @@ def to_dict(self, data_spec): return { 'bpmn_id': data_spec.bpmn_id, 'bpmn_name': data_spec.bpmn_name } def from_dict(self, dct): - return self.spec_class(**dct) + return self.target_class(**dct) -class EventDefinitionConverter(BpmnSpecConverter): +class EventDefinitionConverter(BpmnConverter): """This is the base Event Defintiion Converter. It provides conversions for the great majority of BPMN events as-is, and contains @@ -98,7 +55,7 @@ def to_dict(self, event_definition): return dct def from_dict(self, dct): - event_definition = self.spec_class(**dct) + event_definition = self.target_class(**dct) return event_definition def correlation_properties_to_dict(self, props): @@ -108,7 +65,7 @@ def correlation_properties_from_dict(self, props): return [CorrelationProperty(**prop) for prop in props] -class TaskSpecConverter(BpmnSpecConverter): +class TaskSpecConverter(BpmnConverter): """ This the base Task Spec Converter. @@ -205,11 +162,11 @@ def task_spec_from_dict(self, dct): name = dct.pop('name') bpmn_id = dct.pop('bpmn_id') - spec = self.spec_class(wf_spec, name, **dct) + spec = self.target_class(wf_spec, name, **dct) spec._inputs = inputs spec._outputs = outputs - if issubclass(self.spec_class, BpmnSpecMixin) and bpmn_id != name: + if issubclass(self.target_class, BpmnSpecMixin) and bpmn_id != name: # This is a hack for multiinstance tasks :( At least it is simple. # Ideally I'd fix it in the parser, but I'm afraid of quickly running into a wall there spec.bpmn_id = bpmn_id @@ -220,7 +177,7 @@ def task_spec_from_dict(self, dct): return spec -class WorkflowSpecConverter(BpmnSpecConverter): +class WorkflowSpecConverter(BpmnConverter): """ This is the base converter for a BPMN workflow spec. diff --git a/SpiffWorkflow/bpmn/serializer/workflow.py b/SpiffWorkflow/bpmn/serializer/workflow.py index 5e05156d..97d5c87d 100644 --- a/SpiffWorkflow/bpmn/serializer/workflow.py +++ b/SpiffWorkflow/bpmn/serializer/workflow.py @@ -31,10 +31,10 @@ from .helpers.registry import DefaultRegistry from .helpers.dictionary import DictionaryConverter -from .process_spec import BpmnProcessSpecConverter -from .data_spec import BpmnDataObjectConverter, TaskDataReferenceConverter, IOSpecificationConverter -from .task_spec import DEFAULT_TASK_SPEC_CONVERTER_CLASSES -from .event_definition import DEFAULT_EVENT_CONVERTERS +from .default.process_spec import BpmnProcessSpecConverter +from .default.data_spec import BpmnDataObjectConverter, TaskDataReferenceConverter, IOSpecificationConverter +from .default.task_spec import DEFAULT_TASK_SPEC_CONVERTER_CLASSES +from .default.event_definition import DEFAULT_EVENT_CONVERTERS DEFAULT_SPEC_CONFIG = { 'process': BpmnProcessSpecConverter, diff --git a/SpiffWorkflow/camunda/serializer/config.py b/SpiffWorkflow/camunda/serializer/config.py index 53ac29b2..172ded9d 100644 --- a/SpiffWorkflow/camunda/serializer/config.py +++ b/SpiffWorkflow/camunda/serializer/config.py @@ -20,12 +20,12 @@ from copy import deepcopy from SpiffWorkflow.bpmn.serializer.workflow import DEFAULT_SPEC_CONFIG -from SpiffWorkflow.bpmn.serializer.task_spec import ( +from SpiffWorkflow.bpmn.serializer.default.task_spec import ( UserTaskConverter as DefaultUserTaskConverter, ParallelMultiInstanceTaskConverter as DefaultParallelMIConverter, SequentialMultiInstanceTaskConverter as DefaultSequentialMIConverter, ) -from SpiffWorkflow.bpmn.serializer.event_definition import MessageEventDefinitionConverter as DefaultMessageEventConverter +from SpiffWorkflow.bpmn.serializer.default.event_definition import MessageEventDefinitionConverter as DefaultMessageEventConverter from .task_spec import ( UserTaskConverter, diff --git a/SpiffWorkflow/camunda/serializer/task_spec.py b/SpiffWorkflow/camunda/serializer/task_spec.py index 598fc8d1..b1e61dd4 100644 --- a/SpiffWorkflow/camunda/serializer/task_spec.py +++ b/SpiffWorkflow/camunda/serializer/task_spec.py @@ -18,7 +18,7 @@ # 02110-1301 USA from SpiffWorkflow.bpmn.serializer.helpers.spec import TaskSpecConverter -from SpiffWorkflow.bpmn.serializer.task_spec import MultiInstanceTaskConverter +from SpiffWorkflow.bpmn.serializer.default.task_spec import MultiInstanceTaskConverter from SpiffWorkflow.dmn.serializer.task_spec import BaseBusinessRuleTaskConverter from SpiffWorkflow.camunda.specs.user_task import UserTask, Form @@ -66,4 +66,4 @@ def __init__(self, registry): class SequentialMultiInstanceTaskConverter(MultiInstanceTaskConverter): def __init__(self, registry): - super().__init__(SequentialMultiInstanceTask, registry) \ No newline at end of file + super().__init__(SequentialMultiInstanceTask, registry) diff --git a/SpiffWorkflow/spiff/serializer/config.py b/SpiffWorkflow/spiff/serializer/config.py index 8e3c3fc6..0c7e812b 100644 --- a/SpiffWorkflow/spiff/serializer/config.py +++ b/SpiffWorkflow/spiff/serializer/config.py @@ -20,7 +20,7 @@ from copy import deepcopy from SpiffWorkflow.bpmn.serializer.workflow import DEFAULT_SPEC_CONFIG -from SpiffWorkflow.bpmn.serializer.task_spec import ( +from SpiffWorkflow.bpmn.serializer.default.task_spec import ( SimpleBpmnTaskConverter, BpmnStartTaskConverter, EndJoinConverter, @@ -54,7 +54,7 @@ BusinessRuleTaskConverter, ) -from SpiffWorkflow.bpmn.serializer.event_definition import ( +from SpiffWorkflow.bpmn.serializer.default.event_definition import ( MessageEventDefinitionConverter as DefaultMessageEventDefinitionConverter, SignalEventDefinitionConverter as DefaultSignalEventDefinitionConverter, ErrorEventDefinitionConverter as DefaultErrorEventDefinitionConverter, diff --git a/SpiffWorkflow/spiff/serializer/task_spec.py b/SpiffWorkflow/spiff/serializer/task_spec.py index 6cfe3886..9a28d3c1 100644 --- a/SpiffWorkflow/spiff/serializer/task_spec.py +++ b/SpiffWorkflow/spiff/serializer/task_spec.py @@ -18,7 +18,7 @@ # 02110-1301 USA from SpiffWorkflow.bpmn.serializer.helpers.spec import TaskSpecConverter -from SpiffWorkflow.bpmn.serializer.task_spec import MultiInstanceTaskConverter +from SpiffWorkflow.bpmn.serializer.default.task_spec import MultiInstanceTaskConverter from SpiffWorkflow.dmn.serializer.task_spec import BaseBusinessRuleTaskConverter from SpiffWorkflow.spiff.specs.defaults import ( @@ -177,4 +177,4 @@ def __init__(self, registry): class SequentialMultiInstanceTaskConverter(SpiffMultiInstanceConverter): def __init__(self, registry): - super().__init__(SequentialMultiInstanceTask, registry) \ No newline at end of file + super().__init__(SequentialMultiInstanceTask, registry) diff --git a/tests/SpiffWorkflow/bpmn/BpmnLoaderForTests.py b/tests/SpiffWorkflow/bpmn/BpmnLoaderForTests.py index 2432b0b9..094d5bbe 100644 --- a/tests/SpiffWorkflow/bpmn/BpmnLoaderForTests.py +++ b/tests/SpiffWorkflow/bpmn/BpmnLoaderForTests.py @@ -8,7 +8,8 @@ from SpiffWorkflow.bpmn.parser.task_parsers import ConditionalGatewayParser from SpiffWorkflow.bpmn.parser.util import full_tag -from SpiffWorkflow.bpmn.serializer.helpers.spec import BpmnSpecConverter, TaskSpecConverter +from SpiffWorkflow.bpmn.serializer.helpers.registry import BpmnConverter +from SpiffWorkflow.bpmn.serializer.helpers.spec import TaskSpecConverter __author__ = 'matth' @@ -64,7 +65,7 @@ def set(self, my_task): TestDataStore._value = my_task.data[self.bpmn_id] del my_task.data[self.bpmn_id] -class TestDataStoreConverter(BpmnSpecConverter): +class TestDataStoreConverter(BpmnConverter): def __init__(self, registry): super().__init__(TestDataStore, registry) From 4d36f1f7836803ba917b8e5f961227bef1c60f51 Mon Sep 17 00:00:00 2001 From: Elizabeth Esswein Date: Thu, 14 Sep 2023 11:29:50 -0400 Subject: [PATCH 2/6] split workflow & task conversion from main serializer --- .../bpmn/serializer/default/task_spec.py | 2 +- .../bpmn/serializer/default/workflow.py | 183 +++++++++++++ .../bpmn/serializer/helpers/dictionary.py | 24 +- .../bpmn/serializer/helpers/registry.py | 13 +- .../bpmn/serializer/migration/version_1_3.py | 11 + .../serializer/migration/version_migration.py | 49 ++-- SpiffWorkflow/bpmn/serializer/workflow.py | 250 ++++-------------- SpiffWorkflow/camunda/serializer/config.py | 22 +- SpiffWorkflow/spiff/serializer/config.py | 60 +++-- .../bpmn/BpmnWorkflowTestCase.py | 16 +- .../bpmn/serializer/BaseTestCase.py | 2 +- .../serializer/BpmnWorkflowSerializerTest.py | 4 +- tests/SpiffWorkflow/camunda/BaseTestCase.py | 7 +- tests/SpiffWorkflow/spiff/BaseTestCase.py | 6 +- 14 files changed, 357 insertions(+), 292 deletions(-) create mode 100644 SpiffWorkflow/bpmn/serializer/default/workflow.py diff --git a/SpiffWorkflow/bpmn/serializer/default/task_spec.py b/SpiffWorkflow/bpmn/serializer/default/task_spec.py index 6c995599..2c4d3553 100644 --- a/SpiffWorkflow/bpmn/serializer/default/task_spec.py +++ b/SpiffWorkflow/bpmn/serializer/default/task_spec.py @@ -307,7 +307,7 @@ def __init__(self, registry): super().__init__(EventBasedGateway, registry) -DEFAULT_TASK_SPEC_CONVERTER_CLASSES = [ +DEFAULT_TASK_SPEC_CONVERTERS = [ SimpleBpmnTaskConverter, BpmnStartTaskConverter, EndJoinConverter, diff --git a/SpiffWorkflow/bpmn/serializer/default/workflow.py b/SpiffWorkflow/bpmn/serializer/default/workflow.py new file mode 100644 index 00000000..0b1fa1d8 --- /dev/null +++ b/SpiffWorkflow/bpmn/serializer/default/workflow.py @@ -0,0 +1,183 @@ +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +from uuid import UUID + +from SpiffWorkflow.task import Task +from SpiffWorkflow.bpmn.workflow import BpmnWorkflow, BpmnSubWorkflow +from SpiffWorkflow.bpmn.event import BpmnEvent +from SpiffWorkflow.bpmn.specs.mixins.subworkflow_task import SubWorkflowTask + +from ..helpers.registry import BpmnConverter + +class TaskConverter(BpmnConverter): + + def __init__(self, registry): + super().__init__(Task, registry) + + def to_dict(self, task): + return { + 'id': str(task.id), + 'parent': str(task._parent) if task.parent is not None else None, + 'children': [ str(child) for child in task._children ], + 'last_state_change': task.last_state_change, + 'state': task.state, + 'task_spec': task.task_spec.name, + 'triggered': task.triggered, + 'internal_data': self.registry.convert(task.internal_data), + 'data': self.registry.convert(self.registry.clean(task.data)), + } + + def from_dict(self, dct, workflow): + task_spec = workflow.spec.task_specs.get(dct['task_spec']) + task = Task(workflow, task_spec, state=dct['state'], id=UUID(dct['id'])) + task._parent = UUID(dct['parent']) if dct['parent'] is not None else None + task._children = [UUID(child) for child in dct['children']] + task.last_state_change = dct['last_state_change'] + task.triggered = dct['triggered'] + task.internal_data = self.registry.restore(dct['internal_data']) + task.data = self.registry.restore(dct['data']) + return task + + +class BpmnEventConverter(BpmnConverter): + + def __init__(self, registry): + super().__init__(BpmnEvent, registry) + + def to_dict(self, event): + return { + 'event_definition': self.registry.convert(event.event_definition), + 'payload': self.registry.convert(event.payload), + 'correlations': self.mapping_to_dict(event.correlations), + } + + def from_dict(self, dct): + return BpmnEvent( + self.registry.restore(dct['event_definition']), + self.registry.restore(dct['payload']), + self.mapping_from_dict(dct['correlations']) + ) + + +class WorkflowConverter(BpmnConverter): + + def to_dict(self, workflow): + """Get a dictionary of attributes associated with both top level and subprocesses""" + return { + 'data': self.registry.convert(self.registry.clean(workflow.data)), + 'correlations': workflow.correlations, + 'last_task': str(workflow.last_task.id) if workflow.last_task is not None else None, + 'success': workflow.success, + 'tasks': self.mapping_to_dict(workflow.tasks), + 'root': str(workflow.task_tree.id), + } + + +class BpmnSubWorkflowConverter(WorkflowConverter): + + def __init__(self, registry): + super().__init__(BpmnSubWorkflow, registry) + + def to_dict(self, workflow): + dct = super().to_dict(workflow) + dct['parent_task_id'] = str(workflow.parent_task_id) + dct['spec'] = workflow.spec.name + return dct + + def from_dict(self, dct, task, top_workflow): + spec = top_workflow.subprocess_specs.get(task.task_spec.spec) + subprocess = self.target_class(spec, task.id, top_workflow, deserializing=True) + subprocess.correlations = dct.pop('correlations', {}) + subprocess.tasks = self.mapping_from_dict(dct['tasks'], UUID, workflow=subprocess) + subprocess.task_tree = subprocess.tasks.get(UUID(dct['root'])) + if isinstance(dct['last_task'], str): + subprocess.last_task = subprocess.tasks.get(UUID(dct['last_task'])) + subprocess.success = dct['success'] + subprocess.data = self.registry.restore(dct['data']) + return subprocess + + +class BpmnWorkflowConverter(WorkflowConverter): + + def __init__(self, registry): + super().__init__(BpmnWorkflow, registry) + + def to_dict(self, workflow): + """Return a JSON-serializable dictionary representation of the workflow. + + :param workflow: the workflow + + Returns: + a dictionary representation of the workflow + """ + dct = super().to_dict(workflow) + dct['spec'] = self.registry.convert(workflow.spec) + dct['subprocess_specs'] = self.mapping_to_dict(workflow.subprocess_specs) + dct['subprocesses'] = self.mapping_to_dict(workflow.subprocesses) + dct['bpmn_events'] = self.registry.convert(workflow.bpmn_events) + return dct + + def from_dict(self, dct): + """Create a workflow based on a dictionary representation. + + :param dct: the dictionary representation + + Returns: + a BPMN Workflow object + """ + # Restore the top level spec and the subprocess specs + spec = self.registry.restore(dct.pop('spec')) + subprocess_specs = self.mapping_from_dict(dct.pop('subprocess_specs', {})) + + # Create the top-level workflow + workflow = self.target_class(spec, subprocess_specs, deserializing=True) + + # Restore the remainder of the workflow + workflow.bpmn_events = self.registry.restore(dct.pop('bpmn_events', [])) + workflow.correlations = dct.pop('correlations', {}) + workflow.data = self.registry.restore(dct.pop('data', {})) + workflow.success = dct.pop('success') + workflow.tasks = self.mapping_from_dict(dct['tasks'], UUID, workflow=workflow) + workflow.task_tree = workflow.tasks.get(UUID(dct['root'])) + if dct['last_task'] is not None: + workflow.last_task = workflow.tasks.get(UUID(dct['last_task'])) + + self.subprocesses_from_dict(dct['subprocesses'], workflow) + return workflow + + def subprocesses_from_dict(self, dct, workflow, top_workflow=None): + # This ensures we create parent workflows before their children; we need the tasks they're associated with + top_workflow = top_workflow or workflow + for task in workflow.tasks.values(): + if isinstance(task.task_spec, SubWorkflowTask) and str(task.id) in dct: + sp = self.registry.restore(dct.pop(str(task.id)), task=task, top_workflow=top_workflow) + top_workflow.subprocesses[task.id] = sp + sp.completed_event.connect(task.task_spec._on_subworkflow_completed, task) + if len(sp.spec.data_objects) > 0: + sp.data = task.workflow.data + self.subprocesses_from_dict(dct, sp, top_workflow) + + +DEFAULT_WORKFLOW_CONVERTERS = [ + BpmnWorkflowConverter, + BpmnSubWorkflowConverter, + TaskConverter, + BpmnEventConverter, +] \ No newline at end of file diff --git a/SpiffWorkflow/bpmn/serializer/helpers/dictionary.py b/SpiffWorkflow/bpmn/serializer/helpers/dictionary.py index 57691e11..d2af5b28 100644 --- a/SpiffWorkflow/bpmn/serializer/helpers/dictionary.py +++ b/SpiffWorkflow/bpmn/serializer/helpers/dictionary.py @@ -67,16 +67,16 @@ def register(self, cls, to_dict, from_dict, typename=None): self.convert_from_dict[typename] = partial(self.obj_from_dict, from_dict) @staticmethod - def obj_to_dict(typename, func, obj): - dct = func(obj) + def obj_to_dict(typename, func, obj, **kwargs): + dct = func(obj, **kwargs) dct.update({'typename': typename}) return dct @staticmethod - def obj_from_dict(func, dct): - return func(dct) + def obj_from_dict(func, dct, **kwargs): + return func(dct, **kwargs) - def convert(self, obj): + def convert(self, obj, **kwargs): """ This is the public conversion method. It will be applied to dictionary values, list items, and the object itself, applying the to_dict functions @@ -92,15 +92,15 @@ def convert(self, obj): typename = self.typenames.get(obj.__class__) if typename in self.convert_to_dict: to_dict = self.convert_to_dict.get(typename) - return to_dict(obj) + return to_dict(obj, **kwargs) elif isinstance(obj, dict): - return dict((k, self.convert(v)) for k, v in obj.items()) + return dict((k, self.convert(v, **kwargs)) for k, v in obj.items()) elif isinstance(obj, (list, tuple, set)): - return obj.__class__([ self.convert(item) for item in obj ]) + return obj.__class__([ self.convert(item, **kwargs) for item in obj ]) else: return obj - def restore(self, val): + def restore(self, val, **kwargs): """ This is the public restoration method. It will be applied to dictionary values, list items, and the value itself, checking for a `typename` key and @@ -115,10 +115,10 @@ def restore(self, val): """ if isinstance(val, dict) and 'typename' in val: from_dict = self.convert_from_dict.get(val.pop('typename')) - return from_dict(val) + return from_dict(val, **kwargs) elif isinstance(val, dict): - return dict((k, self.restore(v)) for k, v in val.items()) + return dict((k, self.restore(v, **kwargs)) for k, v in val.items()) if isinstance(val, (list, tuple, set)): - return val.__class__([ self.restore(item) for item in val ]) + return val.__class__([ self.restore(item, **kwargs) for item in val ]) else: return val diff --git a/SpiffWorkflow/bpmn/serializer/helpers/registry.py b/SpiffWorkflow/bpmn/serializer/helpers/registry.py index 2acbedaf..57890a89 100644 --- a/SpiffWorkflow/bpmn/serializer/helpers/registry.py +++ b/SpiffWorkflow/bpmn/serializer/helpers/registry.py @@ -93,4 +93,15 @@ def to_dict(self, spec): raise NotImplementedError def from_dict(self, dct): - raise NotImplementedError \ No newline at end of file + raise NotImplementedError + + def mapping_to_dict(self, mapping, **kwargs): + """Convert both the key and value of a dict with keys that can be converted to strings.""" + return dict((str(k), self.registry.convert(v, **kwargs)) for k, v in mapping.items()) + + def mapping_from_dict(self, mapping, key_class=None, **kwargs): + """Restore a mapping from a dictionary of strings -> objects.""" + if key_class is None: + return dict((k, self.registry.restore(v, **kwargs)) for k, v in mapping.items()) + else: + return dict((key_class(k), self.registry.restore(v, **kwargs)) for k, v in mapping.items()) \ No newline at end of file diff --git a/SpiffWorkflow/bpmn/serializer/migration/version_1_3.py b/SpiffWorkflow/bpmn/serializer/migration/version_1_3.py index 55da3b36..7bfd4781 100644 --- a/SpiffWorkflow/bpmn/serializer/migration/version_1_3.py +++ b/SpiffWorkflow/bpmn/serializer/migration/version_1_3.py @@ -122,3 +122,14 @@ def update(wf): dct['spec']['task_specs'].pop('Root', None) for spec in dct['subprocess_specs'].values(): spec['task_specs'].pop('Root', None) + +def add_new_typenames(dct): + + dct['typename'] = 'BpmnWorkflow' + for task in dct['tasks'].values(): + task['typename'] = 'Task' + + for sp in dct['subprocesses'].values(): + sp['typename'] = 'BpmnSubWorkflow' + for task in sp['tasks'].values(): + task['typename'] = 'Task' \ No newline at end of file diff --git a/SpiffWorkflow/bpmn/serializer/migration/version_migration.py b/SpiffWorkflow/bpmn/serializer/migration/version_migration.py index 2207ca88..7aaf98df 100644 --- a/SpiffWorkflow/bpmn/serializer/migration/version_migration.py +++ b/SpiffWorkflow/bpmn/serializer/migration/version_migration.py @@ -17,8 +17,6 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA -from copy import deepcopy - from .version_1_1 import move_subprocesses_to_top from .version_1_2 import ( convert_timer_expressions, @@ -34,17 +32,18 @@ update_event_definition_attributes, remove_boundary_event_parent, remove_root_task, + add_new_typenames, ) -def from_version_1_2(old): - new = deepcopy(old) - update_event_definition_attributes(new) - remove_boundary_event_parent(new) - remove_root_task(new) - new['VERSION'] = "1.3" - return new +def from_version_1_2(dct): + dct['VERSION'] = "1.3" + update_event_definition_attributes(dct) + remove_boundary_event_parent(dct) + remove_root_task(dct) + add_new_typenames(dct) + -def from_version_1_1(old): +def from_version_1_1(dct): """ Upgrade v1.1 serialization to v1.2. @@ -65,19 +64,18 @@ def from_version_1_1(old): Loop reset tasks were removed. """ - new = deepcopy(old) - convert_timer_expressions(new) - add_default_condition_to_cond_task_specs(new) - create_data_objects_and_io_specs(new) - check_multiinstance(new) - remove_loop_reset(new) - update_task_states(new) - convert_simple_tasks(new) - update_bpmn_attributes(new) - new['VERSION'] = "1.2" - return from_version_1_2(new) + dct['VERSION'] = "1.2" + convert_timer_expressions(dct) + add_default_condition_to_cond_task_specs(dct) + create_data_objects_and_io_specs(dct) + check_multiinstance(dct) + remove_loop_reset(dct) + update_task_states(dct) + convert_simple_tasks(dct) + update_bpmn_attributes(dct) + from_version_1_2(dct) -def from_version_1_0(old): +def from_version_1_0(dct): """ Upgrade v1.0 serializations to v1.1. @@ -90,10 +88,9 @@ def from_version_1_0(old): task list and add them to the appropriate subprocess and recreate the remaining subprocess attributes based on the task states. """ - new = deepcopy(old) - new['VERSION'] = "1.1" - move_subprocesses_to_top(new) - return from_version_1_1(new) + dct['VERSION'] = "1.1" + move_subprocesses_to_top(dct) + from_version_1_1(dct) MIGRATIONS = { '1.0': from_version_1_0, diff --git a/SpiffWorkflow/bpmn/serializer/workflow.py b/SpiffWorkflow/bpmn/serializer/workflow.py index 97d5c87d..cf51e754 100644 --- a/SpiffWorkflow/bpmn/serializer/workflow.py +++ b/SpiffWorkflow/bpmn/serializer/workflow.py @@ -17,31 +17,26 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA -import json -import gzip -from copy import deepcopy -from uuid import UUID - -from SpiffWorkflow.task import Task -from SpiffWorkflow.bpmn.workflow import BpmnWorkflow, BpmnSubWorkflow -from SpiffWorkflow.bpmn.event import BpmnEvent -from SpiffWorkflow.bpmn.specs.mixins.subworkflow_task import SubWorkflowTask +import json, gzip from .migration.version_migration import MIGRATIONS from .helpers.registry import DefaultRegistry -from .helpers.dictionary import DictionaryConverter from .default.process_spec import BpmnProcessSpecConverter from .default.data_spec import BpmnDataObjectConverter, TaskDataReferenceConverter, IOSpecificationConverter -from .default.task_spec import DEFAULT_TASK_SPEC_CONVERTER_CLASSES +from .default.task_spec import DEFAULT_TASK_SPEC_CONVERTERS from .default.event_definition import DEFAULT_EVENT_CONVERTERS +from .default.workflow import DEFAULT_WORKFLOW_CONVERTERS + +DEFAULT_CONFIG = DEFAULT_WORKFLOW_CONVERTERS + DEFAULT_TASK_SPEC_CONVERTERS + DEFAULT_EVENT_CONVERTERS + [ + BpmnProcessSpecConverter, + BpmnDataObjectConverter, + TaskDataReferenceConverter, + IOSpecificationConverter +] -DEFAULT_SPEC_CONFIG = { - 'process': BpmnProcessSpecConverter, - 'data_specs': [IOSpecificationConverter, BpmnDataObjectConverter, TaskDataReferenceConverter], - 'task_specs': DEFAULT_TASK_SPEC_CONVERTER_CLASSES, - 'event_definitions': DEFAULT_EVENT_CONVERTERS, -} +# This is the default version set on the workflow, it can be overwritten in init +VERSION = "1.3" class BpmnWorkflowSerializer: @@ -71,14 +66,10 @@ class BpmnWorkflowSerializer: overhead of converting or restoring the entire thing. """ - # This is the default version set on the workflow, it can be overwritten in init - VERSION = "1.3" - VERSION_KEY = "serializer_version" - DEFAULT_JSON_ENCODER_CLS = None - DEFAULT_JSON_DECODER_CLS = None + VERSION_KEY = "serializer_version" # Why is this customizable? @staticmethod - def configure_workflow_spec_converter(spec_config=None, registry=None): + def configure(config=None, registry=None): """ This method can be used to create a spec converter that uses custom specs. @@ -97,29 +88,22 @@ def configure_workflow_spec_converter(spec_config=None, registry=None): :param spec_config: a dictionary specifying how to save and restore any classes used by the spec :param registry: a `DictionaryConverter` with conversions for custom data (if applicable) """ - config = spec_config or DEFAULT_SPEC_CONFIG - spec_converter = registry or DictionaryConverter() - config['process'](spec_converter) - for cls in config['data_specs'] + config['task_specs'] + config['event_definitions']: - cls(spec_converter) - return spec_converter - - def __init__(self, spec_converter=None, data_converter=None, wf_class=None, sub_wf_class=None, version=VERSION, - json_encoder_cls=DEFAULT_JSON_ENCODER_CLS, json_decoder_cls=DEFAULT_JSON_DECODER_CLS): + config = config or DEFAULT_CONFIG + if registry is None: + registry = DefaultRegistry() + for cls in config: + cls(registry) + return registry + + def __init__(self, registry=None, version=VERSION, json_encoder_cls=None, json_decoder_cls=None): """Intializes a Workflow Serializer with the given Workflow, Task and Data Converters. - :param spec_converter: the workflow spec converter - :param data_converter: the data converter - :param wf_class: the workflow class - :param sub_wf_class: the subworkflow class + :param registry: a registry of conversions to dictionaries :param json_encoder_cls: JSON encoder class to be used for dumps/dump operations :param json_decoder_cls: JSON decoder class to be used for loads/load operations """ super().__init__() - self.spec_converter = spec_converter if spec_converter is not None else self.configure_workflow_spec_converter() - self.data_converter = data_converter if data_converter is not None else DefaultRegistry() - self.wf_class = wf_class if wf_class is not None else BpmnWorkflow - self.sub_wf_class = sub_wf_class if sub_wf_class is not None else BpmnSubWorkflow + self.registry = registry or self.configure() self.json_encoder_cls = json_encoder_cls self.json_decoder_cls = json_decoder_cls self.VERSION = version @@ -132,176 +116,32 @@ def serialize_json(self, workflow, use_gzip=False): Returns: a JSON dump of the dictionary representation """ - dct = self.workflow_to_dict(workflow) + dct = self.to_dict(workflow) dct[self.VERSION_KEY] = self.VERSION json_str = json.dumps(dct, cls=self.json_encoder_cls) return gzip.compress(json_str.encode('utf-8')) if use_gzip else json_str - def __get_dict(self, serialization, use_gzip=False): - if isinstance(serialization, dict): - dct = serialization - elif use_gzip: - dct = json.loads(gzip.decompress(serialization), cls=self.json_decoder_cls) - else: - dct = json.loads(serialization, cls=self.json_decoder_cls) - return dct - def deserialize_json(self, serialization, use_gzip=False): - dct = self.__get_dict(serialization, use_gzip) - return self.workflow_from_dict(dct) - - def get_version(self, serialization, use_gzip=False): - try: - dct = self.__get_dict(serialization, use_gzip) - if self.VERSION_KEY in dct: - return dct[self.VERSION_KEY] - except Exception: # Don't bail out trying to get a version, just return none. - return None - - def workflow_to_dict(self, workflow): - """Return a JSON-serializable dictionary representation of the workflow. - - :param workflow: the workflow + json_str = gzip.decompress(serialization) if use_gzip else serialization + dct = json.loads(json_str, cls=self.json_decoder_cls) + self.migrate(dct) + return self.from_dict(dct) - Returns: - a dictionary representation of the workflow - """ - # These properties are applicable to top level & subprocesses - dct = self.process_to_dict(workflow) - dct['spec'] = self.spec_converter.convert(workflow.spec) - # These are only used at the top-level - dct['subprocess_specs'] = dict( - (name, self.spec_converter.convert(spec)) for name, spec in workflow.subprocess_specs.items() - ) - dct['subprocesses'] = dict( - (str(task_id), self.subworkflow_to_dict(sp)) for task_id, sp in workflow.subprocesses.items() - ) - dct['bpmn_events'] = [self.event_to_dict(event) for event in workflow.bpmn_events] - return dct - - def workflow_from_dict(self, dct): - """Create a workflow based on a dictionary representation. - - :param dct: the dictionary representation - - Returns: - a BPMN Workflow object - """ - dct_copy = deepcopy(dct) + def get_version(self, serialization): + if isinstance(serialization, dict): + return serialization.get(self.VERsiON_KEY) + elif isinstance(serialization, str): + dct = json.loads(serialization, cls=self.json_decoder_cls) + return dct.get(self.VERSION_KEY) + def migrate(self, dct): # Upgrade serialized version if necessary - if self.VERSION_KEY in dct_copy: - version = dct_copy.pop(self.VERSION_KEY) - if version in MIGRATIONS: - dct_copy = MIGRATIONS[version](dct_copy) - - # Restore the top level spec and the subprocess specs - spec = self.spec_converter.restore(dct_copy.pop('spec')) - subprocess_specs = dct_copy.pop('subprocess_specs', {}) - for name, wf_dct in subprocess_specs.items(): - subprocess_specs[name] = self.spec_converter.restore(wf_dct) - - # Create the top-level workflow - workflow = self.wf_class(spec, subprocess_specs, deserializing=True) - - # Restore any unretrieve messages - workflow.bpmn_events = [ self.event_from_dict(msg) for msg in dct_copy.get('bpmn_events', []) ] - - workflow.correlations = dct_copy.pop('correlations', {}) - - # Restore the remainder of the workflow - workflow.data = self.data_converter.restore(dct_copy.pop('data')) - workflow.success = dct_copy.pop('success') - workflow.tasks = dict( - (UUID(task['id']), self.task_from_dict(task, workflow, workflow.spec)) - for task in dct_copy['tasks'].values() - ) - workflow.task_tree = workflow.tasks.get(UUID(dct_copy['root'])) - if dct_copy['last_task'] is not None: - workflow.last_task = workflow.tasks.get(UUID(dct_copy['last_task'])) - - self.subprocesses_from_dict(dct_copy['subprocesses'], workflow) - return workflow - - def subprocesses_from_dict(self, dct, workflow, top_workflow=None): - # This ensures we create parent workflows before their children - top_workflow = top_workflow or workflow - for task in workflow.tasks.values(): - if isinstance(task.task_spec, SubWorkflowTask) and str(task.id) in dct: - sp = self.subworkflow_from_dict(dct.pop(str(task.id)), task, top_workflow) - top_workflow.subprocesses[task.id] = sp - sp.completed_event.connect(task.task_spec._on_subworkflow_completed, task) - if len(sp.spec.data_objects) > 0: - sp.data = task.workflow.data - self.subprocesses_from_dict(dct, sp, top_workflow) - - def subworkflow_to_dict(self, workflow): - dct = self.process_to_dict(workflow) - dct['parent_task_id'] = str(workflow.parent_task_id) - dct['spec'] = workflow.spec.name - return dct - - def subworkflow_from_dict(self, dct, task, top_workflow): - spec = top_workflow.subprocess_specs.get(task.task_spec.spec) - subprocess = self.sub_wf_class(spec, task.id, top_workflow, deserializing=True) - subprocess.correlations = dct.pop('correlations', {}) - subprocess.tasks = dict( - (UUID(task['id']), self.task_from_dict(task, subprocess, spec)) - for task in dct['tasks'].values() - ) - subprocess.task_tree = subprocess.tasks.get(UUID(dct['root'])) - if isinstance(dct['last_task'], str): - subprocess.last_task = subprocess.tasks.get(UUID(dct['last_task'])) - subprocess.success = dct['success'] - subprocess.data = self.data_converter.restore(dct['data']) - return subprocess - - def task_to_dict(self, task): - return { - 'id': str(task.id), - 'parent': str(task._parent) if task.parent is not None else None, - 'children': [ str(child) for child in task._children ], - 'last_state_change': task.last_state_change, - 'state': task.state, - 'task_spec': task.task_spec.name, - 'triggered': task.triggered, - 'internal_data': self.data_converter.convert(task.internal_data), - 'data': self.data_converter.convert(task.data), - } - - def task_from_dict(self, dct, workflow, spec): - - task_spec = spec.task_specs.get(dct['task_spec']) - task = Task(workflow, task_spec, state=dct['state'], id=UUID(dct['id'])) - task._parent = UUID(dct['parent']) if dct['parent'] is not None else None - task._children = [UUID(child) for child in dct['children']] - task.last_state_change = dct['last_state_change'] - task.triggered = dct['triggered'] - task.internal_data = self.data_converter.restore(dct['internal_data']) - task.data = self.data_converter.restore(dct['data']) - return task - - def process_to_dict(self, process): - return { - 'data': self.data_converter.convert(process.data), - 'correlations': process.correlations, - 'last_task': str(process.last_task.id) if process.last_task is not None else None, - 'success': process.success, - 'tasks': dict((str(task.id), self.task_to_dict(task)) for task in process.tasks.values()), - 'root': str(process.task_tree.id), - } - - def event_to_dict(self, event): - dct = { - 'event_definition': self.spec_converter.convert(event.event_definition), - 'payload': self.data_converter.convert(event.payload), - 'correlations': dict([ (k, self.data_converter.convert(v)) for k, v in event.correlations.items() ]), - } - return dct - - def event_from_dict(self, dct): - return BpmnEvent( - self.spec_converter.restore(dct['event_definition']), - self.data_converter.restore(dct['payload']), - dict([ (k, self.data_converter.restore(v)) for k, v in dct['correlations'].items() ]), - ) \ No newline at end of file + version = dct.pop(self.VERSION_KEY) + if version in MIGRATIONS: + MIGRATIONS[version](dct) + + def to_dict(self, obj, **kwargs): + return self.registry.convert(obj, **kwargs) + + def from_dict(self, dct, **kwargs): + return self.registry.restore(dct, **kwargs) \ No newline at end of file diff --git a/SpiffWorkflow/camunda/serializer/config.py b/SpiffWorkflow/camunda/serializer/config.py index 172ded9d..0dbcb83a 100644 --- a/SpiffWorkflow/camunda/serializer/config.py +++ b/SpiffWorkflow/camunda/serializer/config.py @@ -19,7 +19,7 @@ from copy import deepcopy -from SpiffWorkflow.bpmn.serializer.workflow import DEFAULT_SPEC_CONFIG +from SpiffWorkflow.bpmn.serializer.workflow import DEFAULT_CONFIG from SpiffWorkflow.bpmn.serializer.default.task_spec import ( UserTaskConverter as DefaultUserTaskConverter, ParallelMultiInstanceTaskConverter as DefaultParallelMIConverter, @@ -36,14 +36,14 @@ from .event_definition import MessageEventDefinitionConverter -CAMUNDA_SPEC_CONFIG = deepcopy(DEFAULT_SPEC_CONFIG) -CAMUNDA_SPEC_CONFIG['task_specs'].remove(DefaultUserTaskConverter) -CAMUNDA_SPEC_CONFIG['task_specs'].append(UserTaskConverter) -CAMUNDA_SPEC_CONFIG['task_specs'].remove(DefaultParallelMIConverter) -CAMUNDA_SPEC_CONFIG['task_specs'].append(ParallelMultiInstanceTaskConverter) -CAMUNDA_SPEC_CONFIG['task_specs'].remove(DefaultSequentialMIConverter) -CAMUNDA_SPEC_CONFIG['task_specs'].append(SequentialMultiInstanceTaskConverter) -CAMUNDA_SPEC_CONFIG['task_specs'].append(BusinessRuleTaskConverter) +CAMUNDA_CONFIG = deepcopy(DEFAULT_CONFIG) +CAMUNDA_CONFIG.remove(DefaultUserTaskConverter) +CAMUNDA_CONFIG.append(UserTaskConverter) +CAMUNDA_CONFIG.remove(DefaultParallelMIConverter) +CAMUNDA_CONFIG.append(ParallelMultiInstanceTaskConverter) +CAMUNDA_CONFIG.remove(DefaultSequentialMIConverter) +CAMUNDA_CONFIG.append(SequentialMultiInstanceTaskConverter) +CAMUNDA_CONFIG.append(BusinessRuleTaskConverter) -CAMUNDA_SPEC_CONFIG['event_definitions'].remove(DefaultMessageEventConverter) -CAMUNDA_SPEC_CONFIG['event_definitions'].append(MessageEventDefinitionConverter) +CAMUNDA_CONFIG.remove(DefaultMessageEventConverter) +CAMUNDA_CONFIG.append(MessageEventDefinitionConverter) diff --git a/SpiffWorkflow/spiff/serializer/config.py b/SpiffWorkflow/spiff/serializer/config.py index 0c7e812b..ce233e5b 100644 --- a/SpiffWorkflow/spiff/serializer/config.py +++ b/SpiffWorkflow/spiff/serializer/config.py @@ -17,9 +17,7 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA -from copy import deepcopy - -from SpiffWorkflow.bpmn.serializer.workflow import DEFAULT_SPEC_CONFIG +from SpiffWorkflow.bpmn.serializer.default.process_spec import BpmnProcessSpecConverter from SpiffWorkflow.bpmn.serializer.default.task_spec import ( SimpleBpmnTaskConverter, BpmnStartTaskConverter, @@ -55,10 +53,13 @@ ) from SpiffWorkflow.bpmn.serializer.default.event_definition import ( - MessageEventDefinitionConverter as DefaultMessageEventDefinitionConverter, - SignalEventDefinitionConverter as DefaultSignalEventDefinitionConverter, - ErrorEventDefinitionConverter as DefaultErrorEventDefinitionConverter, - EscalationEventDefinitionConverter as DefaultEscalationEventDefinitionConverter, + CancelEventDefinitionConverter, + NoneEventDefinitionConverter, + TerminateEventDefinitionConverter, + TimeDateEventDefinitionConverter, + DurationTimerEventDefinitionConverter, + CycleTimerEventDefinitionConverter, + MultipleEventDefinitionConverter, ) from .event_definition import ( @@ -68,8 +69,20 @@ EscalationEventDefinitionConverter, ) -SPIFF_SPEC_CONFIG = deepcopy(DEFAULT_SPEC_CONFIG) -SPIFF_SPEC_CONFIG['task_specs'] = [ +from SpiffWorkflow.bpmn.serializer.default.data_spec import ( + BpmnDataObjectConverter, + TaskDataReferenceConverter, + IOSpecificationConverter, +) + +from SpiffWorkflow.bpmn.serializer.default.workflow import ( + BpmnWorkflowConverter, + BpmnSubWorkflowConverter, + TaskConverter, + BpmnEventConverter, +) + +SPIFF_CONFIG = [ SimpleBpmnTaskConverter, BpmnStartTaskConverter, EndJoinConverter, @@ -97,13 +110,24 @@ StandardLoopTaskConverter, ParallelMultiInstanceTaskConverter, SequentialMultiInstanceTaskConverter, - BusinessRuleTaskConverter + BusinessRuleTaskConverter, + CancelEventDefinitionConverter, + NoneEventDefinitionConverter, + TerminateEventDefinitionConverter, + TimeDateEventDefinitionConverter, + DurationTimerEventDefinitionConverter, + CycleTimerEventDefinitionConverter, + MultipleEventDefinitionConverter, + MessageEventDefinitionConverter, + SignalEventDefinitionConverter, + ErrorEventDefinitionConverter, + EscalationEventDefinitionConverter, + BpmnDataObjectConverter, + TaskDataReferenceConverter, + IOSpecificationConverter, + BpmnWorkflowConverter, + BpmnSubWorkflowConverter, + TaskConverter, + BpmnEventConverter, + BpmnProcessSpecConverter, ] -SPIFF_SPEC_CONFIG['event_definitions'].remove(DefaultMessageEventDefinitionConverter) -SPIFF_SPEC_CONFIG['event_definitions'].remove(DefaultSignalEventDefinitionConverter) -SPIFF_SPEC_CONFIG['event_definitions'].remove(DefaultErrorEventDefinitionConverter) -SPIFF_SPEC_CONFIG['event_definitions'].remove(DefaultEscalationEventDefinitionConverter) -SPIFF_SPEC_CONFIG['event_definitions'].append(MessageEventDefinitionConverter) -SPIFF_SPEC_CONFIG['event_definitions'].append(SignalEventDefinitionConverter) -SPIFF_SPEC_CONFIG['event_definitions'].append(ErrorEventDefinitionConverter) -SPIFF_SPEC_CONFIG['event_definitions'].append(EscalationEventDefinitionConverter) diff --git a/tests/SpiffWorkflow/bpmn/BpmnWorkflowTestCase.py b/tests/SpiffWorkflow/bpmn/BpmnWorkflowTestCase.py index 6718ce9b..d3e21aa6 100644 --- a/tests/SpiffWorkflow/bpmn/BpmnWorkflowTestCase.py +++ b/tests/SpiffWorkflow/bpmn/BpmnWorkflowTestCase.py @@ -6,19 +6,19 @@ from SpiffWorkflow.util.task import TaskState from SpiffWorkflow.bpmn.workflow import BpmnTaskFilter -from SpiffWorkflow.bpmn.serializer.workflow import BpmnWorkflowSerializer, DEFAULT_SPEC_CONFIG +from SpiffWorkflow.bpmn.serializer.workflow import BpmnWorkflowSerializer, DEFAULT_CONFIG from .BpmnLoaderForTests import TestUserTaskConverter, TestBpmnParser, TestDataStoreConverter __author__ = 'matth' -DEFAULT_SPEC_CONFIG['task_specs'].append(TestUserTaskConverter) -DEFAULT_SPEC_CONFIG['task_specs'].append(TestDataStoreConverter) +DEFAULT_CONFIG.append(TestUserTaskConverter) +DEFAULT_CONFIG.append(TestDataStoreConverter) -wf_spec_converter = BpmnWorkflowSerializer.configure_workflow_spec_converter(spec_config=DEFAULT_SPEC_CONFIG) +registry = BpmnWorkflowSerializer.configure(DEFAULT_CONFIG) class BpmnWorkflowTestCase(unittest.TestCase): - serializer = BpmnWorkflowSerializer(wf_spec_converter) + serializer = BpmnWorkflowSerializer(registry) def get_parser(self, filename, validate=True): f = os.path.join(os.path.dirname(__file__), 'data', filename) @@ -126,9 +126,9 @@ def save_restore(self): before_dump = self.workflow.get_dump() # Check that we can actully convert this to JSON json_str = json.dumps(before_state) - after = self.serializer.workflow_from_dict(json.loads(json_str)) + after = self.serializer.from_dict(json.loads(json_str)) # Check that serializing and deserializing results in the same workflow - after_state = self.serializer.workflow_to_dict(after) + after_state = self.serializer.to_dict(after) after_dump = after.get_dump() self.maxDiff = None self.assertEqual(before_dump, after_dump) @@ -143,4 +143,4 @@ def _get_workflow_state(self, do_steps=True): if do_steps: self.workflow.do_engine_steps() self.workflow.refresh_waiting_tasks() - return self.serializer.workflow_to_dict(self.workflow) + return self.serializer.to_dict(self.workflow) diff --git a/tests/SpiffWorkflow/bpmn/serializer/BaseTestCase.py b/tests/SpiffWorkflow/bpmn/serializer/BaseTestCase.py index 2030fe05..d6983ad0 100644 --- a/tests/SpiffWorkflow/bpmn/serializer/BaseTestCase.py +++ b/tests/SpiffWorkflow/bpmn/serializer/BaseTestCase.py @@ -24,7 +24,7 @@ def load_workflow_spec(self, filename, process_name): def setUp(self): super(BaseTestCase, self).setUp() - wf_spec_converter = BpmnWorkflowSerializer.configure_workflow_spec_converter() + wf_spec_converter = BpmnWorkflowSerializer.configure() self.serializer = BpmnWorkflowSerializer(wf_spec_converter, version=self.SERIALIZER_VERSION) spec, subprocesses = self.load_workflow_spec('random_fact.bpmn', 'random_fact') self.workflow = BpmnWorkflow(spec, subprocesses) diff --git a/tests/SpiffWorkflow/bpmn/serializer/BpmnWorkflowSerializerTest.py b/tests/SpiffWorkflow/bpmn/serializer/BpmnWorkflowSerializerTest.py index bd5de78a..646c9326 100644 --- a/tests/SpiffWorkflow/bpmn/serializer/BpmnWorkflowSerializerTest.py +++ b/tests/SpiffWorkflow/bpmn/serializer/BpmnWorkflowSerializerTest.py @@ -70,7 +70,7 @@ def object_hook(self, z): try: self.assertRaises(TypeError, self.serializer.serialize_json, self.workflow) - wf_spec_converter = BpmnWorkflowSerializer.configure_workflow_spec_converter() + wf_spec_converter = BpmnWorkflowSerializer.configure() custom_serializer = BpmnWorkflowSerializer(wf_spec_converter, version=self.SERIALIZER_VERSION, json_encoder_cls=MyJsonEncoder, @@ -131,7 +131,7 @@ def f(n): return n + 1 user_task.data = { 'f': f } task_id = str(user_task.id) - dct = self.serializer.workflow_to_dict(self.workflow) + dct = self.serializer.to_dict(self.workflow) self.assertNotIn('f', dct['tasks'][task_id]['data']) def testLastTaskIsSetAndWorksThroughRestore(self): diff --git a/tests/SpiffWorkflow/camunda/BaseTestCase.py b/tests/SpiffWorkflow/camunda/BaseTestCase.py index 85f6b969..16ed771f 100644 --- a/tests/SpiffWorkflow/camunda/BaseTestCase.py +++ b/tests/SpiffWorkflow/camunda/BaseTestCase.py @@ -3,19 +3,18 @@ from SpiffWorkflow.bpmn.serializer.workflow import BpmnWorkflowSerializer from SpiffWorkflow.camunda.parser.CamundaParser import CamundaParser -from SpiffWorkflow.camunda.serializer.config import CAMUNDA_SPEC_CONFIG +from SpiffWorkflow.camunda.serializer.config import CAMUNDA_CONFIG from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase +registry = BpmnWorkflowSerializer.configure(CAMUNDA_CONFIG) __author__ = 'danfunk' -wf_spec_converter = BpmnWorkflowSerializer.configure_workflow_spec_converter(CAMUNDA_SPEC_CONFIG) - class BaseTestCase(BpmnWorkflowTestCase): """ Provides some basic tools for loading up and parsing camunda BPMN files """ - serializer = BpmnWorkflowSerializer(wf_spec_converter) + serializer = BpmnWorkflowSerializer(registry) def get_parser(self, filename, dmn_filename=None): f = os.path.join(os.path.dirname(__file__), 'data', filename) diff --git a/tests/SpiffWorkflow/spiff/BaseTestCase.py b/tests/SpiffWorkflow/spiff/BaseTestCase.py index fc54e82b..3354f637 100644 --- a/tests/SpiffWorkflow/spiff/BaseTestCase.py +++ b/tests/SpiffWorkflow/spiff/BaseTestCase.py @@ -2,17 +2,17 @@ import os from SpiffWorkflow.spiff.parser.process import SpiffBpmnParser, VALIDATOR -from SpiffWorkflow.spiff.serializer.config import SPIFF_SPEC_CONFIG +from SpiffWorkflow.spiff.serializer.config import SPIFF_CONFIG from SpiffWorkflow.bpmn.serializer.workflow import BpmnWorkflowSerializer from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase -wf_spec_converter = BpmnWorkflowSerializer.configure_workflow_spec_converter(SPIFF_SPEC_CONFIG) +registry = BpmnWorkflowSerializer.configure(SPIFF_CONFIG) class BaseTestCase(BpmnWorkflowTestCase): """ Provides some basic tools for loading up and parsing Spiff extensions""" - serializer = BpmnWorkflowSerializer(wf_spec_converter) + serializer = BpmnWorkflowSerializer(registry) def load_workflow_spec(self, filename, process_name, dmn_filename=None, validate=True): bpmn = os.path.join(os.path.dirname(__file__), 'data', filename) From 46d6b36b8501ea6ac85f04a091822631a265f498 Mon Sep 17 00:00:00 2001 From: Elizabeth Esswein Date: Thu, 14 Sep 2023 15:12:14 -0400 Subject: [PATCH 3/6] simplify serializer extensions & config --- SpiffWorkflow/bpmn/serializer/config.py | 157 +++++++++++++++ .../bpmn/serializer/default/data_spec.py | 17 +- .../serializer/default/event_definition.py | 88 +-------- .../bpmn/serializer/default/process_spec.py | 6 +- .../bpmn/serializer/default/task_spec.py | 178 ----------------- .../bpmn/serializer/default/workflow.py | 27 +-- SpiffWorkflow/bpmn/serializer/workflow.py | 17 +- SpiffWorkflow/camunda/serializer/config.py | 48 ++--- .../camunda/serializer/event_definition.py | 3 - SpiffWorkflow/camunda/serializer/task_spec.py | 21 +- SpiffWorkflow/spiff/serializer/config.py | 179 ++++++++---------- .../spiff/serializer/event_definition.py | 30 +-- SpiffWorkflow/spiff/serializer/task_spec.py | 63 +----- .../SpiffWorkflow/bpmn/BpmnLoaderForTests.py | 25 +-- .../bpmn/BpmnWorkflowTestCase.py | 10 +- .../camunda/specs/UserTaskSpecTest.py | 2 +- tests/SpiffWorkflow/dmn/HitPolicyTest.py | 6 +- 17 files changed, 289 insertions(+), 588 deletions(-) create mode 100644 SpiffWorkflow/bpmn/serializer/config.py diff --git a/SpiffWorkflow/bpmn/serializer/config.py b/SpiffWorkflow/bpmn/serializer/config.py new file mode 100644 index 00000000..ad2d868d --- /dev/null +++ b/SpiffWorkflow/bpmn/serializer/config.py @@ -0,0 +1,157 @@ +# Copyright (C) 2023 Sartography +# +# This file is part of SpiffWorkflow. +# +# SpiffWorkflow is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# SpiffWorkflow is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +from SpiffWorkflow.task import Task +from SpiffWorkflow.bpmn.workflow import ( + BpmnWorkflow, + BpmnSubWorkflow, +) +from SpiffWorkflow.bpmn.event import BpmnEvent +from SpiffWorkflow.bpmn.specs.data_spec import ( + DataObject, + BpmnIoSpecification, + TaskDataReference, +) +from SpiffWorkflow.bpmn.specs.bpmn_process_spec import BpmnProcessSpec +from SpiffWorkflow.bpmn.specs.defaults import ( + ManualTask, + NoneTask, + UserTask, + ExclusiveGateway, + InclusiveGateway, + ParallelGateway, + EventBasedGateway, + ScriptTask, + ServiceTask, + StandardLoopTask, + ParallelMultiInstanceTask, + SequentialMultiInstanceTask, + SubWorkflowTask, + CallActivity, + TransactionSubprocess, + StartEvent, + EndEvent, + IntermediateCatchEvent, + IntermediateThrowEvent, + BoundaryEvent, + SendTask, + ReceiveTask, +) +from SpiffWorkflow.bpmn.specs.control import ( + BpmnStartTask, + SimpleBpmnTask, + BoundaryEventSplit, + BoundaryEventJoin, + _EndJoin, +) +from SpiffWorkflow.bpmn.specs.event_definitions.simple import ( + NoneEventDefinition, + CancelEventDefinition, + TerminateEventDefinition, +) +from SpiffWorkflow.bpmn.specs.event_definitions.item_aware_event import ( + SignalEventDefinition, + ErrorEventDefinition, + EscalationEventDefinition, +) +from SpiffWorkflow.bpmn.specs.event_definitions.timer import ( + TimeDateEventDefinition, + DurationTimerEventDefinition, + CycleTimerEventDefinition, +) +from SpiffWorkflow.bpmn.specs.event_definitions.message import MessageEventDefinition +from SpiffWorkflow.bpmn.specs.event_definitions.multiple import MultipleEventDefinition + +from .default.workflow import ( + BpmnWorkflowConverter, + BpmnSubWorkflowConverter, + TaskConverter, + BpmnEventConverter, +) +from .helpers.spec import BpmnDataSpecificationConverter, EventDefinitionConverter +from .default.data_spec import IOSpecificationConverter +from .default.process_spec import BpmnProcessSpecConverter +from .default.task_spec import ( + BpmnTaskSpecConverter, + ScriptTaskConverter, + StandardLoopTaskConverter, + MultiInstanceTaskConverter, + SubWorkflowConverter, + BoundaryEventJoinConverter, + ConditionalGatewayConverter, + ExclusiveGatewayConverter, + ParallelGatewayConverter, + EventConverter, + BoundaryEventConverter, +) +from .default.event_definition import ( + TimerEventDefinitionConverter, + ErrorEscalationEventDefinitionConverter, + MessageEventDefinitionConverter, + MultipleEventDefinitionConverter, +) + + +DEFAULT_CONFIG = { + BpmnWorkflow: BpmnWorkflowConverter, + BpmnSubWorkflow: BpmnSubWorkflowConverter, + Task: TaskConverter, + BpmnEvent: BpmnEventConverter, + DataObject: BpmnDataSpecificationConverter, + TaskDataReference: BpmnDataSpecificationConverter, + BpmnIoSpecification: IOSpecificationConverter, + BpmnProcessSpec: BpmnProcessSpecConverter, + SimpleBpmnTask: BpmnTaskSpecConverter, + BpmnStartTask: BpmnTaskSpecConverter, + _EndJoin: BpmnTaskSpecConverter, + NoneTask: BpmnTaskSpecConverter, + ManualTask: BpmnTaskSpecConverter, + UserTask: BpmnTaskSpecConverter, + ScriptTask: ScriptTaskConverter, + StandardLoopTask: StandardLoopTaskConverter, + ParallelMultiInstanceTask: MultiInstanceTaskConverter, + SequentialMultiInstanceTask: MultiInstanceTaskConverter, + SubWorkflowTask: SubWorkflowConverter, + CallActivity: SubWorkflowConverter, + TransactionSubprocess: SubWorkflowConverter, + BoundaryEventSplit: BpmnTaskSpecConverter, + BoundaryEventJoin: BoundaryEventJoinConverter, + ExclusiveGateway: ExclusiveGatewayConverter, + InclusiveGateway: ConditionalGatewayConverter, + ParallelGateway: ParallelGatewayConverter, + StartEvent: EventConverter, + EndEvent: EventConverter, + IntermediateCatchEvent: EventConverter, + IntermediateThrowEvent: EventConverter, + BoundaryEvent: BoundaryEventConverter, + SendTask: EventConverter, + ReceiveTask: EventConverter, + EventBasedGateway: EventConverter, + CancelEventDefinition: EventDefinitionConverter, + ErrorEventDefinition: ErrorEscalationEventDefinitionConverter, + EscalationEventDefinition: ErrorEscalationEventDefinitionConverter, + MessageEventDefinition: MessageEventDefinitionConverter, + NoneEventDefinition: EventDefinitionConverter, + SignalEventDefinition: EventDefinitionConverter, + TerminateEventDefinition: EventDefinitionConverter, + TimeDateEventDefinition: TimerEventDefinitionConverter, + DurationTimerEventDefinition: TimerEventDefinitionConverter, + CycleTimerEventDefinition: TimerEventDefinitionConverter, + MultipleEventDefinition: MultipleEventDefinitionConverter, +} \ No newline at end of file diff --git a/SpiffWorkflow/bpmn/serializer/default/data_spec.py b/SpiffWorkflow/bpmn/serializer/default/data_spec.py index 35bd9ef5..5701e2f3 100644 --- a/SpiffWorkflow/bpmn/serializer/default/data_spec.py +++ b/SpiffWorkflow/bpmn/serializer/default/data_spec.py @@ -17,24 +17,9 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA -from SpiffWorkflow.bpmn.specs.data_spec import DataObject, TaskDataReference, BpmnIoSpecification from ..helpers.registry import BpmnConverter -from ..helpers.spec import BpmnDataSpecificationConverter - - -class BpmnDataObjectConverter(BpmnDataSpecificationConverter): - def __init__(self, registry): - super().__init__(DataObject, registry) - - -class TaskDataReferenceConverter(BpmnDataSpecificationConverter): - def __init__(self, registry): - super().__init__(TaskDataReference, registry) - class IOSpecificationConverter(BpmnConverter): - def __init__(self, registry): - super().__init__(BpmnIoSpecification, registry) def to_dict(self, spec): return { @@ -43,7 +28,7 @@ def to_dict(self, spec): } def from_dict(self, dct): - return BpmnIoSpecification( + return self.target_class( data_inputs=[self.registry.restore(item) for item in dct['data_inputs']], data_outputs=[self.registry.restore(item) for item in dct['data_outputs']], ) diff --git a/SpiffWorkflow/bpmn/serializer/default/event_definition.py b/SpiffWorkflow/bpmn/serializer/default/event_definition.py index a021f7e6..f585f019 100644 --- a/SpiffWorkflow/bpmn/serializer/default/event_definition.py +++ b/SpiffWorkflow/bpmn/serializer/default/event_definition.py @@ -17,46 +17,10 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA -from SpiffWorkflow.bpmn.specs.event_definitions.simple import ( - NoneEventDefinition, - CancelEventDefinition, - TerminateEventDefinition, -) -from SpiffWorkflow.bpmn.specs.event_definitions.item_aware_event import ( - SignalEventDefinition, - ErrorEventDefinition, - EscalationEventDefinition, -) -from SpiffWorkflow.bpmn.specs.event_definitions.timer import ( - TimeDateEventDefinition, - DurationTimerEventDefinition, - CycleTimerEventDefinition, -) -from SpiffWorkflow.bpmn.specs.event_definitions.message import MessageEventDefinition -from SpiffWorkflow.bpmn.specs.event_definitions.multiple import MultipleEventDefinition - from ..helpers.spec import EventDefinitionConverter -class CancelEventDefinitionConverter(EventDefinitionConverter): - def __init__(self, registry): - super().__init__(CancelEventDefinition, registry) - -class ErrorEventDefinitionConverter(EventDefinitionConverter): - - def __init__(self, registry): - super().__init__(ErrorEventDefinition, registry) - - def to_dict(self, event_definition): - dct = super().to_dict(event_definition) - dct['code'] = event_definition.code - return dct - - -class EscalationEventDefinitionConverter(EventDefinitionConverter): - - def __init__(self, registry): - super().__init__(EscalationEventDefinition, registry) +class ErrorEscalationEventDefinitionConverter(EventDefinitionConverter): def to_dict(self, event_definition): dct = super().to_dict(event_definition) @@ -66,9 +30,6 @@ def to_dict(self, event_definition): class MessageEventDefinitionConverter(EventDefinitionConverter): - def __init__(self, registry): - super().__init__(MessageEventDefinition, registry) - def to_dict(self, event_definition): dct = super().to_dict(event_definition) dct['correlation_properties'] = self.correlation_properties_to_dict(event_definition.correlation_properties) @@ -80,21 +41,6 @@ def from_dict(self, dct): return event_definition -class NoneEventDefinitionConverter(EventDefinitionConverter): - def __init__(self, registry): - super().__init__(NoneEventDefinition, registry) - - -class SignalEventDefinitionConverter(EventDefinitionConverter): - def __init__(self, registry): - super().__init__(SignalEventDefinition, registry) - - -class TerminateEventDefinitionConverter(EventDefinitionConverter): - def __init__(self, registry): - super().__init__(TerminateEventDefinition, registry) - - class TimerEventDefinitionConverter(EventDefinitionConverter): def to_dict(self, event_definition): @@ -102,26 +48,9 @@ def to_dict(self, event_definition): dct['expression'] = event_definition.expression return dct -class TimeDateEventDefinitionConverter(TimerEventDefinitionConverter): - def __init__(self, registry): - super().__init__(TimeDateEventDefinition, registry) - - -class DurationTimerEventDefinitionConverter(TimerEventDefinitionConverter): - def __init__(self, registry): - super().__init__(DurationTimerEventDefinition, registry) - - -class CycleTimerEventDefinitionConverter(TimerEventDefinitionConverter): - def __init__(self, registry): - super().__init__(CycleTimerEventDefinition, registry) - class MultipleEventDefinitionConverter(EventDefinitionConverter): - def __init__(self, registry): - super().__init__(MultipleEventDefinition, registry) - def to_dict(self, event_definition): dct = super().to_dict(event_definition) dct['parallel'] = event_definition.parallel @@ -133,18 +62,3 @@ def from_dict(self, dct): event_definition = super().from_dict(dct) event_definition.event_definitions = [self.registry.restore(d) for d in events] return event_definition - - -DEFAULT_EVENT_CONVERTERS = [ - CancelEventDefinitionConverter, - ErrorEventDefinitionConverter, - EscalationEventDefinitionConverter, - MessageEventDefinitionConverter, - NoneEventDefinitionConverter, - SignalEventDefinitionConverter, - TerminateEventDefinitionConverter, - TimeDateEventDefinitionConverter, - DurationTimerEventDefinitionConverter, - CycleTimerEventDefinitionConverter, - MultipleEventDefinitionConverter, -] diff --git a/SpiffWorkflow/bpmn/serializer/default/process_spec.py b/SpiffWorkflow/bpmn/serializer/default/process_spec.py index 30d27b28..bc8fd6b2 100644 --- a/SpiffWorkflow/bpmn/serializer/default/process_spec.py +++ b/SpiffWorkflow/bpmn/serializer/default/process_spec.py @@ -17,14 +17,10 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA -from SpiffWorkflow.bpmn.specs.bpmn_process_spec import BpmnProcessSpec - from ..helpers.spec import WorkflowSpecConverter -class BpmnProcessSpecConverter(WorkflowSpecConverter): - def __init__(self, registry): - super().__init__(BpmnProcessSpec, registry) +class BpmnProcessSpecConverter(WorkflowSpecConverter): def convert_task_spec_extensions(self, task_spec, dct): # Extensions will be moved out of the base parser, but since we currently add them to some diff --git a/SpiffWorkflow/bpmn/serializer/default/task_spec.py b/SpiffWorkflow/bpmn/serializer/default/task_spec.py index 2c4d3553..d599d25b 100644 --- a/SpiffWorkflow/bpmn/serializer/default/task_spec.py +++ b/SpiffWorkflow/bpmn/serializer/default/task_spec.py @@ -19,37 +19,6 @@ from SpiffWorkflow.bpmn.specs.bpmn_task_spec import _BpmnCondition -from SpiffWorkflow.bpmn.specs.control import ( - BpmnStartTask, - _EndJoin, - BoundaryEventSplit, - BoundaryEventJoin, - SimpleBpmnTask -) -from SpiffWorkflow.bpmn.specs.defaults import ( - UserTask, - ManualTask, - NoneTask, - ScriptTask, - ExclusiveGateway, - InclusiveGateway, - ParallelGateway, - StandardLoopTask, - SequentialMultiInstanceTask, - ParallelMultiInstanceTask, - CallActivity, - TransactionSubprocess, - SubWorkflowTask, - StartEvent, - EndEvent, - IntermediateCatchEvent, - IntermediateThrowEvent, - BoundaryEvent, - EventBasedGateway, - SendTask, - ReceiveTask, -) - from ..helpers.spec import TaskSpecConverter @@ -63,40 +32,8 @@ def from_dict(self, dct): return self.task_spec_from_dict(dct) -class SimpleBpmnTaskConverter(BpmnTaskSpecConverter): - def __init__(self, registry): - super().__init__(SimpleBpmnTask, registry) - -class BpmnStartTaskConverter(BpmnTaskSpecConverter): - def __init__(self, registry): - super().__init__(BpmnStartTask, registry) - -class EndJoinConverter(BpmnTaskSpecConverter): - def __init__(self, registry): - super().__init__(_EndJoin, registry) - - - -class NoneTaskConverter(BpmnTaskSpecConverter): - def __init__(self, registry): - super().__init__(NoneTask, registry) - - -class UserTaskConverter(BpmnTaskSpecConverter): - def __init__(self, registry): - super().__init__(UserTask, registry) - - -class ManualTaskConverter(BpmnTaskSpecConverter): - def __init__(self, registry): - super().__init__(ManualTask, registry) - - class ScriptTaskConverter(BpmnTaskSpecConverter): - def __init__(self, registry): - super().__init__(ScriptTask, registry) - def to_dict(self, spec): dct = self.get_default_attributes(spec) dct['script'] = spec.script @@ -105,9 +42,6 @@ def to_dict(self, spec): class StandardLoopTaskConverter(BpmnTaskSpecConverter): - def __init__(self, registry): - super().__init__(StandardLoopTask, registry) - def to_dict(self, spec): dct = self.get_default_attributes(spec) dct.update(self.get_standard_loop_attributes(spec)) @@ -135,24 +69,8 @@ def from_dict(self, dct): return self.task_spec_from_dict(dct) -class ParallelMultiInstanceTaskConverter(MultiInstanceTaskConverter): - def __init__(self, registry): - super().__init__(ParallelMultiInstanceTask, registry) - -class SequentialMultiInstanceTaskConverter(MultiInstanceTaskConverter): - def __init__(self, registry): - super().__init__(SequentialMultiInstanceTask, registry) - - -class BoundaryEventSplitConverter(BpmnTaskSpecConverter): - def __init__(self, registry): - super().__init__(BoundaryEventSplit, registry) - class BoundaryEventJoinConverter(BpmnTaskSpecConverter): - def __init__(self, registry): - super().__init__(BoundaryEventJoin, registry) - def to_dict(self, spec): dct = super().to_dict(spec) dct.update(self.get_join_attributes(spec)) @@ -160,9 +78,6 @@ def to_dict(self, spec): class SubWorkflowConverter(BpmnTaskSpecConverter): - def __init__(self, cls, registry): - super().__init__(cls, registry) - def to_dict(self, spec): dct = super().to_dict(spec) dct.update(self.get_subworkflow_attributes(spec)) @@ -172,18 +87,6 @@ def from_dict(self, dct): dct['subworkflow_spec'] = dct.pop('spec') return self.task_spec_from_dict(dct) -class SubprocessTaskConverter(SubWorkflowConverter): - def __init__(self, registry): - super().__init__(SubWorkflowTask, registry) - -class CallActivityTaskConverter(SubWorkflowConverter): - def __init__(self, registry): - super().__init__(CallActivity, registry) - -class TransactionSubprocessTaskConverter(SubWorkflowConverter): - def __init__(self, registry): - super().__init__(TransactionSubprocess, registry) - class ConditionalGatewayConverter(BpmnTaskSpecConverter): @@ -212,9 +115,6 @@ def bpmn_condition_to_dict(self, condition): class ExclusiveGatewayConverter(ConditionalGatewayConverter): - def __init__(self, registry): - super().__init__(ExclusiveGateway, registry) - def to_dict(self, spec): dct = super().to_dict(spec) dct['default_task_spec'] = spec.default_task_spec @@ -227,16 +127,8 @@ def from_dict(self, dct): return spec -class InclusiveGatewayConverter(ConditionalGatewayConverter): - def __init__(self, registry): - super().__init__(InclusiveGateway, registry) - - class ParallelGatewayConverter(BpmnTaskSpecConverter): - def __init__(self, registry): - super().__init__(ParallelGateway, registry) - def to_dict(self, spec): dct = super().to_dict(spec) dct.update(self.get_join_attributes(spec)) @@ -248,9 +140,6 @@ def from_dict(self, dct): class EventConverter(BpmnTaskSpecConverter): - def __init__(self, spec_class, registry): - super().__init__(spec_class, registry) - def to_dict(self, spec): dct = super().to_dict(spec) dct['event_definition'] = self.registry.convert(spec.event_definition) @@ -261,77 +150,10 @@ def from_dict(self, dct): return self.task_spec_from_dict(dct) -class StartEventConverter(EventConverter): - def __init__(self, registry): - super().__init__(StartEvent, registry) - - -class EndEventConverter(EventConverter): - def __init__(self, registry): - super().__init__(EndEvent, registry) - - -class IntermediateCatchEventConverter(EventConverter): - def __init__(self, registry): - super().__init__(IntermediateCatchEvent, registry) - - -class ReceiveTaskConverter(EventConverter): - def __init__(self, registry): - super().__init__(ReceiveTask, registry) - - -class IntermediateThrowEventConverter(EventConverter): - def __init__(self, registry): - super().__init__(IntermediateThrowEvent, registry) - - -class SendTaskConverter(EventConverter): - def __init__(self, registry): - super().__init__(SendTask, registry) - - class BoundaryEventConverter(EventConverter): - def __init__(self, registry): - super().__init__(BoundaryEvent, registry) - def to_dict(self, spec): dct = super().to_dict(spec) dct['cancel_activity'] = spec.cancel_activity return dct - -class EventBasedGatewayConverter(EventConverter): - def __init__(self, registry): - super().__init__(EventBasedGateway, registry) - - -DEFAULT_TASK_SPEC_CONVERTERS = [ - SimpleBpmnTaskConverter, - BpmnStartTaskConverter, - EndJoinConverter, - NoneTaskConverter, - UserTaskConverter, - ManualTaskConverter, - ScriptTaskConverter, - StandardLoopTaskConverter, - ParallelMultiInstanceTaskConverter, - SequentialMultiInstanceTaskConverter, - SubprocessTaskConverter, - CallActivityTaskConverter, - TransactionSubprocessTaskConverter, - StartEventConverter, - EndEventConverter, - SendTaskConverter, - ReceiveTaskConverter, - IntermediateCatchEventConverter, - IntermediateThrowEventConverter, - EventBasedGatewayConverter, - BoundaryEventConverter, - BoundaryEventSplitConverter, - BoundaryEventJoinConverter, - ParallelGatewayConverter, - ExclusiveGatewayConverter, - InclusiveGatewayConverter, -] \ No newline at end of file diff --git a/SpiffWorkflow/bpmn/serializer/default/workflow.py b/SpiffWorkflow/bpmn/serializer/default/workflow.py index 0b1fa1d8..ed94e8e7 100644 --- a/SpiffWorkflow/bpmn/serializer/default/workflow.py +++ b/SpiffWorkflow/bpmn/serializer/default/workflow.py @@ -19,18 +19,12 @@ from uuid import UUID -from SpiffWorkflow.task import Task -from SpiffWorkflow.bpmn.workflow import BpmnWorkflow, BpmnSubWorkflow -from SpiffWorkflow.bpmn.event import BpmnEvent from SpiffWorkflow.bpmn.specs.mixins.subworkflow_task import SubWorkflowTask from ..helpers.registry import BpmnConverter class TaskConverter(BpmnConverter): - def __init__(self, registry): - super().__init__(Task, registry) - def to_dict(self, task): return { 'id': str(task.id), @@ -46,7 +40,7 @@ def to_dict(self, task): def from_dict(self, dct, workflow): task_spec = workflow.spec.task_specs.get(dct['task_spec']) - task = Task(workflow, task_spec, state=dct['state'], id=UUID(dct['id'])) + task = self.target_class(workflow, task_spec, state=dct['state'], id=UUID(dct['id'])) task._parent = UUID(dct['parent']) if dct['parent'] is not None else None task._children = [UUID(child) for child in dct['children']] task.last_state_change = dct['last_state_change'] @@ -58,9 +52,6 @@ def from_dict(self, dct, workflow): class BpmnEventConverter(BpmnConverter): - def __init__(self, registry): - super().__init__(BpmnEvent, registry) - def to_dict(self, event): return { 'event_definition': self.registry.convert(event.event_definition), @@ -69,7 +60,7 @@ def to_dict(self, event): } def from_dict(self, dct): - return BpmnEvent( + return self.target_class( self.registry.restore(dct['event_definition']), self.registry.restore(dct['payload']), self.mapping_from_dict(dct['correlations']) @@ -92,9 +83,6 @@ def to_dict(self, workflow): class BpmnSubWorkflowConverter(WorkflowConverter): - def __init__(self, registry): - super().__init__(BpmnSubWorkflow, registry) - def to_dict(self, workflow): dct = super().to_dict(workflow) dct['parent_task_id'] = str(workflow.parent_task_id) @@ -116,9 +104,6 @@ def from_dict(self, dct, task, top_workflow): class BpmnWorkflowConverter(WorkflowConverter): - def __init__(self, registry): - super().__init__(BpmnWorkflow, registry) - def to_dict(self, workflow): """Return a JSON-serializable dictionary representation of the workflow. @@ -173,11 +158,3 @@ def subprocesses_from_dict(self, dct, workflow, top_workflow=None): if len(sp.spec.data_objects) > 0: sp.data = task.workflow.data self.subprocesses_from_dict(dct, sp, top_workflow) - - -DEFAULT_WORKFLOW_CONVERTERS = [ - BpmnWorkflowConverter, - BpmnSubWorkflowConverter, - TaskConverter, - BpmnEventConverter, -] \ No newline at end of file diff --git a/SpiffWorkflow/bpmn/serializer/workflow.py b/SpiffWorkflow/bpmn/serializer/workflow.py index cf51e754..18426718 100644 --- a/SpiffWorkflow/bpmn/serializer/workflow.py +++ b/SpiffWorkflow/bpmn/serializer/workflow.py @@ -22,18 +22,7 @@ from .migration.version_migration import MIGRATIONS from .helpers.registry import DefaultRegistry -from .default.process_spec import BpmnProcessSpecConverter -from .default.data_spec import BpmnDataObjectConverter, TaskDataReferenceConverter, IOSpecificationConverter -from .default.task_spec import DEFAULT_TASK_SPEC_CONVERTERS -from .default.event_definition import DEFAULT_EVENT_CONVERTERS -from .default.workflow import DEFAULT_WORKFLOW_CONVERTERS - -DEFAULT_CONFIG = DEFAULT_WORKFLOW_CONVERTERS + DEFAULT_TASK_SPEC_CONVERTERS + DEFAULT_EVENT_CONVERTERS + [ - BpmnProcessSpecConverter, - BpmnDataObjectConverter, - TaskDataReferenceConverter, - IOSpecificationConverter -] +from .config import DEFAULT_CONFIG # This is the default version set on the workflow, it can be overwritten in init VERSION = "1.3" @@ -91,8 +80,8 @@ def configure(config=None, registry=None): config = config or DEFAULT_CONFIG if registry is None: registry = DefaultRegistry() - for cls in config: - cls(registry) + for target_class, converter_class in config.items(): + converter_class(target_class, registry) return registry def __init__(self, registry=None, version=VERSION, json_encoder_cls=None, json_decoder_cls=None): diff --git a/SpiffWorkflow/camunda/serializer/config.py b/SpiffWorkflow/camunda/serializer/config.py index 0dbcb83a..e41a8449 100644 --- a/SpiffWorkflow/camunda/serializer/config.py +++ b/SpiffWorkflow/camunda/serializer/config.py @@ -19,31 +19,35 @@ from copy import deepcopy -from SpiffWorkflow.bpmn.serializer.workflow import DEFAULT_CONFIG -from SpiffWorkflow.bpmn.serializer.default.task_spec import ( - UserTaskConverter as DefaultUserTaskConverter, - ParallelMultiInstanceTaskConverter as DefaultParallelMIConverter, - SequentialMultiInstanceTaskConverter as DefaultSequentialMIConverter, +from SpiffWorkflow.bpmn.serializer.config import DEFAULT_CONFIG +from SpiffWorkflow.bpmn.serializer.config import ( + UserTask as DefaultUserTask, + ParallelMultiInstanceTask as DefaultParallelMITask, + SequentialMultiInstanceTask as DefaultSequentialMITask, + MessageEventDefinition as DefaultMessageEventDefinition, ) -from SpiffWorkflow.bpmn.serializer.default.event_definition import MessageEventDefinitionConverter as DefaultMessageEventConverter -from .task_spec import ( - UserTaskConverter, - BusinessRuleTaskConverter, - ParallelMultiInstanceTaskConverter, - SequentialMultiInstanceTaskConverter -) +from SpiffWorkflow.camunda.specs.user_task import UserTask +from SpiffWorkflow.camunda.specs.multiinstance_task import ParallelMultiInstanceTask, SequentialMultiInstanceTask +from SpiffWorkflow.camunda.specs.business_rule_task import BusinessRuleTask +from SpiffWorkflow.camunda.specs.event_definitions import MessageEventDefinition + +from SpiffWorkflow.bpmn.serializer.default.task_spec import MultiInstanceTaskConverter +from SpiffWorkflow.dmn.serializer.task_spec import BaseBusinessRuleTaskConverter + +from .task_spec import UserTaskConverter from .event_definition import MessageEventDefinitionConverter CAMUNDA_CONFIG = deepcopy(DEFAULT_CONFIG) -CAMUNDA_CONFIG.remove(DefaultUserTaskConverter) -CAMUNDA_CONFIG.append(UserTaskConverter) -CAMUNDA_CONFIG.remove(DefaultParallelMIConverter) -CAMUNDA_CONFIG.append(ParallelMultiInstanceTaskConverter) -CAMUNDA_CONFIG.remove(DefaultSequentialMIConverter) -CAMUNDA_CONFIG.append(SequentialMultiInstanceTaskConverter) -CAMUNDA_CONFIG.append(BusinessRuleTaskConverter) - -CAMUNDA_CONFIG.remove(DefaultMessageEventConverter) -CAMUNDA_CONFIG.append(MessageEventDefinitionConverter) + +CAMUNDA_CONFIG.pop(DefaultUserTask) +CAMUNDA_CONFIG.pop(DefaultParallelMITask) +CAMUNDA_CONFIG.pop(DefaultSequentialMITask) +CAMUNDA_CONFIG.pop(DefaultMessageEventDefinition) + +CAMUNDA_CONFIG[UserTask] = UserTaskConverter +CAMUNDA_CONFIG[ParallelMultiInstanceTask] = MultiInstanceTaskConverter +CAMUNDA_CONFIG[SequentialMultiInstanceTask] = MultiInstanceTaskConverter +CAMUNDA_CONFIG[BusinessRuleTask] = BaseBusinessRuleTaskConverter +CAMUNDA_CONFIG[MessageEventDefinition] = MessageEventDefinitionConverter diff --git a/SpiffWorkflow/camunda/serializer/event_definition.py b/SpiffWorkflow/camunda/serializer/event_definition.py index 9d8d3249..fa48e065 100644 --- a/SpiffWorkflow/camunda/serializer/event_definition.py +++ b/SpiffWorkflow/camunda/serializer/event_definition.py @@ -23,9 +23,6 @@ class MessageEventDefinitionConverter(EventDefinitionConverter): - def __init__(self, registry): - super().__init__(MessageEventDefinition, registry) - def to_dict(self, event_definition): dct = super().to_dict(event_definition) dct['correlation_properties'] = self.correlation_properties_to_dict(event_definition.correlation_properties) diff --git a/SpiffWorkflow/camunda/serializer/task_spec.py b/SpiffWorkflow/camunda/serializer/task_spec.py index b1e61dd4..5ea75978 100644 --- a/SpiffWorkflow/camunda/serializer/task_spec.py +++ b/SpiffWorkflow/camunda/serializer/task_spec.py @@ -18,17 +18,11 @@ # 02110-1301 USA from SpiffWorkflow.bpmn.serializer.helpers.spec import TaskSpecConverter -from SpiffWorkflow.bpmn.serializer.default.task_spec import MultiInstanceTaskConverter -from SpiffWorkflow.dmn.serializer.task_spec import BaseBusinessRuleTaskConverter from SpiffWorkflow.camunda.specs.user_task import UserTask, Form -from SpiffWorkflow.camunda.specs.business_rule_task import BusinessRuleTask -from SpiffWorkflow.camunda.specs.multiinstance_task import ParallelMultiInstanceTask, SequentialMultiInstanceTask -class UserTaskConverter(TaskSpecConverter): - def __init__(self, registry): - super().__init__(UserTask, registry) +class UserTaskConverter(TaskSpecConverter): def to_dict(self, spec): dct = self.get_default_attributes(spec) @@ -54,16 +48,3 @@ def form_to_dict(self, form): new['options'] = [ opt.__dict__ for opt in field.options ] dct['fields'].append(new) return dct - - -class BusinessRuleTaskConverter(BaseBusinessRuleTaskConverter): - def __init__(self, registry): - super().__init__(BusinessRuleTask, registry) - -class ParallelMultiInstanceTaskConverter(MultiInstanceTaskConverter): - def __init__(self, registry): - super().__init__(ParallelMultiInstanceTask, registry) - -class SequentialMultiInstanceTaskConverter(MultiInstanceTaskConverter): - def __init__(self, registry): - super().__init__(SequentialMultiInstanceTask, registry) diff --git a/SpiffWorkflow/spiff/serializer/config.py b/SpiffWorkflow/spiff/serializer/config.py index ce233e5b..4e3a324c 100644 --- a/SpiffWorkflow/spiff/serializer/config.py +++ b/SpiffWorkflow/spiff/serializer/config.py @@ -17,117 +17,96 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA -from SpiffWorkflow.bpmn.serializer.default.process_spec import BpmnProcessSpecConverter -from SpiffWorkflow.bpmn.serializer.default.task_spec import ( - SimpleBpmnTaskConverter, - BpmnStartTaskConverter, - EndJoinConverter, - StartEventConverter, - EndEventConverter, - IntermediateCatchEventConverter, - IntermediateThrowEventConverter, - EventBasedGatewayConverter, - BoundaryEventConverter, - BoundaryEventSplitConverter, - BoundaryEventJoinConverter, - ParallelGatewayConverter, - ExclusiveGatewayConverter, - InclusiveGatewayConverter, +from copy import deepcopy + +from SpiffWorkflow.bpmn.serializer.config import DEFAULT_CONFIG +from SpiffWorkflow.bpmn.serializer.config import ( + NoneTask as DefaultNoneTask, + ManualTask as DefaultManualTask, + UserTask as DefaultUserTask, + SendTask as DefaultSendTask, + ReceiveTask as DefaultReceiveTask, + ScriptTask as DefaultScriptTask, + SubWorkflowTask as DefaultSubWorkflowTask, + TransactionSubprocess as DefaultTransactionSubprocess, + CallActivity as DefaultCallActivity, + StandardLoopTask as DefaultStandardLoopTask, + ParallelMultiInstanceTask as DefaultParallelMultiInstanceTask, + SequentialMultiInstanceTask as DefaultSequentialMultiInstanceTask, + MessageEventDefinition as DefaultMessageEventDefinition, + SignalEventDefinition as DefaultSignalEventDefinition, + ErrorEventDefinition as DefaultErrorEventDefinition, + EscalationEventDefinition as DefaultEscalationEventDefinition, +) + +from SpiffWorkflow.spiff.specs.defaults import ( + BusinessRuleTask, + NoneTask, + ManualTask, + UserTask, + SendTask, + ReceiveTask, + ScriptTask, + ServiceTask, + SubWorkflowTask, + TransactionSubprocess, + CallActivity, + StandardLoopTask, + ParallelMultiInstanceTask, + SequentialMultiInstanceTask, +) +from SpiffWorkflow.spiff.specs.event_definitions import ( + MessageEventDefinition, + SignalEventDefinition, + ErrorEventDefinition, + EscalationEventDefinition, ) from .task_spec import ( - NoneTaskConverter, - ManualTaskConverter, - UserTaskConverter, - SendTaskConverter, - ReceiveTaskConverter, + SpiffBpmnTaskConverter, + SendReceiveTaskConverter, ScriptTaskConverter, ServiceTaskConverter, - SubprocessTaskConverter, - TransactionSubprocessConverter, - CallActivityTaskConverter, + SubWorkflowTaskConverter, StandardLoopTaskConverter, - ParallelMultiInstanceTaskConverter, - SequentialMultiInstanceTaskConverter, + SpiffMultiInstanceConverter, BusinessRuleTaskConverter, ) - -from SpiffWorkflow.bpmn.serializer.default.event_definition import ( - CancelEventDefinitionConverter, - NoneEventDefinitionConverter, - TerminateEventDefinitionConverter, - TimeDateEventDefinitionConverter, - DurationTimerEventDefinitionConverter, - CycleTimerEventDefinitionConverter, - MultipleEventDefinitionConverter, -) - from .event_definition import ( MessageEventDefinitionConverter, - SignalEventDefinitionConverter, - ErrorEventDefinitionConverter, - EscalationEventDefinitionConverter, + ItemAwareEventDefinitionConverter, + ErrorEscalationEventDefinitionConverter, ) -from SpiffWorkflow.bpmn.serializer.default.data_spec import ( - BpmnDataObjectConverter, - TaskDataReferenceConverter, - IOSpecificationConverter, -) +SPIFF_CONFIG = deepcopy(DEFAULT_CONFIG) -from SpiffWorkflow.bpmn.serializer.default.workflow import ( - BpmnWorkflowConverter, - BpmnSubWorkflowConverter, - TaskConverter, - BpmnEventConverter, -) +SPIFF_CONFIG.pop(DefaultNoneTask) +SPIFF_CONFIG.pop(DefaultManualTask) +SPIFF_CONFIG.pop(DefaultUserTask) +SPIFF_CONFIG.pop(DefaultScriptTask) +SPIFF_CONFIG.pop(DefaultSendTask) +SPIFF_CONFIG.pop(DefaultReceiveTask) +SPIFF_CONFIG.pop(DefaultSubWorkflowTask) +SPIFF_CONFIG.pop(DefaultTransactionSubprocess) +SPIFF_CONFIG.pop(DefaultCallActivity) +SPIFF_CONFIG.pop(DefaultStandardLoopTask) +SPIFF_CONFIG.pop(DefaultParallelMultiInstanceTask) +SPIFF_CONFIG.pop(DefaultSequentialMultiInstanceTask) -SPIFF_CONFIG = [ - SimpleBpmnTaskConverter, - BpmnStartTaskConverter, - EndJoinConverter, - StartEventConverter, - EndEventConverter, - IntermediateCatchEventConverter, - IntermediateThrowEventConverter, - EventBasedGatewayConverter, - BoundaryEventConverter, - BoundaryEventSplitConverter, - BoundaryEventJoinConverter, - ParallelGatewayConverter, - ExclusiveGatewayConverter, - InclusiveGatewayConverter, - NoneTaskConverter, - ManualTaskConverter, - UserTaskConverter, - SendTaskConverter, - ReceiveTaskConverter, - ScriptTaskConverter, - ServiceTaskConverter, - SubprocessTaskConverter, - TransactionSubprocessConverter, - CallActivityTaskConverter, - StandardLoopTaskConverter, - ParallelMultiInstanceTaskConverter, - SequentialMultiInstanceTaskConverter, - BusinessRuleTaskConverter, - CancelEventDefinitionConverter, - NoneEventDefinitionConverter, - TerminateEventDefinitionConverter, - TimeDateEventDefinitionConverter, - DurationTimerEventDefinitionConverter, - CycleTimerEventDefinitionConverter, - MultipleEventDefinitionConverter, - MessageEventDefinitionConverter, - SignalEventDefinitionConverter, - ErrorEventDefinitionConverter, - EscalationEventDefinitionConverter, - BpmnDataObjectConverter, - TaskDataReferenceConverter, - IOSpecificationConverter, - BpmnWorkflowConverter, - BpmnSubWorkflowConverter, - TaskConverter, - BpmnEventConverter, - BpmnProcessSpecConverter, -] +SPIFF_CONFIG[NoneTask] = SpiffBpmnTaskConverter +SPIFF_CONFIG[ManualTask] = SpiffBpmnTaskConverter +SPIFF_CONFIG[UserTask] = SpiffBpmnTaskConverter +SPIFF_CONFIG[ScriptTask] = ScriptTaskConverter +SPIFF_CONFIG[ServiceTask] = ServiceTaskConverter +SPIFF_CONFIG[SendTask] = SendReceiveTaskConverter +SPIFF_CONFIG[ReceiveTask] = SendReceiveTaskConverter +SPIFF_CONFIG[SubWorkflowTask] = SubWorkflowTaskConverter +SPIFF_CONFIG[CallActivity] = SubWorkflowTaskConverter +SPIFF_CONFIG[TransactionSubprocess] = SubWorkflowTaskConverter +SPIFF_CONFIG[ParallelMultiInstanceTask] = SpiffMultiInstanceConverter +SPIFF_CONFIG[SequentialMultiInstanceTask] = SpiffMultiInstanceConverter +SPIFF_CONFIG[MessageEventDefinition] = MessageEventDefinitionConverter +SPIFF_CONFIG[SignalEventDefinition] = ItemAwareEventDefinitionConverter +SPIFF_CONFIG[ErrorEventDefinition] = ErrorEscalationEventDefinitionConverter +SPIFF_CONFIG[EscalationEventDefinition] = ErrorEscalationEventDefinitionConverter +SPIFF_CONFIG[BusinessRuleTask] = BusinessRuleTaskConverter diff --git a/SpiffWorkflow/spiff/serializer/event_definition.py b/SpiffWorkflow/spiff/serializer/event_definition.py index 1d50524d..0e53bc7e 100644 --- a/SpiffWorkflow/spiff/serializer/event_definition.py +++ b/SpiffWorkflow/spiff/serializer/event_definition.py @@ -19,18 +19,8 @@ from SpiffWorkflow.bpmn.serializer.helpers.spec import EventDefinitionConverter -from SpiffWorkflow.spiff.specs.event_definitions import ( - MessageEventDefinition, - SignalEventDefinition, - ErrorEventDefinition, - EscalationEventDefinition, -) - class MessageEventDefinitionConverter(EventDefinitionConverter): - def __init__(self, registry): - super().__init__(MessageEventDefinition, registry) - def to_dict(self, event_definition): dct = super().to_dict(event_definition) dct['correlation_properties'] = self.correlation_properties_to_dict(event_definition.correlation_properties) @@ -53,27 +43,9 @@ def to_dict(self, event_definition): return dct -class SignalEventDefinitionConverter(ItemAwareEventDefinitionConverter): - def __init__(self, registry): - super().__init__(SignalEventDefinition, registry) - - -class ErrorEventDefinitionConverter(ItemAwareEventDefinitionConverter): - - def __init__(self, registry): - super().__init__(ErrorEventDefinition, registry) +class ErrorEscalationEventDefinitionConverter(ItemAwareEventDefinitionConverter): def to_dict(self, event_definition): dct = super().to_dict(event_definition) dct['code'] = event_definition.code return dct - -class EscalationEventDefinitionConverter(ItemAwareEventDefinitionConverter): - - def __init__(self, registry): - super().__init__(EscalationEventDefinition, registry) - - def to_dict(self, event_definition): - dct = super().to_dict(event_definition) - dct['code'] = event_definition.code - return dct \ No newline at end of file diff --git a/SpiffWorkflow/spiff/serializer/task_spec.py b/SpiffWorkflow/spiff/serializer/task_spec.py index 9a28d3c1..30aa4f4b 100644 --- a/SpiffWorkflow/spiff/serializer/task_spec.py +++ b/SpiffWorkflow/spiff/serializer/task_spec.py @@ -50,24 +50,7 @@ def from_dict(self, dct): return self.task_spec_from_dict(dct) -class NoneTaskConverter(SpiffBpmnTaskConverter): - def __init__(self, registry): - super().__init__(NoneTask, registry) - - -class ManualTaskConverter(SpiffBpmnTaskConverter): - def __init__(self, registry): - super().__init__(ManualTask, registry) - - -class UserTaskConverter(SpiffBpmnTaskConverter): - def __init__(self, registry): - super().__init__(UserTask, registry) - - class BusinessRuleTaskConverter(BaseBusinessRuleTaskConverter, SpiffBpmnTaskConverter): - def __init__(self, registry): - super().__init__(BusinessRuleTask, registry) def to_dict(self, spec): dct = BaseBusinessRuleTaskConverter.to_dict(self, spec) @@ -75,24 +58,7 @@ def to_dict(self, spec): return dct -class SendTaskConverter(SpiffBpmnTaskConverter): - - def __init__(self, registry, typename=None): - super().__init__(SendTask, registry, typename) - - def to_dict(self, spec): - dct = super().to_dict(spec) - dct['event_definition'] = self.registry.convert(spec.event_definition) - return dct - - def from_dict(self, dct): - dct['event_definition'] = self.registry.restore(dct['event_definition']) - return super().from_dict(dct) - - -class ReceiveTaskConverter(SpiffBpmnTaskConverter): - def __init__(self, registry, typename=None): - super().__init__(ReceiveTask, registry, typename) +class SendReceiveTaskConverter(SpiffBpmnTaskConverter): def to_dict(self, spec): dct = super().to_dict(spec) @@ -105,8 +71,6 @@ def from_dict(self, dct): class ScriptTaskConverter(SpiffBpmnTaskConverter): - def __init__(self, registry): - super().__init__(ScriptTask, registry) def to_dict(self, spec): dct = super().to_dict(spec) @@ -115,8 +79,6 @@ def to_dict(self, spec): class ServiceTaskConverter(SpiffBpmnTaskConverter): - def __init__(self, registry): - super().__init__(ServiceTask, registry) def to_dict(self, spec): dct = super().to_dict(spec) @@ -140,24 +102,9 @@ def from_dict(self, dct): dct['subworkflow_spec'] = dct.pop('spec') return super().task_spec_from_dict(dct) -class SubprocessTaskConverter(SubWorkflowTaskConverter): - def __init__(self, registry): - super().__init__(SubWorkflowTask, registry) - -class TransactionSubprocessConverter(SubWorkflowTaskConverter): - def __init__(self, registry): - super().__init__(TransactionSubprocess, registry) - -class CallActivityTaskConverter(SubWorkflowTaskConverter): - def __init__(self, registry): - super().__init__(CallActivity, registry) - class StandardLoopTaskConverter(SpiffBpmnTaskConverter): - def __init__(self, registry): - super().__init__(StandardLoopTask, registry) - def to_dict(self, spec): dct = self.get_default_attributes(spec) dct.update(self.get_standard_loop_attributes(spec)) @@ -170,11 +117,3 @@ def to_dict(self, spec): dct = MultiInstanceTaskConverter.to_dict(self, spec) dct.update(SpiffBpmnTaskConverter.to_dict(self, spec)) return dct - -class ParallelMultiInstanceTaskConverter(SpiffMultiInstanceConverter): - def __init__(self, registry): - super().__init__(ParallelMultiInstanceTask, registry) - -class SequentialMultiInstanceTaskConverter(SpiffMultiInstanceConverter): - def __init__(self, registry): - super().__init__(SequentialMultiInstanceTask, registry) diff --git a/tests/SpiffWorkflow/bpmn/BpmnLoaderForTests.py b/tests/SpiffWorkflow/bpmn/BpmnLoaderForTests.py index 094d5bbe..4ae8b028 100644 --- a/tests/SpiffWorkflow/bpmn/BpmnLoaderForTests.py +++ b/tests/SpiffWorkflow/bpmn/BpmnLoaderForTests.py @@ -1,4 +1,4 @@ -# -*- coding: utf-8 -*- +from copy import deepcopy from SpiffWorkflow.bpmn.specs.data_spec import BpmnDataStoreSpecification from SpiffWorkflow.bpmn.specs.defaults import ExclusiveGateway @@ -9,7 +9,9 @@ from SpiffWorkflow.bpmn.parser.util import full_tag from SpiffWorkflow.bpmn.serializer.helpers.registry import BpmnConverter -from SpiffWorkflow.bpmn.serializer.helpers.spec import TaskSpecConverter +from SpiffWorkflow.bpmn.serializer.default.task_spec import BpmnTaskSpecConverter +from SpiffWorkflow.bpmn.serializer.config import DEFAULT_CONFIG + __author__ = 'matth' @@ -40,18 +42,6 @@ def parse_condition(self, sequence_flow_node): return cond return "choice == '%s'" % sequence_flow_node.get('name', None) -class TestUserTaskConverter(TaskSpecConverter): - - def __init__(self, data_converter=None): - super().__init__(TestUserTask, data_converter) - - def to_dict(self, spec): - dct = self.get_default_attributes(spec) - return dct - - def from_dict(self, dct): - return self.task_spec_from_dict(dct) - class TestDataStore(BpmnDataStoreSpecification): _value = None @@ -67,9 +57,6 @@ def set(self, my_task): class TestDataStoreConverter(BpmnConverter): - def __init__(self, registry): - super().__init__(TestDataStore, registry) - def to_dict(self, spec): return { "bpmn_id": spec.bpmn_id, @@ -94,3 +81,7 @@ class TestBpmnParser(BpmnParser): DATA_STORE_CLASSES = { "TestDataStore": TestDataStore, } + +SERIALIZER_CONFIG = deepcopy(DEFAULT_CONFIG) +SERIALIZER_CONFIG[TestUserTask] = BpmnTaskSpecConverter +SERIALIZER_CONFIG[TestDataStore] = TestDataStoreConverter diff --git a/tests/SpiffWorkflow/bpmn/BpmnWorkflowTestCase.py b/tests/SpiffWorkflow/bpmn/BpmnWorkflowTestCase.py index d3e21aa6..77eee7e1 100644 --- a/tests/SpiffWorkflow/bpmn/BpmnWorkflowTestCase.py +++ b/tests/SpiffWorkflow/bpmn/BpmnWorkflowTestCase.py @@ -6,15 +6,13 @@ from SpiffWorkflow.util.task import TaskState from SpiffWorkflow.bpmn.workflow import BpmnTaskFilter -from SpiffWorkflow.bpmn.serializer.workflow import BpmnWorkflowSerializer, DEFAULT_CONFIG -from .BpmnLoaderForTests import TestUserTaskConverter, TestBpmnParser, TestDataStoreConverter +from SpiffWorkflow.bpmn.serializer.workflow import BpmnWorkflowSerializer +from .BpmnLoaderForTests import TestBpmnParser, SERIALIZER_CONFIG -__author__ = 'matth' -DEFAULT_CONFIG.append(TestUserTaskConverter) -DEFAULT_CONFIG.append(TestDataStoreConverter) +__author__ = 'matth' -registry = BpmnWorkflowSerializer.configure(DEFAULT_CONFIG) +registry = BpmnWorkflowSerializer.configure(SERIALIZER_CONFIG) class BpmnWorkflowTestCase(unittest.TestCase): diff --git a/tests/SpiffWorkflow/camunda/specs/UserTaskSpecTest.py b/tests/SpiffWorkflow/camunda/specs/UserTaskSpecTest.py index 7c805f6e..1d44371c 100644 --- a/tests/SpiffWorkflow/camunda/specs/UserTaskSpecTest.py +++ b/tests/SpiffWorkflow/camunda/specs/UserTaskSpecTest.py @@ -52,7 +52,7 @@ def testSerialize(self): self.form.add_field(field1) self.form.add_field(field2) - converter = UserTaskConverter(DictionaryConverter()) + converter = UserTaskConverter(UserTask, DictionaryConverter()) dct = converter.to_dict(self.user_spec) self.assertEqual(dct['name'], 'userTask') self.assertEqual(dct['form'], { diff --git a/tests/SpiffWorkflow/dmn/HitPolicyTest.py b/tests/SpiffWorkflow/dmn/HitPolicyTest.py index 47fcc175..4c372ef1 100644 --- a/tests/SpiffWorkflow/dmn/HitPolicyTest.py +++ b/tests/SpiffWorkflow/dmn/HitPolicyTest.py @@ -2,10 +2,10 @@ import unittest from SpiffWorkflow.bpmn.serializer.helpers.dictionary import DictionaryConverter -from SpiffWorkflow.camunda.serializer.task_spec import BusinessRuleTaskConverter +from SpiffWorkflow.dmn.serializer.task_spec import BaseBusinessRuleTaskConverter +from SpiffWorkflow.camunda.specs.business_rule_task import BusinessRuleTask from .python_engine.PythonDecisionRunner import PythonDecisionRunner - class HitPolicyTest(unittest.TestCase): def testHitPolicyUnique(self): @@ -32,7 +32,7 @@ def testSerializeHitPolicy(self): runner = PythonDecisionRunner(file_name) decision_table = runner.decision_table self.assertEqual("COLLECT", decision_table.hit_policy) - converter = BusinessRuleTaskConverter(DictionaryConverter()) + converter = BaseBusinessRuleTaskConverter(BusinessRuleTask, DictionaryConverter()) dict = converter.decision_table_to_dict(decision_table) new_table = converter.decision_table_from_dict(dict) self.assertEqual("COLLECT", new_table.hit_policy) From 3baebb20223d8887394375b7b5ffc0e981de3e7e Mon Sep 17 00:00:00 2001 From: Elizabeth Esswein Date: Sat, 16 Sep 2023 08:48:05 -0400 Subject: [PATCH 4/6] documentation & cleanup --- SpiffWorkflow/bpmn/serializer/config.py | 2 +- .../bpmn/serializer/default/data_spec.py | 34 ----- .../bpmn/serializer/default/process_spec.py | 4 +- .../bpmn/serializer/default/task_spec.py | 70 ++++++++- .../bpmn/serializer/helpers/dictionary.py | 50 +++--- .../bpmn/serializer/helpers/registry.py | 54 ++++--- SpiffWorkflow/bpmn/serializer/helpers/spec.py | 144 +++++++++--------- SpiffWorkflow/bpmn/serializer/workflow.py | 124 ++++++++++----- SpiffWorkflow/bpmn/specs/bpmn_task_spec.py | 5 +- SpiffWorkflow/specs/base.py | 4 +- 10 files changed, 300 insertions(+), 191 deletions(-) delete mode 100644 SpiffWorkflow/bpmn/serializer/default/data_spec.py diff --git a/SpiffWorkflow/bpmn/serializer/config.py b/SpiffWorkflow/bpmn/serializer/config.py index ad2d868d..84ce01cd 100644 --- a/SpiffWorkflow/bpmn/serializer/config.py +++ b/SpiffWorkflow/bpmn/serializer/config.py @@ -85,7 +85,6 @@ BpmnEventConverter, ) from .helpers.spec import BpmnDataSpecificationConverter, EventDefinitionConverter -from .default.data_spec import IOSpecificationConverter from .default.process_spec import BpmnProcessSpecConverter from .default.task_spec import ( BpmnTaskSpecConverter, @@ -99,6 +98,7 @@ ParallelGatewayConverter, EventConverter, BoundaryEventConverter, + IOSpecificationConverter, ) from .default.event_definition import ( TimerEventDefinitionConverter, diff --git a/SpiffWorkflow/bpmn/serializer/default/data_spec.py b/SpiffWorkflow/bpmn/serializer/default/data_spec.py deleted file mode 100644 index 5701e2f3..00000000 --- a/SpiffWorkflow/bpmn/serializer/default/data_spec.py +++ /dev/null @@ -1,34 +0,0 @@ -# Copyright (C) 2023 Sartography -# -# This file is part of SpiffWorkflow. -# -# SpiffWorkflow is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 3.0 of the License, or (at your option) any later version. -# -# SpiffWorkflow is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -# 02110-1301 USA - -from ..helpers.registry import BpmnConverter - -class IOSpecificationConverter(BpmnConverter): - - def to_dict(self, spec): - return { - 'data_inputs': [self.registry.convert(item) for item in spec.data_inputs], - 'data_outputs': [self.registry.convert(item) for item in spec.data_outputs], - } - - def from_dict(self, dct): - return self.target_class( - data_inputs=[self.registry.restore(item) for item in dct['data_inputs']], - data_outputs=[self.registry.restore(item) for item in dct['data_outputs']], - ) diff --git a/SpiffWorkflow/bpmn/serializer/default/process_spec.py b/SpiffWorkflow/bpmn/serializer/default/process_spec.py index bc8fd6b2..62c2bb3c 100644 --- a/SpiffWorkflow/bpmn/serializer/default/process_spec.py +++ b/SpiffWorkflow/bpmn/serializer/default/process_spec.py @@ -17,10 +17,10 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA -from ..helpers.spec import WorkflowSpecConverter +from ..helpers.registry import BpmnConverter -class BpmnProcessSpecConverter(WorkflowSpecConverter): +class BpmnProcessSpecConverter(BpmnConverter): def convert_task_spec_extensions(self, task_spec, dct): # Extensions will be moved out of the base parser, but since we currently add them to some diff --git a/SpiffWorkflow/bpmn/serializer/default/task_spec.py b/SpiffWorkflow/bpmn/serializer/default/task_spec.py index d599d25b..e6b95eaa 100644 --- a/SpiffWorkflow/bpmn/serializer/default/task_spec.py +++ b/SpiffWorkflow/bpmn/serializer/default/task_spec.py @@ -19,20 +19,77 @@ from SpiffWorkflow.bpmn.specs.bpmn_task_spec import _BpmnCondition +from ..helpers.spec import BpmnConverter from ..helpers.spec import TaskSpecConverter +class IOSpecificationConverter(BpmnConverter): + """The converter for an IOSpecification""" + + def to_dict(self, spec): + """Converts an IO spec to a dictionary representation + + Arguments: + spec (BpmnIOSpecification): the `BpmnIOSpecification` of a `BpmnTaskSpec` + + Returns: + dict: a dictionary representation of the IO spec + """ + return { + 'data_inputs': [self.registry.convert(item) for item in spec.data_inputs], + 'data_outputs': [self.registry.convert(item) for item in spec.data_outputs], + } + + def from_dict(self, dct): + """Restore a `BpmnIOSpecification` from a dictionary representation + + Arguments: + dct (dict): the dictionary representation + + Returns: + `BpmnIOSpecification`: a `BpmnTaskSpec` IO spec + """ + return self.target_class( + data_inputs=[self.registry.restore(item) for item in dct['data_inputs']], + data_outputs=[self.registry.restore(item) for item in dct['data_outputs']], + ) + class BpmnTaskSpecConverter(TaskSpecConverter): + """The base converter for a `BpmnTaskSpec` + + This converter can be extended for customized task specs with additional attributes (e.g. the + ones defined in this module, which can serve as examples for anyone who has created a custom + BPMN task spec. + """ def to_dict(self, spec): - dct = self.get_default_attributes(spec) - return dct + """Create a dictionary representation of the shared `BpmnTaskSpec` attributes + + Arguments: + spec: the spec to be converter to a dictionary + + Returns: + dict: a dictionary representation of shared attributes + """ + return self.get_default_attributes(spec) def from_dict(self, dct): + """Restore a `BpmnTaskSpec` from a dictionary of attributes + + If you have added only custom attributes that can be passed to `__init__`, you won't need + to extend this. + + Arguments: + dct (dict): the task spec's dictionary representation + + Returns: + an instance of the target class + """ return self.task_spec_from_dict(dct) class ScriptTaskConverter(BpmnTaskSpecConverter): + """The default converter for `ScriptTask`""" def to_dict(self, spec): dct = self.get_default_attributes(spec) @@ -41,6 +98,7 @@ def to_dict(self, spec): class StandardLoopTaskConverter(BpmnTaskSpecConverter): + """The default converter for `StandardLoopTask`""" def to_dict(self, spec): dct = self.get_default_attributes(spec) @@ -49,6 +107,7 @@ def to_dict(self, spec): class MultiInstanceTaskConverter(BpmnTaskSpecConverter): + """The default converter for Parallel and Sequential MultiInstance Tasks""" def to_dict(self, spec): dct = self.get_default_attributes(spec) @@ -70,6 +129,7 @@ def from_dict(self, dct): class BoundaryEventJoinConverter(BpmnTaskSpecConverter): + """The default converter for `BoundaryEventJoin`""" def to_dict(self, spec): dct = super().to_dict(spec) @@ -77,6 +137,7 @@ def to_dict(self, spec): return dct class SubWorkflowConverter(BpmnTaskSpecConverter): + """The default converter for subworkflows (`SubWOrkflowTask`, `CallActivity`, `TransactionSubprocess`)""" def to_dict(self, spec): dct = super().to_dict(spec) @@ -89,6 +150,7 @@ def from_dict(self, dct): class ConditionalGatewayConverter(BpmnTaskSpecConverter): + """A converter class that adds attributes for a `TaskSpec` with conditional outputs""" def to_dict(self, spec): dct = super().to_dict(spec) @@ -114,6 +176,7 @@ def bpmn_condition_to_dict(self, condition): class ExclusiveGatewayConverter(ConditionalGatewayConverter): + """THe default converterfor `ExclusiveGateway`task specs""" def to_dict(self, spec): dct = super().to_dict(spec) @@ -128,6 +191,7 @@ def from_dict(self, dct): class ParallelGatewayConverter(BpmnTaskSpecConverter): + """The default converter for `ParallelGateway` task specs """ def to_dict(self, spec): dct = super().to_dict(spec) @@ -139,6 +203,7 @@ def from_dict(self, dct): class EventConverter(BpmnTaskSpecConverter): + """The default converter for BPMN events""" def to_dict(self, spec): dct = super().to_dict(spec) @@ -151,6 +216,7 @@ def from_dict(self, dct): class BoundaryEventConverter(EventConverter): + """The default converter for `BoundaryEvent` task specs""" def to_dict(self, spec): dct = super().to_dict(spec) diff --git a/SpiffWorkflow/bpmn/serializer/helpers/dictionary.py b/SpiffWorkflow/bpmn/serializer/helpers/dictionary.py index d2af5b28..7481abdf 100644 --- a/SpiffWorkflow/bpmn/serializer/helpers/dictionary.py +++ b/SpiffWorkflow/bpmn/serializer/helpers/dictionary.py @@ -41,8 +41,12 @@ class DictionaryConverter: If a registered `typename` is found, the supplied `from_dict` function will be called. Unrecognized objects will be returned as-is. - For a simple example of how to use this class, see the `BpmnDataConverter` in - `registry`. + For a simple example of how to use this class, see `registry.DefaultRegistry`. + + Attributes: + typenames (dict): a mapping class to typename + convert_to_dict (dict): a mapping of typename to function + convert_from_dct (dict): a mapping of typename to function """ def __init__(self): @@ -53,41 +57,46 @@ def __init__(self): def register(self, cls, to_dict, from_dict, typename=None): """Register a conversion/restoration. - The `to_dict` function must return a dictionary; if no `typename` is given, - the unquallified class name will be used. + Arguments: + cls: the class that will be converted/restored + to_dict (function): a function that will be called with the object as an argument + from_dict (function): a function that restores the object from the dict + typename (str): an optional typename for identifying the converted object - :param cls: the class that will be converted/restored - :param to_dict: a function that will be called with the object as an argument - :param from_dict: a function that restores the object from the dict - :param typename: an optional typename for identifying the converted object + Notes: + The `to_dict` function must return a dictionary; if no `typename` is given, + the unquallified class name will be used. """ typename = cls.__name__ if typename is None else typename self.typenames[cls] = typename - self.convert_to_dict[typename] = partial(self.obj_to_dict, typename, to_dict) - self.convert_from_dict[typename] = partial(self.obj_from_dict, from_dict) + self.convert_to_dict[typename] = partial(self._obj_to_dict, typename, to_dict) + self.convert_from_dict[typename] = partial(self._obj_from_dict, from_dict) @staticmethod - def obj_to_dict(typename, func, obj, **kwargs): + def _obj_to_dict(typename, func, obj, **kwargs): + """A method for automatically inserting the typename in the dictionary returned by to_dict.""" dct = func(obj, **kwargs) dct.update({'typename': typename}) return dct @staticmethod - def obj_from_dict(func, dct, **kwargs): + def _obj_from_dict(func, dct, **kwargs): + """A method for calling the from_dict function on recognized objects.""" return func(dct, **kwargs) def convert(self, obj, **kwargs): - """ + """Convert a known object to a dictionary. + This is the public conversion method. It will be applied to dictionary values, list items, and the object itself, applying the to_dict functions of any registered type to the objects, or return the object unchanged if it is not recognized. - :param obj: the object to be converter + Arguments: + obj: the object to be converter Returns: - the dictionary representation for registered objects or the original - for unregistered objects + dict: the dictionary representation for registered objects or the original for unregistered objects """ typename = self.typenames.get(obj.__class__) if typename in self.convert_to_dict: @@ -101,17 +110,18 @@ def convert(self, obj, **kwargs): return obj def restore(self, val, **kwargs): - """ + """Restore a known object from a dictionary. + This is the public restoration method. It will be applied to dictionary values, list items, and the value itself, checking for a `typename` key and applying the from_dict function of any registered type, or return the value unchanged if it is not recognized. - :param val: the value to be converted + Arguments: + val: the value to be converted Returns: - the restored object for registered objects or the original for - unregistered values + dict: the restored object for registered objects or the original for unregistered values """ if isinstance(val, dict) and 'typename' in val: from_dict = self.convert_from_dict.get(val.pop('typename')) diff --git a/SpiffWorkflow/bpmn/serializer/helpers/registry.py b/SpiffWorkflow/bpmn/serializer/helpers/registry.py index 57890a89..9cb441b6 100644 --- a/SpiffWorkflow/bpmn/serializer/helpers/registry.py +++ b/SpiffWorkflow/bpmn/serializer/helpers/registry.py @@ -23,13 +23,11 @@ from .dictionary import DictionaryConverter class DefaultRegistry(DictionaryConverter): - """ - The default converter for task and workflow data. It allows some commonly used python objects - to be converted to a form that can be serialized with JSOM - - It also serves as a simple example for anyone who needs custom data serialization. If you have - custom objects or python objects not included here in your workflow/task data, then you should - replace or extend this with one that can handle the contents of your workflow. + """This class forms the basis of serialization for BPMN workflows. + + It contains serialization rules for a few python data types that are not JSON serializable by default which + are used internally by Spiff. It can be instantiated and customized to handle arbitrary task or workflow + data as well (see `dictionary.DictionaryConverter`). """ def __init__(self): @@ -39,11 +37,28 @@ def __init__(self): self.register(timedelta, lambda v: { 'days': v.days, 'seconds': v.seconds }, lambda v: timedelta(**v)) def convert(self, obj): + """Convert an object to a dictionary, with preprocessing. + + Arguments: + obj: the object to preprocess and convert + + Returns: + the result of `convert` conversion after preprocessing + """ cleaned = self.clean(obj) return super().convert(cleaned) def clean(self, obj): - # This can be used to remove functions and other callables; by default we remove these from task data + """A method that can be used to preprocess an object before conversion to a dict. + + It is used internally by Spiff to remove callables from task data before serialization. + + Arguments: + obj: the object to preprocess + + Returns: + the preprocessed object + """ if isinstance(obj, dict): return dict((k, v) for k, v in obj.items() if not callable(v)) else: @@ -53,17 +68,16 @@ def clean(self, obj): class BpmnConverter: """The base class for conversion of BPMN classes. - In general, most classes that extend this would simply take an existing registry as an - argument and automatically supply the class along with the implementations of the - conversion functions `to_dict` and `from_dict`. + In general, most classes that extend this would simply take an existing registry as an argument + nd supply the class along with the implementations of the conversion functions `to_dict` and + `from_dict`. The operation of the converter is a little opaque, but hopefully makes sense with a little explanation. The registry is a `DictionaryConverter` that registers conversion methods by class. It can be pre-populated with methods for custom data (though this is not required) and is passed into - each of these sublclasses. When a subclass of this one gets instantiated, it adds the spec it - is intended to operate on to this registry. + subclasses of this one, which will consolidate conversions as classes are instantiated. There is a lot of interdependence across the classes in spiff -- most of them need to know about many of the other classes. Subclassing this is intended to consolidate the boiler plate required @@ -74,15 +88,15 @@ class BpmnConverter: So for example, it is not necessary to re-implemnent any of the event-based task spec conversions because, eg, the `MessageEventDefintion` was modified; the existing `MessageEventDefinitionConverter` - can be replaced with a customized one and it will automatically be used when the event specs are - transformed. + can be replaced with a customized one and it will automatically be used with any event-based task. """ def __init__(self, target_class, registry, typename=None): - """Constructor for a BPMN class. + """Constructor for a dictionary converter for a BPMN class. - :param spec_class: the class of the spec the subclass provides conversions for - :param registry: a registry of conversions to which this one should be added - :param typename: the name of the class as it will appear in the serialization + Arguemnts: + target_class: the type the subclass provides conversions for + registry (`DictionaryConverter`): a registry of conversions to which this one should be added + typename (str): the name of the class as it will appear in the serialization """ self.target_class = target_class self.registry = registry @@ -90,9 +104,11 @@ def __init__(self, target_class, registry, typename=None): self.registry.register(target_class, self.to_dict, self.from_dict, self.typename) def to_dict(self, spec): + """This method should take an object and convert it to a dictionary that is JSON-serializable""" raise NotImplementedError def from_dict(self, dct): + """This method take a dictionary and restore the original object""" raise NotImplementedError def mapping_to_dict(self, mapping, **kwargs): diff --git a/SpiffWorkflow/bpmn/serializer/helpers/spec.py b/SpiffWorkflow/bpmn/serializer/helpers/spec.py index 54f9a628..e08288cd 100644 --- a/SpiffWorkflow/bpmn/serializer/helpers/spec.py +++ b/SpiffWorkflow/bpmn/serializer/helpers/spec.py @@ -29,13 +29,30 @@ class BpmnDataSpecificationConverter(BpmnConverter): """This is the base Data Spec converter. - Currently the only use is Data Objects. + This is used for `DataObject` and `TaskDataReference`; it can be extended for other + types of data specs. """ def to_dict(self, data_spec): + """Convert a data specification into a dictionary. + + Arguments: + data_spec (`BpmnDataSpecification): a BPMN data specification + + Returns: + dict: a dictionary representation of the data spec + """ return { 'bpmn_id': data_spec.bpmn_id, 'bpmn_name': data_spec.bpmn_name } def from_dict(self, dct): + """Restores a data specification. + + Arguments: + dct (dict): the dictionary representation + + Returns: + an instance of the target class + """ return self.target_class(**dct) @@ -48,6 +65,14 @@ class EventDefinitionConverter(BpmnConverter): """ def to_dict(self, event_definition): + """Convert an event definition into a dictionary. + + Arguments: + event_definition: the event definition + + Returns: + dict: a dictionary representation of the event definition + """ dct = { 'description': event_definition.description, 'name': event_definition.name @@ -55,19 +80,42 @@ def to_dict(self, event_definition): return dct def from_dict(self, dct): + """Restores an event definition. + + Arguments: + dct: the dictionary representation + + Returns; + an instance of the target event definition + """ event_definition = self.target_class(**dct) return event_definition def correlation_properties_to_dict(self, props): + """Convert correlation properties to a dictionary representation. + + Arguments: + list(`CorrelationProperty`): the correlation properties associated with a message + + Returns: + list(dict): a list of dictionary representations of the correlation properties + """ return [prop.__dict__ for prop in props] def correlation_properties_from_dict(self, props): + """Restore correlation properties from a dictionary representation + + Arguments: + props (list(dict)): a list if dictionary representations of correlation properties + + Returns: + a list of `CorrelationProperty` of a message + """ return [CorrelationProperty(**prop) for prop in props] class TaskSpecConverter(BpmnConverter): - """ - This the base Task Spec Converter. + """Base Task Spec Converter. It contains methods for parsing generic and BPMN task spec attributes. @@ -75,17 +123,17 @@ class TaskSpecConverter(BpmnConverter): implement a converter for those task spec types. You'll need to implement the `to_dict` and `from_dict` methods on any inheriting classes. - The default task spec converters are in the `task`, 'process_spec`, and 'event_definitions` - modules of this package; the `camunda`,`dmn`, and `spiff` serialization packages contain other - examples. + The default task spec converters are in the `default.task_spec` modules of this package; the + `camunda`,`dmn`, and `spiff` serialization packages contain other examples. """ def get_default_attributes(self, spec): - """Extracts the default Spiff attributes from a task spec. + """Extracts the default BPMN attributes from a task spec. - :param spec: the task spec to be converted + Arguments: + spec: the task spec to be converted Returns: - a dictionary of standard task spec attributes + dict: a dictionary of standard task spec attributes """ return { 'name': spec.name, @@ -98,18 +146,19 @@ def get_default_attributes(self, spec): 'bpmn_name': spec.bpmn_name, 'lane': spec.lane, 'documentation': spec.documentation, - 'data_input_associations': [ self.registry.convert(obj) for obj in spec.data_input_associations ], - 'data_output_associations': [ self.registry.convert(obj) for obj in spec.data_output_associations ], + 'data_input_associations': self.registry.convert(spec.data_input_associations), + 'data_output_associations': self.registry.convert(spec.data_output_associations), 'io_specification': self.registry.convert(spec.io_specification), } def get_join_attributes(self, spec): """Extracts attributes for task specs that inherit from `Join`. - :param spec: the task spec to be converted + Arguments: + spec: the task spec to be converted Returns: - a dictionary of `Join` task spec attributes + dict: a dictionary of `Join` task spec attributes """ return { 'split_task': spec.split_task, @@ -120,20 +169,22 @@ def get_join_attributes(self, spec): def get_subworkflow_attributes(self, spec): """Extracts attributes for task specs that inherit from `SubWorkflowTask`. - :param spec: the task spec to be converted + Arguments: + spec: the task spec to be converted Returns: - a dictionary of subworkflow task spec attributes + dict: a dictionary of subworkflow task spec attributes """ return {'spec': spec.spec} def get_standard_loop_attributes(self, spec): """Extracts attributes for standard loop tasks. - - :param spec: the task spec to be converted + + Arguments: + spec: the task spec to be converted Returns: - a dictionary of standard loop task spec attributes + dict: a dictionary of standard loop task spec attributes """ return { 'task_spec': spec.task_spec, @@ -143,72 +194,29 @@ def get_standard_loop_attributes(self, spec): } def task_spec_from_dict(self, dct): - """ - Creates a task spec based on the supplied dictionary. It handles setting the default - task spec attributes as well as attributes added by `BpmnSpecMixin`. + """Creates a task spec based on the supplied dictionary. + + It handles setting the default task spec attributes as well as attributes added by `BpmnSpecMixin`. - :param dct: the dictionary to create the task spec from + Arguments: + dct (dict): the dictionary to create the task spec from Returns: a restored task spec """ dct['data_input_associations'] = self.registry.restore(dct.pop('data_input_associations', [])) dct['data_output_associations'] = self.registry.restore(dct.pop('data_output_associations', [])) - - inputs = dct.pop('inputs') - outputs = dct.pop('outputs') + dct['io_specification'] = self.registry.restore(dct.pop('io_specification', None)) wf_spec = dct.pop('wf_spec') name = dct.pop('name') bpmn_id = dct.pop('bpmn_id') spec = self.target_class(wf_spec, name, **dct) - spec._inputs = inputs - spec._outputs = outputs if issubclass(self.target_class, BpmnSpecMixin) and bpmn_id != name: # This is a hack for multiinstance tasks :( At least it is simple. # Ideally I'd fix it in the parser, but I'm afraid of quickly running into a wall there spec.bpmn_id = bpmn_id - if isinstance(spec, BpmnSpecMixin): - spec.io_specification = self.registry.restore(dct.pop('io_specification', None)) - - return spec - - -class WorkflowSpecConverter(BpmnConverter): - """ - This is the base converter for a BPMN workflow spec. - - It will register converters for the task spec types contained in the workflow, as well as - the workflow spec class itself. - - This class can be extended if you implement a custom workflow spec type. See the converter - in `workflow_spec_converter` for an example. - """ - - def __init__(self, spec_class, registry): - """ - Converter for a BPMN workflow spec class. - - The `to_dict` and `from_dict` methods of the given task spec converter classes will - be registered, so that they can be restored automatically. - - The data_converter applied to task *spec* data, not task data, and may be `None`. See - `BpmnTaskSpecConverter` for more discussion. - - :param spec_class: the workflow spec class - :param task_spec_converters: a list of `BpmnTaskSpecConverter` classes - """ - super().__init__(spec_class, registry) - - # Leaving these as-as, as I can't imagine anyone would need or want to extend - self.registry.register(Attrib, self.attrib_to_dict, partial(self.attrib_from_dict, Attrib)) - self.registry.register(PathAttrib, self.attrib_to_dict, partial(self.attrib_from_dict, PathAttrib)) - - def attrib_to_dict(self, attrib): - return { 'name': attrib.name } - - def attrib_from_dict(self, attrib_class, dct): - return attrib_class(dct['name']) \ No newline at end of file + return spec \ No newline at end of file diff --git a/SpiffWorkflow/bpmn/serializer/workflow.py b/SpiffWorkflow/bpmn/serializer/workflow.py index 18426718..84149254 100644 --- a/SpiffWorkflow/bpmn/serializer/workflow.py +++ b/SpiffWorkflow/bpmn/serializer/workflow.py @@ -24,58 +24,57 @@ from .config import DEFAULT_CONFIG -# This is the default version set on the workflow, it can be overwritten in init +# This is the default version set on the workflow, it can be overridden in init VERSION = "1.3" class BpmnWorkflowSerializer: - """ - This class implements a customizable BPMN Workflow serializer, based on a Workflow Spec Converter - and a Data Converter. - - The goal is to provide modular serialization capabilities. - - You'll need to configure a Workflow Spec Converter with converters for any task, data, or event types - present in your workflows. + """This class implements a customizable BPMN Workflow serializer, based on the `DefaultRegistry`. - If you have implemented any custom specs, you'll need to write a converter to handle them and - replace the converter from the default confiuration with your own. + Workflows contain two types of objects: workflows/tasks/standard specs (objects that Spiff provides + serialization for automatically) and arbitrary data (associated with tasks and workflows). The goal + of this serializer is to provide a mechanism that allows for handling both, as well as the ability + to replace one of the default internal conversion mechanisms with your own if you've extended any of + the classes. - If your workflow contains non-JSON-serializable objects, you'll need to extend or replace the - default data converter with one that will handle them. This converter needs to implement - `convert` and `restore` methods. + See `configure` for more details on customization. Serialization occurs in two phases: the first is to convert everything in the workflow to a - dictionary containing only JSON-serializable objects and the second is dumping to JSON. - - This means that you can call the `workflow_to_dict` or `workflow_from_dict` methods separately from - conversion to JSON for further manipulation of the state, or selective serialization of only certain - parts of the workflow more conveniently. You can of course call methods from the Workflow Spec and - Data Converters via the `spec_converter` and `data_converter` attributes as well to bypass the - overhead of converting or restoring the entire thing. + dictionary containing only JSON-serializable objects and the second is dumping to JSON, which happens + only at the very end. + + Attributes: + registry (`DictionaryConverter`): a registry that keeps track of all objects the serializer knows + json_encoder_cls: passed into `convert` to provides additional json encding capabilities (optional) + json_decoder_cls: passed into `restore` to provide additional json decoding capabilities (optional) + version (str): the serializer version """ VERSION_KEY = "serializer_version" # Why is this customizable? @staticmethod def configure(config=None, registry=None): - """ - This method can be used to create a spec converter that uses custom specs. + """Can be used to create a with custom Spiff classes. + + If you have replaced any of the default classes that Spiff uses with your own, Spiff will not know + how to serialize them and you'll have to provide conversion mechanisms. - The task specs may contain arbitrary data, though none of the default task specs use it. We don't - recommend that you do this, as we may disallow it in the future. However, if you have task spec data, - then you'll also need to make sure it can be serialized. + The `config` is a dictionary with keys for each (Spiff) class that needs to be handled that map to a + converter for that class. There are some basic converters which provide from methods for handling + essential Spiff attributes in the `helpers` package of this module; the default converters, found in + the `defaults` package of this module extend these. The default configuration is found in `config`. - The workflow spec serializer is based on the `DictionaryConverter` in the `helpers` package. You can - create one of your own, add custom data serializtion to that and pass that in as the `registry`. The - conversion classes in the spec_config will be added this "registry" and any classes with entries there - will be serialized/deserialized. + The `registry` contains optional custom data conversions and the items in `config` will be added to + it, to create one repository of information about serialization. See `DictionaryConverter` for more + information about customized data. This parameter is optional and if not provided, `DefaultRegistry` + will be used. - See the documentation for `helpers.spec.BpmnSpecConverter` for more information about what's going - on here. + Objects that are unknown to the `registry` will be passed on as-is and serialization can be handled + through custom JSON encoding/decoding as an alternative. - :param spec_config: a dictionary specifying how to save and restore any classes used by the spec - :param registry: a `DictionaryConverter` with conversions for custom data (if applicable) + Arguments: + spec_config (dict): a mapping of class -> objects containing `BpmnConverter` + registry (`DictionaryConverter`): with conversions for custom data (if applicable) """ config = config or DEFAULT_CONFIG if registry is None: @@ -85,11 +84,13 @@ def configure(config=None, registry=None): return registry def __init__(self, registry=None, version=VERSION, json_encoder_cls=None, json_decoder_cls=None): - """Intializes a Workflow Serializer with the given Workflow, Task and Data Converters. + """Intializes a Workflow Serializer. - :param registry: a registry of conversions to dictionaries - :param json_encoder_cls: JSON encoder class to be used for dumps/dump operations - :param json_decoder_cls: JSON decoder class to be used for loads/load operations + Arguments: + registry (`DictionaryConverter`): a registry that keeps track of all objects the serializer knows + version (str): the serializer version + json_encoder_cls: passed into `convert` to provides additional json encding capabilities (optional) + json_decoder_cls: passed into `restore` to provide additional json decoding capabilities (optional) """ super().__init__() self.registry = registry or self.configure() @@ -100,10 +101,12 @@ def __init__(self, registry=None, version=VERSION, json_encoder_cls=None, json_d def serialize_json(self, workflow, use_gzip=False): """Serialize the dictionary representation of the workflow to JSON. - :param workflow: the workflow to serialize + Arguments: + workflow: the workflow to serialize + use_gzip (bool): optionally gzip the resulting string Returns: - a JSON dump of the dictionary representation + a JSON dump of the dictionary representation or a gzipped version of it """ dct = self.to_dict(workflow) dct[self.VERSION_KEY] = self.VERSION @@ -111,12 +114,29 @@ def serialize_json(self, workflow, use_gzip=False): return gzip.compress(json_str.encode('utf-8')) if use_gzip else json_str def deserialize_json(self, serialization, use_gzip=False): + """Deserialize a workflow from an optionally zipped JSON-dumped workflow. + + Arguments: + serialization: the serialization to restore + use_gzip (bool): optionally gunzip the input + + Returns: + the restored workflow + """ json_str = gzip.decompress(serialization) if use_gzip else serialization dct = json.loads(json_str, cls=self.json_decoder_cls) self.migrate(dct) return self.from_dict(dct) def get_version(self, serialization): + """Get the version specified in the serialization + + Arguments: + serialization: a string or dictionary representation of a workflow + + Returns: + the version of the serializer the serilization we done with, if present + """ if isinstance(serialization, dict): return serialization.get(self.VERsiON_KEY) elif isinstance(serialization, str): @@ -124,13 +144,35 @@ def get_version(self, serialization): return dct.get(self.VERSION_KEY) def migrate(self, dct): - # Upgrade serialized version if necessary + """Update the serialization format, if necessaary.""" version = dct.pop(self.VERSION_KEY) if version in MIGRATIONS: MIGRATIONS[version](dct) def to_dict(self, obj, **kwargs): + """Apply any know conversions to an object. + + Arguments: + obj: the object + + Keyword arguments: + optional keyword args that will be passed to `self.registry.convert` + + Returns: + a dictionary representation of the object + """ return self.registry.convert(obj, **kwargs) def from_dict(self, dct, **kwargs): + """Restore an known object from a dict. + + Arguments: + dct: the dictionary representation of the object + + Keyword arguments: + optional keyword args that will be passed to `self.registry.restore` + + Returns: + a restored object + """ return self.registry.restore(dct, **kwargs) \ No newline at end of file diff --git a/SpiffWorkflow/bpmn/specs/bpmn_task_spec.py b/SpiffWorkflow/bpmn/specs/bpmn_task_spec.py index 22e805ef..a3002113 100644 --- a/SpiffWorkflow/bpmn/specs/bpmn_task_spec.py +++ b/SpiffWorkflow/bpmn/specs/bpmn_task_spec.py @@ -28,7 +28,8 @@ class BpmnTaskSpec(TaskSpec): easy way of knowing whether a task appearson the diagram. """ def __init__(self, wf_spec, name, lane=None, documentation=None, - data_input_associations=None, data_output_associations=None, **kwargs): + data_input_associations=None, data_output_associations=None, + io_specification=None, **kwargs): """ :param lane: Indicates the name of the lane that this task belongs to :param documentation: the contents of the documentation element @@ -42,7 +43,7 @@ def __init__(self, wf_spec, name, lane=None, documentation=None, self.documentation = documentation self.data_input_associations = data_input_associations or [] self.data_output_associations = data_output_associations or [] - self.io_specification = None + self.io_specification = io_specification if self.description is None: self.description = 'BPMN Task' diff --git a/SpiffWorkflow/specs/base.py b/SpiffWorkflow/specs/base.py index 8b92ce77..7e9ef2f9 100644 --- a/SpiffWorkflow/specs/base.py +++ b/SpiffWorkflow/specs/base.py @@ -92,8 +92,8 @@ def __init__(self, wf_spec, name, **kwargs): self._wf_spec = wf_spec self.name = str(name) self.description = kwargs.get('description', None) - self._inputs = [] - self._outputs = [] + self._inputs = kwargs.get('inputs', []) + self._outputs = kwargs.get('outputs', []) self.manual = kwargs.get('manual', False) self.data = kwargs.get('data', {}) self.defines = kwargs.get('defines', {}) From 9bf0760aad468459e677a0463ef381fd85b074c8 Mon Sep 17 00:00:00 2001 From: jasquat Date: Tue, 10 Oct 2023 16:23:55 -0400 Subject: [PATCH 5/6] merged in main and added init file to default seralizer dir to avoid issues in backend w/ burnettk --- SpiffWorkflow/bpmn/serializer/default/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 SpiffWorkflow/bpmn/serializer/default/__init__.py diff --git a/SpiffWorkflow/bpmn/serializer/default/__init__.py b/SpiffWorkflow/bpmn/serializer/default/__init__.py new file mode 100644 index 00000000..e69de29b From 61480256417fbd7e7916cb79d439293be0535024 Mon Sep 17 00:00:00 2001 From: burnettk Date: Wed, 11 Oct 2023 11:58:56 -0400 Subject: [PATCH 6/6] typo --- .../bpmn/serializer/helpers/registry.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/SpiffWorkflow/bpmn/serializer/helpers/registry.py b/SpiffWorkflow/bpmn/serializer/helpers/registry.py index 9cb441b6..0c1a70a0 100644 --- a/SpiffWorkflow/bpmn/serializer/helpers/registry.py +++ b/SpiffWorkflow/bpmn/serializer/helpers/registry.py @@ -23,8 +23,8 @@ from .dictionary import DictionaryConverter class DefaultRegistry(DictionaryConverter): - """This class forms the basis of serialization for BPMN workflows. - + """This class forms the basis of serialization for BPMN workflows. + It contains serialization rules for a few python data types that are not JSON serializable by default which are used internally by Spiff. It can be instantiated and customized to handle arbitrary task or workflow data as well (see `dictionary.DictionaryConverter`). @@ -41,7 +41,7 @@ def convert(self, obj): Arguments: obj: the object to preprocess and convert - + Returns: the result of `convert` conversion after preprocessing """ @@ -68,8 +68,8 @@ def clean(self, obj): class BpmnConverter: """The base class for conversion of BPMN classes. - In general, most classes that extend this would simply take an existing registry as an argument - nd supply the class along with the implementations of the conversion functions `to_dict` and + In general, most classes that extend this would simply take an existing registry as an argument + and supply the class along with the implementations of the conversion functions `to_dict` and `from_dict`. The operation of the converter is a little opaque, but hopefully makes sense with a little @@ -85,7 +85,7 @@ class BpmnConverter: The goal is to be able to replace the conversion mechanism for a particular entity without delving into the details of other things spiff knows about. - + So for example, it is not necessary to re-implemnent any of the event-based task spec conversions because, eg, the `MessageEventDefintion` was modified; the existing `MessageEventDefinitionConverter` can be replaced with a customized one and it will automatically be used with any event-based task. @@ -100,8 +100,8 @@ def __init__(self, target_class, registry, typename=None): """ self.target_class = target_class self.registry = registry - self.typename = typename if typename is not None else target_class.__name__ - self.registry.register(target_class, self.to_dict, self.from_dict, self.typename) + self.typename = typename if typename is not None else target_class.__name__ + self.registry.register(target_class, self.to_dict, self.from_dict, self.typename) def to_dict(self, spec): """This method should take an object and convert it to a dictionary that is JSON-serializable""" @@ -120,4 +120,4 @@ def mapping_from_dict(self, mapping, key_class=None, **kwargs): if key_class is None: return dict((k, self.registry.restore(v, **kwargs)) for k, v in mapping.items()) else: - return dict((key_class(k), self.registry.restore(v, **kwargs)) for k, v in mapping.items()) \ No newline at end of file + return dict((key_class(k), self.registry.restore(v, **kwargs)) for k, v in mapping.items())