mirror of
https://github.com/sartography/spiff-arena.git
synced 2025-02-28 17:10:42 +00:00
Merge commit 'e2022f401a5968e76286645e9f4eb6ee4efb2196'
This commit is contained in:
commit
73a4012a87
32
SpiffWorkflow/.github/workflows/tests.yaml
vendored
Normal file
32
SpiffWorkflow/.github/workflows/tests.yaml
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
name: Unit Tests in Python 3.8, 3.9, 3.10, 3.11
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.8", "3.9", "3.10", "3.11"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install ruff pytest
|
||||
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
|
||||
# - name: Lint with ruff
|
||||
# run: |
|
||||
# stop the build if there are Python syntax errors or undefined names
|
||||
# ruff --format=github --select=E9,F63,F7,F82 --target-version=py37 .
|
||||
# default set of ruff rules with GitHub Annotations
|
||||
# ruff --format=github --target-version=py37 .
|
||||
- name: Test with pytest
|
||||
run: |
|
||||
python -m unittest discover -v -s ./tests/SpiffWorkflow/ -p *Test.py
|
@ -1,7 +0,0 @@
|
||||
sonar.organization=sartography
|
||||
sonar.projectKey=sartography_SpiffWorkflow
|
||||
sonar.host.url=https://sonarcloud.io
|
||||
sonar.exclusions=*.bpmn,*.dmn,doc/**
|
||||
sonar.sources=SpiffWorkflow
|
||||
sonar.test.inclusions=tests
|
||||
sonar.python.coverage.reportPaths=tests/SpiffWorkflow/coverage.xml
|
@ -57,4 +57,4 @@ New versions of SpiffWorkflow are automatically published to PyPi whenever
|
||||
a maintainer of our GitHub repository creates a new release on GitHub. This
|
||||
is managed through GitHub's actions. The configuration of which can be
|
||||
found in .github/workflows/....
|
||||
Just create a release in GitHub that mathches the release number in doc/conf.py
|
||||
Just create a release in GitHub that matches the release number in doc/conf.py
|
||||
|
@ -19,9 +19,7 @@ strategy for building Low-Code applications.
|
||||
|
||||
## Build status
|
||||
[](https://travis-ci.org/sartography/SpiffWorkflow)
|
||||
[](https://sonarcloud.io/dashboard?id=sartography_SpiffWorkflow)
|
||||
[](https://sonarcloud.io/dashboard?id=sartography_SpiffWorkflow)
|
||||
[](https://sonarcloud.io/dashboard?id=sartography_SpiffWorkflow)
|
||||
[](https://github.com/sartography/SpiffWorkflow/actions/workflows/tests.yaml)
|
||||
[](http://spiffworkflow.readthedocs.io/en/latest/?badge=latest)
|
||||
[](https://github.com/sartography/SpiffWorkflow/issues)
|
||||
[](https://github.com/sartography/SpiffWorkflow/pulls)
|
||||
|
138
SpiffWorkflow/RELEASE_NOTES.md
Normal file
138
SpiffWorkflow/RELEASE_NOTES.md
Normal file
@ -0,0 +1,138 @@
|
||||
## What's Changed
|
||||
|
||||
We've done a lot of work over the last 8 months to the SpiffWorkflow library as we've developed [SpiffArena](https://www.spiffworkflow.org/), a general purpose workflow management system built on top of this library.
|
||||
This has resulted in just a handful of new features.
|
||||
Our main focus was on making SpiffWorkflow more predictable, easier to use, and internally consistent.
|
||||
|
||||
## Breaking Changes from 1.x:
|
||||
* We heavily refactored the way we handle multi-instance tasks internally. This will break any serialized workflows that contain multi-instance tasks.
|
||||
* Internal structure of our code, the names classes, and common methods have changed. Please see our [ReadTheDocs] (https://readthedocs.org/projects/spiffworkflow/) documenation for version 2.0.0.
|
||||
|
||||
## Features and Improvements
|
||||
|
||||
### Task States, Transitions, Hooks, and Execution
|
||||
Previous to 2.0, SpiffWorklow was a little weird about its states, performing the actual execution in the on_complete() hook.
|
||||
This was VERY confusing.
|
||||
Tasks now have a _run() command separate from state change hooks.
|
||||
The return value of the _run() command can be true (worked), false (failure), or None (not yet done).
|
||||
This opens the door for better overall state management at the moment it is most critical (when the task is actually executing).
|
||||
We also added new task state called "STARTED" that describes when a task was started, but hasn't finished yet, an oddly missing state in previous versions.
|
||||
|
||||
* Improvement/execution and serialization cleanup by @essweine in https://github.com/sartography/SpiffWorkflow/pull/289
|
||||
* Bugfix/execute tasks on ready by @essweine in https://github.com/sartography/SpiffWorkflow/pull/303
|
||||
* Feature/standardize task execution by @essweine in https://github.com/sartography/SpiffWorkflow/pull/307
|
||||
* do not execute boundary events in catch by @essweine in https://github.com/sartography/SpiffWorkflow/pull/312
|
||||
* Feature/new task states by @essweine in https://github.com/sartography/SpiffWorkflow/pull/315
|
||||
|
||||
### Improved Events
|
||||
We refactored the way we handle events, making them more powerful and adaptable.
|
||||
Timer events are now parsed according to the [ISO 8601 standard](https://en.wikipedia.org/wiki/ISO_8601).
|
||||
* Feature/multiple event definition by @essweine in https://github.com/sartography/SpiffWorkflow/pull/268
|
||||
* hacks to handle timer events like regular events by @essweine in https://github.com/sartography/SpiffWorkflow/pull/273
|
||||
* Feature/improved timer events by @essweine in https://github.com/sartography/SpiffWorkflow/pull/284
|
||||
* reset boundary events in loops by @essweine in https://github.com/sartography/SpiffWorkflow/pull/294
|
||||
* Bugfix/execute event gateways on ready by @essweine in https://github.com/sartography/SpiffWorkflow/pull/308
|
||||
|
||||
### Improved Multi-Instance Tasks
|
||||
We refactored how Multi-instance tasks are handled internally, vastly simplifying their representation during execution and serialization.
|
||||
No more 'phantom gateways.'
|
||||
* Feature/multiinstance refactor by @essweine in https://github.com/sartography/SpiffWorkflow/pull/292
|
||||
|
||||
### Improved SubProcesses
|
||||
SpiffWorkflow did not previously distinguish between a Call Activity and a SubProcess, but they handle Data Objects very differently.
|
||||
A SubProcess is now able to access its parent data objects, a Call Activity can not.
|
||||
We also wanted the ability to execute Call Activities independently of the parent process.
|
||||
|
||||
* Bugfix/subprocess access to data objects by @essweine in https://github.com/sartography/SpiffWorkflow/pull/296
|
||||
* start workflow while subprocess is waiting by @essweine in https://github.com/sartography/SpiffWorkflow/pull/302
|
||||
* use same data objects & references in subprocesses after deserialization by @essweine in https://github.com/sartography/SpiffWorkflow/pull/314
|
||||
|
||||
### Improved Data Objects / Data Stores
|
||||
This work will continue in subsequent releases, but we have added support for Data Stores, and it is possible to provide your own implementations.
|
||||
* Data stores by @jbirddog in https://github.com/sartography/SpiffWorkflow/pull/298
|
||||
* make data objects available to gateways by @essweine in https://github.com/sartography/SpiffWorkflow/pull/325
|
||||
|
||||
### Improved Inclusive Gateways
|
||||
We added support for Inclusive Gateways.
|
||||
* Feature/inclusive gateway support by @essweine in https://github.com/sartography/SpiffWorkflow/pull/286
|
||||
|
||||
### Pre and Post Script Fixes
|
||||
We previously supported adding a pre-script or post-script to any task but there were a few lingering bugs that needed fixing.
|
||||
* parse spiff script extensions in service tasks by @essweine in https://github.com/sartography/SpiffWorkflow/pull/257
|
||||
* pass script to workflow task exec exception by @essweine in https://github.com/sartography/SpiffWorkflow/pull/258
|
||||
* update execution order for postscripts by @essweine in https://github.com/sartography/SpiffWorkflow/pull/259
|
||||
|
||||
### DMN Improvements
|
||||
We now support a new hit policy of "COLLECT" which allows you to match on an array of items. DMN support is still limited, but
|
||||
we are making headway. We would love to know if people are using these features.
|
||||
* Support for the "COLLECT" hit policy. by @danfunk in https://github.com/sartography/SpiffWorkflow/pull/267
|
||||
* Bugfix/handle dash in DMN by @essweine in https://github.com/sartography/SpiffWorkflow/pull/323
|
||||
|
||||
### BPMN Validation
|
||||
We improved validation of BPMN and DMN Files to catch errors earlier.
|
||||
* Feature/xml validation by @essweine and @danfunk in https://github.com/sartography/SpiffWorkflow/pull/256
|
||||
|
||||
### New Serializer
|
||||
There are some breaking changes in the new serializer, but it is much faster and more stable. We do attempt to upgrade
|
||||
your serialized workflows to the new format, but you will definitely encounter issues if you were using multi-instance tasks.
|
||||
* update serializer version by @essweine in https://github.com/sartography/SpiffWorkflow/pull/277
|
||||
* Feature/remove old serializer by @essweine in https://github.com/sartography/SpiffWorkflow/pull/278
|
||||
|
||||
### Lightning Fast, Stable Tests
|
||||
* Fix ResourceWarning: unclosed file BpmnParser.py:60 by @jbirddog in https://github.com/sartography/SpiffWorkflow/pull/270
|
||||
* Option to run tests in parallel by @jbirddog in https://github.com/sartography/SpiffWorkflow/pull/271
|
||||
|
||||
### Better Errors
|
||||
* Feature/better errors by @danfunk in https://github.com/sartography/SpiffWorkflow/pull/283
|
||||
* Workflow Data Exceptions were broken in the previous error refactor. … by @danfunk in https://github.com/sartography/SpiffWorkflow/pull/287
|
||||
* added an exception for task not found w/ @burnettk by @jasquat in https://github.com/sartography/SpiffWorkflow/pull/310
|
||||
* give us a better error if for some reason a task does not exist by @burnettk in https://github.com/sartography/SpiffWorkflow/pull/311
|
||||
|
||||
### Flexible Data Management
|
||||
* Allow for other PythonScriptEngine environments besides task data by @jbirddog in https://github.com/sartography/SpiffWorkflow/pull/288
|
||||
|
||||
### Various Enhancements
|
||||
Make it easier to reference SpiffWorkflow library classes from your own code.
|
||||
* Feature/add init to schema by @jasquat in https://github.com/sartography/SpiffWorkflow/pull/260
|
||||
* cleaning up code smell by @danfunk in https://github.com/sartography/SpiffWorkflow/pull/261
|
||||
* Feature/cleanup task completion by @essweine in https://github.com/sartography/SpiffWorkflow/pull/263
|
||||
* disambiguate DMN expressions by @essweine in https://github.com/sartography/SpiffWorkflow/pull/264
|
||||
* Add in memory BPMN/DMN parser functions by @jbirddog in https://github.com/sartography/SpiffWorkflow/pull/320
|
||||
|
||||
### Better Introspection
|
||||
Added the ability to ask SpiffWorkflow some useful questions about a specification such as, "What call activities does this depend on?",
|
||||
"What messages does this process send and receive", and "What lanes exist on this workflow specification?"
|
||||
* Parser Information about messages, correlation keys, and the presence of lanes by @danfunk in https://github.com/sartography/SpiffWorkflow/pull/262
|
||||
* Called elements by @jbirddog in https://github.com/sartography/SpiffWorkflow/pull/316
|
||||
|
||||
### Code Cleanup
|
||||
* Improvement/task spec attributes by @essweine in https://github.com/sartography/SpiffWorkflow/pull/328
|
||||
* update license by @essweine in https://github.com/sartography/SpiffWorkflow/pull/324
|
||||
* Feature/remove unused BPMN attributes and methods by @essweine in https://github.com/sartography/SpiffWorkflow/pull/280
|
||||
* Improvement/remove camunda from base and misc cleanup by @essweine in https://github.com/sartography/SpiffWorkflow/pull/295
|
||||
* remove minidom by @essweine in https://github.com/sartography/SpiffWorkflow/pull/300
|
||||
* Feature/remove loop reset by @essweine in https://github.com/sartography/SpiffWorkflow/pull/305
|
||||
* Feature/create core test package by @essweine in https://github.com/sartography/SpiffWorkflow/pull/306
|
||||
* remove celery task and dependency by @essweine in https://github.com/sartography/SpiffWorkflow/pull/322
|
||||
* remove one deprecated and unused feature by @essweine in https://github.com/sartography/SpiffWorkflow/pull/329
|
||||
* change the order of tasks when calling get_tasks() by @danfunk in https://github.com/sartography/SpiffWorkflow/pull/319
|
||||
|
||||
### Improved Documentation
|
||||
* Fixes grammar, typos, and spellings by @rachfop in https://github.com/sartography/SpiffWorkflow/pull/291
|
||||
* Updates for 2.0 release by @essweine in https://github.com/sartography/SpiffWorkflow/pull/330
|
||||
* Bugfix/non BPMN tutorial by @essweine in https://github.com/sartography/SpiffWorkflow/pull/317
|
||||
|
||||
### Bug Fixes
|
||||
* correct xpath for extensions by @essweine in https://github.com/sartography/SpiffWorkflow/pull/265
|
||||
* prevent output associations from being removed twice by @essweine in https://github.com/sartography/SpiffWorkflow/pull/275
|
||||
* fix for workflowspec dump by @subhakarks in https://github.com/sartography/SpiffWorkflow/pull/282
|
||||
* add checks for len == 0 when copying based on io spec by @essweine in https://github.com/sartography/SpiffWorkflow/pull/297
|
||||
* Improvement/allow duplicate subprocess names by @essweine in https://github.com/sartography/SpiffWorkflow/pull/321
|
||||
* Resets to tasks with Boundary Events by @danfunk in https://github.com/sartography/SpiffWorkflow/pull/326
|
||||
* Sub-workflow tasks should be marked as "Future" when resetting to a task before the sub-process. by @danfunk in https://github.com/sartography/SpiffWorkflow/pull/327
|
||||
|
||||
## New Contributors
|
||||
* @subhakarks made their first contribution in https://github.com/sartography/SpiffWorkflow/pull/282
|
||||
* @rachfop made their first contribution in https://github.com/sartography/SpiffWorkflow/pull/291
|
||||
|
||||
**Full Changelog**: https://github.com/sartography/SpiffWorkflow/compare/v1.2.1...v2.0.0
|
@ -0,0 +1,16 @@
|
||||
# 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
|
@ -1,20 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import re
|
||||
import datetime
|
||||
import operator
|
||||
from datetime import timedelta
|
||||
from decimal import Decimal
|
||||
from .PythonScriptEngine import PythonScriptEngine
|
||||
|
||||
# Copyright (C) 2020 Kelly McDonald
|
||||
# Copyright (C) 2020 Kelly McDonald, 2023 Sartography
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# 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 2.1 of the License, or (at your option) any later version.
|
||||
# version 3.0 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# 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.
|
||||
@ -24,6 +17,13 @@ from .PythonScriptEngine import PythonScriptEngine
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
# 02110-1301 USA
|
||||
|
||||
import re
|
||||
import datetime
|
||||
import operator
|
||||
from datetime import timedelta
|
||||
from decimal import Decimal
|
||||
from .PythonScriptEngine import PythonScriptEngine
|
||||
|
||||
|
||||
def feelConvertTime(datestr,parsestr):
|
||||
return datetime.datetime.strptime(datestr,parsestr)
|
||||
@ -79,8 +79,8 @@ class FeelNot():
|
||||
|
||||
def feelConcatenate(*lst):
|
||||
ilist = []
|
||||
for l in lst:
|
||||
ilist = ilist + l
|
||||
for list_item in lst:
|
||||
ilist = ilist + list_item
|
||||
return ilist
|
||||
|
||||
def feelAppend(lst,item):
|
||||
@ -144,7 +144,7 @@ def feelFilter(var,a,b,op,column=None):
|
||||
newvar.append({'key':key,'value':var[key]})
|
||||
var = newvar
|
||||
|
||||
if column!=None:
|
||||
if column is not None:
|
||||
return [x.get(column) for x in var if opmap[op](x.get(a), b)]
|
||||
else:
|
||||
return [x for x in var if opmap[op](x.get(a), b)]
|
||||
@ -306,7 +306,7 @@ class FeelLikeScriptEngine(PythonScriptEngine):
|
||||
if external_methods is None:
|
||||
external_methods = {}
|
||||
external_methods.update(externalFuncs)
|
||||
super(PythonScriptEngine).execute(task, script, external_methods)
|
||||
super().execute(task, script, external_methods)
|
||||
|
||||
|
||||
|
||||
|
@ -1,21 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import ast
|
||||
import sys
|
||||
import traceback
|
||||
import warnings
|
||||
|
||||
from .PythonScriptEngineEnvironment import TaskDataEnvironment
|
||||
from ..exceptions import SpiffWorkflowException, WorkflowTaskException
|
||||
|
||||
|
||||
# Copyright (C) 2020 Kelly McDonald
|
||||
# Copyright (C) 2020 Kelly McDonald, 2023 Sartography
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# 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 2.1 of the License, or (at your option) any later version.
|
||||
# version 3.0 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# 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.
|
||||
@ -25,6 +17,15 @@ from ..exceptions import SpiffWorkflowException, WorkflowTaskException
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
# 02110-1301 USA
|
||||
|
||||
import ast
|
||||
import sys
|
||||
import traceback
|
||||
import warnings
|
||||
|
||||
from SpiffWorkflow.exceptions import SpiffWorkflowException
|
||||
from SpiffWorkflow.bpmn.exceptions import WorkflowTaskException
|
||||
from .PythonScriptEngineEnvironment import TaskDataEnvironment
|
||||
|
||||
|
||||
class PythonScriptEngine(object):
|
||||
"""
|
||||
@ -40,8 +41,8 @@ class PythonScriptEngine(object):
|
||||
def __init__(self, default_globals=None, scripting_additions=None, environment=None):
|
||||
|
||||
if default_globals is not None or scripting_additions is not None:
|
||||
warnings.warn(f'default_globals and scripting_additions are deprecated. '
|
||||
f'Please provide an environment such as TaskDataEnvrionment',
|
||||
warnings.warn('default_globals and scripting_additions are deprecated. '
|
||||
'Please provide an environment such as TaskDataEnvrionment',
|
||||
DeprecationWarning, stacklevel=2)
|
||||
|
||||
if environment is None:
|
||||
|
@ -1,3 +1,22 @@
|
||||
# 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
|
||||
|
||||
import copy
|
||||
import warnings
|
||||
|
||||
@ -96,7 +115,7 @@ class Box(dict):
|
||||
def __getattr__(self, attr):
|
||||
try:
|
||||
output = self[attr]
|
||||
except:
|
||||
except Exception:
|
||||
raise AttributeError(
|
||||
"Dictionary has no attribute '%s' " % str(attr))
|
||||
return output
|
||||
|
@ -1,12 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (C) 2012 Matthew Hampton
|
||||
# Copyright (C) 2012 Matthew Hampton, 2023 Sartography
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# 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 2.1 of the License, or (at your option) any later version.
|
||||
# version 3.0 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# 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.
|
||||
|
@ -1,4 +1,90 @@
|
||||
from SpiffWorkflow.exceptions import WorkflowTaskException
|
||||
# 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
|
||||
import re
|
||||
|
||||
from SpiffWorkflow.util import levenshtein
|
||||
from SpiffWorkflow.exceptions import WorkflowException
|
||||
|
||||
|
||||
class WorkflowTaskException(WorkflowException):
|
||||
"""WorkflowException that provides task_trace information."""
|
||||
|
||||
def __init__(self, error_msg, task=None, exception=None, line_number=None, offset=None, error_line=None):
|
||||
"""
|
||||
Exception initialization.
|
||||
|
||||
:param task: the task that threw the exception
|
||||
:type task: Task
|
||||
:param error_msg: a human readable error message
|
||||
:type error_msg: str
|
||||
:param exception: an exception to wrap, if any
|
||||
:type exception: Exception
|
||||
"""
|
||||
self.task = task
|
||||
self.line_number = line_number
|
||||
self.offset = offset
|
||||
self.error_line = error_line
|
||||
if exception:
|
||||
self.error_type = exception.__class__.__name__
|
||||
else:
|
||||
self.error_type = "unknown"
|
||||
super().__init__(error_msg, task_spec=task.task_spec)
|
||||
|
||||
if isinstance(exception, SyntaxError) and not line_number:
|
||||
# Line number and offset can be recovered directly from syntax errors,
|
||||
# otherwise they must be passed in.
|
||||
self.line_number = exception.lineno
|
||||
self.offset = exception.offset
|
||||
elif isinstance(exception, NameError):
|
||||
self.add_note(self.did_you_mean_from_name_error(exception, list(task.data.keys())))
|
||||
|
||||
# If encountered in a sub-workflow, this traces back up the stack,
|
||||
# so we can tell how we got to this particular task, no matter how
|
||||
# deeply nested in sub-workflows it is. Takes the form of:
|
||||
# task-description (file-name)
|
||||
self.task_trace = self.get_task_trace(task)
|
||||
|
||||
@staticmethod
|
||||
def get_task_trace(task):
|
||||
task_trace = [f"{task.task_spec.bpmn_name} ({task.workflow.spec.file})"]
|
||||
workflow = task.workflow
|
||||
while workflow != workflow.outer_workflow:
|
||||
caller = workflow.name
|
||||
workflow = workflow.outer_workflow
|
||||
task_trace.append(f"{workflow.spec.task_specs[caller].bpmn_name} ({workflow.spec.file})")
|
||||
return task_trace
|
||||
|
||||
@staticmethod
|
||||
def did_you_mean_from_name_error(name_exception, options):
|
||||
"""Returns a string along the lines of 'did you mean 'dog'? Given
|
||||
a name_error, and a set of possible things that could have been called,
|
||||
or an empty string if no valid suggestions come up. """
|
||||
def_match = re.match("name '(.+)' is not defined", str(name_exception))
|
||||
if def_match:
|
||||
bad_variable = re.match("name '(.+)' is not defined", str(name_exception)).group(1)
|
||||
most_similar = levenshtein.most_similar(bad_variable, options, 3)
|
||||
error_msg = ""
|
||||
if len(most_similar) == 1:
|
||||
error_msg += f' Did you mean \'{most_similar[0]}\'?'
|
||||
if len(most_similar) > 1:
|
||||
error_msg += f' Did you mean one of \'{most_similar}\'?'
|
||||
return error_msg
|
||||
|
||||
|
||||
class WorkflowDataException(WorkflowTaskException):
|
||||
|
@ -1,13 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2012 Matthew Hampton
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# 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 2.1 of the License, or (at your option) any later version.
|
||||
# version 3.0 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# 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.
|
||||
@ -23,26 +23,36 @@ import os
|
||||
from lxml import etree
|
||||
from lxml.etree import LxmlError
|
||||
|
||||
from SpiffWorkflow.bpmn.specs.events.event_definitions import NoneEventDefinition
|
||||
from SpiffWorkflow.bpmn.specs.bpmn_process_spec import BpmnProcessSpec
|
||||
from SpiffWorkflow.bpmn.specs.defaults import (
|
||||
UserTask,
|
||||
ManualTask,
|
||||
NoneTask,
|
||||
ScriptTask,
|
||||
ServiceTask,
|
||||
CallActivity,
|
||||
SubWorkflowTask,
|
||||
TransactionSubprocess,
|
||||
InclusiveGateway,
|
||||
ExclusiveGateway,
|
||||
ParallelGateway,
|
||||
StartEvent,
|
||||
EndEvent,
|
||||
IntermediateCatchEvent,
|
||||
IntermediateThrowEvent,
|
||||
SendTask,
|
||||
ReceiveTask,
|
||||
BoundaryEvent,
|
||||
EventBasedGateway
|
||||
)
|
||||
from SpiffWorkflow.bpmn.specs.event_definitions import NoneEventDefinition, TimerEventDefinition
|
||||
from SpiffWorkflow.bpmn.specs.mixins.subworkflow_task import SubWorkflowTask as SubWorkflowTaskMixin
|
||||
from SpiffWorkflow.bpmn.specs.mixins.events.start_event import StartEvent as StartEventMixin
|
||||
|
||||
from .ValidationException import ValidationException
|
||||
from ..specs.BpmnProcessSpec import BpmnProcessSpec
|
||||
from ..specs.data_spec import BpmnDataStoreSpecification
|
||||
from ..specs.events.EndEvent import EndEvent
|
||||
from ..specs.events.StartEvent import StartEvent
|
||||
from ..specs.events.IntermediateEvent import BoundaryEvent, IntermediateCatchEvent, IntermediateThrowEvent, EventBasedGateway
|
||||
from ..specs.events.IntermediateEvent import SendTask, ReceiveTask
|
||||
from ..specs.SubWorkflowTask import CallActivity, SubWorkflowTask, TransactionSubprocess
|
||||
from ..specs.ExclusiveGateway import ExclusiveGateway
|
||||
from ..specs.InclusiveGateway import InclusiveGateway
|
||||
from ..specs.ManualTask import ManualTask
|
||||
from ..specs.NoneTask import NoneTask
|
||||
from ..specs.ParallelGateway import ParallelGateway
|
||||
from ..specs.ScriptTask import ScriptTask
|
||||
from ..specs.ServiceTask import ServiceTask
|
||||
from ..specs.UserTask import UserTask
|
||||
from .ProcessParser import ProcessParser
|
||||
from .node_parser import DEFAULT_NSMAP
|
||||
from .spec_description import SPEC_DESCRIPTIONS
|
||||
from .util import full_tag, xpath_eval, first
|
||||
from .TaskParser import TaskParser
|
||||
from .task_parsers import (
|
||||
@ -86,7 +96,7 @@ class BpmnValidator:
|
||||
except ValidationException as ve:
|
||||
ve.file_name = filename
|
||||
ve.line_number = self.validator.error_log.last_error.line
|
||||
except LxmlError as le:
|
||||
except LxmlError:
|
||||
last_error = self.validator.error_log.last_error
|
||||
raise ValidationException(last_error.message, file_name=filename,
|
||||
line_number=last_error.line)
|
||||
@ -132,14 +142,14 @@ class BpmnParser(object):
|
||||
|
||||
DATA_STORE_CLASSES = {}
|
||||
|
||||
def __init__(self, namespaces=None, validator=None):
|
||||
def __init__(self, namespaces=None, validator=None, spec_descriptions=SPEC_DESCRIPTIONS):
|
||||
"""
|
||||
Constructor.
|
||||
"""
|
||||
self.namespaces = namespaces or DEFAULT_NSMAP
|
||||
self.validator = validator
|
||||
self.spec_descriptions = spec_descriptions
|
||||
self.process_parsers = {}
|
||||
self.process_parsers_by_name = {}
|
||||
self.collaborations = {}
|
||||
self.process_dependencies = set()
|
||||
self.messages = {}
|
||||
@ -153,15 +163,13 @@ class BpmnParser(object):
|
||||
return self.PARSER_CLASSES[tag]
|
||||
return None, None
|
||||
|
||||
def get_process_parser(self, process_id_or_name):
|
||||
def get_process_parser(self, process_id):
|
||||
"""
|
||||
Returns the ProcessParser for the given process ID or name. It matches
|
||||
by name first.
|
||||
"""
|
||||
if process_id_or_name in self.process_parsers_by_name:
|
||||
return self.process_parsers_by_name[process_id_or_name]
|
||||
elif process_id_or_name in self.process_parsers:
|
||||
return self.process_parsers[process_id_or_name]
|
||||
if process_id in self.process_parsers:
|
||||
return self.process_parsers[process_id]
|
||||
|
||||
def get_process_ids(self):
|
||||
"""Returns a list of process IDs"""
|
||||
@ -186,7 +194,19 @@ class BpmnParser(object):
|
||||
"""
|
||||
for filename in filenames:
|
||||
with open(filename, 'r') as f:
|
||||
self.add_bpmn_xml(etree.parse(f), filename=filename)
|
||||
self.add_bpmn_io(f, filename)
|
||||
|
||||
def add_bpmn_io(self, file_like_object, filename=None):
|
||||
"""
|
||||
Add the given BPMN file like object to the parser's set.
|
||||
"""
|
||||
self.add_bpmn_xml(etree.parse(file_like_object), filename)
|
||||
|
||||
def add_bpmn_str(self, bpmn_str, filename=None):
|
||||
"""
|
||||
Add the given BPMN string to the parser's set.
|
||||
"""
|
||||
self.add_bpmn_xml(etree.fromstring(bpmn_str), filename)
|
||||
|
||||
def add_bpmn_xml(self, bpmn, filename=None):
|
||||
"""
|
||||
@ -286,36 +306,36 @@ class BpmnParser(object):
|
||||
|
||||
def create_parser(self, node, filename=None, lane=None):
|
||||
parser = self.PROCESS_PARSER_CLASS(self, node, self.namespaces, self.data_stores, filename=filename, lane=lane)
|
||||
if parser.get_id() in self.process_parsers:
|
||||
raise ValidationException(f'Duplicate process ID: {parser.get_id()}', node=node, file_name=filename)
|
||||
if parser.get_name() in self.process_parsers_by_name:
|
||||
raise ValidationException(f'Duplicate process name: {parser.get_name()}', node=node, file_name=filename)
|
||||
self.process_parsers[parser.get_id()] = parser
|
||||
self.process_parsers_by_name[parser.get_name()] = parser
|
||||
if parser.bpmn_id in self.process_parsers:
|
||||
raise ValidationException(f'Duplicate process ID: {parser.bpmn_id}', node=node, file_name=filename)
|
||||
self.process_parsers[parser.bpmn_id] = parser
|
||||
|
||||
def get_process_dependencies(self):
|
||||
return self.process_dependencies
|
||||
|
||||
def get_spec(self, process_id_or_name):
|
||||
def get_spec(self, process_id, required=True):
|
||||
"""
|
||||
Parses the required subset of the BPMN files, in order to provide an
|
||||
instance of BpmnProcessSpec (i.e. WorkflowSpec)
|
||||
for the given process ID or name. The Name is matched first.
|
||||
"""
|
||||
parser = self.get_process_parser(process_id_or_name)
|
||||
if parser is None:
|
||||
parser = self.get_process_parser(process_id)
|
||||
if required and parser is None:
|
||||
raise ValidationException(
|
||||
f"The process '{process_id_or_name}' was not found. "
|
||||
f"The process '{process_id}' was not found. "
|
||||
f"Did you mean one of the following: "
|
||||
f"{', '.join(self.get_process_ids())}?")
|
||||
elif parser is not None:
|
||||
return parser.get_spec()
|
||||
|
||||
def get_subprocess_specs(self, name, specs=None):
|
||||
def get_subprocess_specs(self, name, specs=None, require_call_activity_specs=True):
|
||||
used = specs or {}
|
||||
wf_spec = self.get_spec(name)
|
||||
for task_spec in wf_spec.task_specs.values():
|
||||
if isinstance(task_spec, SubWorkflowTask) and task_spec.spec not in used:
|
||||
used[task_spec.spec] = self.get_spec(task_spec.spec)
|
||||
if isinstance(task_spec, SubWorkflowTaskMixin) and task_spec.spec not in used:
|
||||
subprocess_spec = self.get_spec(task_spec.spec, required=require_call_activity_specs)
|
||||
used[task_spec.spec] = subprocess_spec
|
||||
if subprocess_spec is not None:
|
||||
self.get_subprocess_specs(task_spec.spec, used)
|
||||
return used
|
||||
|
||||
@ -332,16 +352,24 @@ class BpmnParser(object):
|
||||
self.find_all_specs()
|
||||
spec = BpmnProcessSpec(name)
|
||||
subprocesses = {}
|
||||
start = StartEvent(spec, 'Start Collaboration', NoneEventDefinition())
|
||||
participant_type = self._get_parser_class(full_tag('callActivity'))[1]
|
||||
start_type = self._get_parser_class(full_tag('startEvent'))[1]
|
||||
end_type = self._get_parser_class(full_tag('endEvent'))[1]
|
||||
start = start_type(spec, 'Start Collaboration', NoneEventDefinition())
|
||||
spec.start.connect(start)
|
||||
end = EndEvent(spec, 'End Collaboration', NoneEventDefinition())
|
||||
end = end_type(spec, 'End Collaboration', NoneEventDefinition())
|
||||
end.connect(spec.end)
|
||||
for process in self.collaborations[name]:
|
||||
process_parser = self.get_process_parser(process)
|
||||
if process_parser and process_parser.process_executable:
|
||||
participant = CallActivity(spec, process, process)
|
||||
sp_spec = self.get_spec(process)
|
||||
subprocesses[process] = sp_spec
|
||||
subprocesses.update(self.get_subprocess_specs(process))
|
||||
if len([s for s in sp_spec.task_specs.values() if
|
||||
isinstance(s, StartEventMixin) and
|
||||
isinstance(s.event_definition, (NoneEventDefinition, TimerEventDefinition))
|
||||
]):
|
||||
participant = participant_type(spec, process, process)
|
||||
start.connect(participant)
|
||||
participant.connect(end)
|
||||
subprocesses[process] = self.get_spec(process)
|
||||
subprocesses.update(self.get_subprocess_specs(process))
|
||||
return spec, subprocesses
|
||||
|
@ -1,13 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2012 Matthew Hampton
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# 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 2.1 of the License, or (at your option) any later version.
|
||||
# version 3.0 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# 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.
|
||||
@ -18,7 +18,7 @@
|
||||
# 02110-1301 USA
|
||||
|
||||
from .ValidationException import ValidationException
|
||||
from ..specs.BpmnProcessSpec import BpmnProcessSpec
|
||||
from ..specs.bpmn_process_spec import BpmnProcessSpec
|
||||
from ..specs.data_spec import DataObject
|
||||
from .node_parser import NodeParser
|
||||
from .util import first
|
||||
@ -54,7 +54,7 @@ class ProcessParser(NodeParser):
|
||||
"""
|
||||
Returns the process name (or ID, if no name is included in the file)
|
||||
"""
|
||||
return self.node.get('name', default=self.get_id())
|
||||
return self.node.get('name', default=self.bpmn_id)
|
||||
|
||||
def has_lanes(self) -> bool:
|
||||
"""Returns true if this process has one or more named lanes """
|
||||
@ -117,14 +117,14 @@ class ProcessParser(NodeParser):
|
||||
start_node_list = self.xpath('./bpmn:startEvent')
|
||||
if not start_node_list and self.process_executable:
|
||||
raise ValidationException("No start event found", node=self.node, file_name=self.filename)
|
||||
self.spec = BpmnProcessSpec(name=self.get_id(), description=self.get_name(), filename=self.filename)
|
||||
self.spec = BpmnProcessSpec(name=self.bpmn_id, description=self.get_name(), filename=self.filename)
|
||||
|
||||
self.spec.data_objects.update(self.inherited_data_objects)
|
||||
|
||||
# Get the data objects
|
||||
for obj in self.xpath('./bpmn:dataObject'):
|
||||
data_object = self.parse_data_object(obj)
|
||||
self.spec.data_objects[data_object.name] = data_object
|
||||
self.spec.data_objects[data_object.bpmn_id] = data_object
|
||||
|
||||
# Check for an IO Specification.
|
||||
io_spec = first(self.xpath('./bpmn:ioSpecification'))
|
||||
|
@ -1,13 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2012 Matthew Hampton
|
||||
# Copyright (C) 2012 Matthew Hampton, 2023 Sartography
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# 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 2.1 of the License, or (at your option) any later version.
|
||||
# version 3.0 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# 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.
|
||||
@ -17,17 +17,21 @@
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
# 02110-1301 USA
|
||||
|
||||
from .ValidationException import ValidationException
|
||||
from ..specs.events.IntermediateEvent import _BoundaryEventParent
|
||||
from ..specs.events.event_definitions import CancelEventDefinition
|
||||
from ..specs.MultiInstanceTask import StandardLoopTask, SequentialMultiInstanceTask, ParallelMultiInstanceTask
|
||||
from ..specs.SubWorkflowTask import TransactionSubprocess
|
||||
from ..specs.ExclusiveGateway import ExclusiveGateway
|
||||
from ..specs.InclusiveGateway import InclusiveGateway
|
||||
from ..specs.data_spec import TaskDataReference
|
||||
from SpiffWorkflow.bpmn.specs.mixins.subworkflow_task import TransactionSubprocess
|
||||
from SpiffWorkflow.bpmn.specs.mixins.exclusive_gateway import ExclusiveGateway
|
||||
from SpiffWorkflow.bpmn.specs.mixins.inclusive_gateway import InclusiveGateway
|
||||
from SpiffWorkflow.bpmn.specs.defaults import (
|
||||
StandardLoopTask,
|
||||
SequentialMultiInstanceTask,
|
||||
ParallelMultiInstanceTask
|
||||
)
|
||||
from SpiffWorkflow.bpmn.specs.control import _BoundaryEventParent
|
||||
from SpiffWorkflow.bpmn.specs.event_definitions import CancelEventDefinition
|
||||
from SpiffWorkflow.bpmn.specs.data_spec import TaskDataReference
|
||||
|
||||
from .util import one
|
||||
from .node_parser import NodeParser
|
||||
from .ValidationException import ValidationException
|
||||
|
||||
|
||||
class TaskParser(NodeParser):
|
||||
@ -100,9 +104,11 @@ class TaskParser(NodeParser):
|
||||
cardinality = self.xpath(f'./{prefix}/bpmn:loopCardinality')
|
||||
loop_input = self.xpath(f'./{prefix}/bpmn:loopDataInputRef')
|
||||
if len(cardinality) == 0 and len(loop_input) == 0:
|
||||
self.raise_validation_exception("A multiinstance task must specify a cardinality or a loop input data reference")
|
||||
self.raise_validation_exception(
|
||||
"A multiinstance task must specify a cardinality or a loop input data reference")
|
||||
elif len(cardinality) > 0 and len(loop_input) > 0:
|
||||
self.raise_validation_exception("A multiinstance task must specify exactly one of cardinality or loop input data reference")
|
||||
self.raise_validation_exception(
|
||||
"A multiinstance task must specify exactly one of cardinality or loop input data reference")
|
||||
cardinality = cardinality[0].text if len(cardinality) > 0 else None
|
||||
|
||||
loop_input = loop_input[0].text if len(loop_input) > 0 else None
|
||||
@ -110,7 +116,7 @@ class TaskParser(NodeParser):
|
||||
if self.task.io_specification is not None:
|
||||
try:
|
||||
loop_input = [v for v in self.task.io_specification.data_inputs if v.name == loop_input][0]
|
||||
except:
|
||||
except Exception:
|
||||
self.raise_validation_exception('The loop input data reference is missing from the IO specification')
|
||||
else:
|
||||
loop_input = TaskDataReference(loop_input)
|
||||
@ -125,7 +131,7 @@ class TaskParser(NodeParser):
|
||||
try:
|
||||
refs = set(self.task.io_specification.data_inputs + self.task.io_specification.data_outputs)
|
||||
loop_output = [v for v in refs if v.name == loop_output][0]
|
||||
except:
|
||||
except Exception:
|
||||
self.raise_validation_exception('The loop output data reference is missing from the IO specification')
|
||||
else:
|
||||
loop_output = TaskDataReference(loop_output)
|
||||
@ -155,7 +161,7 @@ class TaskParser(NodeParser):
|
||||
def _add_boundary_event(self, children):
|
||||
|
||||
parent = _BoundaryEventParent(
|
||||
self.spec, '%s.BoundaryEventParent' % self.get_id(),
|
||||
self.spec, '%s.BoundaryEventParent' % self.bpmn_id,
|
||||
self.task, lane=self.task.lane)
|
||||
self.process_parser.parsed_nodes[self.node.get('id')] = parent
|
||||
parent.connect(self.task)
|
||||
@ -176,10 +182,6 @@ class TaskParser(NodeParser):
|
||||
# Why do we just set random attributes willy nilly everywhere in the code????
|
||||
# And we still pass around a gigantic kwargs dict whenever we create anything!
|
||||
self.task.extensions = self.parse_extensions()
|
||||
self.task.documentation = self.parse_documentation()
|
||||
# And now I have to add more of the same crappy thing.
|
||||
self.task.data_input_associations = self.parse_incoming_data_references()
|
||||
self.task.data_output_associations = self.parse_outgoing_data_references()
|
||||
|
||||
io_spec = self.xpath('./bpmn:ioSpecification')
|
||||
if len(io_spec) > 0:
|
||||
@ -193,30 +195,32 @@ class TaskParser(NodeParser):
|
||||
if len(mi_loop_characteristics) > 0:
|
||||
self._add_multiinstance_task(mi_loop_characteristics[0])
|
||||
|
||||
boundary_event_nodes = self.doc_xpath('.//bpmn:boundaryEvent[@attachedToRef="%s"]' % self.get_id())
|
||||
boundary_event_nodes = self.doc_xpath('.//bpmn:boundaryEvent[@attachedToRef="%s"]' % self.bpmn_id)
|
||||
if boundary_event_nodes:
|
||||
parent = self._add_boundary_event(boundary_event_nodes)
|
||||
else:
|
||||
self.process_parser.parsed_nodes[self.node.get('id')] = self.task
|
||||
|
||||
children = []
|
||||
outgoing = self.doc_xpath('.//bpmn:sequenceFlow[@sourceRef="%s"]' % self.get_id())
|
||||
outgoing = self.doc_xpath('.//bpmn:sequenceFlow[@sourceRef="%s"]' % self.bpmn_id)
|
||||
if len(outgoing) > 1 and not self.handles_multiple_outgoing():
|
||||
self.raise_validation_exception('Multiple outgoing flows are not supported for tasks of type')
|
||||
for sequence_flow in outgoing:
|
||||
target_ref = sequence_flow.get('targetRef')
|
||||
try:
|
||||
target_node = one(self.doc_xpath('.//bpmn:*[@id="%s"]'% target_ref))
|
||||
except:
|
||||
except Exception:
|
||||
self.raise_validation_exception('When looking for a task spec, we found two items, '
|
||||
'perhaps a form has the same ID? (%s)' % target_ref)
|
||||
|
||||
c = self.process_parser.parse_node(target_node)
|
||||
position = c.position
|
||||
position = self.get_position(target_node)
|
||||
children.append((position, c, target_node, sequence_flow))
|
||||
|
||||
if children:
|
||||
# Sort children by their y coordinate.
|
||||
# Why?? Isn't the point of parallel tasks that they can be executed in any order (or simultaneously)?
|
||||
# And what if they're arranged horizontally?
|
||||
children = sorted(children, key=lambda tup: float(tup[0]["y"]))
|
||||
|
||||
default_outgoing = self.node.get('default')
|
||||
@ -238,17 +242,14 @@ class TaskParser(NodeParser):
|
||||
"""
|
||||
Returns a unique task spec name for this task (or the targeted one)
|
||||
"""
|
||||
return target_ref or self.get_id()
|
||||
return target_ref or self.bpmn_id
|
||||
|
||||
def create_task(self):
|
||||
"""
|
||||
Create an instance of the task appropriately. A subclass can override
|
||||
this method to get extra information from the node.
|
||||
"""
|
||||
return self.spec_class(self.spec, self.get_task_spec_name(),
|
||||
lane=self.lane,
|
||||
description=self.node.get('name', None),
|
||||
position=self.position)
|
||||
return self.spec_class(self.spec, self.bpmn_id, **self.bpmn_attributes)
|
||||
|
||||
def connect_outgoing(self, outgoing_task, sequence_flow_node, is_default):
|
||||
"""
|
||||
|
@ -1,12 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (C) 2012 Matthew Hampton, 2023 Dan Funk
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# 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 2.1 of the License, or (at your option) any later version.
|
||||
# version 3.0 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# 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.
|
||||
|
@ -1,12 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (C) 2012 Matthew Hampton
|
||||
# Copyright (C) 2012 Matthew Hampton, 2023 Sartography
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# 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 2.1 of the License, or (at your option) any later version.
|
||||
# version 3.0 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# 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.
|
||||
|
@ -1,11 +1,28 @@
|
||||
from lxml import etree
|
||||
# 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.bpmn.specs.events.event_definitions import CorrelationProperty
|
||||
from lxml import etree
|
||||
|
||||
from .ValidationException import ValidationException
|
||||
from .TaskParser import TaskParser
|
||||
from .util import first, one
|
||||
from ..specs.events.event_definitions import (
|
||||
from ..specs.event_definitions import (
|
||||
MultipleEventDefinition,
|
||||
TimeDateEventDefinition,
|
||||
DurationTimerEventDefinition,
|
||||
@ -16,7 +33,8 @@ from ..specs.events.event_definitions import (
|
||||
SignalEventDefinition,
|
||||
CancelEventDefinition,
|
||||
TerminateEventDefinition,
|
||||
NoneEventDefinition
|
||||
NoneEventDefinition,
|
||||
CorrelationProperty
|
||||
)
|
||||
|
||||
CANCEL_EVENT_XPATH = './/bpmn:cancelEventDefinition'
|
||||
@ -31,8 +49,26 @@ TIMER_EVENT_XPATH = './/bpmn:timerEventDefinition'
|
||||
class EventDefinitionParser(TaskParser):
|
||||
"""This class provvides methods for parsing different event definitions."""
|
||||
|
||||
def parse_cancel_event(self):
|
||||
return CancelEventDefinition()
|
||||
def __init__(self, process_parser, spec_class, node, nsmap=None, lane=None):
|
||||
super().__init__(process_parser, spec_class, node, nsmap, lane)
|
||||
self.event_nodes = []
|
||||
|
||||
def get_description(self):
|
||||
spec_description = super().get_description()
|
||||
if spec_description is not None:
|
||||
if len(self.event_nodes) == 0:
|
||||
event_description = 'Default'
|
||||
elif len(self.event_nodes) > 1:
|
||||
event_description = 'Multiple'
|
||||
elif len(self.event_nodes) == 1:
|
||||
event_description = self.process_parser.parser.spec_descriptions.get(self.event_nodes[0].tag)
|
||||
return f'{event_description} {spec_description}'
|
||||
|
||||
def get_event_description(self, event):
|
||||
return self.process_parser.parser.spec_descriptions.get(event.tag)
|
||||
|
||||
def parse_cancel_event(self, event):
|
||||
return CancelEventDefinition(description=self.get_event_description(event))
|
||||
|
||||
def parse_error_event(self, error_event):
|
||||
"""Parse the errorEventDefinition node and return an instance of ErrorEventDefinition."""
|
||||
@ -43,7 +79,7 @@ class EventDefinitionParser(TaskParser):
|
||||
name = error.get('name')
|
||||
else:
|
||||
name, error_code = 'None Error Event', None
|
||||
return ErrorEventDefinition(name, error_code)
|
||||
return ErrorEventDefinition(name, error_code, description=self.get_event_description(error_event))
|
||||
|
||||
def parse_escalation_event(self, escalation_event):
|
||||
"""Parse the escalationEventDefinition node and return an instance of EscalationEventDefinition."""
|
||||
@ -55,7 +91,7 @@ class EventDefinitionParser(TaskParser):
|
||||
name = escalation.get('name')
|
||||
else:
|
||||
name, escalation_code = 'None Escalation Event', None
|
||||
return EscalationEventDefinition(name, escalation_code)
|
||||
return EscalationEventDefinition(name, escalation_code, description=self.get_event_description(escalation_event))
|
||||
|
||||
def parse_message_event(self, message_event):
|
||||
|
||||
@ -63,11 +99,13 @@ class EventDefinitionParser(TaskParser):
|
||||
if message_ref is not None:
|
||||
message = one(self.doc_xpath('.//bpmn:message[@id="%s"]' % message_ref))
|
||||
name = message.get('name')
|
||||
description = self.get_event_description(message_event)
|
||||
correlations = self.get_message_correlations(message_ref)
|
||||
else:
|
||||
name = message_event.getparent().get('name')
|
||||
description = 'Message'
|
||||
correlations = {}
|
||||
return MessageEventDefinition(name, correlations)
|
||||
return MessageEventDefinition(name, correlations, description=description)
|
||||
|
||||
def parse_signal_event(self, signal_event):
|
||||
"""Parse the signalEventDefinition node and return an instance of SignalEventDefinition."""
|
||||
@ -78,26 +116,26 @@ class EventDefinitionParser(TaskParser):
|
||||
name = signal.get('name')
|
||||
else:
|
||||
name = signal_event.getparent().get('name')
|
||||
return SignalEventDefinition(name)
|
||||
return SignalEventDefinition(name, description=self.get_event_description(signal_event))
|
||||
|
||||
def parse_terminate_event(self):
|
||||
def parse_terminate_event(self, event):
|
||||
"""Parse the terminateEventDefinition node and return an instance of TerminateEventDefinition."""
|
||||
return TerminateEventDefinition()
|
||||
return TerminateEventDefinition(description=self.get_event_description(event))
|
||||
|
||||
def parse_timer_event(self):
|
||||
def parse_timer_event(self, event):
|
||||
"""Parse the timerEventDefinition node and return an instance of TimerEventDefinition."""
|
||||
|
||||
try:
|
||||
description = self.get_event_description(event)
|
||||
name = self.node.get('name', self.node.get('id'))
|
||||
time_date = first(self.xpath('.//bpmn:timeDate'))
|
||||
if time_date is not None:
|
||||
return TimeDateEventDefinition(name, time_date.text)
|
||||
return TimeDateEventDefinition(name, time_date.text, description=description)
|
||||
time_duration = first(self.xpath('.//bpmn:timeDuration'))
|
||||
if time_duration is not None:
|
||||
return DurationTimerEventDefinition(name, time_duration.text)
|
||||
return DurationTimerEventDefinition(name, time_duration.text, description=description)
|
||||
time_cycle = first(self.xpath('.//bpmn:timeCycle'))
|
||||
if time_cycle is not None:
|
||||
return CycleTimerEventDefinition(name, time_cycle.text)
|
||||
return CycleTimerEventDefinition(name, time_cycle.text, description=description)
|
||||
raise ValidationException("Unknown Time Specification", node=self.node, file_name=self.filename)
|
||||
except Exception as e:
|
||||
raise ValidationException("Time Specification Error. " + str(e), node=self.node, file_name=self.filename)
|
||||
@ -125,16 +163,14 @@ class EventDefinitionParser(TaskParser):
|
||||
if prop.name not in self.spec.correlation_keys[key]:
|
||||
self.spec.correlation_keys[key].append(prop.name)
|
||||
|
||||
kwargs = {
|
||||
'lane': self.lane,
|
||||
'description': self.node.get('name', None),
|
||||
'position': self.position,
|
||||
}
|
||||
kwargs = self.bpmn_attributes
|
||||
if cancel_activity is not None:
|
||||
kwargs['cancel_activity'] = cancel_activity
|
||||
interrupt = 'Interrupting' if cancel_activity else 'Non-Interrupting'
|
||||
kwargs['description'] = interrupt + ' ' + kwargs['description']
|
||||
if parallel is not None:
|
||||
kwargs['parallel'] = parallel
|
||||
return self.spec_class(self.spec, self.get_task_spec_name(), event_definition, **kwargs)
|
||||
return self.spec_class(self.spec, self.bpmn_id, event_definition=event_definition, **kwargs)
|
||||
|
||||
def get_event_definition(self, xpaths):
|
||||
"""Returns all event definitions it can find in given list of xpaths"""
|
||||
@ -142,29 +178,31 @@ class EventDefinitionParser(TaskParser):
|
||||
event_definitions = []
|
||||
for path in xpaths:
|
||||
for event in self.xpath(path):
|
||||
if event is not None:
|
||||
self.event_nodes.append(event)
|
||||
if path == MESSAGE_EVENT_XPATH:
|
||||
event_definitions.append(self.parse_message_event(event))
|
||||
elif path == SIGNAL_EVENT_XPATH:
|
||||
event_definitions.append(self.parse_signal_event(event))
|
||||
elif path == TIMER_EVENT_XPATH:
|
||||
event_definitions.append(self.parse_timer_event())
|
||||
event_definitions.append(self.parse_timer_event(event))
|
||||
elif path == CANCEL_EVENT_XPATH:
|
||||
event_definitions.append(self.parse_cancel_event())
|
||||
event_definitions.append(self.parse_cancel_event(event))
|
||||
elif path == ERROR_EVENT_XPATH:
|
||||
event_definitions.append(self.parse_error_event(event))
|
||||
elif path == ESCALATION_EVENT_XPATH:
|
||||
event_definitions.append(self.parse_escalation_event(event))
|
||||
elif path == TERMINATION_EVENT_XPATH:
|
||||
event_definitions.append(self.parse_terminate_event())
|
||||
event_definitions.append(self.parse_terminate_event(event))
|
||||
|
||||
parallel = self.node.get('parallelMultiple') == 'true'
|
||||
|
||||
if len(event_definitions) == 0:
|
||||
return NoneEventDefinition()
|
||||
return NoneEventDefinition(description='Default')
|
||||
elif len(event_definitions) == 1:
|
||||
return event_definitions[0]
|
||||
else:
|
||||
return MultipleEventDefinition(event_definitions, parallel)
|
||||
return MultipleEventDefinition(event_definitions, parallel, description='Multiple')
|
||||
|
||||
|
||||
class StartEventParser(EventDefinitionParser):
|
||||
|
@ -1,3 +1,22 @@
|
||||
# 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.bpmn.parser.ValidationException import ValidationException
|
||||
from SpiffWorkflow.bpmn.specs.data_spec import TaskDataReference, BpmnIoSpecification
|
||||
from .util import first
|
||||
@ -17,11 +36,25 @@ class NodeParser:
|
||||
self.nsmap = nsmap or DEFAULT_NSMAP
|
||||
self.filename = filename
|
||||
self.lane = self._get_lane() or lane
|
||||
self.position = self._get_position() or {'x': 0.0, 'y': 0.0}
|
||||
|
||||
def get_id(self):
|
||||
@property
|
||||
def bpmn_id(self):
|
||||
return self.node.get('id')
|
||||
|
||||
@property
|
||||
def bpmn_attributes(self):
|
||||
return {
|
||||
'description': self.get_description(),
|
||||
'lane': self.lane,
|
||||
'bpmn_name': self.node.get('name'),
|
||||
'documentation': self.parse_documentation(),
|
||||
'data_input_associations': self.parse_incoming_data_references(),
|
||||
'data_output_associations': self.parse_outgoing_data_references(),
|
||||
}
|
||||
|
||||
def get_description(self):
|
||||
return self.process_parser.parser.spec_descriptions.get(self.node.tag)
|
||||
|
||||
def xpath(self, xpath, extra_ns=None):
|
||||
return self._xpath(self.node, xpath, extra_ns)
|
||||
|
||||
@ -76,10 +109,10 @@ class NodeParser:
|
||||
data_refs = {}
|
||||
for elem in self.xpath('./bpmn:ioSpecification/bpmn:dataInput'):
|
||||
ref = self.create_data_spec(elem, TaskDataReference)
|
||||
data_refs[ref.name] = ref
|
||||
data_refs[ref.bpmn_id] = ref
|
||||
for elem in self.xpath('./bpmn:ioSpecification/bpmn:dataOutput'):
|
||||
ref = self.create_data_spec(elem, TaskDataReference)
|
||||
data_refs[ref.name] = ref
|
||||
data_refs[ref.bpmn_id] = ref
|
||||
|
||||
inputs, outputs = [], []
|
||||
for ref in self.xpath('./bpmn:ioSpecification/bpmn:inputSet/bpmn:dataInputRefs'):
|
||||
@ -96,15 +129,19 @@ class NodeParser:
|
||||
def parse_extensions(self, node=None):
|
||||
return {}
|
||||
|
||||
def _get_lane(self):
|
||||
noderef = first(self.doc_xpath(f".//bpmn:flowNodeRef[text()='{self.get_id()}']"))
|
||||
if noderef is not None:
|
||||
return noderef.getparent().get('name')
|
||||
|
||||
def _get_position(self):
|
||||
bounds = first(self.doc_xpath(f".//bpmndi:BPMNShape[@bpmnElement='{self.get_id()}']//dc:Bounds"))
|
||||
def get_position(self, node=None):
|
||||
node = node if node is not None else self.node
|
||||
nodeid = node.get('id')
|
||||
if nodeid is not None:
|
||||
bounds = first(self.doc_xpath(f".//bpmndi:BPMNShape[@bpmnElement='{nodeid}']//dc:Bounds"))
|
||||
if bounds is not None:
|
||||
return {'x': float(bounds.get('x', 0)), 'y': float(bounds.get('y', 0))}
|
||||
return {'x': 0.0, 'y': 0.0}
|
||||
|
||||
def _get_lane(self):
|
||||
noderef = first(self.doc_xpath(f".//bpmn:flowNodeRef[text()='{self.bpmn_id}']"))
|
||||
if noderef is not None:
|
||||
return noderef.getparent().get('name')
|
||||
|
||||
def _xpath(self, node, xpath, extra_ns=None):
|
||||
if extra_ns is not None:
|
||||
|
52
SpiffWorkflow/SpiffWorkflow/bpmn/parser/spec_description.py
Normal file
52
SpiffWorkflow/SpiffWorkflow/bpmn/parser/spec_description.py
Normal file
@ -0,0 +1,52 @@
|
||||
# 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 .util import full_tag
|
||||
|
||||
# Having this configurable via the parser makes a lot more sense than requiring a subclass
|
||||
# This can be further streamlined if we ever replace our parser
|
||||
|
||||
SPEC_DESCRIPTIONS = {
|
||||
full_tag('startEvent'): 'Start Event',
|
||||
full_tag('endEvent'): 'End Event',
|
||||
full_tag('userTask'): 'User Task',
|
||||
full_tag('task'): 'Task',
|
||||
full_tag('subProcess'): 'Subprocess',
|
||||
full_tag('manualTask'): 'Manual Task',
|
||||
full_tag('exclusiveGateway'): 'Exclusive Gateway',
|
||||
full_tag('parallelGateway'): 'Parallel Gateway',
|
||||
full_tag('inclusiveGateway'): 'Inclusive Gateway',
|
||||
full_tag('callActivity'): 'Call Activity',
|
||||
full_tag('transaction'): 'Transaction',
|
||||
full_tag('scriptTask'): 'Script Task',
|
||||
full_tag('serviceTask'): 'Service Task',
|
||||
full_tag('intermediateCatchEvent'): 'Intermediate Catch Event',
|
||||
full_tag('intermediateThrowEvent'): 'Intermediate Throw Event',
|
||||
full_tag('boundaryEvent'): 'Boundary Event',
|
||||
full_tag('receiveTask'): 'Receive Task',
|
||||
full_tag('sendTask'): 'Send Task',
|
||||
full_tag('eventBasedGateway'): 'Event Based Gateway',
|
||||
full_tag('cancelEventDefinition'): 'Cancel',
|
||||
full_tag('errorEventDefinition'): 'Error',
|
||||
full_tag('escalationEventDefinition'): 'Escalation',
|
||||
full_tag('terminateEventDefinition'): 'Terminate',
|
||||
full_tag('messageEventDefinition'): 'Message',
|
||||
full_tag('signalEventDefinition'): 'Signal',
|
||||
full_tag('timerEventDefinition'): 'Timer',
|
||||
}
|
@ -1,12 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (C) 2012 Matthew Hampton
|
||||
# Copyright (C) 2012 Matthew Hampton, 2023 Sartography
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# 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 2.1 of the License, or (at your option) any later version.
|
||||
# version 3.0 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# 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.
|
||||
@ -85,13 +86,6 @@ class SubprocessParser:
|
||||
'No "calledElement" attribute for Call Activity.',
|
||||
node=task_parser.node,
|
||||
file_name=task_parser.filename)
|
||||
parser = task_parser.process_parser.parser.get_process_parser(called_element)
|
||||
if parser is None:
|
||||
raise ValidationException(
|
||||
f"The process '{called_element}' was not found. Did you mean one of the following: "
|
||||
f"{', '.join(task_parser.process_parser.parser.get_process_ids())}?",
|
||||
node=task_parser.node,
|
||||
file_name=task_parser.filename)
|
||||
return called_element
|
||||
|
||||
|
||||
@ -99,10 +93,7 @@ class SubWorkflowParser(TaskParser):
|
||||
|
||||
def create_task(self):
|
||||
subworkflow_spec = SubprocessParser.get_subprocess_spec(self)
|
||||
return self.spec_class(
|
||||
self.spec, self.get_task_spec_name(), subworkflow_spec,
|
||||
lane=self.lane, position=self.position,
|
||||
description=self.node.get('name', None))
|
||||
return self.spec_class(self.spec, self.bpmn_id, subworkflow_spec=subworkflow_spec, **self.bpmn_attributes)
|
||||
|
||||
|
||||
class CallActivityParser(TaskParser):
|
||||
@ -110,23 +101,14 @@ class CallActivityParser(TaskParser):
|
||||
|
||||
def create_task(self):
|
||||
subworkflow_spec = SubprocessParser.get_call_activity_spec(self)
|
||||
return self.spec_class(
|
||||
self.spec, self.get_task_spec_name(), subworkflow_spec,
|
||||
lane=self.lane, position=self.position,
|
||||
description=self.node.get('name', None))
|
||||
return self.spec_class(self.spec, self.bpmn_id, subworkflow_spec=subworkflow_spec, **self.bpmn_attributes)
|
||||
|
||||
|
||||
class ScriptTaskParser(TaskParser):
|
||||
"""
|
||||
Parses a script task
|
||||
"""
|
||||
"""Parses a script task"""
|
||||
|
||||
def create_task(self):
|
||||
script = self.get_script()
|
||||
return self.spec_class(self.spec, self.get_task_spec_name(), script,
|
||||
lane=self.lane,
|
||||
position=self.position,
|
||||
description=self.node.get('name', None))
|
||||
return self.spec_class(self.spec, self.bpmn_id, script=self.get_script(), **self.bpmn_attributes)
|
||||
|
||||
def get_script(self):
|
||||
"""
|
||||
@ -138,6 +120,6 @@ class ScriptTaskParser(TaskParser):
|
||||
return one(self.xpath('.//bpmn:script')).text
|
||||
except AssertionError as ae:
|
||||
raise ValidationException(
|
||||
f"Invalid Script Task. No Script Provided. " + str(ae),
|
||||
"Invalid Script Task. No Script Provided. " + str(ae),
|
||||
node=self.node, file_name=self.filename)
|
||||
|
||||
|
@ -1,12 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (C) 2012 Matthew Hampton
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# 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 2.1 of the License, or (at your option) any later version.
|
||||
# version 3.0 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# 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.
|
||||
|
@ -1,12 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (C) 2012 Matthew Hampton
|
||||
# Copyright (C) 2012 Matthew Hampton, 2023 Sartography
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# 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 2.1 of the License, or (at your option) any later version.
|
||||
# version 3.0 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# 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.
|
||||
|
@ -1,3 +1,22 @@
|
||||
# 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 ..specs.data_spec import DataObject, TaskDataReference, BpmnIoSpecification
|
||||
from .helpers.spec import BpmnSpecConverter, BpmnDataSpecificationConverter
|
||||
|
||||
|
@ -1,6 +1,23 @@
|
||||
from .helpers.spec import EventDefinitionConverter
|
||||
# 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 ..specs.events.event_definitions import (
|
||||
from SpiffWorkflow.bpmn.specs.event_definitions import (
|
||||
CancelEventDefinition,
|
||||
ErrorEventDefinition,
|
||||
EscalationEventDefinition,
|
||||
@ -13,6 +30,7 @@ from ..specs.events.event_definitions import (
|
||||
CycleTimerEventDefinition,
|
||||
MultipleEventDefinition,
|
||||
)
|
||||
from .helpers.spec import EventDefinitionConverter
|
||||
|
||||
class CancelEventDefinitionConverter(EventDefinitionConverter):
|
||||
def __init__(self, registry):
|
||||
|
@ -0,0 +1,18 @@
|
||||
# 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
|
@ -1,3 +1,22 @@
|
||||
# Copyright (C) 2023 Elizabeth Esswein, 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 functools import partial
|
||||
|
||||
class DictionaryConverter:
|
||||
|
@ -1,3 +1,22 @@
|
||||
# Copyright (C) 2023 Elizabeth Esswein, 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 datetime import datetime, timedelta
|
||||
|
||||
|
@ -1,9 +1,32 @@
|
||||
# Copyright (C) 2023 Elizabeth Esswein, 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 functools import partial
|
||||
|
||||
from ...specs.BpmnSpecMixin import BpmnSpecMixin
|
||||
from ...specs.events.event_definitions import NamedEventDefinition, TimerEventDefinition
|
||||
from ...specs.events.event_definitions import CorrelationProperty
|
||||
from ....operators import Attrib, PathAttrib
|
||||
from SpiffWorkflow.operators import Attrib, PathAttrib
|
||||
|
||||
from SpiffWorkflow.bpmn.specs.mixins.bpmn_spec_mixin import BpmnSpecMixin
|
||||
from SpiffWorkflow.bpmn.specs.event_definitions import (
|
||||
NamedEventDefinition,
|
||||
TimerEventDefinition,
|
||||
CorrelationProperty
|
||||
)
|
||||
|
||||
|
||||
class BpmnSpecConverter:
|
||||
@ -57,7 +80,7 @@ class BpmnDataSpecificationConverter(BpmnSpecConverter):
|
||||
"""
|
||||
|
||||
def to_dict(self, data_spec):
|
||||
return { 'name': data_spec.name, 'description': data_spec.description }
|
||||
return { 'bpmn_id': data_spec.bpmn_id, 'bpmn_name': data_spec.bpmn_name }
|
||||
|
||||
def from_dict(self, dct):
|
||||
return self.spec_class(**dct)
|
||||
@ -72,7 +95,11 @@ class EventDefinitionConverter(BpmnSpecConverter):
|
||||
"""
|
||||
|
||||
def to_dict(self, event_definition):
|
||||
dct = {'internal': event_definition.internal, 'external': event_definition.external}
|
||||
dct = {
|
||||
'internal': event_definition.internal,
|
||||
'external': event_definition.external,
|
||||
'description': event_definition.description,
|
||||
}
|
||||
if isinstance(event_definition, (NamedEventDefinition, TimerEventDefinition)):
|
||||
dct['name'] = event_definition.name
|
||||
return dct
|
||||
@ -105,7 +132,7 @@ class TaskSpecConverter(BpmnSpecConverter):
|
||||
modules of this package; the `camunda`,`dmn`, and `spiff` serialization packages contain other
|
||||
examples.
|
||||
"""
|
||||
def get_default_attributes(self, spec, include_data=False):
|
||||
def get_default_attributes(self, spec):
|
||||
"""Extracts the default Spiff attributes from a task spec.
|
||||
|
||||
:param spec: the task spec to be converted
|
||||
@ -113,38 +140,17 @@ class TaskSpecConverter(BpmnSpecConverter):
|
||||
Returns:
|
||||
a dictionary of standard task spec attributes
|
||||
"""
|
||||
dct = {
|
||||
'id': spec.id,
|
||||
return {
|
||||
'name': spec.name,
|
||||
'description': spec.description,
|
||||
'manual': spec.manual,
|
||||
'internal': spec.internal,
|
||||
'lookahead': spec.lookahead,
|
||||
'inputs': [task.name for task in spec.inputs],
|
||||
'outputs': [task.name for task in spec.outputs],
|
||||
}
|
||||
# This stuff is also all defined in the base task spec, but can contain data, so we need
|
||||
# our data serializer. I think we should try to get this stuff out of the base task spec.
|
||||
if include_data:
|
||||
dct['data'] = self.registry.convert(spec.data)
|
||||
dct['defines'] = self.registry.convert(spec.defines)
|
||||
dct['pre_assign'] = self.registry.convert(spec.pre_assign)
|
||||
dct['post_assign'] = self.registry.convert(spec.post_assign)
|
||||
|
||||
return dct
|
||||
|
||||
def get_bpmn_attributes(self, spec):
|
||||
"""Extracts the attributes added by the `BpmnSpecMixin` class.
|
||||
|
||||
:param spec: the task spec to be converted
|
||||
|
||||
Returns:
|
||||
a dictionary of BPMN task spec attributes
|
||||
"""
|
||||
return {
|
||||
'bpmn_id': spec.bpmn_id,
|
||||
'bpmn_name': spec.bpmn_name,
|
||||
'lane': spec.lane,
|
||||
'documentation': spec.documentation,
|
||||
'position': spec.position,
|
||||
'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 ],
|
||||
'io_specification': self.registry.convert(spec.io_specification),
|
||||
@ -189,38 +195,36 @@ class TaskSpecConverter(BpmnSpecConverter):
|
||||
'test_before': spec.test_before,
|
||||
}
|
||||
|
||||
def task_spec_from_dict(self, dct, include_data=False):
|
||||
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`.
|
||||
|
||||
:param dct: the dictionary to create the task spec from
|
||||
:param include_data: whether or not to include task spec data attributes
|
||||
|
||||
Returns:
|
||||
a restored task spec
|
||||
"""
|
||||
internal = dct.pop('internal')
|
||||
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')
|
||||
|
||||
spec = self.spec_class(**dct)
|
||||
spec.internal = internal
|
||||
wf_spec = dct.pop('wf_spec')
|
||||
name = dct.pop('name')
|
||||
bpmn_id = dct.pop('bpmn_id')
|
||||
|
||||
spec = self.spec_class(wf_spec, name, **dct)
|
||||
spec.inputs = inputs
|
||||
spec.outputs = outputs
|
||||
spec.id = dct['id']
|
||||
|
||||
if include_data:
|
||||
spec.data = self.registry.restore(dct.get('data', {}))
|
||||
spec.defines = self.registry.restore(dct.get('defines', {}))
|
||||
spec.pre_assign = self.registry.restore(dct.get('pre_assign', {}))
|
||||
spec.post_assign = self.registry.restore(dct.get('post_assign', {}))
|
||||
if issubclass(self.spec_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.documentation = dct.pop('documentation', None)
|
||||
spec.lane = dct.pop('lane', None)
|
||||
spec.data_input_associations = self.registry.restore(dct.pop('data_input_associations', []))
|
||||
spec.data_output_associations = self.registry.restore(dct.pop('data_output_associations', []))
|
||||
spec.io_specification = self.registry.restore(dct.pop('io_specification', None))
|
||||
|
||||
return spec
|
||||
|
@ -1,3 +1,22 @@
|
||||
# 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.exceptions import WorkflowException
|
||||
|
||||
class VersionMigrationError(WorkflowException):
|
||||
|
@ -1,3 +1,22 @@
|
||||
# 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
|
||||
|
||||
def move_subprocesses_to_top(dct):
|
||||
subprocesses = dict((sp, { 'tasks': {}, 'root': None, 'data': {}, 'success': True }) for sp in dct['subprocesses'])
|
||||
|
||||
|
@ -1,7 +1,26 @@
|
||||
# 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 datetime import datetime, timedelta
|
||||
|
||||
from SpiffWorkflow.task import TaskState
|
||||
from SpiffWorkflow.bpmn.specs.events.event_definitions import LOCALTZ
|
||||
from SpiffWorkflow.bpmn.specs.event_definitions import LOCALTZ
|
||||
|
||||
from .exceptions import VersionMigrationError
|
||||
|
||||
@ -29,7 +48,7 @@ def convert_timer_expressions(dct):
|
||||
elif isinstance(dt, timedelta):
|
||||
spec['event_definition']['expression'] = f"'{td_to_iso(dt)}'"
|
||||
spec['event_definition']['typename'] = 'DurationTimerEventDefinition'
|
||||
except:
|
||||
except Exception:
|
||||
raise VersionMigrationError(message.format(spec=spec['name']))
|
||||
|
||||
def convert_cycle(spec, task):
|
||||
@ -47,7 +66,7 @@ def convert_timer_expressions(dct):
|
||||
'next': datetime.combine(dt.date(), dt.time(), LOCALTZ).isoformat(),
|
||||
'duration': duration.total_seconds(),
|
||||
}
|
||||
except:
|
||||
except Exception:
|
||||
raise VersionMigrationError(message.format(spec=spec['name']))
|
||||
|
||||
if spec['typename'] == 'StartEvent':
|
||||
@ -65,7 +84,8 @@ def convert_timer_expressions(dct):
|
||||
task['children'].remove(remove['id'])
|
||||
dct['tasks'].pop(remove['id'])
|
||||
|
||||
has_timer = lambda ts: 'event_definition' in ts and ts['event_definition']['typename'] in [ 'CycleTimerEventDefinition', 'TimerEventDefinition']
|
||||
def has_timer(ts):
|
||||
return "event_definition" in ts and ts["event_definition"]["typename"] in ["CycleTimerEventDefinition", "TimerEventDefinition"]
|
||||
for spec in [ ts for ts in dct['spec']['task_specs'].values() if has_timer(ts) ]:
|
||||
spec['event_definition']['name'] = spec['event_definition'].pop('label')
|
||||
if spec['event_definition']['typename'] == 'TimerEventDefinition':
|
||||
@ -143,3 +163,97 @@ def update_task_states(dct):
|
||||
update(dct)
|
||||
for sp in dct['subprocesses'].values():
|
||||
update(sp)
|
||||
|
||||
def convert_simple_tasks(dct):
|
||||
|
||||
def update_specs(task_specs):
|
||||
for name, spec in task_specs.items():
|
||||
if spec['typename'] == 'StartTask':
|
||||
spec['typename'] = 'BpmnStartTask'
|
||||
elif spec['typename'] == 'Simple':
|
||||
spec['typename'] = 'SimpleBpmnTask'
|
||||
|
||||
update_specs(dct['spec']['task_specs'])
|
||||
for subprocess_spec in dct['subprocess_specs'].values():
|
||||
update_specs(subprocess_spec['task_specs'])
|
||||
|
||||
def update_bpmn_attributes(dct):
|
||||
|
||||
descriptions = {
|
||||
'StartEvent': 'Start Event',
|
||||
'EndEvent': 'End Event',
|
||||
'UserTask': 'User Task',
|
||||
'Task': 'Task',
|
||||
'SubProcess': 'Subprocess',
|
||||
'ManualTask': 'Manual Task',
|
||||
'ExclusiveGateway': 'Exclusive Gateway',
|
||||
'ParallelGateway': 'Parallel Gateway',
|
||||
'InclusiveGateway': 'Inclusive Gateway',
|
||||
'CallActivity': 'Call Activity',
|
||||
'TransactionSubprocess': 'Transaction',
|
||||
'ScriptTask': 'Script Task',
|
||||
'ServiceTask': 'Service Task',
|
||||
'IntermediateCatchEvent': 'Intermediate Catch Event',
|
||||
'IntermediateThrowEvent': 'Intermediate Throw Event',
|
||||
'BoundaryEvent': 'Boundary Event',
|
||||
'ReceiveTask': 'Receive Task',
|
||||
'SendTask': 'Send Task',
|
||||
'EventBasedGateway': 'Event Based Gateway',
|
||||
'CancelEventDefinition': 'Cancel',
|
||||
'ErrorEventDefinition': 'Error',
|
||||
'EscalationEventDefinition': 'Escalation',
|
||||
'TerminateEventDefinition': 'Terminate',
|
||||
'MessageEventDefinition': 'Message',
|
||||
'SignalEventDefinition': 'Signal',
|
||||
'TimerEventDefinition': 'Timer',
|
||||
'NoneEventDefinition': 'Default',
|
||||
'MultipleEventDefinition': 'Multiple'
|
||||
}
|
||||
|
||||
def update_data_spec(obj):
|
||||
obj['bpmn_id'] = obj.pop('name')
|
||||
obj['bpmn_name'] = obj.pop('description', None)
|
||||
|
||||
def update_io_spec(io_spec):
|
||||
for obj in io_spec['data_inputs']:
|
||||
update_data_spec(obj)
|
||||
for obj in io_spec['data_outputs']:
|
||||
update_data_spec(obj)
|
||||
|
||||
def update_task_specs(spec):
|
||||
for spec in spec['task_specs'].values():
|
||||
spec['bpmn_id'] = None
|
||||
if spec['typename'] not in ['BpmnStartTask', 'SimpleBpmnTask', '_EndJoin', '_BoundaryEventParent']:
|
||||
spec['bpmn_id'] = spec['name']
|
||||
spec['bpmn_name'] = spec['description'] or None
|
||||
if 'event_definition' in spec and spec['event_definition']['typename'] in descriptions:
|
||||
spec_desc = descriptions.get(spec['typename'])
|
||||
event_desc = descriptions.get(spec['event_definition']['typename'])
|
||||
cancelling = spec.get('cancel_activity')
|
||||
interrupt = 'Interrupting ' if cancelling else 'Non-Interrupting ' if not cancelling else ''
|
||||
desc = f'{interrupt}{event_desc} {spec_desc}'
|
||||
elif spec['typename'] in descriptions:
|
||||
desc = descriptions.get(spec['typename'])
|
||||
else:
|
||||
desc = None
|
||||
spec['description'] = desc
|
||||
else:
|
||||
spec['bpmn_name'] = None
|
||||
spec['description'] = None
|
||||
if spec.get('io_specification') is not None:
|
||||
update_io_spec(spec['io_specification'])
|
||||
for obj in spec.get('data_input_associations', []):
|
||||
update_data_spec(obj)
|
||||
for obj in spec.get('data_output_associations', []):
|
||||
update_data_spec(obj)
|
||||
|
||||
update_task_specs(dct['spec'])
|
||||
for obj in dct['spec'].get('data_objects', {}).values():
|
||||
update_data_spec(obj)
|
||||
|
||||
for subprocess_spec in dct['subprocess_specs'].values():
|
||||
update_task_specs(subprocess_spec)
|
||||
for obj in subprocess_spec.get('data_objects', {}).values():
|
||||
update_data_spec(obj)
|
||||
if subprocess_spec.get('io_specification') is not None:
|
||||
update_io_spec(subprocess_spec['io_specification'])
|
||||
|
@ -1,3 +1,22 @@
|
||||
# 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 copy import deepcopy
|
||||
|
||||
from .version_1_1 import move_subprocesses_to_top
|
||||
@ -8,6 +27,8 @@ from .version_1_2 import (
|
||||
check_multiinstance,
|
||||
remove_loop_reset,
|
||||
update_task_states,
|
||||
convert_simple_tasks,
|
||||
update_bpmn_attributes,
|
||||
)
|
||||
|
||||
def from_version_1_1(old):
|
||||
@ -38,6 +59,8 @@ def from_version_1_1(old):
|
||||
check_multiinstance(new)
|
||||
remove_loop_reset(new)
|
||||
update_task_states(new)
|
||||
convert_simple_tasks(new)
|
||||
update_bpmn_attributes(new)
|
||||
new['VERSION'] = "1.2"
|
||||
return new
|
||||
|
||||
|
@ -1,5 +1,24 @@
|
||||
from ..specs.BpmnProcessSpec import BpmnProcessSpec
|
||||
from ..specs.events.IntermediateEvent import _BoundaryEventParent
|
||||
# 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.bpmn.specs.bpmn_process_spec import BpmnProcessSpec
|
||||
from SpiffWorkflow.bpmn.specs.control import _BoundaryEventParent
|
||||
|
||||
from .helpers.spec import WorkflowSpecConverter
|
||||
|
||||
@ -39,12 +58,9 @@ class BpmnProcessSpecConverter(WorkflowSpecConverter):
|
||||
def from_dict(self, dct):
|
||||
|
||||
spec = self.spec_class(name=dct['name'], description=dct['description'], filename=dct['file'])
|
||||
# There a nostart arg in the base workflow spec class that prevents start task creation, but
|
||||
# the BPMN process spec doesn't pass it in, so we have to delete the auto generated Start task.
|
||||
# These are automatically created with a workflow and should be replaced
|
||||
del spec.task_specs['Start']
|
||||
spec.start = None
|
||||
|
||||
# These are also automatically created with a workflow and should be replaced
|
||||
del spec.task_specs['End']
|
||||
del spec.task_specs[f'{spec.name}.EndJoin']
|
||||
|
||||
@ -60,6 +76,7 @@ class BpmnProcessSpecConverter(WorkflowSpecConverter):
|
||||
# Add messaging related stuff
|
||||
spec.correlation_keys = dct.pop('correlation_keys', {})
|
||||
|
||||
dct['task_specs'].pop('Root', None)
|
||||
for name, task_dict in dct['task_specs'].items():
|
||||
# I hate this, but I need to pass in the workflow spec when I create the task.
|
||||
# IMO storing the workflow spec on the task spec is a TERRIBLE idea, but that's
|
||||
|
@ -1,68 +1,75 @@
|
||||
from .helpers.spec import TaskSpecConverter
|
||||
# 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 ...specs.StartTask import StartTask
|
||||
from ...specs.Simple import Simple
|
||||
|
||||
from ..specs.BpmnProcessSpec import _EndJoin
|
||||
from ..specs.BpmnSpecMixin import _BpmnCondition
|
||||
from ..specs.NoneTask import NoneTask
|
||||
from ..specs.UserTask import UserTask
|
||||
from ..specs.ManualTask import ManualTask
|
||||
from ..specs.ScriptTask import ScriptTask
|
||||
from ..specs.MultiInstanceTask import StandardLoopTask, SequentialMultiInstanceTask, ParallelMultiInstanceTask
|
||||
from ..specs.SubWorkflowTask import CallActivity, TransactionSubprocess, SubWorkflowTask
|
||||
from ..specs.ExclusiveGateway import ExclusiveGateway
|
||||
from ..specs.InclusiveGateway import InclusiveGateway
|
||||
from ..specs.ParallelGateway import ParallelGateway
|
||||
from ..specs.events.StartEvent import StartEvent
|
||||
from ..specs.events.EndEvent import EndEvent
|
||||
from ..specs.events.IntermediateEvent import (
|
||||
BoundaryEvent,
|
||||
_BoundaryEventParent,
|
||||
EventBasedGateway,
|
||||
from SpiffWorkflow.bpmn.specs.control import BpmnStartTask, _EndJoin, _BoundaryEventParent, SimpleBpmnTask
|
||||
from SpiffWorkflow.bpmn.specs.bpmn_task_spec import _BpmnCondition
|
||||
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,
|
||||
)
|
||||
|
||||
|
||||
class DefaultTaskSpecConverter(TaskSpecConverter):
|
||||
|
||||
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 SimpleTaskConverter(DefaultTaskSpecConverter):
|
||||
def __init__(self, registry):
|
||||
super().__init__(Simple, registry)
|
||||
|
||||
|
||||
class StartTaskConverter(DefaultTaskSpecConverter):
|
||||
def __init__(self, registry):
|
||||
super().__init__(StartTask, registry)
|
||||
|
||||
|
||||
class EndJoinConverter(DefaultTaskSpecConverter):
|
||||
def __init__(self, registry):
|
||||
super().__init__(_EndJoin, registry)
|
||||
from .helpers.spec import TaskSpecConverter
|
||||
|
||||
|
||||
class BpmnTaskSpecConverter(TaskSpecConverter):
|
||||
|
||||
def to_dict(self, spec):
|
||||
dct = self.get_default_attributes(spec)
|
||||
dct.update(self.get_bpmn_attributes(spec))
|
||||
return dct
|
||||
|
||||
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)
|
||||
@ -85,7 +92,6 @@ class ScriptTaskConverter(BpmnTaskSpecConverter):
|
||||
|
||||
def to_dict(self, spec):
|
||||
dct = self.get_default_attributes(spec)
|
||||
dct.update(self.get_bpmn_attributes(spec))
|
||||
dct['script'] = spec.script
|
||||
return dct
|
||||
|
||||
@ -97,7 +103,6 @@ class StandardLoopTaskConverter(BpmnTaskSpecConverter):
|
||||
|
||||
def to_dict(self, spec):
|
||||
dct = self.get_default_attributes(spec)
|
||||
dct.update(self.get_bpmn_attributes(spec))
|
||||
dct.update(self.get_standard_loop_attributes(spec))
|
||||
return dct
|
||||
|
||||
@ -106,7 +111,6 @@ class MultiInstanceTaskConverter(BpmnTaskSpecConverter):
|
||||
|
||||
def to_dict(self, spec):
|
||||
dct = self.get_default_attributes(spec)
|
||||
dct.update(self.get_bpmn_attributes(spec))
|
||||
dct['task_spec'] = spec.task_spec
|
||||
dct['cardinality'] = spec.cardinality
|
||||
dct['data_input'] = self.registry.convert(spec.data_input)
|
||||
@ -294,8 +298,8 @@ class EventBasedGatewayConverter(EventConverter):
|
||||
|
||||
|
||||
DEFAULT_TASK_SPEC_CONVERTER_CLASSES = [
|
||||
SimpleTaskConverter,
|
||||
StartTaskConverter,
|
||||
SimpleBpmnTaskConverter,
|
||||
BpmnStartTaskConverter,
|
||||
EndJoinConverter,
|
||||
NoneTaskConverter,
|
||||
UserTaskConverter,
|
||||
|
@ -1,11 +1,30 @@
|
||||
# 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
|
||||
|
||||
import json
|
||||
import gzip
|
||||
from copy import deepcopy
|
||||
from uuid import UUID
|
||||
|
||||
from ..workflow import BpmnMessage, BpmnWorkflow
|
||||
from ..specs.SubWorkflowTask import SubWorkflowTask
|
||||
from ...task import Task
|
||||
from SpiffWorkflow.task import Task
|
||||
from SpiffWorkflow.bpmn.workflow import BpmnMessage, BpmnWorkflow
|
||||
from SpiffWorkflow.bpmn.specs.mixins.subworkflow_task import SubWorkflowTask
|
||||
|
||||
from .migration.version_migration import MIGRATIONS
|
||||
from .helpers.registry import DefaultRegistry
|
||||
@ -134,7 +153,7 @@ class BpmnWorkflowSerializer:
|
||||
dct = self.__get_dict(serialization, use_gzip)
|
||||
if self.VERSION_KEY in dct:
|
||||
return dct[self.VERSION_KEY]
|
||||
except: # Don't bail out trying to get a version, just return none.
|
||||
except Exception: # Don't bail out trying to get a version, just return none.
|
||||
return None
|
||||
|
||||
def workflow_to_dict(self, workflow):
|
||||
@ -260,7 +279,7 @@ class BpmnWorkflowSerializer:
|
||||
|
||||
for child_task_id in task_dict['children']:
|
||||
if child_task_id in process_dct['tasks']:
|
||||
child = process_dct['tasks'][child_task_id]
|
||||
process_dct['tasks'][child_task_id]
|
||||
self.task_tree_from_dict(process_dct, child_task_id, task, process, top, top_dct)
|
||||
else:
|
||||
raise ValueError(f"Task {task_id} ({task_spec.name}) has child {child_task_id}, but no such task exists")
|
||||
|
@ -1,78 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (C) 2012 Matthew Hampton
|
||||
#
|
||||
# This library 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 2.1 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library 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 ...task import TaskState
|
||||
from .UnstructuredJoin import UnstructuredJoin
|
||||
from ...specs.Simple import Simple
|
||||
from ...specs.WorkflowSpec import WorkflowSpec
|
||||
|
||||
|
||||
class _EndJoin(UnstructuredJoin):
|
||||
|
||||
def _check_threshold_unstructured(self, my_task, force=False):
|
||||
# Look at the tree to find all ready and waiting tasks (excluding
|
||||
# ourself). The EndJoin waits for everyone!
|
||||
waiting_tasks = []
|
||||
for task in my_task.workflow.get_tasks(TaskState.READY | TaskState.WAITING):
|
||||
if task.thread_id != my_task.thread_id:
|
||||
continue
|
||||
if task.task_spec == my_task.task_spec:
|
||||
continue
|
||||
|
||||
is_mine = False
|
||||
w = task.workflow
|
||||
if w == my_task.workflow:
|
||||
is_mine = True
|
||||
while w and w.outer_workflow != w:
|
||||
w = w.outer_workflow
|
||||
if w == my_task.workflow:
|
||||
is_mine = True
|
||||
if is_mine:
|
||||
waiting_tasks.append(task)
|
||||
|
||||
return force or len(waiting_tasks) == 0, waiting_tasks
|
||||
|
||||
def _run_hook(self, my_task):
|
||||
result = super(_EndJoin, self)._run_hook(my_task)
|
||||
my_task.workflow.data.update(my_task.data)
|
||||
return result
|
||||
|
||||
|
||||
class BpmnProcessSpec(WorkflowSpec):
|
||||
"""
|
||||
This class represents the specification of a BPMN process workflow. This
|
||||
specialises the standard Spiff WorkflowSpec class with a few extra methods
|
||||
and attributes.
|
||||
"""
|
||||
|
||||
def __init__(self, name=None, description=None, filename=None, svg=None):
|
||||
"""
|
||||
Constructor.
|
||||
|
||||
:param svg: This provides the SVG representation of the workflow as an
|
||||
LXML node. (optional)
|
||||
"""
|
||||
super(BpmnProcessSpec, self).__init__(name=name, filename=filename)
|
||||
self.end = _EndJoin(self, '%s.EndJoin' % (self.name))
|
||||
self.end.connect(Simple(self, 'End'))
|
||||
self.svg = svg
|
||||
self.description = description
|
||||
self.io_specification = None
|
||||
self.data_objects = {}
|
||||
self.data_stores = {}
|
||||
self.correlation_keys = {}
|
@ -1,113 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2012 Matthew Hampton
|
||||
#
|
||||
# This library 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 2.1 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library 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 ..exceptions import WorkflowDataException
|
||||
from ...operators import Operator
|
||||
from ...specs.base import TaskSpec
|
||||
|
||||
|
||||
class _BpmnCondition(Operator):
|
||||
|
||||
def __init__(self, *args):
|
||||
if len(args) > 1:
|
||||
raise TypeError("Too many arguments")
|
||||
super(_BpmnCondition, self).__init__(*args)
|
||||
|
||||
def _matches(self, task):
|
||||
return task.workflow.script_engine.evaluate(task, self.args[0])
|
||||
|
||||
|
||||
class BpmnSpecMixin(TaskSpec):
|
||||
"""
|
||||
All BPMN spec classes should mix this superclass in. It adds a number of
|
||||
methods that are BPMN specific to the TaskSpec.
|
||||
"""
|
||||
|
||||
def __init__(self, wf_spec, name, lane=None, position=None, **kwargs):
|
||||
"""
|
||||
Constructor.
|
||||
|
||||
:param lane: Indicates the name of the lane that this task belongs to
|
||||
(optional).
|
||||
"""
|
||||
super(BpmnSpecMixin, self).__init__(wf_spec, name, **kwargs)
|
||||
self.lane = lane
|
||||
self.position = position or {'x': 0, 'y': 0}
|
||||
self.documentation = None
|
||||
self.data_input_associations = []
|
||||
self.data_output_associations = []
|
||||
self.io_specification = None
|
||||
|
||||
@property
|
||||
def spec_type(self):
|
||||
return 'BPMN Task'
|
||||
|
||||
def connect_outgoing_if(self, condition, taskspec):
|
||||
"""
|
||||
Connect this task spec to the indicated child, if the condition
|
||||
evaluates to true. This should only be called if the task has a
|
||||
connect_if method (e.g. ExclusiveGateway).
|
||||
"""
|
||||
if condition is None:
|
||||
self.connect(taskspec)
|
||||
else:
|
||||
self.connect_if(_BpmnCondition(condition), taskspec)
|
||||
|
||||
def _update_hook(self, my_task):
|
||||
|
||||
super()._update_hook(my_task)
|
||||
# This copies data from data objects
|
||||
for obj in self.data_input_associations:
|
||||
obj.get(my_task)
|
||||
|
||||
# If an IO spec was given, require all inputs are present, and remove all other inputs.
|
||||
if self.io_specification is not None and len(self.io_specification.data_inputs) > 0:
|
||||
data = {}
|
||||
for var in self.io_specification.data_inputs:
|
||||
if var.name not in my_task.data:
|
||||
raise WorkflowDataException(f"Missing data input", task=my_task, data_input=var)
|
||||
data[var.name] = my_task.data[var.name]
|
||||
my_task.data = data
|
||||
|
||||
return True
|
||||
|
||||
def _on_complete_hook(self, my_task):
|
||||
|
||||
if isinstance(my_task.parent.task_spec, BpmnSpecMixin):
|
||||
my_task.parent.task_spec._child_complete_hook(my_task)
|
||||
|
||||
if self.io_specification is not None and len(self.io_specification.data_outputs) > 0:
|
||||
data = {}
|
||||
for var in self.io_specification.data_outputs:
|
||||
if var.name not in my_task.data:
|
||||
raise WorkflowDataException(f"Missing data ouput", task=my_task, data_output=var)
|
||||
data[var.name] = my_task.data[var.name]
|
||||
my_task.data = data
|
||||
|
||||
for obj in self.data_output_associations:
|
||||
obj.set(my_task)
|
||||
|
||||
for obj in self.data_input_associations:
|
||||
# Remove the any copied input variables that might not have already been removed
|
||||
my_task.data.pop(obj.name, None)
|
||||
|
||||
super(BpmnSpecMixin, self)._on_complete_hook(my_task)
|
||||
|
||||
def _child_complete_hook(self, child_task):
|
||||
pass
|
@ -1,17 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from .ScriptTask import ScriptEngineTask
|
||||
|
||||
|
||||
class ServiceTask(ScriptEngineTask):
|
||||
|
||||
"""
|
||||
Task Spec for a bpmn:serviceTask node.
|
||||
"""
|
||||
|
||||
def __init__(self, wf_spec, name, **kwargs):
|
||||
super(ServiceTask, self).__init__(wf_spec, name, **kwargs)
|
||||
|
||||
@property
|
||||
def spec_type(self):
|
||||
return 'Service Task'
|
@ -1,12 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (C) 2012 Matthew Hampton
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# 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 2.1 of the License, or (at your option) any later version.
|
||||
# version 3.0 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# 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.
|
||||
|
50
SpiffWorkflow/SpiffWorkflow/bpmn/specs/bpmn_process_spec.py
Normal file
50
SpiffWorkflow/SpiffWorkflow/bpmn/specs/bpmn_process_spec.py
Normal file
@ -0,0 +1,50 @@
|
||||
# Copyright (C) 2012 Matthew Hampton, 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.specs.WorkflowSpec import WorkflowSpec
|
||||
from SpiffWorkflow.bpmn.specs.control import _EndJoin, BpmnStartTask, SimpleBpmnTask
|
||||
|
||||
|
||||
class BpmnProcessSpec(WorkflowSpec):
|
||||
"""
|
||||
This class represents the specification of a BPMN process workflow. This
|
||||
specialises the standard Spiff WorkflowSpec class with a few extra methods
|
||||
and attributes.
|
||||
"""
|
||||
|
||||
def __init__(self, name=None, description=None, filename=None, svg=None):
|
||||
"""
|
||||
Constructor.
|
||||
|
||||
:param svg: This provides the SVG representation of the workflow as an
|
||||
LXML node. (optional)
|
||||
"""
|
||||
super(BpmnProcessSpec, self).__init__(name=name, filename=filename, nostart=True)
|
||||
# Add a root task to ensure all tasks in the workflow are bpmn tasks
|
||||
# The serializer ignores this task
|
||||
SimpleBpmnTask(self, 'Root')
|
||||
self.start = BpmnStartTask(self, 'Start')
|
||||
self.end = _EndJoin(self, '%s.EndJoin' % (self.name))
|
||||
self.end.connect(SimpleBpmnTask(self, 'End'))
|
||||
self.svg = svg
|
||||
self.description = description
|
||||
self.io_specification = None
|
||||
self.data_objects = {}
|
||||
self.data_stores = {}
|
||||
self.correlation_keys = {}
|
101
SpiffWorkflow/SpiffWorkflow/bpmn/specs/bpmn_task_spec.py
Normal file
101
SpiffWorkflow/SpiffWorkflow/bpmn/specs/bpmn_task_spec.py
Normal file
@ -0,0 +1,101 @@
|
||||
|
||||
from SpiffWorkflow.bpmn.exceptions import WorkflowDataException
|
||||
from SpiffWorkflow.operators import Operator
|
||||
from SpiffWorkflow.specs.base import TaskSpec
|
||||
|
||||
|
||||
class _BpmnCondition(Operator):
|
||||
|
||||
def __init__(self, *args):
|
||||
if len(args) > 1:
|
||||
raise TypeError("Too many arguments")
|
||||
super(_BpmnCondition, self).__init__(*args)
|
||||
|
||||
def _matches(self, task):
|
||||
return task.workflow.script_engine.evaluate(task, self.args[0], external_methods=task.workflow.data)
|
||||
|
||||
|
||||
class BpmnTaskSpec(TaskSpec):
|
||||
"""
|
||||
This class provides BPMN-specific attributes.
|
||||
|
||||
It is intended to be used with all tasks in a BPMN workflow. Spiff internal tasks (such
|
||||
as Root, EndJoin, etc) inherit directly from this.
|
||||
|
||||
Visible tasks inherit from `BpmnSpecMixin`, which will assign the `bpmn_id` and `bpmn_name`.
|
||||
|
||||
The intent is to (1) give all tasks in the workflow the same attributes and (2) provide an
|
||||
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):
|
||||
"""
|
||||
:param lane: Indicates the name of the lane that this task belongs to
|
||||
:param documentation: the contents of the documentation element
|
||||
:param data_input_associations: a list of data references to be used as inputs to the task
|
||||
:param data_output_associations: a list of data references to be used as inputs to the task
|
||||
"""
|
||||
super().__init__(wf_spec, name, **kwargs)
|
||||
self.bpmn_id = None
|
||||
self.bpmn_name = None
|
||||
self.lane = lane
|
||||
self.documentation = documentation
|
||||
self.data_input_associations = data_input_associations or []
|
||||
self.data_output_associations = data_output_associations or []
|
||||
self.io_specification = None
|
||||
if self.description is None:
|
||||
self.description = 'BPMN Task'
|
||||
|
||||
def connect_outgoing_if(self, condition, taskspec):
|
||||
"""
|
||||
Connect this task spec to the indicated child, if the condition
|
||||
evaluates to true. This should only be called if the task has a
|
||||
connect_if method (e.g. ExclusiveGateway).
|
||||
"""
|
||||
if condition is None:
|
||||
self.connect(taskspec)
|
||||
else:
|
||||
self.connect_if(_BpmnCondition(condition), taskspec)
|
||||
|
||||
def _update_hook(self, my_task):
|
||||
|
||||
super()._update_hook(my_task)
|
||||
# This copies data from data objects
|
||||
for obj in self.data_input_associations:
|
||||
obj.get(my_task)
|
||||
|
||||
# If an IO spec was given, require all inputs are present, and remove all other inputs.
|
||||
if self.io_specification is not None and len(self.io_specification.data_inputs) > 0:
|
||||
data = {}
|
||||
for var in self.io_specification.data_inputs:
|
||||
if var.bpmn_id not in my_task.data:
|
||||
raise WorkflowDataException("Missing data input", task=my_task, data_input=var)
|
||||
data[var.bpmn_id] = my_task.data[var.bpmn_id]
|
||||
my_task.data = data
|
||||
|
||||
return True
|
||||
|
||||
def _on_complete_hook(self, my_task):
|
||||
|
||||
if my_task.parent:
|
||||
my_task.parent.task_spec._child_complete_hook(my_task)
|
||||
|
||||
if self.io_specification is not None and len(self.io_specification.data_outputs) > 0:
|
||||
data = {}
|
||||
for var in self.io_specification.data_outputs:
|
||||
if var.bpmn_id not in my_task.data:
|
||||
raise WorkflowDataException("Missing data ouput", task=my_task, data_output=var)
|
||||
data[var.bpmn_id] = my_task.data[var.bpmn_id]
|
||||
my_task.data = data
|
||||
|
||||
for obj in self.data_output_associations:
|
||||
obj.set(my_task)
|
||||
|
||||
for obj in self.data_input_associations:
|
||||
# Remove the any copied input variables that might not have already been removed
|
||||
my_task.data.pop(obj.bpmn_id, None)
|
||||
|
||||
super()._on_complete_hook(my_task)
|
||||
|
||||
def _child_complete_hook(self, child_task):
|
||||
pass
|
@ -1,13 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2012 Matthew Hampton
|
||||
# Copyright (C) 2023 Sartography
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# 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 2.1 of the License, or (at your option) any later version.
|
||||
# version 3.0 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# 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.
|
||||
@ -17,40 +18,20 @@
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
# 02110-1301 USA
|
||||
|
||||
from .event_types import ThrowingEvent, CatchingEvent
|
||||
from ..BpmnSpecMixin import BpmnSpecMixin
|
||||
from ....specs.Simple import Simple
|
||||
from ....task import TaskState
|
||||
|
||||
class SendTask(ThrowingEvent):
|
||||
|
||||
@property
|
||||
def spec_type(self):
|
||||
return 'Send Task'
|
||||
from SpiffWorkflow.task import TaskState
|
||||
from SpiffWorkflow.specs.StartTask import StartTask
|
||||
from SpiffWorkflow.bpmn.specs.bpmn_task_spec import BpmnTaskSpec
|
||||
from SpiffWorkflow.bpmn.specs.mixins.unstructured_join import UnstructuredJoin
|
||||
from SpiffWorkflow.bpmn.specs.mixins.events.intermediate_event import BoundaryEvent
|
||||
|
||||
|
||||
class ReceiveTask(CatchingEvent):
|
||||
class BpmnStartTask(BpmnTaskSpec, StartTask):
|
||||
pass
|
||||
|
||||
@property
|
||||
def spec_type(self):
|
||||
return 'Receive Task'
|
||||
class SimpleBpmnTask(BpmnTaskSpec):
|
||||
pass
|
||||
|
||||
|
||||
class IntermediateCatchEvent(CatchingEvent):
|
||||
|
||||
@property
|
||||
def spec_type(self):
|
||||
return f'{self.event_definition.event_type} Catching Event'
|
||||
|
||||
|
||||
class IntermediateThrowEvent(ThrowingEvent):
|
||||
|
||||
@property
|
||||
def spec_type(self):
|
||||
return f'{self.event_definition.event_type} Throwing Event'
|
||||
|
||||
|
||||
class _BoundaryEventParent(Simple, BpmnSpecMixin):
|
||||
class _BoundaryEventParent(BpmnTaskSpec):
|
||||
"""This task is inserted before a task with boundary events."""
|
||||
|
||||
# I wonder if this would be better modelled as some type of join.
|
||||
@ -58,8 +39,7 @@ class _BoundaryEventParent(Simple, BpmnSpecMixin):
|
||||
# they're attached to be inputs rather than outputs.
|
||||
|
||||
def __init__(self, wf_spec, name, main_child_task_spec, **kwargs):
|
||||
|
||||
super(_BoundaryEventParent, self).__init__(wf_spec, name)
|
||||
super(_BoundaryEventParent, self).__init__(wf_spec, name, **kwargs)
|
||||
self.main_child_task_spec = main_child_task_spec
|
||||
|
||||
@property
|
||||
@ -93,39 +73,32 @@ class _BoundaryEventParent(Simple, BpmnSpecMixin):
|
||||
child._set_state(state)
|
||||
|
||||
|
||||
class BoundaryEvent(CatchingEvent):
|
||||
"""Task Spec for a bpmn:boundaryEvent node."""
|
||||
class _EndJoin(UnstructuredJoin, BpmnTaskSpec):
|
||||
|
||||
def __init__(self, wf_spec, name, event_definition, cancel_activity, **kwargs):
|
||||
"""
|
||||
Constructor.
|
||||
def _check_threshold_unstructured(self, my_task, force=False):
|
||||
# Look at the tree to find all ready and waiting tasks (excluding
|
||||
# ourself). The EndJoin waits for everyone!
|
||||
waiting_tasks = []
|
||||
for task in my_task.workflow.get_tasks(TaskState.READY | TaskState.WAITING):
|
||||
if task.thread_id != my_task.thread_id:
|
||||
continue
|
||||
if task.task_spec == my_task.task_spec:
|
||||
continue
|
||||
|
||||
:param cancel_activity: True if this is a Cancelling boundary event.
|
||||
"""
|
||||
super(BoundaryEvent, self).__init__(wf_spec, name, event_definition, **kwargs)
|
||||
self.cancel_activity = cancel_activity
|
||||
is_mine = False
|
||||
w = task.workflow
|
||||
if w == my_task.workflow:
|
||||
is_mine = True
|
||||
while w and w.outer_workflow != w:
|
||||
w = w.outer_workflow
|
||||
if w == my_task.workflow:
|
||||
is_mine = True
|
||||
if is_mine:
|
||||
waiting_tasks.append(task)
|
||||
|
||||
@property
|
||||
def spec_type(self):
|
||||
interrupting = 'Interrupting' if self.cancel_activity else 'Non-Interrupting'
|
||||
return f'{interrupting} {self.event_definition.event_type} Event'
|
||||
return force or len(waiting_tasks) == 0, waiting_tasks
|
||||
|
||||
def catches(self, my_task, event_definition, correlations=None):
|
||||
# Boundary events should only be caught while waiting
|
||||
return super(BoundaryEvent, self).catches(my_task, event_definition, correlations) and my_task.state == TaskState.WAITING
|
||||
|
||||
|
||||
class EventBasedGateway(CatchingEvent):
|
||||
|
||||
@property
|
||||
def spec_type(self):
|
||||
return 'Event Based Gateway'
|
||||
|
||||
def _predict_hook(self, my_task):
|
||||
my_task._sync_children(self.outputs, state=TaskState.MAYBE)
|
||||
|
||||
def _on_ready_hook(self, my_task):
|
||||
seen_events = my_task.internal_data.get('seen_events', [])
|
||||
for child in my_task.children:
|
||||
if child.task_spec.event_definition not in seen_events:
|
||||
child.cancel()
|
||||
def _run_hook(self, my_task):
|
||||
result = super(_EndJoin, self)._run_hook(my_task)
|
||||
my_task.workflow.data.update(my_task.data)
|
||||
return result
|
@ -1,3 +1,22 @@
|
||||
# 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
|
||||
|
||||
import logging
|
||||
from copy import deepcopy
|
||||
|
||||
@ -8,13 +27,13 @@ data_log = logging.getLogger('spiff.data')
|
||||
|
||||
class BpmnDataSpecification:
|
||||
|
||||
def __init__(self, name, description=None):
|
||||
def __init__(self, bpmn_id, bpmn_name=None):
|
||||
"""
|
||||
:param name: the variable (the BPMN ID)
|
||||
:param description: a human readable name (the BPMN name)
|
||||
"""
|
||||
self.name = name
|
||||
self.description = description or name
|
||||
self.bpmn_id = bpmn_id
|
||||
self.bpmn_name = bpmn_name
|
||||
# In the future, we can add schemas defining the objects here.
|
||||
|
||||
def get(self, my_task, **kwargs):
|
||||
@ -25,7 +44,7 @@ class BpmnDataSpecification:
|
||||
|
||||
|
||||
class BpmnDataStoreSpecification(BpmnDataSpecification):
|
||||
def __init__(self, name, description, capacity=None, is_unlimited=None):
|
||||
def __init__(self, bpmn_id, bpmn_name, capacity=None, is_unlimited=None):
|
||||
"""
|
||||
:param name: the name of the task data variable and data store key (the BPMN ID)
|
||||
:param description: the task description (the BPMN name)
|
||||
@ -35,7 +54,7 @@ class BpmnDataStoreSpecification(BpmnDataSpecification):
|
||||
self.capacity = capacity or 0
|
||||
self.is_unlimited = is_unlimited or True
|
||||
# In the future, we can add schemas defining the objects here.
|
||||
super().__init__(name, description)
|
||||
super().__init__(bpmn_id, bpmn_name)
|
||||
|
||||
|
||||
class BpmnIoSpecification:
|
||||
@ -50,20 +69,20 @@ class DataObject(BpmnDataSpecification):
|
||||
|
||||
def get(self, my_task):
|
||||
"""Copy a value form the workflow data to the task data."""
|
||||
if self.name not in my_task.workflow.data:
|
||||
message = f"The data object could not be read; '{self.name}' does not exist in the process."
|
||||
if self.bpmn_id not in my_task.workflow.data:
|
||||
message = f"The data object could not be read; '{self.bpmn_id}' does not exist in the process."
|
||||
raise WorkflowDataException(message, my_task, data_input=self)
|
||||
my_task.data[self.name] = deepcopy(my_task.workflow.data[self.name])
|
||||
data_log.info(f'Read workflow variable {self.name}', extra=my_task.log_info())
|
||||
my_task.data[self.bpmn_id] = deepcopy(my_task.workflow.data[self.bpmn_id])
|
||||
data_log.info(f'Read workflow variable {self.bpmn_id}', extra=my_task.log_info())
|
||||
|
||||
def set(self, my_task):
|
||||
"""Copy a value from the task data to the workflow data"""
|
||||
if self.name not in my_task.data:
|
||||
message = f"A data object could not be set; '{self.name}' not exist in the task."
|
||||
if self.bpmn_id not in my_task.data:
|
||||
message = f"A data object could not be set; '{self.bpmn_id}' not exist in the task."
|
||||
raise WorkflowDataException(message, my_task, data_output=self)
|
||||
my_task.workflow.data[self.name] = deepcopy(my_task.data[self.name])
|
||||
del my_task.data[self.name]
|
||||
data_log.info(f'Set workflow variable {self.name}', extra=my_task.log_info())
|
||||
my_task.workflow.data[self.bpmn_id] = deepcopy(my_task.data[self.bpmn_id])
|
||||
del my_task.data[self.bpmn_id]
|
||||
data_log.info(f'Set workflow variable {self.bpmn_id}', extra=my_task.log_info())
|
||||
|
||||
|
||||
class TaskDataReference(BpmnDataSpecification):
|
||||
|
122
SpiffWorkflow/SpiffWorkflow/bpmn/specs/defaults.py
Normal file
122
SpiffWorkflow/SpiffWorkflow/bpmn/specs/defaults.py
Normal file
@ -0,0 +1,122 @@
|
||||
# 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 .mixins.bpmn_spec_mixin import BpmnSpecMixin
|
||||
|
||||
from .mixins.manual_task import ManualTask as ManualTaskMixin
|
||||
from .mixins.none_task import NoneTask as NoneTaskMixin
|
||||
from .mixins.user_task import UserTask as UserTaskMixin
|
||||
|
||||
from .mixins.exclusive_gateway import ExclusiveGateway as ExclusiveGatewayMixin
|
||||
from .mixins.inclusive_gateway import InclusiveGateway as InclusiveGatewayMixin
|
||||
from .mixins.parallel_gateway import ParallelGateway as ParallelGatewayMixin
|
||||
|
||||
from .mixins.script_task import ScriptTask as ScriptTaskMixin
|
||||
from .mixins.service_task import ServiceTask as ServiceTaskMixin
|
||||
from .mixins.multiinstance_task import (
|
||||
StandardLoopTask as StandardLoopTaskMixin,
|
||||
ParallelMultiInstanceTask as ParallelMultiInstanceTaskMixin,
|
||||
SequentialMultiInstanceTask as SequentialMultiInstanceTaskMixin,
|
||||
)
|
||||
|
||||
from .mixins.subworkflow_task import (
|
||||
SubWorkflowTask as SubworkflowTaskMixin,
|
||||
CallActivity as CallActivityMixin,
|
||||
TransactionSubprocess as TransactionSubprocessMixin,
|
||||
)
|
||||
|
||||
from .mixins.events.start_event import StartEvent as StartEventMixin
|
||||
from .mixins.events.end_event import EndEvent as EndEventMixin
|
||||
from .mixins.events.intermediate_event import (
|
||||
IntermediateCatchEvent as IntermediateCatchEventMixin,
|
||||
IntermediateThrowEvent as IntermediateThrowEventMixin,
|
||||
SendTask as SendTaskMixin,
|
||||
ReceiveTask as ReceiveTaskMixin,
|
||||
EventBasedGateway as EventBasedGatewayMixin,
|
||||
BoundaryEvent as BoundaryEventMixin,
|
||||
)
|
||||
|
||||
# In the future, we could have the parser take a bpmn task spec and construct these classes automatically
|
||||
# However, I am NOT going to try to do that with the parser we have now
|
||||
|
||||
class ManualTask(ManualTaskMixin, BpmnSpecMixin):
|
||||
pass
|
||||
|
||||
class NoneTask(NoneTaskMixin, BpmnSpecMixin):
|
||||
pass
|
||||
|
||||
class UserTask(UserTaskMixin, BpmnSpecMixin):
|
||||
pass
|
||||
|
||||
class ExclusiveGateway(ExclusiveGatewayMixin, BpmnSpecMixin):
|
||||
pass
|
||||
|
||||
class InclusiveGateway(InclusiveGatewayMixin, BpmnSpecMixin):
|
||||
pass
|
||||
|
||||
class ParallelGateway(ParallelGatewayMixin, BpmnSpecMixin):
|
||||
pass
|
||||
|
||||
class ScriptTask(ScriptTaskMixin, BpmnSpecMixin):
|
||||
pass
|
||||
|
||||
class ServiceTask(ServiceTaskMixin, BpmnSpecMixin):
|
||||
pass
|
||||
|
||||
class StandardLoopTask(StandardLoopTaskMixin, BpmnSpecMixin):
|
||||
pass
|
||||
|
||||
class ParallelMultiInstanceTask(ParallelMultiInstanceTaskMixin, BpmnSpecMixin):
|
||||
pass
|
||||
|
||||
class SequentialMultiInstanceTask(SequentialMultiInstanceTaskMixin, BpmnSpecMixin):
|
||||
pass
|
||||
|
||||
class SubWorkflowTask(SubworkflowTaskMixin, BpmnSpecMixin):
|
||||
pass
|
||||
|
||||
class CallActivity(CallActivityMixin, BpmnSpecMixin):
|
||||
pass
|
||||
|
||||
class TransactionSubprocess(TransactionSubprocessMixin, BpmnSpecMixin):
|
||||
pass
|
||||
|
||||
class StartEvent(StartEventMixin, BpmnSpecMixin):
|
||||
pass
|
||||
|
||||
class EndEvent(EndEventMixin, BpmnSpecMixin):
|
||||
pass
|
||||
|
||||
class IntermediateCatchEvent(IntermediateCatchEventMixin, BpmnSpecMixin):
|
||||
pass
|
||||
|
||||
class IntermediateThrowEvent(IntermediateThrowEventMixin, BpmnSpecMixin):
|
||||
pass
|
||||
|
||||
class SendTask(SendTaskMixin, BpmnSpecMixin):
|
||||
pass
|
||||
|
||||
class ReceiveTask(ReceiveTaskMixin, BpmnSpecMixin):
|
||||
pass
|
||||
|
||||
class EventBasedGateway(EventBasedGatewayMixin, BpmnSpecMixin):
|
||||
pass
|
||||
|
||||
class BoundaryEvent(BoundaryEventMixin, BpmnSpecMixin):
|
||||
pass
|
@ -1,13 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2012 Matthew Hampton
|
||||
# Copyright (C) 2012 Matthew Hampton, 2023 Sartography
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# 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 2.1 of the License, or (at your option) any later version.
|
||||
# version 3.0 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# 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.
|
||||
@ -24,7 +24,6 @@ from time import timezone as tzoffset, altzone as dstoffset, daylight as isdst
|
||||
from copy import deepcopy
|
||||
|
||||
from SpiffWorkflow.exceptions import WorkflowException
|
||||
from SpiffWorkflow.task import TaskState
|
||||
|
||||
seconds_from_utc = dstoffset if isdst else tzoffset
|
||||
LOCALTZ = timezone(timedelta(seconds=-1 * seconds_from_utc))
|
||||
@ -42,11 +41,12 @@ class EventDefinition(object):
|
||||
and external flags.
|
||||
Default catch behavior is to set the event to fired
|
||||
"""
|
||||
def __init__(self):
|
||||
def __init__(self, description=None):
|
||||
# Ideally I'd mke these parameters, but I don't want to them to be parameters
|
||||
# for any subclasses (as they are based on event type, not user choice) and
|
||||
# I don't want to write a separate deserializer for every every type.
|
||||
self.internal, self.external = True, True
|
||||
self.description = description
|
||||
|
||||
@property
|
||||
def event_type(self):
|
||||
@ -92,8 +92,8 @@ class NamedEventDefinition(EventDefinition):
|
||||
:param name: the name of this event
|
||||
"""
|
||||
|
||||
def __init__(self, name):
|
||||
super(NamedEventDefinition, self).__init__()
|
||||
def __init__(self, name, **kwargs):
|
||||
super(NamedEventDefinition, self).__init__(**kwargs)
|
||||
self.name = name
|
||||
|
||||
def reset(self, my_task):
|
||||
@ -108,14 +108,10 @@ class CancelEventDefinition(EventDefinition):
|
||||
Cancel events are only handled by the outerworkflow, as they can only be used inside
|
||||
of transaction subprocesses.
|
||||
"""
|
||||
def __init__(self):
|
||||
super(CancelEventDefinition, self).__init__()
|
||||
def __init__(self, **kwargs):
|
||||
super(CancelEventDefinition, self).__init__(**kwargs)
|
||||
self.internal = False
|
||||
|
||||
@property
|
||||
def event_type(self):
|
||||
return 'Cancel'
|
||||
|
||||
|
||||
class ErrorEventDefinition(NamedEventDefinition):
|
||||
"""
|
||||
@ -123,15 +119,11 @@ class ErrorEventDefinition(NamedEventDefinition):
|
||||
matched by code rather than name.
|
||||
"""
|
||||
|
||||
def __init__(self, name, error_code=None):
|
||||
super(ErrorEventDefinition, self).__init__(name)
|
||||
def __init__(self, name, error_code=None, **kwargs):
|
||||
super(ErrorEventDefinition, self).__init__(name,**kwargs)
|
||||
self.error_code = error_code
|
||||
self.internal = False
|
||||
|
||||
@property
|
||||
def event_type(self):
|
||||
return 'Error'
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.__class__.__name__ == other.__class__.__name__ and self.error_code in [ None, other.error_code ]
|
||||
|
||||
@ -142,20 +134,16 @@ class EscalationEventDefinition(NamedEventDefinition):
|
||||
the spec says that the escalation code should be matched.
|
||||
"""
|
||||
|
||||
def __init__(self, name, escalation_code=None):
|
||||
def __init__(self, name, escalation_code=None, **kwargs):
|
||||
"""
|
||||
Constructor.
|
||||
|
||||
:param escalation_code: The escalation code this event should
|
||||
react to. If None then all escalations will activate this event.
|
||||
"""
|
||||
super(EscalationEventDefinition, self).__init__(name)
|
||||
super(EscalationEventDefinition, self).__init__(name, **kwargs)
|
||||
self.escalation_code = escalation_code
|
||||
|
||||
@property
|
||||
def event_type(self):
|
||||
return 'Escalation'
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.__class__.__name__ == other.__class__.__name__ and self.escalation_code in [ None, other.escalation_code ]
|
||||
|
||||
@ -171,17 +159,13 @@ class CorrelationProperty:
|
||||
class MessageEventDefinition(NamedEventDefinition):
|
||||
"""The default message event."""
|
||||
|
||||
def __init__(self, name, correlation_properties=None):
|
||||
super().__init__(name)
|
||||
def __init__(self, name, correlation_properties=None, **kwargs):
|
||||
super().__init__(name, **kwargs)
|
||||
self.correlation_properties = correlation_properties or []
|
||||
self.payload = None
|
||||
self.internal = False
|
||||
|
||||
@property
|
||||
def event_type(self):
|
||||
return 'Message'
|
||||
|
||||
def catch(self, my_task, event_definition = None):
|
||||
def catch(self, my_task, event_definition=None):
|
||||
self.update_internal_data(my_task, event_definition)
|
||||
super(MessageEventDefinition, self).catch(my_task, event_definition)
|
||||
|
||||
@ -243,13 +227,10 @@ class NoneEventDefinition(EventDefinition):
|
||||
"""
|
||||
This class defines behavior for NoneEvents. We override throw to do nothing.
|
||||
"""
|
||||
def __init__(self):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.internal, self.external = False, False
|
||||
|
||||
@property
|
||||
def event_type(self):
|
||||
return 'Default'
|
||||
|
||||
def throw(self, my_task):
|
||||
"""It's a 'none' event, so nothing to throw."""
|
||||
pass
|
||||
@ -261,26 +242,21 @@ class NoneEventDefinition(EventDefinition):
|
||||
|
||||
class SignalEventDefinition(NamedEventDefinition):
|
||||
"""The SignalEventDefinition is the implementation of event definition used for Signal Events."""
|
||||
def __init__(self, name, **kwargs):
|
||||
super().__init__(name, **kwargs)
|
||||
|
||||
@property
|
||||
def spec_type(self):
|
||||
return 'Signal'
|
||||
|
||||
class TerminateEventDefinition(EventDefinition):
|
||||
"""The TerminateEventDefinition is the implementation of event definition used for Termination Events."""
|
||||
|
||||
def __init__(self):
|
||||
super(TerminateEventDefinition, self).__init__()
|
||||
def __init__(self, **kwargs):
|
||||
super(TerminateEventDefinition, self).__init__(**kwargs)
|
||||
self.external = False
|
||||
|
||||
@property
|
||||
def event_type(self):
|
||||
return 'Terminate'
|
||||
|
||||
|
||||
class TimerEventDefinition(EventDefinition):
|
||||
|
||||
def __init__(self, name, expression):
|
||||
def __init__(self, name, expression, **kwargs):
|
||||
"""
|
||||
Constructor.
|
||||
|
||||
@ -288,7 +264,7 @@ class TimerEventDefinition(EventDefinition):
|
||||
|
||||
:param expression: An ISO 8601 datetime or interval expression.
|
||||
"""
|
||||
super().__init__()
|
||||
super().__init__(**kwargs)
|
||||
self.name = name
|
||||
self.expression = expression
|
||||
|
||||
@ -362,7 +338,7 @@ class TimerEventDefinition(EventDefinition):
|
||||
@staticmethod
|
||||
def parse_iso_week(expression):
|
||||
# https://en.wikipedia.org/wiki/ISO_8601#Week_dates
|
||||
m = re.match('(\d{4})W(\d{2})(\d)(T.+)?', expression.upper().replace('-', ''))
|
||||
m = re.match(r'(\d{4})W(\d{2})(\d)(T.+)?', expression.upper().replace('-', ''))
|
||||
year, month, day, ts = m.groups()
|
||||
ds = datetime.fromisocalendar(int(year), int(month), int(day)).strftime('%Y-%m-%d')
|
||||
return TimerEventDefinition.get_datetime(ds + (ts or ''))
|
||||
@ -409,10 +385,6 @@ class TimerEventDefinition(EventDefinition):
|
||||
class TimeDateEventDefinition(TimerEventDefinition):
|
||||
"""A Timer event represented by a specific date/time."""
|
||||
|
||||
@property
|
||||
def event_type(self):
|
||||
return 'Time Date Timer'
|
||||
|
||||
def has_fired(self, my_task):
|
||||
event_value = my_task._get_internal_data('event_value')
|
||||
if event_value is None:
|
||||
@ -429,10 +401,6 @@ class TimeDateEventDefinition(TimerEventDefinition):
|
||||
class DurationTimerEventDefinition(TimerEventDefinition):
|
||||
"""A timer event represented by a duration"""
|
||||
|
||||
@property
|
||||
def event_type(self):
|
||||
return 'Duration Timer'
|
||||
|
||||
def has_fired(self, my_task):
|
||||
event_value = my_task._get_internal_data("event_value")
|
||||
if event_value is None:
|
||||
@ -450,10 +418,6 @@ class DurationTimerEventDefinition(TimerEventDefinition):
|
||||
|
||||
class CycleTimerEventDefinition(TimerEventDefinition):
|
||||
|
||||
@property
|
||||
def event_type(self):
|
||||
return 'Cycle Timer'
|
||||
|
||||
def cycle_complete(self, my_task):
|
||||
|
||||
event_value = my_task._get_internal_data('event_value')
|
||||
@ -489,15 +453,11 @@ class CycleTimerEventDefinition(TimerEventDefinition):
|
||||
|
||||
class MultipleEventDefinition(EventDefinition):
|
||||
|
||||
def __init__(self, event_definitions=None, parallel=False):
|
||||
super().__init__()
|
||||
def __init__(self, event_definitions=None, parallel=False, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.event_definitions = event_definitions or []
|
||||
self.parallel = parallel
|
||||
|
||||
@property
|
||||
def event_type(self):
|
||||
return 'Multiple'
|
||||
|
||||
def has_fired(self, my_task):
|
||||
|
||||
seen_events = my_task.internal_data.get('seen_events', [])
|
@ -0,0 +1,28 @@
|
||||
# Copyright (C) 2012 Matthew Hampton, 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 ..bpmn_task_spec import BpmnTaskSpec
|
||||
|
||||
|
||||
class BpmnSpecMixin(BpmnTaskSpec):
|
||||
|
||||
def __init__(self, wf_spec, bpmn_id, **kwargs):
|
||||
super().__init__(wf_spec, bpmn_id, **kwargs)
|
||||
self.bpmn_id = bpmn_id
|
||||
self.bpmn_name = kwargs.get('bpmn_name')
|
@ -0,0 +1,18 @@
|
||||
# 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
|
@ -1,13 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2012 Matthew Hampton
|
||||
# Copyright (C) 2012 Matthew Hampton, 2023 Sartography
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# 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 2.1 of the License, or (at your option) any later version.
|
||||
# version 3.0 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# 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.
|
||||
@ -17,9 +17,9 @@
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
# 02110-1301 USA
|
||||
|
||||
from SpiffWorkflow.task import TaskState
|
||||
from .event_types import ThrowingEvent
|
||||
from .event_definitions import TerminateEventDefinition, CancelEventDefinition
|
||||
from ....task import TaskState
|
||||
from ...event_definitions import TerminateEventDefinition, CancelEventDefinition
|
||||
|
||||
|
||||
class EndEvent(ThrowingEvent):
|
||||
@ -41,14 +41,6 @@ class EndEvent(ThrowingEvent):
|
||||
Gateways, one of the associated Events has been triggered.
|
||||
* There is no token remaining within the Process instance.
|
||||
"""
|
||||
|
||||
def __init__(self, wf_spec, name, event_definition, **kwargs):
|
||||
super(EndEvent, self).__init__(wf_spec, name, event_definition, **kwargs)
|
||||
|
||||
@property
|
||||
def spec_type(self):
|
||||
return 'End Event'
|
||||
|
||||
def _on_complete_hook(self, my_task):
|
||||
|
||||
super(EndEvent, self)._on_complete_hook(my_task)
|
@ -1,13 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2012 Matthew Hampton
|
||||
# Copyright (C) 2012 Matthew Hampton, 2023 Sartography
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# 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 2.1 of the License, or (at your option) any later version.
|
||||
# version 3.0 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# 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.
|
||||
@ -16,22 +16,22 @@
|
||||
# License along with this library; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
# 02110-1301 USA
|
||||
import time
|
||||
from SpiffWorkflow.task import TaskState
|
||||
from SpiffWorkflow.specs.base import TaskSpec
|
||||
from ...event_definitions import MessageEventDefinition, NoneEventDefinition, CycleTimerEventDefinition
|
||||
|
||||
from .event_definitions import MessageEventDefinition, NoneEventDefinition, CycleTimerEventDefinition
|
||||
from ..BpmnSpecMixin import BpmnSpecMixin
|
||||
from ....specs.Simple import Simple
|
||||
from ....task import TaskState
|
||||
|
||||
class CatchingEvent(Simple, BpmnSpecMixin):
|
||||
class CatchingEvent(TaskSpec):
|
||||
"""Base Task Spec for Catching Event nodes."""
|
||||
|
||||
def __init__(self, wf_spec, name, event_definition, **kwargs):
|
||||
def __init__(self, wf_spec, bpmn_id, event_definition, **kwargs):
|
||||
"""
|
||||
Constructor.
|
||||
|
||||
:param event_definition: the EventDefinition that we must wait for.
|
||||
"""
|
||||
super(CatchingEvent, self).__init__(wf_spec, name, **kwargs)
|
||||
super(CatchingEvent, self).__init__(wf_spec, bpmn_id, **kwargs)
|
||||
self.event_definition = event_definition
|
||||
|
||||
def catches(self, my_task, event_definition, correlations=None):
|
||||
@ -46,6 +46,7 @@ class CatchingEvent(Simple, BpmnSpecMixin):
|
||||
definition, at which point we can update our task's state.
|
||||
"""
|
||||
self.event_definition.catch(my_task, event_definition)
|
||||
my_task.last_update_time = time.time()
|
||||
my_task._set_state(TaskState.WAITING)
|
||||
|
||||
def _update_hook(self, my_task):
|
||||
@ -74,26 +75,17 @@ class CatchingEvent(Simple, BpmnSpecMixin):
|
||||
self.event_definition.reset(my_task)
|
||||
return super(CatchingEvent, self)._run_hook(my_task)
|
||||
|
||||
# This fixes the problem of boundary events remaining cancelled if the task is reused.
|
||||
# It pains me to add these methods, but unless we can get rid of the loop reset task we're stuck
|
||||
|
||||
def task_should_set_children_future(self, my_task):
|
||||
return True
|
||||
|
||||
def task_will_set_children_future(self, my_task):
|
||||
my_task.internal_data = {}
|
||||
|
||||
|
||||
class ThrowingEvent(Simple, BpmnSpecMixin):
|
||||
class ThrowingEvent(TaskSpec):
|
||||
"""Base Task Spec for Throwing Event nodes."""
|
||||
|
||||
def __init__(self, wf_spec, name, event_definition, **kwargs):
|
||||
def __init__(self, wf_spec, bpmn_id, event_definition, **kwargs):
|
||||
"""
|
||||
Constructor.
|
||||
|
||||
:param event_definition: the EventDefinition to be thrown.
|
||||
"""
|
||||
super(ThrowingEvent, self).__init__(wf_spec, name, **kwargs)
|
||||
super(ThrowingEvent, self).__init__(wf_spec, bpmn_id, **kwargs)
|
||||
self.event_definition = event_definition
|
||||
|
||||
def _run_hook(self, my_task):
|
@ -0,0 +1,63 @@
|
||||
# Copyright (C) 2012 Matthew Hampton, 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 TaskState
|
||||
from .event_types import ThrowingEvent, CatchingEvent
|
||||
|
||||
|
||||
class SendTask(ThrowingEvent):
|
||||
pass
|
||||
|
||||
class ReceiveTask(CatchingEvent):
|
||||
pass
|
||||
|
||||
class IntermediateCatchEvent(CatchingEvent):
|
||||
pass
|
||||
|
||||
class IntermediateThrowEvent(ThrowingEvent):
|
||||
pass
|
||||
|
||||
|
||||
class BoundaryEvent(CatchingEvent):
|
||||
"""Task Spec for a bpmn:boundaryEvent node."""
|
||||
|
||||
def __init__(self, wf_spec, bpmn_id, event_definition, cancel_activity, **kwargs):
|
||||
"""
|
||||
Constructor.
|
||||
|
||||
:param cancel_activity: True if this is a Cancelling boundary event.
|
||||
"""
|
||||
super(BoundaryEvent, self).__init__(wf_spec, bpmn_id, event_definition, **kwargs)
|
||||
self.cancel_activity = cancel_activity
|
||||
|
||||
def catches(self, my_task, event_definition, correlations=None):
|
||||
# Boundary events should only be caught while waiting
|
||||
return super(BoundaryEvent, self).catches(my_task, event_definition, correlations) and my_task.state == TaskState.WAITING
|
||||
|
||||
|
||||
class EventBasedGateway(CatchingEvent):
|
||||
|
||||
def _predict_hook(self, my_task):
|
||||
my_task._sync_children(self.outputs, state=TaskState.MAYBE)
|
||||
|
||||
def _on_ready_hook(self, my_task):
|
||||
seen_events = my_task.internal_data.get('seen_events', [])
|
||||
for child in my_task.children:
|
||||
if child.task_spec.event_definition not in seen_events:
|
||||
child.cancel()
|
@ -1,13 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2012 Matthew Hampton
|
||||
# Copyright (C) 2012 Matthew Hampton, 2023 Sartography
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# 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 2.1 of the License, or (at your option) any later version.
|
||||
# version 3.0 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# 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.
|
||||
@ -17,27 +17,16 @@
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
# 02110-1301 USA
|
||||
|
||||
from SpiffWorkflow.task import TaskState
|
||||
from .event_types import CatchingEvent
|
||||
from ....task import TaskState
|
||||
|
||||
|
||||
class StartEvent(CatchingEvent):
|
||||
"""Task Spec for a bpmn:startEvent node with an optional event definition."""
|
||||
|
||||
def __init__(self, wf_spec, name, event_definition, **kwargs):
|
||||
super(StartEvent, self).__init__(wf_spec, name, event_definition, **kwargs)
|
||||
|
||||
@property
|
||||
def spec_type(self):
|
||||
return f'{self.event_definition.event_type} Start Event'
|
||||
|
||||
def catch(self, my_task, event_definition):
|
||||
|
||||
# We might need to revisit a start event after it completes or
|
||||
# if it got cancelled so we'll still catch messages even if we're finished
|
||||
if my_task.state == TaskState.COMPLETED or my_task.state == TaskState.CANCELLED:
|
||||
my_task.set_children_future()
|
||||
my_task._set_state(TaskState.WAITING)
|
||||
|
||||
my_task.workflow.reset_from_task_id(my_task.id)
|
||||
super(StartEvent, self).catch(my_task, event_definition)
|
||||
|
@ -1,13 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2012 Matthew Hampton
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# 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 2.1 of the License, or (at your option) any later version.
|
||||
# version 3.0 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# 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.
|
||||
@ -17,12 +17,11 @@
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
# 02110-1301 USA
|
||||
|
||||
from .BpmnSpecMixin import BpmnSpecMixin
|
||||
from ...specs.ExclusiveChoice import ExclusiveChoice
|
||||
from ...specs.MultiChoice import MultiChoice
|
||||
from SpiffWorkflow.specs.ExclusiveChoice import ExclusiveChoice
|
||||
from SpiffWorkflow.specs.MultiChoice import MultiChoice
|
||||
|
||||
|
||||
class ExclusiveGateway(ExclusiveChoice, BpmnSpecMixin):
|
||||
class ExclusiveGateway(ExclusiveChoice):
|
||||
"""
|
||||
Task Spec for a bpmn:exclusiveGateway node.
|
||||
"""
|
||||
@ -31,6 +30,3 @@ class ExclusiveGateway(ExclusiveChoice, BpmnSpecMixin):
|
||||
# Bypass the check for no default output -- this is not required in BPMN
|
||||
MultiChoice.test(self)
|
||||
|
||||
@property
|
||||
def spec_type(self):
|
||||
return 'Exclusive Gateway'
|
@ -1,13 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2012 Matthew Hampton
|
||||
# Copyright (C) 2012 Matthew Hampton, 2023 Sartography
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# 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 2.1 of the License, or (at your option) any later version.
|
||||
# version 3.0 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# 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.
|
||||
@ -17,10 +17,11 @@
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
# 02110-1301 USA
|
||||
|
||||
from SpiffWorkflow.exceptions import WorkflowTaskException
|
||||
from ...task import TaskState
|
||||
from .UnstructuredJoin import UnstructuredJoin
|
||||
from ...specs.MultiChoice import MultiChoice
|
||||
from SpiffWorkflow.bpmn.exceptions import WorkflowTaskException
|
||||
from SpiffWorkflow.task import TaskState
|
||||
from SpiffWorkflow.specs.MultiChoice import MultiChoice
|
||||
from .unstructured_join import UnstructuredJoin
|
||||
|
||||
|
||||
|
||||
class InclusiveGateway(MultiChoice, UnstructuredJoin):
|
||||
@ -113,10 +114,6 @@ class InclusiveGateway(MultiChoice, UnstructuredJoin):
|
||||
def _run_hook(self, my_task):
|
||||
outputs = self._get_matching_outputs(my_task)
|
||||
if len(outputs) == 0:
|
||||
raise WorkflowTaskException(f'No conditions satisfied on gateway', task=my_task)
|
||||
raise WorkflowTaskException('No conditions satisfied on gateway', task=my_task)
|
||||
my_task._sync_children(outputs, TaskState.FUTURE)
|
||||
return True
|
||||
|
||||
@property
|
||||
def spec_type(self):
|
||||
return 'Inclusive Gateway'
|
@ -1,13 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2012 Matthew Hampton
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# 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 2.1 of the License, or (at your option) any later version.
|
||||
# version 3.0 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# 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.
|
||||
@ -16,16 +16,13 @@
|
||||
# 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 ...bpmn.specs.BpmnSpecMixin import BpmnSpecMixin
|
||||
|
||||
from ...specs.Simple import Simple
|
||||
from SpiffWorkflow.specs.base import TaskSpec
|
||||
|
||||
|
||||
class ManualTask(Simple, BpmnSpecMixin):
|
||||
class ManualTask(TaskSpec):
|
||||
"""Task Spec for a bpmn:manualTask node."""
|
||||
|
||||
def is_engine_task(self):
|
||||
return False
|
||||
|
||||
@property
|
||||
def spec_type(self):
|
||||
return 'Manual Task'
|
||||
def __init__(self, wf_spec, bpmn_id, **kwargs):
|
||||
super().__init__(wf_spec, bpmn_id, **kwargs)
|
||||
self.manual = True
|
@ -1,13 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2020 Sartography
|
||||
# Copyright (C) 2023 Sartography
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# 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 2.1 of the License, or (at your option) any later version.
|
||||
# version 3.0 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# 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.
|
||||
@ -20,13 +20,13 @@
|
||||
from copy import deepcopy
|
||||
from collections.abc import Iterable, Sequence, Mapping, MutableSequence, MutableMapping
|
||||
|
||||
from ...task import TaskState
|
||||
from ...util.deep_merge import DeepMerge
|
||||
from ..exceptions import WorkflowDataException
|
||||
from .BpmnSpecMixin import BpmnSpecMixin
|
||||
from SpiffWorkflow.task import TaskState
|
||||
from SpiffWorkflow.specs.base import TaskSpec
|
||||
from SpiffWorkflow.util.deep_merge import DeepMerge
|
||||
from SpiffWorkflow.bpmn.exceptions import WorkflowDataException
|
||||
|
||||
|
||||
class LoopTask(BpmnSpecMixin):
|
||||
class LoopTask(TaskSpec):
|
||||
|
||||
def process_children(self, my_task):
|
||||
"""
|
||||
@ -50,8 +50,8 @@ class LoopTask(BpmnSpecMixin):
|
||||
|
||||
class StandardLoopTask(LoopTask):
|
||||
|
||||
def __init__(self, wf_spec, name, task_spec, maximum, condition, test_before, **kwargs):
|
||||
super().__init__(wf_spec, name, **kwargs)
|
||||
def __init__(self, wf_spec, bpmn_id, task_spec, maximum, condition, test_before, **kwargs):
|
||||
super().__init__(wf_spec, bpmn_id, **kwargs)
|
||||
self.task_spec = task_spec
|
||||
self.maximum = maximum
|
||||
self.condition = condition
|
||||
@ -59,7 +59,9 @@ class StandardLoopTask(LoopTask):
|
||||
|
||||
def _update_hook(self, my_task):
|
||||
|
||||
if my_task.state != TaskState.WAITING:
|
||||
super()._update_hook(my_task)
|
||||
|
||||
child_running = self.process_children(my_task)
|
||||
if child_running:
|
||||
# We're in the middle of an iteration; we're not done and we can't create a new task
|
||||
@ -72,7 +74,7 @@ class StandardLoopTask(LoopTask):
|
||||
if my_task.state != TaskState.WAITING:
|
||||
my_task._set_state(TaskState.WAITING)
|
||||
task_spec = my_task.workflow.spec.task_specs[self.task_spec]
|
||||
child = my_task._add_child(task_spec, TaskState.READY)
|
||||
child = my_task._add_child(task_spec, TaskState.WAITING)
|
||||
child.data = deepcopy(my_task.data)
|
||||
|
||||
def child_completed_action(self, my_task, child):
|
||||
@ -92,11 +94,11 @@ class StandardLoopTask(LoopTask):
|
||||
|
||||
class MultiInstanceTask(LoopTask):
|
||||
|
||||
def __init__(self, wf_spec, name, task_spec, cardinality=None, data_input=None,
|
||||
def __init__(self, wf_spec, bpmn_id, task_spec, cardinality=None, data_input=None,
|
||||
data_output=None, input_item=None, output_item=None, condition=None,
|
||||
**kwargs):
|
||||
|
||||
super().__init__(wf_spec, name, **kwargs)
|
||||
super().__init__(wf_spec, bpmn_id, **kwargs)
|
||||
self.task_spec = task_spec
|
||||
self.cardinality = cardinality
|
||||
self.data_input = data_input
|
||||
@ -109,13 +111,13 @@ class MultiInstanceTask(LoopTask):
|
||||
"""This merges child data into this task's data."""
|
||||
|
||||
if self.data_output is not None and self.output_item is not None:
|
||||
if self.output_item.name not in child.data:
|
||||
if self.output_item.bpmn_id not in child.data:
|
||||
self.raise_data_exception("Expected an output item", child)
|
||||
item = child.data[self.output_item.name]
|
||||
item = child.data[self.output_item.bpmn_id]
|
||||
key_or_index = child.internal_data.get('key_or_index')
|
||||
data_output = my_task.data[self.data_output.name]
|
||||
data_input = my_task.data[self.data_input.name] if self.data_input is not None else None
|
||||
if isinstance(data_output, Mapping) or data_input is data_output:
|
||||
data_output = my_task.data[self.data_output.bpmn_id]
|
||||
data_input = my_task.data[self.data_input.bpmn_id] if self.data_input is not None else None
|
||||
if key_or_index is not None and (isinstance(data_output, Mapping) or data_input is data_output):
|
||||
data_output[key_or_index] = item
|
||||
else:
|
||||
data_output.append(item)
|
||||
@ -128,7 +130,7 @@ class MultiInstanceTask(LoopTask):
|
||||
child = my_task._add_child(task_spec, TaskState.WAITING)
|
||||
child.data = deepcopy(my_task.data)
|
||||
if self.input_item is not None:
|
||||
child.data[self.input_item.name] = deepcopy(item)
|
||||
child.data[self.input_item.bpmn_id] = deepcopy(item)
|
||||
if key_or_index is not None:
|
||||
child.internal_data['key_or_index'] = key_or_index
|
||||
child.task_spec._update(child)
|
||||
@ -142,7 +144,7 @@ class MultiInstanceTask(LoopTask):
|
||||
|
||||
def init_data_output_with_input_data(self, my_task, input_data):
|
||||
|
||||
name = self.data_output.name
|
||||
name = self.data_output.bpmn_id
|
||||
if name not in my_task.data:
|
||||
if isinstance(input_data, (MutableMapping, MutableSequence)):
|
||||
# We can use the same class if it implements __setitem__
|
||||
@ -154,7 +156,7 @@ class MultiInstanceTask(LoopTask):
|
||||
# For all other types, we'll append to a list
|
||||
my_task.data[name] = list()
|
||||
else:
|
||||
output_data = my_task.data[self.data_output.name]
|
||||
output_data = my_task.data[self.data_output.bpmn_id]
|
||||
if not isinstance(output_data, (MutableSequence, MutableMapping)):
|
||||
self.raise_data_exception("Only a mutable map (dict) or sequence (list) can be used for output", my_task)
|
||||
if input_data is not output_data and not isinstance(output_data, Mapping) and len(output_data) > 0:
|
||||
@ -163,7 +165,7 @@ class MultiInstanceTask(LoopTask):
|
||||
|
||||
def init_data_output_with_cardinality(self, my_task):
|
||||
|
||||
name = self.data_output.name
|
||||
name = self.data_output.bpmn_id
|
||||
if name not in my_task.data:
|
||||
my_task.data[name] = list()
|
||||
elif not isinstance(my_task.data[name], MutableMapping) and len(my_task.data[name]) > 0:
|
||||
@ -207,7 +209,7 @@ class SequentialMultiInstanceTask(MultiInstanceTask):
|
||||
|
||||
def get_next_input_item(self, my_task):
|
||||
|
||||
input_data = my_task.data[self.data_input.name]
|
||||
input_data = my_task.data[self.data_input.bpmn_id]
|
||||
remaining = my_task.internal_data.get('remaining')
|
||||
|
||||
if remaining is None:
|
||||
@ -229,9 +231,9 @@ class SequentialMultiInstanceTask(MultiInstanceTask):
|
||||
|
||||
def init_remaining_items(self, my_task):
|
||||
|
||||
if self.data_input.name not in my_task.data:
|
||||
if self.data_input.bpmn_id not in my_task.data:
|
||||
self.raise_data_exception("Missing data input for multiinstance task", my_task)
|
||||
input_data = my_task.data[self.data_input.name]
|
||||
input_data = my_task.data[self.data_input.bpmn_id]
|
||||
|
||||
# This is internal bookkeeping, so we know where we are; we get the actual items when we create the task
|
||||
if isinstance(input_data, Sequence):
|
||||
@ -287,7 +289,7 @@ class ParallelMultiInstanceTask(MultiInstanceTask):
|
||||
|
||||
def create_children(self, my_task):
|
||||
|
||||
data_input = my_task.data[self.data_input.name] if self.data_input is not None else None
|
||||
data_input = my_task.data[self.data_input.bpmn_id] if self.data_input is not None else None
|
||||
if data_input is not None:
|
||||
# We have to preserve the key or index for maps/sequences, in case we're updating in place, or the output is a mapping
|
||||
if isinstance(data_input, Mapping):
|
||||
@ -306,7 +308,7 @@ class ParallelMultiInstanceTask(MultiInstanceTask):
|
||||
|
||||
if self.data_output is not None:
|
||||
if self.data_input is not None:
|
||||
self.init_data_output_with_input_data(my_task, my_task.data[self.data_input.name])
|
||||
self.init_data_output_with_input_data(my_task, my_task.data[self.data_input.bpmn_id])
|
||||
else:
|
||||
self.init_data_output_with_cardinality(my_task)
|
||||
|
@ -1,13 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2012 Matthew Hampton
|
||||
# Copyright (C) 2012 Matthew Hampton, 2023 Sartography
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# 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 2.1 of the License, or (at your option) any later version.
|
||||
# version 3.0 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# 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.
|
||||
@ -16,16 +16,13 @@
|
||||
# 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 ...specs.Simple import Simple
|
||||
|
||||
from ...bpmn.specs.BpmnSpecMixin import BpmnSpecMixin
|
||||
from SpiffWorkflow.specs.base import TaskSpec
|
||||
|
||||
|
||||
class NoneTask(Simple, BpmnSpecMixin):
|
||||
class NoneTask(TaskSpec):
|
||||
"""Task Spec for a bpmn:task node."""
|
||||
|
||||
def is_engine_task(self):
|
||||
return False
|
||||
|
||||
@property
|
||||
def spec_type(self):
|
||||
return 'Task'
|
||||
def __init__(self, wf_spec, bpmn_id, **kwargs):
|
||||
super().__init__(wf_spec, bpmn_id, **kwargs)
|
||||
self.manual = True
|
@ -1,13 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2012 Matthew Hampton
|
||||
# Copyright (C) 2012 Matthew Hampton, 2023 Sartography
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# 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 2.1 of the License, or (at your option) any later version.
|
||||
# version 3.0 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# 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.
|
||||
@ -16,7 +16,8 @@
|
||||
# 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 .UnstructuredJoin import UnstructuredJoin
|
||||
|
||||
from .unstructured_join import UnstructuredJoin
|
||||
|
||||
|
||||
class ParallelGateway(UnstructuredJoin):
|
||||
@ -43,7 +44,3 @@ class ParallelGateway(UnstructuredJoin):
|
||||
def _check_threshold_unstructured(self, my_task, force=False):
|
||||
completed_inputs, waiting_tasks = self._get_inputs_with_tokens(my_task)
|
||||
return force or len(completed_inputs) >= len(self.inputs), waiting_tasks
|
||||
|
||||
@property
|
||||
def spec_type(self):
|
||||
return 'Parallel Gateway'
|
@ -1,13 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2012 Matthew Hampton
|
||||
# Copyright (C) 2012 Matthew Hampton, 2023 Sartography
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# 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 2.1 of the License, or (at your option) any later version.
|
||||
# version 3.0 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# 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.
|
||||
@ -17,11 +17,10 @@
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
# 02110-1301 USA
|
||||
|
||||
from .BpmnSpecMixin import BpmnSpecMixin
|
||||
from ...specs.Simple import Simple
|
||||
from SpiffWorkflow.specs.base import TaskSpec
|
||||
|
||||
|
||||
class ScriptEngineTask(Simple, BpmnSpecMixin):
|
||||
class ScriptEngineTask(TaskSpec):
|
||||
"""Task Spec for a bpmn:scriptTask node"""
|
||||
|
||||
def _execute(self, task):
|
||||
@ -34,18 +33,14 @@ class ScriptEngineTask(Simple, BpmnSpecMixin):
|
||||
|
||||
class ScriptTask(ScriptEngineTask):
|
||||
|
||||
def __init__(self, wf_spec, name, script, **kwargs):
|
||||
def __init__(self, wf_spec, bpmn_id, script, **kwargs):
|
||||
"""
|
||||
Constructor.
|
||||
|
||||
:param script: the script that must be executed by the script engine.
|
||||
"""
|
||||
super(ScriptTask, self).__init__(wf_spec, name, **kwargs)
|
||||
super(ScriptTask, self).__init__(wf_spec, bpmn_id, **kwargs)
|
||||
self.script = script
|
||||
|
||||
@property
|
||||
def spec_type(self):
|
||||
return 'Script Task'
|
||||
|
||||
def _execute(self, task):
|
||||
return task.workflow.script_engine.execute(task, self.script)
|
@ -1,13 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2012 Matthew Hampton
|
||||
# Copyright (C) 2023 Sartography
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# 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 2.1 of the License, or (at your option) any later version.
|
||||
# version 3.0 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# 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.
|
||||
@ -17,19 +17,14 @@
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
# 02110-1301 USA
|
||||
|
||||
from .BpmnSpecMixin import BpmnSpecMixin
|
||||
from ...specs.Simple import Simple
|
||||
from .script_task import ScriptEngineTask
|
||||
|
||||
|
||||
class UserTask(Simple, BpmnSpecMixin):
|
||||
class ServiceTask(ScriptEngineTask):
|
||||
|
||||
"""
|
||||
Task Spec for a bpmn:userTask node.
|
||||
Task Spec for a bpmn:serviceTask node.
|
||||
"""
|
||||
|
||||
def is_engine_task(self):
|
||||
return False
|
||||
|
||||
@property
|
||||
def spec_type(self):
|
||||
return 'User Task'
|
||||
def __init__(self, wf_spec, bpmn_id, **kwargs):
|
||||
super(ServiceTask, self).__init__(wf_spec, bpmn_id, **kwargs)
|
@ -1,30 +1,47 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 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 copy import deepcopy
|
||||
|
||||
from SpiffWorkflow.task import TaskState
|
||||
from .BpmnSpecMixin import BpmnSpecMixin
|
||||
from ..exceptions import WorkflowDataException
|
||||
from SpiffWorkflow.specs.base import TaskSpec
|
||||
from SpiffWorkflow.bpmn.specs.control import _BoundaryEventParent
|
||||
from SpiffWorkflow.bpmn.specs.mixins.events.intermediate_event import BoundaryEvent
|
||||
|
||||
from SpiffWorkflow.bpmn.exceptions import WorkflowDataException
|
||||
|
||||
|
||||
class SubWorkflowTask(BpmnSpecMixin):
|
||||
class SubWorkflowTask(TaskSpec):
|
||||
"""
|
||||
Task Spec for a bpmn node containing a subworkflow.
|
||||
"""
|
||||
def __init__(self, wf_spec, name, subworkflow_spec, transaction=False, **kwargs):
|
||||
def __init__(self, wf_spec, bpmn_id, subworkflow_spec, transaction=False, **kwargs):
|
||||
"""
|
||||
Constructor.
|
||||
|
||||
:param bpmn_wf_spec: the BpmnProcessSpec for the sub process.
|
||||
:param bpmn_wf_class: the BpmnWorkflow class to instantiate
|
||||
"""
|
||||
super(SubWorkflowTask, self).__init__(wf_spec, name, **kwargs)
|
||||
super(SubWorkflowTask, self).__init__(wf_spec, bpmn_id, **kwargs)
|
||||
self.spec = subworkflow_spec
|
||||
self.transaction = transaction
|
||||
|
||||
@property
|
||||
def spec_type(self):
|
||||
return 'Subprocess'
|
||||
|
||||
def _on_subworkflow_completed(self, subworkflow, my_task):
|
||||
self.update_data(my_task, subworkflow)
|
||||
|
||||
@ -68,14 +85,11 @@ class SubWorkflowTask(BpmnSpecMixin):
|
||||
child.task_spec._update(child)
|
||||
my_task._set_state(TaskState.WAITING)
|
||||
|
||||
def task_will_set_children_future(self, my_task):
|
||||
my_task.workflow.delete_subprocess(my_task)
|
||||
|
||||
|
||||
class CallActivity(SubWorkflowTask):
|
||||
|
||||
def __init__(self, wf_spec, name, subworkflow_spec, **kwargs):
|
||||
super(CallActivity, self).__init__(wf_spec, name, subworkflow_spec, False, **kwargs)
|
||||
def __init__(self, wf_spec, bpmn_id, subworkflow_spec, **kwargs):
|
||||
super(CallActivity, self).__init__(wf_spec, bpmn_id, subworkflow_spec, False, **kwargs)
|
||||
|
||||
def copy_data(self, my_task, subworkflow):
|
||||
|
||||
@ -86,13 +100,13 @@ class CallActivity(SubWorkflowTask):
|
||||
else:
|
||||
# Otherwise copy only task data with the specified names
|
||||
for var in subworkflow.spec.io_specification.data_inputs:
|
||||
if var.name not in my_task.data:
|
||||
if var.bpmn_id not in my_task.data:
|
||||
raise WorkflowDataException(
|
||||
"You are missing a required Data Input for a call activity.",
|
||||
task=my_task,
|
||||
data_input=var,
|
||||
)
|
||||
start[0].data[var.name] = my_task.data[var.name]
|
||||
start[0].data[var.bpmn_id] = my_task.data[var.bpmn_id]
|
||||
|
||||
def update_data(self, my_task, subworkflow):
|
||||
|
||||
@ -103,25 +117,36 @@ class CallActivity(SubWorkflowTask):
|
||||
end = subworkflow.get_tasks_from_spec_name('End', workflow=subworkflow)
|
||||
# Otherwise only copy data with the specified names
|
||||
for var in subworkflow.spec.io_specification.data_outputs:
|
||||
if var.name not in end[0].data:
|
||||
if var.bpmn_id not in end[0].data:
|
||||
raise WorkflowDataException(
|
||||
f"The Data Output was not available in the subprocess output.",
|
||||
"The Data Output was not available in the subprocess output.",
|
||||
task=my_task,
|
||||
data_output=var,
|
||||
)
|
||||
my_task.data[var.name] = end[0].data[var.name]
|
||||
|
||||
@property
|
||||
def spec_type(self):
|
||||
return 'Call Activity'
|
||||
my_task.data[var.bpmn_id] = end[0].data[var.bpmn_id]
|
||||
|
||||
|
||||
class TransactionSubprocess(SubWorkflowTask):
|
||||
|
||||
def __init__(self, wf_spec, name, subworkflow_spec, **kwargs):
|
||||
super(TransactionSubprocess, self).__init__(wf_spec, name, subworkflow_spec, True, **kwargs)
|
||||
|
||||
@property
|
||||
def spec_type(self):
|
||||
return 'Transactional Subprocess'
|
||||
def __init__(self, wf_spec, bpmn_id, subworkflow_spec, **kwargs):
|
||||
super(TransactionSubprocess, self).__init__(wf_spec, bpmn_id, subworkflow_spec, True, **kwargs)
|
||||
|
||||
def _on_complete_hook(self, my_task):
|
||||
# It is possible that a transaction could end by throwing an event caught by a boundary event attached to it
|
||||
# In that case both the subprocess and the boundary event become ready and whichever one gets executed
|
||||
# first will cancel the other.
|
||||
# So here I'm checking whether this has happened and cancelling this task in that case.
|
||||
# I really hate this fix, so I'm only putting it in transactions because that's where I'm having the problem,
|
||||
# but it's likely to be a general issue that we miraculously haven't run up against.
|
||||
# We desperately need to get rid of this BonudaryEventParent BS.
|
||||
parent = my_task.parent
|
||||
if isinstance(parent.task_spec, _BoundaryEventParent) and len(
|
||||
[t for t in parent.children if
|
||||
isinstance(t.task_spec, BoundaryEvent) and
|
||||
t.task_spec.cancel_activity and
|
||||
t.state==TaskState.READY
|
||||
]):
|
||||
my_task._drop_children()
|
||||
my_task._set_state(TaskState.CANCELLED)
|
||||
else:
|
||||
super()._on_complete_hook(my_task)
|
@ -1,13 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2012 Matthew Hampton
|
||||
# Copyright (C) 2012 Matthew Hampton, 2023 Sartography
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# 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 2.1 of the License, or (at your option) any later version.
|
||||
# version 3.0 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# 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.
|
||||
@ -17,12 +17,11 @@
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
# 02110-1301 USA
|
||||
|
||||
from ...task import TaskState
|
||||
from .BpmnSpecMixin import BpmnSpecMixin
|
||||
from ...specs.Join import Join
|
||||
from SpiffWorkflow.task import TaskState
|
||||
from SpiffWorkflow.specs.Join import Join
|
||||
|
||||
|
||||
class UnstructuredJoin(Join, BpmnSpecMixin):
|
||||
class UnstructuredJoin(Join):
|
||||
"""
|
||||
A helper subclass of Join that makes it work in a slightly friendlier way
|
||||
for the BPMN style threading
|
||||
@ -83,13 +82,3 @@ class UnstructuredJoin(Join, BpmnSpecMixin):
|
||||
task._drop_children()
|
||||
else:
|
||||
task.data.update(collected_data)
|
||||
|
||||
def task_should_set_children_future(self, my_task):
|
||||
return True
|
||||
|
||||
def task_will_set_children_future(self, my_task):
|
||||
# go find all of the gateways with the same name as this one,
|
||||
# drop children and set state to WAITING
|
||||
for t in list(my_task.workflow.task_tree):
|
||||
if t.task_spec.name == self.name and t.state == TaskState.COMPLETED:
|
||||
t._set_state(TaskState.WAITING)
|
27
SpiffWorkflow/SpiffWorkflow/bpmn/specs/mixins/user_task.py
Normal file
27
SpiffWorkflow/SpiffWorkflow/bpmn/specs/mixins/user_task.py
Normal file
@ -0,0 +1,27 @@
|
||||
# Copyright (C) 2012 Matthew Hampton, 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.specs.base import TaskSpec
|
||||
|
||||
class UserTask(TaskSpec):
|
||||
"""Task Spec for a bpmn:userTask node."""
|
||||
|
||||
def __init__(self, wf_spec, bpmn_id, **kwargs):
|
||||
super().__init__(wf_spec, bpmn_id, **kwargs)
|
||||
self.manual = True
|
@ -1,12 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (C) 2012 Matthew Hampton
|
||||
# Copyright (C) 2012 Matthew Hampton, 2023 Sartography
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# 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 2.1 of the License, or (at your option) any later version.
|
||||
# version 3.0 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# 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.
|
||||
@ -15,21 +16,26 @@
|
||||
# License along with this library; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
# 02110-1301 USA
|
||||
|
||||
import copy
|
||||
|
||||
from SpiffWorkflow.bpmn.specs.events.event_definitions import (
|
||||
from SpiffWorkflow.task import TaskState, Task
|
||||
from SpiffWorkflow.workflow import Workflow
|
||||
from SpiffWorkflow.exceptions import WorkflowException, TaskNotFoundException
|
||||
from SpiffWorkflow.bpmn.exceptions import WorkflowTaskException
|
||||
|
||||
from SpiffWorkflow.bpmn.specs.mixins.events.event_types import CatchingEvent
|
||||
from SpiffWorkflow.bpmn.specs.mixins.events.start_event import StartEvent
|
||||
from SpiffWorkflow.bpmn.specs.mixins.subworkflow_task import CallActivity
|
||||
from SpiffWorkflow.bpmn.specs.event_definitions import (
|
||||
MessageEventDefinition,
|
||||
MultipleEventDefinition,
|
||||
NamedEventDefinition,
|
||||
TimerEventDefinition,
|
||||
)
|
||||
|
||||
from SpiffWorkflow.bpmn.specs.control import _BoundaryEventParent
|
||||
from .PythonScriptEngine import PythonScriptEngine
|
||||
from .specs.events.event_types import CatchingEvent
|
||||
from .specs.events.StartEvent import StartEvent
|
||||
from .specs.SubWorkflowTask import CallActivity
|
||||
from ..task import TaskState, Task
|
||||
from ..workflow import Workflow
|
||||
from ..exceptions import TaskNotFoundException, WorkflowException, WorkflowTaskException
|
||||
|
||||
|
||||
class BpmnMessage:
|
||||
@ -86,6 +92,7 @@ class BpmnWorkflow(Workflow):
|
||||
|
||||
def delete_subprocess(self, my_task):
|
||||
workflow = self._get_outermost_workflow(my_task)
|
||||
if my_task.id in workflow.subprocesses:
|
||||
del workflow.subprocesses[my_task.id]
|
||||
|
||||
def get_subprocess(self, my_task):
|
||||
@ -94,7 +101,17 @@ class BpmnWorkflow(Workflow):
|
||||
|
||||
def connect_subprocess(self, spec_name, name):
|
||||
# This creates a new task associated with a process when an event that kicks of a process is received
|
||||
new = CallActivity(self.spec, name, spec_name)
|
||||
# I need to know what class is being used to create new processes in this case, and this seems slightly
|
||||
# less bad than adding yet another argument. Still sucks though.
|
||||
# TODO: Make collaborations a class rather than trying to shoehorn them into a process.
|
||||
for spec in self.spec.task_specs.values():
|
||||
if isinstance(spec, CallActivity):
|
||||
spec_class = spec.__class__
|
||||
break
|
||||
else:
|
||||
# Default to the mixin class, which will probably fail in many cases.
|
||||
spec_class = CallActivity
|
||||
new = spec_class(self.spec, name, spec_name)
|
||||
self.spec.start.connect(new)
|
||||
task = Task(self, new)
|
||||
start = self.get_tasks_from_spec_name('Start', workflow=self)[0]
|
||||
@ -135,7 +152,7 @@ class BpmnWorkflow(Workflow):
|
||||
:param event_definition: the thrown event
|
||||
"""
|
||||
# Start a subprocess for known specs with start events that catch this
|
||||
# This is total hypocritical of me given how I've argued that specs should
|
||||
# This is totally hypocritical of me given how I've argued that specs should
|
||||
# be immutable, but I see no other way of doing this.
|
||||
for name, spec in self.subprocess_specs.items():
|
||||
for task_spec in list(spec.task_specs.values()):
|
||||
@ -183,8 +200,8 @@ class BpmnWorkflow(Workflow):
|
||||
conversation = task.task_spec.event_definition.conversation()
|
||||
if not conversation:
|
||||
raise WorkflowTaskException(
|
||||
f"The waiting task and message payload can not be matched to any correlation key (conversation topic). "
|
||||
f"And is therefor unable to respond to the given message.", task)
|
||||
"The waiting task and message payload can not be matched to any correlation key (conversation topic). "
|
||||
"And is therefor unable to respond to the given message.", task)
|
||||
updated_props = self._correlate(conversation, payload, task)
|
||||
task.task_spec.catch(task, event_definition)
|
||||
self.refresh_waiting_tasks()
|
||||
@ -229,7 +246,7 @@ class BpmnWorkflow(Workflow):
|
||||
elif isinstance(event_definition, MessageEventDefinition):
|
||||
value = event_definition.correlation_properties
|
||||
events.append({
|
||||
'event_type': event_definition.event_type,
|
||||
'event_type': event_definition.__class__.__name__,
|
||||
'name': event_definition.name if isinstance(event_definition, NamedEventDefinition) else None,
|
||||
'value': value
|
||||
})
|
||||
@ -246,7 +263,7 @@ class BpmnWorkflow(Workflow):
|
||||
:param will_complete_task: Callback that will be called prior to completing a task
|
||||
:param did_complete_task: Callback that will be called after completing a task
|
||||
"""
|
||||
engine_steps = list([t for t in self.get_tasks(TaskState.READY) if self._is_engine_task(t.task_spec)])
|
||||
engine_steps = list([t for t in self.get_tasks(TaskState.READY) if not t.task_spec.manual])
|
||||
while engine_steps:
|
||||
for task in engine_steps:
|
||||
if will_complete_task is not None:
|
||||
@ -256,7 +273,7 @@ class BpmnWorkflow(Workflow):
|
||||
did_complete_task(task)
|
||||
if task.task_spec.name == exit_at:
|
||||
return task
|
||||
engine_steps = list([t for t in self.get_tasks(TaskState.READY) if self._is_engine_task(t.task_spec)])
|
||||
engine_steps = list([t for t in self.get_tasks(TaskState.READY) if not t.task_spec.manual])
|
||||
|
||||
def refresh_waiting_tasks(self,
|
||||
will_refresh_task=None,
|
||||
@ -292,10 +309,10 @@ class BpmnWorkflow(Workflow):
|
||||
# almost surely be in a different state than the tasks we want
|
||||
for task in Workflow.get_tasks_iterator(wf):
|
||||
subprocess = top.subprocesses.get(task.id)
|
||||
if subprocess is not None:
|
||||
tasks.extend(subprocess.get_tasks(state, subprocess))
|
||||
if task._has_state(state):
|
||||
tasks.append(task)
|
||||
if subprocess is not None:
|
||||
tasks.extend(subprocess.get_tasks(state, subprocess))
|
||||
return tasks
|
||||
|
||||
def get_task_from_id(self, task_id, workflow=None):
|
||||
@ -308,11 +325,9 @@ class BpmnWorkflow(Workflow):
|
||||
"""Returns a list of User Tasks that are READY for user action"""
|
||||
if lane is not None:
|
||||
return [t for t in self.get_tasks(TaskState.READY, workflow)
|
||||
if (not self._is_engine_task(t.task_spec))
|
||||
and (t.task_spec.lane == lane)]
|
||||
if t.task_spec.manual and t.task_spec.lane == lane]
|
||||
else:
|
||||
return [t for t in self.get_tasks(TaskState.READY, workflow)
|
||||
if not self._is_engine_task(t.task_spec)]
|
||||
return [t for t in self.get_tasks(TaskState.READY, workflow) if t.task_spec.manual]
|
||||
|
||||
def get_waiting_tasks(self, workflow=None):
|
||||
"""Returns a list of all WAITING tasks"""
|
||||
@ -321,5 +336,58 @@ class BpmnWorkflow(Workflow):
|
||||
def get_catching_tasks(self, workflow=None):
|
||||
return [task for task in self.get_tasks(workflow=workflow) if isinstance(task.task_spec, CatchingEvent)]
|
||||
|
||||
def _is_engine_task(self, task_spec):
|
||||
return (not hasattr(task_spec, 'is_engine_task') or task_spec.is_engine_task())
|
||||
def reset_from_task_id(self, task_id, data=None):
|
||||
"""Override method from base class, and assures that if the task
|
||||
being reset has a boundary event parent, we reset that parent and
|
||||
run it rather than resetting to the current task. This assures
|
||||
our boundary events are set to the correct state."""
|
||||
|
||||
task = self.get_task_from_id(task_id)
|
||||
run_task_at_end = False
|
||||
|
||||
if isinstance(task.parent.task_spec, _BoundaryEventParent):
|
||||
task = task.parent
|
||||
run_task_at_end = True # we jumped up one level, so exectute so we are on the correct task as requested.
|
||||
|
||||
descendants = super().reset_from_task_id(task_id, data)
|
||||
descendant_ids = [t.id for t in descendants]
|
||||
top = self._get_outermost_workflow()
|
||||
|
||||
delete, reset = [], []
|
||||
for sp_id, sp in top.subprocesses.items():
|
||||
if sp_id in descendant_ids:
|
||||
delete.append(sp_id)
|
||||
delete.extend([t.id for t in sp.get_tasks() if t.id in top.subprocesses])
|
||||
if task in sp.get_tasks():
|
||||
reset.append(sp_id)
|
||||
|
||||
# Remove any subprocesses for removed tasks
|
||||
for sp_id in delete:
|
||||
del top.subprocesses[sp_id]
|
||||
|
||||
# Reset any containing subprocesses
|
||||
for sp_id in reset:
|
||||
descendants.extend(self.reset_from_task_id(sp_id))
|
||||
sp_task = self.get_task_from_id(sp_id)
|
||||
sp_task.state = TaskState.WAITING
|
||||
|
||||
if run_task_at_end:
|
||||
task.run()
|
||||
|
||||
return descendants
|
||||
|
||||
def cancel(self, workflow=None):
|
||||
|
||||
wf = workflow or self
|
||||
cancelled = Workflow.cancel(wf)
|
||||
cancelled_ids = [t.id for t in cancelled]
|
||||
top = self._get_outermost_workflow()
|
||||
to_cancel = []
|
||||
for sp_id, sp in top.subprocesses.items():
|
||||
if sp_id in cancelled_ids:
|
||||
to_cancel.append(sp)
|
||||
|
||||
for sp in to_cancel:
|
||||
cancelled.extend(self.cancel(sp))
|
||||
|
||||
return cancelled
|
||||
|
@ -0,0 +1,18 @@
|
||||
# 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
|
@ -1,14 +1,40 @@
|
||||
|
||||
from SpiffWorkflow.bpmn.parser.BpmnParser import full_tag, DEFAULT_NSMAP
|
||||
|
||||
from SpiffWorkflow.bpmn.specs.ManualTask import ManualTask
|
||||
from SpiffWorkflow.bpmn.specs.NoneTask import NoneTask
|
||||
from SpiffWorkflow.bpmn.specs.ScriptTask import ScriptTask
|
||||
from SpiffWorkflow.bpmn.specs.SubWorkflowTask import CallActivity, TransactionSubprocess
|
||||
# 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.dmn.parser.BpmnDmnParser import BpmnDmnParser
|
||||
from SpiffWorkflow.dmn.specs.BusinessRuleTask import BusinessRuleTask
|
||||
from SpiffWorkflow.camunda.specs.UserTask import UserTask
|
||||
from SpiffWorkflow.bpmn.parser.BpmnParser import full_tag, DEFAULT_NSMAP
|
||||
|
||||
from SpiffWorkflow.bpmn.specs.defaults import (
|
||||
ManualTask,
|
||||
NoneTask,
|
||||
ScriptTask,
|
||||
CallActivity,
|
||||
TransactionSubprocess,
|
||||
StartEvent,
|
||||
EndEvent,
|
||||
IntermediateThrowEvent,
|
||||
IntermediateCatchEvent,
|
||||
BoundaryEvent
|
||||
)
|
||||
from SpiffWorkflow.camunda.specs.business_rule_task import BusinessRuleTask
|
||||
from SpiffWorkflow.camunda.specs.user_task import UserTask
|
||||
|
||||
from SpiffWorkflow.camunda.parser.task_spec import (
|
||||
CamundaTaskParser,
|
||||
BusinessRuleTaskParser,
|
||||
@ -18,10 +44,6 @@ from SpiffWorkflow.camunda.parser.task_spec import (
|
||||
ScriptTaskParser,
|
||||
CAMUNDA_MODEL_NS
|
||||
)
|
||||
|
||||
from SpiffWorkflow.bpmn.specs.events.StartEvent import StartEvent
|
||||
from SpiffWorkflow.bpmn.specs.events.EndEvent import EndEvent
|
||||
from SpiffWorkflow.bpmn.specs.events.IntermediateEvent import IntermediateThrowEvent, IntermediateCatchEvent, BoundaryEvent
|
||||
from .event_parsers import (
|
||||
CamundaStartEventParser,
|
||||
CamundaEndEventParser,
|
||||
|
@ -0,0 +1,18 @@
|
||||
# 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
|
@ -1,7 +1,31 @@
|
||||
from SpiffWorkflow.bpmn.parser.event_parsers import EventDefinitionParser
|
||||
from SpiffWorkflow.bpmn.parser.event_parsers import StartEventParser, EndEventParser, \
|
||||
IntermediateCatchEventParser, IntermediateThrowEventParser, BoundaryEventParser
|
||||
from SpiffWorkflow.camunda.specs.events.event_definitions import MessageEventDefinition
|
||||
# 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.bpmn.parser.event_parsers import (
|
||||
EventDefinitionParser,
|
||||
StartEventParser,
|
||||
EndEventParser,
|
||||
IntermediateCatchEventParser,
|
||||
IntermediateThrowEventParser,
|
||||
BoundaryEventParser
|
||||
)
|
||||
from SpiffWorkflow.camunda.specs.event_definitions import MessageEventDefinition
|
||||
from SpiffWorkflow.bpmn.parser.util import one
|
||||
|
||||
|
||||
|
@ -1,4 +1,21 @@
|
||||
from ...camunda.specs.UserTask import Form, FormField, EnumFormField
|
||||
# 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.bpmn.specs.data_spec import TaskDataReference
|
||||
from SpiffWorkflow.bpmn.parser.util import one
|
||||
@ -6,9 +23,9 @@ from SpiffWorkflow.bpmn.parser.ValidationException import ValidationException
|
||||
from SpiffWorkflow.bpmn.parser.TaskParser import TaskParser
|
||||
from SpiffWorkflow.bpmn.parser.task_parsers import SubprocessParser
|
||||
|
||||
from SpiffWorkflow.dmn.specs.BusinessRuleTask import BusinessRuleTask
|
||||
|
||||
from SpiffWorkflow.camunda.specs.business_rule_task import BusinessRuleTask
|
||||
from SpiffWorkflow.camunda.specs.multiinstance_task import SequentialMultiInstanceTask, ParallelMultiInstanceTask
|
||||
from SpiffWorkflow.camunda.specs.user_task import Form, FormField, EnumFormField
|
||||
|
||||
CAMUNDA_MODEL_NS = 'http://camunda.org/schema/1.0/bpmn'
|
||||
|
||||
@ -66,11 +83,9 @@ class BusinessRuleTaskParser(CamundaTaskParser):
|
||||
|
||||
def create_task(self):
|
||||
decision_ref = self.get_decision_ref(self.node)
|
||||
return BusinessRuleTask(self.spec, self.get_task_spec_name(),
|
||||
return BusinessRuleTask(self.spec, self.bpmn_id,
|
||||
dmnEngine=self.process_parser.parser.get_engine(decision_ref, self.node),
|
||||
lane=self.lane, position=self.position,
|
||||
description=self.node.get('name', None),
|
||||
)
|
||||
**self.bpmn_attributes)
|
||||
|
||||
@staticmethod
|
||||
def get_decision_ref(node):
|
||||
@ -82,10 +97,7 @@ class UserTaskParser(CamundaTaskParser):
|
||||
|
||||
def create_task(self):
|
||||
form = self.get_form()
|
||||
return self.spec_class(self.spec, self.get_task_spec_name(), form,
|
||||
lane=self.lane,
|
||||
position=self.position,
|
||||
description=self.node.get('name', None))
|
||||
return self.spec_class(self.spec, self.bpmn_id, form=form, **self.bpmn_attributes)
|
||||
|
||||
def get_form(self):
|
||||
"""Camunda provides a simple form builder, this will extract the
|
||||
@ -138,10 +150,7 @@ class SubWorkflowParser(CamundaTaskParser):
|
||||
|
||||
def create_task(self):
|
||||
subworkflow_spec = SubprocessParser.get_subprocess_spec(self)
|
||||
return self.spec_class(
|
||||
self.spec, self.get_task_spec_name(), subworkflow_spec,
|
||||
lane=self.lane, position=self.position,
|
||||
description=self.node.get('name', None))
|
||||
return self.spec_class(self.spec, self.bpmn_id, subworkflow_spec=subworkflow_spec, **self.bpmn_attributes)
|
||||
|
||||
|
||||
class CallActivityParser(CamundaTaskParser):
|
||||
@ -149,10 +158,7 @@ class CallActivityParser(CamundaTaskParser):
|
||||
|
||||
def create_task(self):
|
||||
subworkflow_spec = SubprocessParser.get_call_activity_spec(self)
|
||||
return self.spec_class(
|
||||
self.spec, self.get_task_spec_name(), subworkflow_spec,
|
||||
lane=self.lane, position=self.position,
|
||||
description=self.node.get('name', None))
|
||||
return self.spec_class(self.spec, self.bpmn_id, subworkflow_spec=subworkflow_spec, **self.bpmn_attributes)
|
||||
|
||||
|
||||
class ScriptTaskParser(TaskParser):
|
||||
@ -162,10 +168,7 @@ class ScriptTaskParser(TaskParser):
|
||||
|
||||
def create_task(self):
|
||||
script = self.get_script()
|
||||
return self.spec_class(self.spec, self.get_task_spec_name(), script,
|
||||
lane=self.lane,
|
||||
position=self.position,
|
||||
description=self.node.get('name', None))
|
||||
return self.spec_class(self.spec, self.bpmn_id, script=script, **self.bpmn_attributes)
|
||||
|
||||
def get_script(self):
|
||||
"""
|
||||
@ -177,5 +180,5 @@ class ScriptTaskParser(TaskParser):
|
||||
return one(self.xpath('.//bpmn:script')).text
|
||||
except AssertionError as ae:
|
||||
raise ValidationException(
|
||||
f"Invalid Script Task. No Script Provided. " + str(ae),
|
||||
"Invalid Script Task. No Script Provided. " + str(ae),
|
||||
node=self.node, file_name=self.filename)
|
||||
|
@ -0,0 +1,18 @@
|
||||
# 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
|
@ -1,3 +1,22 @@
|
||||
# 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 copy import deepcopy
|
||||
|
||||
from SpiffWorkflow.bpmn.serializer.workflow import DEFAULT_SPEC_CONFIG
|
||||
@ -8,8 +27,12 @@ from SpiffWorkflow.bpmn.serializer.task_spec import (
|
||||
)
|
||||
from SpiffWorkflow.bpmn.serializer.event_definition import MessageEventDefinitionConverter as DefaultMessageEventConverter
|
||||
|
||||
|
||||
from .task_spec import UserTaskConverter, ParallelMultiInstanceTaskConverter, SequentialMultiInstanceTaskConverter
|
||||
from .task_spec import (
|
||||
UserTaskConverter,
|
||||
BusinessRuleTaskConverter,
|
||||
ParallelMultiInstanceTaskConverter,
|
||||
SequentialMultiInstanceTaskConverter
|
||||
)
|
||||
from .event_definition import MessageEventDefinitionConverter
|
||||
|
||||
|
||||
@ -20,6 +43,7 @@ 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_SPEC_CONFIG['event_definitions'].remove(DefaultMessageEventConverter)
|
||||
CAMUNDA_SPEC_CONFIG['event_definitions'].append(MessageEventDefinitionConverter)
|
||||
|
@ -1,5 +1,24 @@
|
||||
# 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.bpmn.serializer.helpers.spec import EventDefinitionConverter
|
||||
from ..specs.events.event_definitions import MessageEventDefinition
|
||||
from ..specs.event_definitions import MessageEventDefinition
|
||||
|
||||
|
||||
class MessageEventDefinitionConverter(EventDefinitionConverter):
|
||||
|
@ -1,7 +1,28 @@
|
||||
# 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.bpmn.serializer.helpers.spec import TaskSpecConverter
|
||||
from SpiffWorkflow.bpmn.serializer.task_spec import MultiInstanceTaskConverter
|
||||
from SpiffWorkflow.dmn.serializer.task_spec import BaseBusinessRuleTaskConverter
|
||||
|
||||
from SpiffWorkflow.camunda.specs.UserTask import UserTask, Form
|
||||
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):
|
||||
@ -11,7 +32,6 @@ class UserTaskConverter(TaskSpecConverter):
|
||||
|
||||
def to_dict(self, spec):
|
||||
dct = self.get_default_attributes(spec)
|
||||
dct.update(self.get_bpmn_attributes(spec))
|
||||
dct['form'] = self.form_to_dict(spec.form)
|
||||
return dct
|
||||
|
||||
@ -36,6 +56,10 @@ class UserTaskConverter(TaskSpecConverter):
|
||||
return dct
|
||||
|
||||
|
||||
class BusinessRuleTaskConverter(BaseBusinessRuleTaskConverter):
|
||||
def __init__(self, registry):
|
||||
super().__init__(BusinessRuleTask, registry)
|
||||
|
||||
class ParallelMultiInstanceTaskConverter(MultiInstanceTaskConverter):
|
||||
def __init__(self, registry):
|
||||
super().__init__(ParallelMultiInstanceTask, registry)
|
||||
|
@ -0,0 +1,18 @@
|
||||
# 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
|
@ -0,0 +1,5 @@
|
||||
from SpiffWorkflow.dmn.specs.business_rule_task_mixin import BusinessRuleTaskMixin
|
||||
from SpiffWorkflow.bpmn.specs.mixins.bpmn_spec_mixin import BpmnSpecMixin
|
||||
|
||||
class BusinessRuleTask(BusinessRuleTaskMixin, BpmnSpecMixin):
|
||||
pass
|
@ -1,4 +1,23 @@
|
||||
from SpiffWorkflow.bpmn.specs.events.event_definitions import MessageEventDefinition
|
||||
# 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.bpmn.specs.event_definitions import MessageEventDefinition
|
||||
|
||||
class MessageEventDefinition(MessageEventDefinition):
|
||||
"""
|
||||
@ -10,9 +29,9 @@ class MessageEventDefinition(MessageEventDefinition):
|
||||
# this should be revisited: for one thing, we're relying on some Camunda-specific
|
||||
# properties.
|
||||
|
||||
def __init__(self, name, correlation_properties=None, payload=None, result_var=None):
|
||||
def __init__(self, name, correlation_properties=None, payload=None, result_var=None, **kwargs):
|
||||
|
||||
super(MessageEventDefinition, self).__init__(name, correlation_properties)
|
||||
super(MessageEventDefinition, self).__init__(name, correlation_properties, **kwargs)
|
||||
self.payload = payload
|
||||
self.result_var = result_var
|
||||
|
@ -1,8 +1,27 @@
|
||||
# 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 TaskState
|
||||
from SpiffWorkflow.bpmn.specs.BpmnSpecMixin import BpmnSpecMixin
|
||||
from SpiffWorkflow.bpmn.specs.mixins.bpmn_spec_mixin import BpmnSpecMixin
|
||||
from SpiffWorkflow.bpmn.specs.data_spec import TaskDataReference
|
||||
|
||||
from SpiffWorkflow.bpmn.specs.MultiInstanceTask import (
|
||||
from SpiffWorkflow.bpmn.specs.defaults import (
|
||||
SequentialMultiInstanceTask as BpmnSequentialMITask,
|
||||
ParallelMultiInstanceTask as BpmnParallelMITask,
|
||||
)
|
||||
@ -20,19 +39,19 @@ def update_task_spec(my_task):
|
||||
|
||||
if task_spec.cardinality is None:
|
||||
# Use the same collection for input and output
|
||||
task_spec.data_input = TaskDataReference(task_spec.data_output.name)
|
||||
task_spec.input_item = TaskDataReference(task_spec.output_item.name)
|
||||
task_spec.data_input = TaskDataReference(task_spec.data_output.bpmn_id)
|
||||
task_spec.input_item = TaskDataReference(task_spec.output_item.bpmn_id)
|
||||
else:
|
||||
cardinality = my_task.workflow.script_engine.evaluate(my_task, task_spec.cardinality)
|
||||
if not isinstance(cardinality, int):
|
||||
# The input data was supplied via "cardinality"
|
||||
# We'll use the same reference for input and output item
|
||||
task_spec.data_input = TaskDataReference(task_spec.cardinality)
|
||||
task_spec.input_item = TaskDataReference(task_spec.output_item.name) if task_spec.output_item is not None else None
|
||||
task_spec.input_item = TaskDataReference(task_spec.output_item.bpmn_id) if task_spec.output_item is not None else None
|
||||
task_spec.cardinality = None
|
||||
else:
|
||||
# This will be the index
|
||||
task_spec.input_item = TaskDataReference(task_spec.output_item.name) if task_spec.output_item is not None else None
|
||||
task_spec.input_item = TaskDataReference(task_spec.output_item.bpmn_id) if task_spec.output_item is not None else None
|
||||
|
||||
|
||||
class SequentialMultiInstanceTask(BpmnSequentialMITask):
|
||||
|
@ -1,11 +1,26 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 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.bpmn.specs.defaults import UserTask as DefaultUserTask
|
||||
|
||||
|
||||
from ...bpmn.specs.UserTask import UserTask
|
||||
from ...bpmn.specs.BpmnSpecMixin import BpmnSpecMixin
|
||||
|
||||
|
||||
class UserTask(UserTask, BpmnSpecMixin):
|
||||
class UserTask(DefaultUserTask):
|
||||
"""Task Spec for a bpmn:userTask node with Camunda forms."""
|
||||
|
||||
def __init__(self, wf_spec, name, form, **kwargs):
|
||||
@ -17,12 +32,6 @@ class UserTask(UserTask, BpmnSpecMixin):
|
||||
super(UserTask, self).__init__(wf_spec, name, **kwargs)
|
||||
self.form = form
|
||||
|
||||
def _on_trigger(self, my_task):
|
||||
pass
|
||||
|
||||
def is_engine_task(self):
|
||||
return False
|
||||
|
||||
|
||||
class FormField(object):
|
||||
def __init__(self, form_type="text"):
|
@ -0,0 +1,18 @@
|
||||
# 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
|
@ -1,10 +1,29 @@
|
||||
# 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
|
||||
|
||||
import logging
|
||||
import re
|
||||
|
||||
from SpiffWorkflow.exceptions import SpiffWorkflowException
|
||||
from SpiffWorkflow.bpmn.exceptions import WorkflowTaskException
|
||||
|
||||
from ..specs.model import HitPolicy
|
||||
from ...exceptions import SpiffWorkflowException, WorkflowTaskException
|
||||
from ...util import levenshtein
|
||||
from ...workflow import WorkflowException
|
||||
|
||||
logger = logging.getLogger('spiff.dmn')
|
||||
|
||||
@ -37,7 +56,7 @@ class DMNEngine:
|
||||
for rule in matched_rules:
|
||||
rule_output = rule.output_as_dict(task)
|
||||
for key in rule_output.keys():
|
||||
if not key in result:
|
||||
if key not in result:
|
||||
result[key] = []
|
||||
result[key].append(rule_output[key])
|
||||
elif len(matched_rules) > 0:
|
||||
@ -98,7 +117,7 @@ class DMNEngine:
|
||||
# NOTE: It should only do this replacement outside of quotes.
|
||||
# for example, provided "This thing?" in quotes, it should not
|
||||
# do the replacement.
|
||||
match_expr = re.sub('(\?)(?=(?:[^\'"]|[\'"][^\'"]*[\'"])*$)', 'dmninputexpr', match_expr)
|
||||
match_expr = re.sub(r'(\?)(?=(?:[^\'"]|[\'"][^\'"]*[\'"])*$)', 'dmninputexpr', match_expr)
|
||||
if 'dmninputexpr' in match_expr:
|
||||
external_methods = {
|
||||
'dmninputexpr': script_engine.evaluate(task, input_expr)
|
||||
|
@ -0,0 +1,18 @@
|
||||
# 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
|
@ -1,3 +1,22 @@
|
||||
# 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
|
||||
|
||||
import glob
|
||||
import os
|
||||
|
||||
@ -53,7 +72,7 @@ class BpmnDmnParser(BpmnParser):
|
||||
validator.validate(node, filename)
|
||||
|
||||
dmn_parser = DMNParser(self, node, nsmap, filename=filename)
|
||||
self.dmn_parsers[dmn_parser.get_id()] = dmn_parser
|
||||
self.dmn_parsers[dmn_parser.bpmn_id] = dmn_parser
|
||||
self.dmn_parsers_by_name[dmn_parser.get_name()] = dmn_parser
|
||||
|
||||
def add_dmn_file(self, filename):
|
||||
@ -75,7 +94,19 @@ class BpmnDmnParser(BpmnParser):
|
||||
"""
|
||||
for filename in filenames:
|
||||
with open(filename, 'r') as f:
|
||||
self.add_dmn_xml(etree.parse(f).getroot(), filename=filename)
|
||||
self.add_dmn_io(f, filename=filename)
|
||||
|
||||
def add_dmn_io(self, file_like_object, filename=None):
|
||||
"""
|
||||
Add the given DMN file like object to the parser's set.
|
||||
"""
|
||||
self.add_dmn_xml(etree.parse(file_like_object).getroot(), filename)
|
||||
|
||||
def add_dmn_str(self, dmn_str, filename=None):
|
||||
"""
|
||||
Add the given DMN string to the parser's set.
|
||||
"""
|
||||
self.add_dmn_xml(etree.fromstring(dmn_str), filename)
|
||||
|
||||
def get_dependencies(self):
|
||||
return self.process_dependencies.union(self.dmn_dependencies)
|
||||
|
@ -1,12 +1,38 @@
|
||||
# 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
|
||||
|
||||
import ast
|
||||
|
||||
from SpiffWorkflow.bpmn.parser.node_parser import NodeParser, DEFAULT_NSMAP
|
||||
from ...bpmn.parser.ValidationException import ValidationException
|
||||
from SpiffWorkflow.bpmn.parser.ValidationException import ValidationException
|
||||
|
||||
from ...bpmn.parser.util import xpath_eval
|
||||
from SpiffWorkflow.bpmn.parser.util import xpath_eval
|
||||
|
||||
from ...dmn.specs.model import Decision, DecisionTable, InputEntry, \
|
||||
OutputEntry, Input, Output, Rule
|
||||
from SpiffWorkflow.dmn.specs.model import (
|
||||
Decision,
|
||||
DecisionTable,
|
||||
InputEntry,
|
||||
OutputEntry,
|
||||
Input,
|
||||
Output,
|
||||
Rule,
|
||||
)
|
||||
|
||||
def get_dmn_ns(node):
|
||||
"""
|
||||
@ -55,7 +81,8 @@ class DMNParser(NodeParser):
|
||||
def parse(self):
|
||||
self.decision = self._parse_decision(self.node.findall('{*}decision'))
|
||||
|
||||
def get_id(self):
|
||||
@property
|
||||
def bpmn_id(self):
|
||||
"""
|
||||
Returns the process ID
|
||||
"""
|
||||
@ -172,9 +199,7 @@ class DMNParser(NodeParser):
|
||||
return rule
|
||||
|
||||
def _parse_input_output_element(self, decision_table, element, cls, idx):
|
||||
input_or_output = (
|
||||
decision_table.inputs if cls == InputEntry else decision_table.outputs if cls == OutputEntry else None)[
|
||||
idx]
|
||||
input_or_output = (decision_table.inputs if cls == InputEntry else decision_table.outputs)[idx]
|
||||
entry = cls(element.attrib['id'], input_or_output)
|
||||
for child in element:
|
||||
if child.tag.endswith('description'):
|
||||
@ -182,7 +207,8 @@ class DMNParser(NodeParser):
|
||||
elif child.tag.endswith('text'):
|
||||
entry.text = child.text
|
||||
if cls == InputEntry:
|
||||
entry.lhs.append(entry.text)
|
||||
# DMN renders 'no input specification' with '-'; assume this is intended if somebody has added '-'
|
||||
entry.lhs.append(entry.text if entry.text != '-' else None)
|
||||
elif cls == OutputEntry:
|
||||
if entry.text and entry.text != '':
|
||||
try:
|
||||
|
@ -0,0 +1,18 @@
|
||||
# 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
|
@ -0,0 +1,18 @@
|
||||
# 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
|
@ -1,6 +1,24 @@
|
||||
# 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 ...bpmn.serializer.helpers.spec import TaskSpecConverter
|
||||
|
||||
from ..specs.BusinessRuleTask import BusinessRuleTask
|
||||
from ..specs.model import DecisionTable, Rule, HitPolicy
|
||||
from ..specs.model import Input, InputEntry, Output, OutputEntry
|
||||
from ..engine.DMNEngine import DMNEngine
|
||||
@ -9,7 +27,6 @@ class BaseBusinessRuleTaskConverter(TaskSpecConverter):
|
||||
|
||||
def to_dict(self, spec):
|
||||
dct = self.get_default_attributes(spec)
|
||||
dct.update(self.get_bpmn_attributes(spec))
|
||||
# We only ever use one decision table
|
||||
dct['decision_table'] = self.decision_table_to_dict(spec.dmnEngine.decision_table)
|
||||
return dct
|
||||
@ -95,8 +112,3 @@ class BaseBusinessRuleTaskConverter(TaskSpecConverter):
|
||||
rule.outputEntries = [self.output_entry_from_dict(entry, outputs)
|
||||
for entry in dct['output_entries']]
|
||||
return rule
|
||||
|
||||
|
||||
class BusinessRuleTaskConverter(BaseBusinessRuleTaskConverter):
|
||||
def __init__(self, registry):
|
||||
super().__init__(BusinessRuleTask, registry)
|
@ -1,37 +0,0 @@
|
||||
from SpiffWorkflow.exceptions import WorkflowTaskException, SpiffWorkflowException
|
||||
|
||||
from ...specs.Simple import Simple
|
||||
|
||||
from ...bpmn.specs.BpmnSpecMixin import BpmnSpecMixin
|
||||
from ...util.deep_merge import DeepMerge
|
||||
|
||||
|
||||
class BusinessRuleTask(Simple, BpmnSpecMixin):
|
||||
"""
|
||||
Task Spec for a bpmn:businessTask (DMB Decision Reference) node.
|
||||
"""
|
||||
|
||||
def _on_trigger(self, my_task):
|
||||
pass
|
||||
|
||||
def __init__(self, wf_spec, name, dmnEngine, **kwargs):
|
||||
super().__init__(wf_spec, name, **kwargs)
|
||||
self.dmnEngine = dmnEngine
|
||||
self.resDict = None
|
||||
|
||||
@property
|
||||
def spec_class(self):
|
||||
return 'Business Rule Task'
|
||||
|
||||
def _run_hook(self, my_task):
|
||||
try:
|
||||
my_task.data = DeepMerge.merge(my_task.data, self.dmnEngine.result(my_task))
|
||||
super(BusinessRuleTask, self)._run_hook(my_task)
|
||||
except SpiffWorkflowException as we:
|
||||
we.add_note(f"Business Rule Task '{my_task.task_spec.description}'.")
|
||||
raise we
|
||||
except Exception as e:
|
||||
error = WorkflowTaskException(str(e), task=my_task)
|
||||
error.add_note(f"Business Rule Task '{my_task.task_spec.description}'.")
|
||||
raise error
|
||||
return True
|
@ -0,0 +1,18 @@
|
||||
# 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
|
@ -0,0 +1,49 @@
|
||||
# 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.exceptions import SpiffWorkflowException
|
||||
from SpiffWorkflow.bpmn.exceptions import WorkflowTaskException
|
||||
from SpiffWorkflow.specs.base import TaskSpec
|
||||
from SpiffWorkflow.util.deep_merge import DeepMerge
|
||||
|
||||
|
||||
class BusinessRuleTaskMixin(TaskSpec):
|
||||
"""Task Spec for a bpmn:businessTask (DMB Decision Reference) node."""
|
||||
|
||||
def __init__(self, wf_spec, name, dmnEngine, **kwargs):
|
||||
super().__init__(wf_spec, name, **kwargs)
|
||||
self.dmnEngine = dmnEngine
|
||||
self.resDict = None
|
||||
|
||||
@property
|
||||
def spec_class(self):
|
||||
return 'Business Rule Task'
|
||||
|
||||
def _run_hook(self, my_task):
|
||||
try:
|
||||
my_task.data = DeepMerge.merge(my_task.data, self.dmnEngine.result(my_task))
|
||||
super(BusinessRuleTaskMixin, self)._run_hook(my_task)
|
||||
except SpiffWorkflowException as we:
|
||||
we.add_note(f"Business Rule Task '{my_task.task_spec.bpmn_name}'.")
|
||||
raise we
|
||||
except Exception as e:
|
||||
error = WorkflowTaskException(str(e), task=my_task)
|
||||
error.add_note(f"Business Rule Task '{my_task.task_spec.bpmn_name}'.")
|
||||
raise error
|
||||
return True
|
@ -1,3 +1,22 @@
|
||||
# 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 collections import OrderedDict
|
||||
from enum import Enum
|
||||
|
||||
|
@ -1,12 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (C) 2007 Samuel Abels
|
||||
# Copyright (C) 2007 Samuel Abels, 2023 Sartography
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# 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 2.1 of the License, or (at your option) any later version.
|
||||
# version 3.0 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# 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.
|
||||
@ -15,10 +16,6 @@
|
||||
# License along with this library; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
# 02110-1301 USA
|
||||
import re
|
||||
|
||||
from SpiffWorkflow.util import levenshtein
|
||||
|
||||
|
||||
class SpiffWorkflowException(Exception):
|
||||
"""
|
||||
@ -55,81 +52,6 @@ class WorkflowException(SpiffWorkflowException):
|
||||
# Points to the TaskSpec that generated the exception.
|
||||
self.task_spec = task_spec
|
||||
|
||||
@staticmethod
|
||||
def get_task_trace(task):
|
||||
task_trace = [f"{task.task_spec.description} ({task.workflow.spec.file})"]
|
||||
workflow = task.workflow
|
||||
while workflow != workflow.outer_workflow:
|
||||
caller = workflow.name
|
||||
workflow = workflow.outer_workflow
|
||||
task_trace.append(f"{workflow.spec.task_specs[caller].description} ({workflow.spec.file})")
|
||||
return task_trace
|
||||
|
||||
@staticmethod
|
||||
def did_you_mean_from_name_error(name_exception, options):
|
||||
"""Returns a string along the lines of 'did you mean 'dog'? Given
|
||||
a name_error, and a set of possible things that could have been called,
|
||||
or an empty string if no valid suggestions come up. """
|
||||
if isinstance(name_exception, NameError):
|
||||
def_match = re.match("name '(.+)' is not defined", str(name_exception))
|
||||
if def_match:
|
||||
bad_variable = re.match("name '(.+)' is not defined",
|
||||
str(name_exception)).group(1)
|
||||
most_similar = levenshtein.most_similar(bad_variable,
|
||||
options, 3)
|
||||
error_msg = ""
|
||||
if len(most_similar) == 1:
|
||||
error_msg += f' Did you mean \'{most_similar[0]}\'?'
|
||||
if len(most_similar) > 1:
|
||||
error_msg += f' Did you mean one of \'{most_similar}\'?'
|
||||
return error_msg
|
||||
|
||||
|
||||
class WorkflowTaskException(WorkflowException):
|
||||
"""WorkflowException that provides task_trace information."""
|
||||
|
||||
def __init__(self, error_msg, task=None, exception=None,
|
||||
line_number=None, offset=None, error_line=None):
|
||||
"""
|
||||
Exception initialization.
|
||||
|
||||
:param task: the task that threw the exception
|
||||
:type task: Task
|
||||
:param error_msg: a human readable error message
|
||||
:type error_msg: str
|
||||
:param exception: an exception to wrap, if any
|
||||
:type exception: Exception
|
||||
"""
|
||||
|
||||
self.task = task
|
||||
self.line_number = line_number
|
||||
self.offset = offset
|
||||
self.error_line = error_line
|
||||
if exception:
|
||||
self.error_type = exception.__class__.__name__
|
||||
else:
|
||||
self.error_type = "unknown"
|
||||
super().__init__(error_msg, task_spec=task.task_spec)
|
||||
|
||||
if isinstance(exception, SyntaxError) and not line_number:
|
||||
# Line number and offset can be recovered directly from syntax errors,
|
||||
# otherwise they must be passed in.
|
||||
self.line_number = exception.lineno
|
||||
self.offset = exception.offset
|
||||
elif isinstance(exception, NameError):
|
||||
self.add_note(self.did_you_mean_from_name_error(exception, list(task.data.keys())))
|
||||
|
||||
# If encountered in a sub-workflow, this traces back up the stack,
|
||||
# so we can tell how we got to this particular task, no matter how
|
||||
# deeply nested in sub-workflows it is. Takes the form of:
|
||||
# task-description (file-name)
|
||||
self.task_trace = self.get_task_trace(task)
|
||||
|
||||
|
||||
|
||||
class StorageException(SpiffWorkflowException):
|
||||
pass
|
||||
|
||||
|
||||
class TaskNotFoundException(WorkflowException):
|
||||
pass
|
||||
|
@ -1,13 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2007 Samuel Abels
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# 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 2.1 of the License, or (at your option) any later version.
|
||||
# version 3.0 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# 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.
|
||||
@ -16,6 +16,7 @@
|
||||
# License along with this library; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
# 02110-1301 USA
|
||||
|
||||
import logging
|
||||
import re
|
||||
|
||||
@ -198,8 +199,8 @@ def valueof(scope, op, default=None):
|
||||
|
||||
def is_number(text):
|
||||
try:
|
||||
x = int(text)
|
||||
except:
|
||||
int(text)
|
||||
except Exception:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
@ -0,0 +1,16 @@
|
||||
# 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
|
@ -1,12 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from builtins import object
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# 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 2.1 of the License, or (at your option) any later version.
|
||||
# version 3.0 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# 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.
|
||||
@ -20,7 +19,6 @@ from .. import operators
|
||||
from ..specs.AcquireMutex import AcquireMutex
|
||||
from ..specs.Cancel import Cancel
|
||||
from ..specs.CancelTask import CancelTask
|
||||
from ..specs.Celery import Celery
|
||||
from ..specs.Choose import Choose
|
||||
from ..specs.ExclusiveChoice import ExclusiveChoice
|
||||
from ..specs.Execute import Execute
|
||||
@ -46,7 +44,6 @@ def spec_map():
|
||||
'acquire-mutex': AcquireMutex,
|
||||
'cancel': Cancel,
|
||||
'cancel-task': CancelTask,
|
||||
'celery': Celery,
|
||||
'choose': Choose,
|
||||
'exclusive-choice': ExclusiveChoice,
|
||||
'execute': Execute,
|
||||
|
@ -1,14 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
import json
|
||||
from builtins import str
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# 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 2.1 of the License, or (at your option) any later version.
|
||||
# version 3.0 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# 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.
|
||||
@ -17,17 +14,19 @@ from builtins import str
|
||||
# License along with this library; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
# 02110-1301 USA
|
||||
|
||||
import json
|
||||
|
||||
import pickle
|
||||
from base64 import b64encode, b64decode
|
||||
from ..workflow import Workflow
|
||||
from ..util.impl import get_class
|
||||
from ..task import Task, TaskState
|
||||
from ..operators import (Attrib, PathAttrib, Equal, NotEqual, Operator, GreaterThan, LessThan, Match)
|
||||
from ..operators import Attrib, PathAttrib, Equal, NotEqual, Operator, GreaterThan, LessThan, Match
|
||||
from ..specs.base import TaskSpec
|
||||
from ..specs.AcquireMutex import AcquireMutex
|
||||
from ..specs.Cancel import Cancel
|
||||
from ..specs.CancelTask import CancelTask
|
||||
from ..specs.Celery import Celery
|
||||
from ..specs.Choose import Choose
|
||||
from ..specs.ExclusiveChoice import ExclusiveChoice
|
||||
from ..specs.Execute import Execute
|
||||
@ -51,25 +50,17 @@ import warnings
|
||||
|
||||
class DictionarySerializer(Serializer):
|
||||
|
||||
def __init__(self):
|
||||
# When deserializing, this is a set of specs for sub-workflows.
|
||||
# This prevents us from serializing a copy of the same spec many
|
||||
# times, which can create very large files.
|
||||
self.SPEC_STATES = {}
|
||||
|
||||
def serialize_dict(self, thedict):
|
||||
return dict(
|
||||
(str(k), b64encode(pickle.dumps(v,
|
||||
protocol=pickle.HIGHEST_PROTOCOL)))
|
||||
for k, v in list(thedict.items()))
|
||||
(str(k), b64encode(pickle.dumps(v, protocol=pickle.HIGHEST_PROTOCOL)))
|
||||
for k, v in list(thedict.items())
|
||||
)
|
||||
|
||||
def deserialize_dict(self, s_state):
|
||||
return dict((k, pickle.loads(b64decode(v)))
|
||||
for k, v in list(s_state.items()))
|
||||
return dict((k, pickle.loads(b64decode(v))) for k, v in list(s_state.items()))
|
||||
|
||||
def serialize_list(self, thelist):
|
||||
return [b64encode(pickle.dumps(v, protocol=pickle.HIGHEST_PROTOCOL))
|
||||
for v in thelist]
|
||||
return [b64encode(pickle.dumps(v, protocol=pickle.HIGHEST_PROTOCOL)) for v in thelist]
|
||||
|
||||
def deserialize_list(self, s_state):
|
||||
return [pickle.loads(b64decode(v)) for v in s_state]
|
||||
@ -149,48 +140,34 @@ class DictionarySerializer(Serializer):
|
||||
return ret
|
||||
|
||||
def serialize_task_spec(self, spec):
|
||||
s_state = dict(id=spec.id,
|
||||
name=spec.name,
|
||||
s_state = dict(name=spec.name,
|
||||
description=spec.description,
|
||||
manual=spec.manual,
|
||||
internal=spec.internal,
|
||||
lookahead=spec.lookahead)
|
||||
module_name = spec.__class__.__module__
|
||||
s_state['class'] = module_name + '.' + spec.__class__.__name__
|
||||
s_state['inputs'] = [t.id for t in spec.inputs]
|
||||
s_state['outputs'] = [t.id for t in spec.outputs]
|
||||
s_state['inputs'] = [t.name for t in spec.inputs]
|
||||
s_state['outputs'] = [t.name for t in spec.outputs]
|
||||
s_state['data'] = self.serialize_dict(spec.data)
|
||||
if hasattr(spec, 'position'):
|
||||
s_state['position'] = self.serialize_dict(spec.position)
|
||||
|
||||
s_state['defines'] = self.serialize_dict(spec.defines)
|
||||
s_state['pre_assign'] = self.serialize_list(spec.pre_assign)
|
||||
s_state['post_assign'] = self.serialize_list(spec.post_assign)
|
||||
|
||||
# Note: Events are not serialized; this is documented in
|
||||
# the TaskSpec API docs.
|
||||
|
||||
return s_state
|
||||
|
||||
def deserialize_task_spec(self, wf_spec, s_state, spec):
|
||||
spec.id = s_state.get('id', None)
|
||||
spec.description = s_state.get('description', '')
|
||||
spec.manual = s_state.get('manual', False)
|
||||
spec.internal = s_state.get('internal', False)
|
||||
spec.lookahead = s_state.get('lookahead', 2)
|
||||
|
||||
spec.data = self.deserialize_dict(s_state.get('data', {}))
|
||||
if 'position' in s_state.keys():
|
||||
spec.position = self.deserialize_dict(s_state.get('position', {}))
|
||||
spec.defines = self.deserialize_dict(s_state.get('defines', {}))
|
||||
spec.pre_assign = self.deserialize_list(s_state.get('pre_assign', []))
|
||||
spec.post_assign = self.deserialize_list(
|
||||
s_state.get('post_assign', []))
|
||||
spec.post_assign = self.deserialize_list(s_state.get('post_assign', []))
|
||||
# We can't restore inputs and outputs yet because they may not be
|
||||
# deserialized yet. So keep the names, and resolve them in the end.
|
||||
spec.inputs = s_state.get('inputs', [])[:]
|
||||
spec.outputs = s_state.get('outputs', [])[:]
|
||||
|
||||
return spec
|
||||
|
||||
def serialize_acquire_mutex(self, spec):
|
||||
@ -210,8 +187,7 @@ class DictionarySerializer(Serializer):
|
||||
return s_state
|
||||
|
||||
def deserialize_cancel(self, wf_spec, s_state):
|
||||
spec = Cancel(wf_spec, s_state['name'],
|
||||
success=s_state.get('cancel_successfully', False))
|
||||
spec = Cancel(wf_spec, s_state['name'], success=s_state.get('cancel_successfully', False))
|
||||
self.deserialize_task_spec(wf_spec, s_state, spec=spec)
|
||||
return spec
|
||||
|
||||
@ -226,26 +202,6 @@ class DictionarySerializer(Serializer):
|
||||
self.deserialize_task_spec(wf_spec, s_state, spec=spec)
|
||||
return spec
|
||||
|
||||
def serialize_celery(self, spec):
|
||||
args = self.serialize_list(spec.args)
|
||||
kwargs = self.serialize_dict(spec.kwargs)
|
||||
s_state = self.serialize_task_spec(spec)
|
||||
s_state['call'] = spec.call
|
||||
s_state['args'] = args
|
||||
s_state['kwargs'] = kwargs
|
||||
s_state['result_key'] = spec.result_key
|
||||
return s_state
|
||||
|
||||
def deserialize_celery(self, wf_spec, s_state):
|
||||
args = self.deserialize_list(s_state['args'])
|
||||
kwargs = self.deserialize_dict(s_state.get('kwargs', {}))
|
||||
spec = Celery(wf_spec, s_state['name'], s_state['call'],
|
||||
call_args=args,
|
||||
result_key=s_state['result_key'],
|
||||
**kwargs)
|
||||
self.deserialize_task_spec(wf_spec, s_state, spec)
|
||||
return spec
|
||||
|
||||
def serialize_choose(self, spec):
|
||||
s_state = self.serialize_task_spec(spec)
|
||||
s_state['context'] = spec.context
|
||||
@ -305,12 +261,12 @@ class DictionarySerializer(Serializer):
|
||||
s_state['cancel_remaining'] = spec.cancel_remaining
|
||||
return s_state
|
||||
|
||||
def deserialize_join(self, wf_spec, s_state, cls=Join):
|
||||
def deserialize_join(self, wf_spec, s_state):
|
||||
if isinstance(s_state['threshold'],dict):
|
||||
byte_payload = s_state['threshold']['__bytes__']
|
||||
else:
|
||||
byte_payload = s_state['threshold']
|
||||
spec = cls(wf_spec,
|
||||
spec = Join(wf_spec,
|
||||
s_state['name'],
|
||||
split_task=s_state['split_task'],
|
||||
threshold=pickle.loads(b64decode(byte_payload)),
|
||||
@ -347,36 +303,25 @@ class DictionarySerializer(Serializer):
|
||||
s_state = self.serialize_task_spec(spec)
|
||||
# here we need to add in all of the things that would get serialized
|
||||
# for other classes that the MultiInstance could be -
|
||||
#
|
||||
|
||||
if isinstance(spec, SubWorkflow):
|
||||
br_state = self.serialize_sub_workflow(spec)
|
||||
s_state['file'] = br_state['file']
|
||||
s_state['in_assign'] = br_state['in_assign']
|
||||
s_state['out_assign'] = br_state['out_assign']
|
||||
|
||||
s_state['times'] = self.serialize_arg(spec.times)
|
||||
s_state['prevtaskclass'] = spec.prevtaskclass
|
||||
return s_state
|
||||
|
||||
def deserialize_multi_instance(self, wf_spec, s_state, cls=None):
|
||||
if cls == None:
|
||||
cls = MultiInstance(wf_spec,
|
||||
s_state['name'],
|
||||
times=self.deserialize_arg(s_state['times']))
|
||||
if isinstance(s_state['times'],list):
|
||||
s_state['times'] = self.deserialize_arg(s_state['times'])
|
||||
cls.times = s_state['times']
|
||||
if isinstance(cls, SubWorkflow):
|
||||
def deserialize_multi_instance(self, wf_spec, s_state):
|
||||
spec = MultiInstance(wf_spec, s_state['name'], times=self.deserialize_arg(s_state['times']))
|
||||
if isinstance(spec, SubWorkflow):
|
||||
if s_state.get('file'):
|
||||
cls.file = self.deserialize_arg(s_state['file'])
|
||||
spec.file = self.deserialize_arg(s_state['file'])
|
||||
else:
|
||||
cls.file = None
|
||||
cls.in_assign = self.deserialize_list(s_state['in_assign'])
|
||||
cls.out_assign = self.deserialize_list(s_state['out_assign'])
|
||||
|
||||
self.deserialize_task_spec(wf_spec, s_state, spec=cls)
|
||||
return cls
|
||||
spec.file = None
|
||||
spec.in_assign = self.deserialize_list(s_state['in_assign'])
|
||||
spec.out_assign = self.deserialize_list(s_state['out_assign'])
|
||||
self.deserialize_task_spec(wf_spec, s_state, spec=spec)
|
||||
return spec
|
||||
|
||||
def serialize_release_mutex(self, spec):
|
||||
s_state = self.serialize_task_spec(spec)
|
||||
@ -398,7 +343,6 @@ class DictionarySerializer(Serializer):
|
||||
self.deserialize_task_spec(wf_spec, s_state, spec=spec)
|
||||
return spec
|
||||
|
||||
|
||||
def deserialize_generic(self, wf_spec, s_state,newclass):
|
||||
assert isinstance(wf_spec, WorkflowSpec)
|
||||
spec = newclass(wf_spec, s_state['name'])
|
||||
@ -486,70 +430,32 @@ class DictionarySerializer(Serializer):
|
||||
return spec
|
||||
|
||||
def serialize_workflow_spec(self, spec, **kwargs):
|
||||
s_state = dict(name=spec.name,
|
||||
description=spec.description,
|
||||
file=spec.file)
|
||||
|
||||
if 'Root' not in spec.task_specs:
|
||||
# This is to fix up the case when we
|
||||
# load in a task spec and there is no root object.
|
||||
# it causes problems when we deserialize and then re-serialize
|
||||
# because the deserialize process adds a root.
|
||||
root = Simple(spec, 'Root')
|
||||
spec.task_specs['Root'] = root
|
||||
|
||||
mylist = [(k, v.serialize(self)) for k, v in list(spec.task_specs.items())]
|
||||
|
||||
# As we serialize back up, keep only one copy of any sub_workflow
|
||||
s_state['sub_workflows'] = {}
|
||||
for name, task in mylist:
|
||||
if 'spec' in task:
|
||||
spec = json.loads(task['spec'])
|
||||
if 'sub_workflows' in spec:
|
||||
s_state['sub_workflows'].update(spec['sub_workflows'])
|
||||
del spec['sub_workflows']
|
||||
if spec['name'] not in s_state['sub_workflows']:
|
||||
s_state['sub_workflows'][spec['name']] = json.dumps(spec)
|
||||
task['spec_name'] = spec['name']
|
||||
del task['spec']
|
||||
|
||||
if hasattr(spec,'end'):
|
||||
s_state['end']=spec.end.id
|
||||
s_state['task_specs'] = dict(mylist)
|
||||
s_state = dict(name=spec.name, description=spec.description, file=spec.file)
|
||||
s_state['task_specs'] = dict(
|
||||
(k, v.serialize(self))
|
||||
for k, v in list(spec.task_specs.items())
|
||||
)
|
||||
return s_state
|
||||
|
||||
def _deserialize_workflow_spec_task_spec(self, spec, task_spec, name):
|
||||
task_spec.inputs = [spec.get_task_spec_from_id(t) for t in task_spec.inputs]
|
||||
task_spec.outputs = [spec.get_task_spec_from_id(t) for t in task_spec.outputs]
|
||||
|
||||
def _prevtaskclass_bases(self, oldtask):
|
||||
return (oldtask)
|
||||
task_spec.inputs = [spec.get_task_spec_from_name(t) for t in task_spec.inputs]
|
||||
task_spec.outputs = [spec.get_task_spec_from_name(t) for t in task_spec.outputs]
|
||||
|
||||
def deserialize_workflow_spec(self, s_state, **kwargs):
|
||||
spec = WorkflowSpec(s_state['name'], filename=s_state['file'])
|
||||
spec.description = s_state['description']
|
||||
|
||||
# Handle Start Task
|
||||
spec.start = None
|
||||
|
||||
# Store all sub-workflows so they can be referenced.
|
||||
if 'sub_workflows' in s_state:
|
||||
# Hate the whole json dumps thing, why do we do this?
|
||||
self.SPEC_STATES.update(s_state['sub_workflows'])
|
||||
|
||||
del spec.task_specs['Start']
|
||||
start_task_spec_state = s_state['task_specs']['Start']
|
||||
start_task_spec = StartTask.deserialize(self, spec, start_task_spec_state)
|
||||
spec.start = start_task_spec
|
||||
|
||||
spec.task_specs['Start'] = start_task_spec
|
||||
for name, task_spec_state in list(s_state['task_specs'].items()):
|
||||
if name == 'Start':
|
||||
continue
|
||||
prevtask = task_spec_state.get('prevtaskclass', None)
|
||||
if prevtask:
|
||||
oldtask = get_class(prevtask)
|
||||
task_spec_cls = type(task_spec_state['class'],
|
||||
self._prevtaskclass_bases(oldtask), {})
|
||||
else:
|
||||
task_spec_cls = get_class(task_spec_state['class'])
|
||||
task_spec = task_spec_cls.deserialize(self, spec, task_spec_state)
|
||||
spec.task_specs[name] = task_spec
|
||||
@ -558,7 +464,7 @@ class DictionarySerializer(Serializer):
|
||||
self._deserialize_workflow_spec_task_spec(spec, task_spec, name)
|
||||
|
||||
if s_state.get('end', None):
|
||||
spec.end = spec.get_task_spec_from_id(s_state['end'])
|
||||
spec.end = spec.get_task_spec_from_name(s_state['end'])
|
||||
|
||||
assert spec.start is spec.get_task_spec_from_name('Start')
|
||||
return spec
|
||||
@ -578,15 +484,11 @@ class DictionarySerializer(Serializer):
|
||||
|
||||
return s_state
|
||||
|
||||
def deserialize_workflow(self, s_state, wf_class=Workflow, wf_spec=None, **kwargs):
|
||||
def deserialize_workflow(self, s_state, wf_class=Workflow, **kwargs):
|
||||
"""It is possible to override the workflow class, and specify a
|
||||
workflow_spec, otherwise the spec is assumed to be serialized in the
|
||||
s_state['wf_spec']"""
|
||||
|
||||
if wf_spec is None:
|
||||
# The json serializer serializes the spec as a string and then serializes it again, hence this check
|
||||
# I'm not confident that this is going to actually work, but this serializer is so fundamentally flawed
|
||||
# that I'm not going to put the effort in to be sure this works.
|
||||
if isinstance(s_state['wf_spec'], str):
|
||||
spec_dct = json.loads(s_state['wf_spec'])
|
||||
else:
|
||||
@ -595,8 +497,6 @@ class DictionarySerializer(Serializer):
|
||||
for name in reset_specs:
|
||||
s_state['wf_spec']['task_specs'].pop(name)
|
||||
wf_spec = self.deserialize_workflow_spec(s_state['wf_spec'], **kwargs)
|
||||
else:
|
||||
reset_specs = []
|
||||
|
||||
workflow = wf_class(wf_spec)
|
||||
workflow.data = self.deserialize_dict(s_state['data'])
|
||||
@ -623,35 +523,24 @@ class DictionarySerializer(Serializer):
|
||||
|
||||
return workflow
|
||||
|
||||
def serialize_task(self, task, skip_children=False, allow_subs=False):
|
||||
"""
|
||||
:param allow_subs: Allows sub-serialization to take place, otherwise
|
||||
assumes that the subworkflow is stored in internal data and raises an error.
|
||||
"""
|
||||
|
||||
def serialize_task(self, task, skip_children=False):
|
||||
assert isinstance(task, Task)
|
||||
|
||||
# Please note, the BPMN Serializer DOES allow sub-workflows. This is
|
||||
# for backwards compatibility and support of the original parsers.
|
||||
if not allow_subs and isinstance(task.task_spec, SubWorkflow):
|
||||
if isinstance(task.task_spec, SubWorkflow):
|
||||
raise TaskNotSupportedError(
|
||||
"Subworkflow tasks cannot be serialized (due to their use of" +
|
||||
" internal_data to store the subworkflow).")
|
||||
|
||||
s_state = dict()
|
||||
s_state['id'] = task.id
|
||||
s_state['workflow_name'] = task.workflow.name
|
||||
s_state['parent'] = task.parent.id if task.parent is not None else None
|
||||
if not skip_children:
|
||||
s_state['children'] = [
|
||||
self.serialize_task(child) for child in task.children]
|
||||
s_state['children'] = [self.serialize_task(child) for child in task.children]
|
||||
s_state['state'] = task.state
|
||||
s_state['triggered'] = task.triggered
|
||||
s_state['task_spec'] = task.task_spec.name
|
||||
s_state['last_state_change'] = task.last_state_change
|
||||
s_state['data'] = self.serialize_dict(task.data)
|
||||
s_state['internal_data'] = task.internal_data
|
||||
|
||||
return s_state
|
||||
|
||||
def deserialize_task(self, workflow, s_state, ignored_specs=None):
|
||||
@ -664,9 +553,6 @@ class DictionarySerializer(Serializer):
|
||||
raise MissingSpecError("Unknown task spec: " + old_spec_name)
|
||||
task = Task(workflow, task_spec)
|
||||
|
||||
if getattr(task_spec,'isSequential',False) and s_state['internal_data'].get('splits') is not None:
|
||||
task.task_spec.expanded = s_state['internal_data']['splits']
|
||||
|
||||
task.id = s_state['id']
|
||||
# as the task_tree might not be complete yet
|
||||
# keep the ids so they can be processed at the end
|
||||
|
@ -1,11 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# 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 2.1 of the License, or (at your option) any later version.
|
||||
# version 3.0 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# 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.
|
||||
|
@ -1,3 +1,20 @@
|
||||
# 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
|
||||
|
||||
class TaskSpecNotSupportedError(ValueError):
|
||||
pass
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# 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 2.1 of the License, or (at your option) any later version.
|
||||
# version 3.0 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# 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.
|
||||
@ -14,6 +14,7 @@
|
||||
# License along with this library; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
# 02110-1301 USA
|
||||
|
||||
import json
|
||||
import uuid
|
||||
from ..operators import Attrib
|
||||
|
@ -1,13 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2007-2012 Samuel Abels
|
||||
# Copyright (C) 2007-2012 Samuel Abels, 2023 Sartography
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# 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 2.1 of the License, or (at your option) any later version.
|
||||
# version 3.0 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# 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.
|
||||
@ -16,6 +16,7 @@
|
||||
# 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 .. import operators
|
||||
from ..specs.Simple import Simple
|
||||
from ..specs.WorkflowSpec import WorkflowSpec
|
||||
|
@ -1,12 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from builtins import str
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# 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 2.1 of the License, or (at your option) any later version.
|
||||
# version 3.0 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# 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.
|
||||
@ -15,6 +14,7 @@ from builtins import str
|
||||
# License along with this library; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
# 02110-1301 USA
|
||||
|
||||
import warnings
|
||||
from lxml import etree
|
||||
from lxml.etree import SubElement
|
||||
@ -24,7 +24,6 @@ from ..operators import (Attrib, Assign, PathAttrib, Equal, NotEqual, GreaterTha
|
||||
from ..specs.AcquireMutex import AcquireMutex
|
||||
from ..specs.Cancel import Cancel
|
||||
from ..specs.CancelTask import CancelTask
|
||||
from ..specs.Celery import Celery
|
||||
from ..specs.Choose import Choose
|
||||
from ..specs.ExclusiveChoice import ExclusiveChoice
|
||||
from ..specs.Execute import Execute
|
||||
@ -287,15 +286,11 @@ class XmlSerializer(Serializer):
|
||||
"""
|
||||
Serializes common attributes of :meth:`SpiffWorkflow.specs.TaskSpec`.
|
||||
"""
|
||||
if spec.id is not None:
|
||||
SubElement(elem, 'id').text = str(spec.id)
|
||||
SubElement(elem, 'name').text = spec.name
|
||||
if spec.description:
|
||||
SubElement(elem, 'description').text = spec.description
|
||||
if spec.manual:
|
||||
SubElement(elem, 'manual')
|
||||
if spec.internal:
|
||||
SubElement(elem, 'internal')
|
||||
SubElement(elem, 'lookahead').text = str(spec.lookahead)
|
||||
inputs = [t.name for t in spec.inputs]
|
||||
outputs = [t.name for t in spec.outputs]
|
||||
@ -316,11 +311,8 @@ class XmlSerializer(Serializer):
|
||||
def deserialize_task_spec(self, wf_spec, elem, spec_cls, **kwargs):
|
||||
name = elem.findtext('name')
|
||||
spec = spec_cls(wf_spec, name, **kwargs)
|
||||
theid = elem.findtext('id')
|
||||
spec.id = theid if theid is not None else None
|
||||
spec.description = elem.findtext('description', spec.description)
|
||||
spec.manual = elem.findtext('manual', spec.manual)
|
||||
spec.internal = elem.find('internal') is not None
|
||||
spec.lookahead = int(elem.findtext('lookahead', spec.lookahead))
|
||||
|
||||
data_elem = elem.find('data')
|
||||
@ -384,37 +376,6 @@ class XmlSerializer(Serializer):
|
||||
def deserialize_cancel_task(self, wf_spec, elem, cls=CancelTask, **kwargs):
|
||||
return self.deserialize_trigger(wf_spec, elem, cls, **kwargs)
|
||||
|
||||
def serialize_celery(self, spec, elem=None):
|
||||
if elem is None:
|
||||
elem = etree.Element('celery')
|
||||
|
||||
SubElement(elem, 'call').text = spec.call
|
||||
args_elem = SubElement(elem, 'args')
|
||||
self.serialize_value_list(args_elem, spec.args)
|
||||
kwargs_elem = SubElement(elem, 'kwargs')
|
||||
self.serialize_value_map(kwargs_elem, spec.kwargs)
|
||||
if spec.merge_results:
|
||||
SubElement(elem, 'merge-results')
|
||||
SubElement(elem, 'result-key').text = spec.result_key
|
||||
|
||||
return self.serialize_task_spec(spec, elem)
|
||||
|
||||
def deserialize_celery(self, wf_spec, elem, cls=Celery, **kwargs):
|
||||
call = elem.findtext('call')
|
||||
args = self.deserialize_value_list(elem.find('args'))
|
||||
result_key = elem.findtext('call')
|
||||
merge_results = elem.find('merge-results') is not None
|
||||
spec = self.deserialize_task_spec(wf_spec,
|
||||
elem,
|
||||
cls,
|
||||
call=call,
|
||||
call_args=args,
|
||||
result_key=result_key,
|
||||
merge_results=merge_results,
|
||||
**kwargs)
|
||||
spec.kwargs = self.deserialize_value_map(elem.find('kwargs'))
|
||||
return spec
|
||||
|
||||
def serialize_choose(self, spec, elem=None):
|
||||
if elem is None:
|
||||
elem = etree.Element('choose')
|
||||
@ -526,7 +487,7 @@ class XmlSerializer(Serializer):
|
||||
|
||||
def deserialize_multi_instance(self, wf_spec, elem, cls=None,
|
||||
**kwargs):
|
||||
if cls == None:
|
||||
if cls is None:
|
||||
cls = MultiInstance
|
||||
#cls = MultiInstance(wf_spec,elem.find('name'),elem.find('times'))
|
||||
times = self.deserialize_value(elem.find('times'))
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user