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
|
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
|
is managed through GitHub's actions. The configuration of which can be
|
||||||
found in .github/workflows/....
|
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
|
## Build status
|
||||||
[](https://travis-ci.org/sartography/SpiffWorkflow)
|
[](https://travis-ci.org/sartography/SpiffWorkflow)
|
||||||
[](https://sonarcloud.io/dashboard?id=sartography_SpiffWorkflow)
|
[](https://github.com/sartography/SpiffWorkflow/actions/workflows/tests.yaml)
|
||||||
[](https://sonarcloud.io/dashboard?id=sartography_SpiffWorkflow)
|
|
||||||
[](https://sonarcloud.io/dashboard?id=sartography_SpiffWorkflow)
|
|
||||||
[](http://spiffworkflow.readthedocs.io/en/latest/?badge=latest)
|
[](http://spiffworkflow.readthedocs.io/en/latest/?badge=latest)
|
||||||
[](https://github.com/sartography/SpiffWorkflow/issues)
|
[](https://github.com/sartography/SpiffWorkflow/issues)
|
||||||
[](https://github.com/sartography/SpiffWorkflow/pulls)
|
[](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 -*-
|
# Copyright (C) 2020 Kelly McDonald, 2023 Sartography
|
||||||
|
|
||||||
import re
|
|
||||||
import datetime
|
|
||||||
import operator
|
|
||||||
from datetime import timedelta
|
|
||||||
from decimal import Decimal
|
|
||||||
from .PythonScriptEngine import PythonScriptEngine
|
|
||||||
|
|
||||||
# Copyright (C) 2020 Kelly McDonald
|
|
||||||
#
|
#
|
||||||
# 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
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
# License as published by the Free Software Foundation; either
|
# 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
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
# Lesser General Public License for more details.
|
# Lesser General Public License for more details.
|
||||||
@ -24,6 +17,13 @@ from .PythonScriptEngine import PythonScriptEngine
|
|||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||||
# 02110-1301 USA
|
# 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):
|
def feelConvertTime(datestr,parsestr):
|
||||||
return datetime.datetime.strptime(datestr,parsestr)
|
return datetime.datetime.strptime(datestr,parsestr)
|
||||||
@ -79,8 +79,8 @@ class FeelNot():
|
|||||||
|
|
||||||
def feelConcatenate(*lst):
|
def feelConcatenate(*lst):
|
||||||
ilist = []
|
ilist = []
|
||||||
for l in lst:
|
for list_item in lst:
|
||||||
ilist = ilist + l
|
ilist = ilist + list_item
|
||||||
return ilist
|
return ilist
|
||||||
|
|
||||||
def feelAppend(lst,item):
|
def feelAppend(lst,item):
|
||||||
@ -144,7 +144,7 @@ def feelFilter(var,a,b,op,column=None):
|
|||||||
newvar.append({'key':key,'value':var[key]})
|
newvar.append({'key':key,'value':var[key]})
|
||||||
var = newvar
|
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)]
|
return [x.get(column) for x in var if opmap[op](x.get(a), b)]
|
||||||
else:
|
else:
|
||||||
return [x for x in var if opmap[op](x.get(a), b)]
|
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:
|
if external_methods is None:
|
||||||
external_methods = {}
|
external_methods = {}
|
||||||
external_methods.update(externalFuncs)
|
external_methods.update(externalFuncs)
|
||||||
super(PythonScriptEngine).execute(task, script, external_methods)
|
super().execute(task, script, external_methods)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,21 +1,13 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# Copyright (C) 2020 Kelly McDonald, 2023 Sartography
|
||||||
import ast
|
|
||||||
import sys
|
|
||||||
import traceback
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
from .PythonScriptEngineEnvironment import TaskDataEnvironment
|
|
||||||
from ..exceptions import SpiffWorkflowException, WorkflowTaskException
|
|
||||||
|
|
||||||
|
|
||||||
# Copyright (C) 2020 Kelly McDonald
|
|
||||||
#
|
#
|
||||||
# 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
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
# License as published by the Free Software Foundation; either
|
# 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
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
# Lesser General Public License for more details.
|
# 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
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||||
# 02110-1301 USA
|
# 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):
|
class PythonScriptEngine(object):
|
||||||
"""
|
"""
|
||||||
@ -40,8 +41,8 @@ class PythonScriptEngine(object):
|
|||||||
def __init__(self, default_globals=None, scripting_additions=None, environment=None):
|
def __init__(self, default_globals=None, scripting_additions=None, environment=None):
|
||||||
|
|
||||||
if default_globals is not None or scripting_additions is not None:
|
if default_globals is not None or scripting_additions is not None:
|
||||||
warnings.warn(f'default_globals and scripting_additions are deprecated. '
|
warnings.warn('default_globals and scripting_additions are deprecated. '
|
||||||
f'Please provide an environment such as TaskDataEnvrionment',
|
'Please provide an environment such as TaskDataEnvrionment',
|
||||||
DeprecationWarning, stacklevel=2)
|
DeprecationWarning, stacklevel=2)
|
||||||
|
|
||||||
if environment is None:
|
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 copy
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
@ -96,7 +115,7 @@ class Box(dict):
|
|||||||
def __getattr__(self, attr):
|
def __getattr__(self, attr):
|
||||||
try:
|
try:
|
||||||
output = self[attr]
|
output = self[attr]
|
||||||
except:
|
except Exception:
|
||||||
raise AttributeError(
|
raise AttributeError(
|
||||||
"Dictionary has no attribute '%s' " % str(attr))
|
"Dictionary has no attribute '%s' " % str(attr))
|
||||||
return output
|
return output
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# Copyright (C) 2012 Matthew Hampton, 2023 Sartography
|
||||||
# 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
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
# License as published by the Free Software Foundation; either
|
# 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
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
# Lesser General Public License for more details.
|
# Lesser General Public License for more details.
|
||||||
@ -14,4 +15,4 @@
|
|||||||
# You should have received a copy of the GNU Lesser General Public
|
# You should have received a copy of the GNU Lesser General Public
|
||||||
# License along with this library; if not, write to the Free Software
|
# License along with this library; if not, write to the Free Software
|
||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||||
# 02110-1301 USA
|
# 02110-1301 USA
|
@ -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):
|
class WorkflowDataException(WorkflowTaskException):
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# Copyright (C) 2012 Matthew Hampton
|
# 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
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
# License as published by the Free Software Foundation; either
|
# 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
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
# Lesser General Public License for more details.
|
# Lesser General Public License for more details.
|
||||||
@ -23,26 +23,36 @@ import os
|
|||||||
from lxml import etree
|
from lxml import etree
|
||||||
from lxml.etree import LxmlError
|
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 .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 .ProcessParser import ProcessParser
|
||||||
from .node_parser import DEFAULT_NSMAP
|
from .node_parser import DEFAULT_NSMAP
|
||||||
|
from .spec_description import SPEC_DESCRIPTIONS
|
||||||
from .util import full_tag, xpath_eval, first
|
from .util import full_tag, xpath_eval, first
|
||||||
from .TaskParser import TaskParser
|
from .TaskParser import TaskParser
|
||||||
from .task_parsers import (
|
from .task_parsers import (
|
||||||
@ -86,7 +96,7 @@ class BpmnValidator:
|
|||||||
except ValidationException as ve:
|
except ValidationException as ve:
|
||||||
ve.file_name = filename
|
ve.file_name = filename
|
||||||
ve.line_number = self.validator.error_log.last_error.line
|
ve.line_number = self.validator.error_log.last_error.line
|
||||||
except LxmlError as le:
|
except LxmlError:
|
||||||
last_error = self.validator.error_log.last_error
|
last_error = self.validator.error_log.last_error
|
||||||
raise ValidationException(last_error.message, file_name=filename,
|
raise ValidationException(last_error.message, file_name=filename,
|
||||||
line_number=last_error.line)
|
line_number=last_error.line)
|
||||||
@ -132,14 +142,14 @@ class BpmnParser(object):
|
|||||||
|
|
||||||
DATA_STORE_CLASSES = {}
|
DATA_STORE_CLASSES = {}
|
||||||
|
|
||||||
def __init__(self, namespaces=None, validator=None):
|
def __init__(self, namespaces=None, validator=None, spec_descriptions=SPEC_DESCRIPTIONS):
|
||||||
"""
|
"""
|
||||||
Constructor.
|
Constructor.
|
||||||
"""
|
"""
|
||||||
self.namespaces = namespaces or DEFAULT_NSMAP
|
self.namespaces = namespaces or DEFAULT_NSMAP
|
||||||
self.validator = validator
|
self.validator = validator
|
||||||
|
self.spec_descriptions = spec_descriptions
|
||||||
self.process_parsers = {}
|
self.process_parsers = {}
|
||||||
self.process_parsers_by_name = {}
|
|
||||||
self.collaborations = {}
|
self.collaborations = {}
|
||||||
self.process_dependencies = set()
|
self.process_dependencies = set()
|
||||||
self.messages = {}
|
self.messages = {}
|
||||||
@ -153,15 +163,13 @@ class BpmnParser(object):
|
|||||||
return self.PARSER_CLASSES[tag]
|
return self.PARSER_CLASSES[tag]
|
||||||
return None, None
|
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
|
Returns the ProcessParser for the given process ID or name. It matches
|
||||||
by name first.
|
by name first.
|
||||||
"""
|
"""
|
||||||
if process_id_or_name in self.process_parsers_by_name:
|
if process_id in self.process_parsers:
|
||||||
return self.process_parsers_by_name[process_id_or_name]
|
return self.process_parsers[process_id]
|
||||||
elif process_id_or_name in self.process_parsers:
|
|
||||||
return self.process_parsers[process_id_or_name]
|
|
||||||
|
|
||||||
def get_process_ids(self):
|
def get_process_ids(self):
|
||||||
"""Returns a list of process IDs"""
|
"""Returns a list of process IDs"""
|
||||||
@ -186,7 +194,19 @@ class BpmnParser(object):
|
|||||||
"""
|
"""
|
||||||
for filename in filenames:
|
for filename in filenames:
|
||||||
with open(filename, 'r') as f:
|
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):
|
def add_bpmn_xml(self, bpmn, filename=None):
|
||||||
"""
|
"""
|
||||||
@ -286,37 +306,37 @@ class BpmnParser(object):
|
|||||||
|
|
||||||
def create_parser(self, node, filename=None, lane=None):
|
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)
|
parser = self.PROCESS_PARSER_CLASS(self, node, self.namespaces, self.data_stores, filename=filename, lane=lane)
|
||||||
if parser.get_id() in self.process_parsers:
|
if parser.bpmn_id in self.process_parsers:
|
||||||
raise ValidationException(f'Duplicate process ID: {parser.get_id()}', node=node, file_name=filename)
|
raise ValidationException(f'Duplicate process ID: {parser.bpmn_id}', node=node, file_name=filename)
|
||||||
if parser.get_name() in self.process_parsers_by_name:
|
self.process_parsers[parser.bpmn_id] = parser
|
||||||
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
|
|
||||||
|
|
||||||
def get_process_dependencies(self):
|
def get_process_dependencies(self):
|
||||||
return self.process_dependencies
|
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
|
Parses the required subset of the BPMN files, in order to provide an
|
||||||
instance of BpmnProcessSpec (i.e. WorkflowSpec)
|
instance of BpmnProcessSpec (i.e. WorkflowSpec)
|
||||||
for the given process ID or name. The Name is matched first.
|
for the given process ID or name. The Name is matched first.
|
||||||
"""
|
"""
|
||||||
parser = self.get_process_parser(process_id_or_name)
|
parser = self.get_process_parser(process_id)
|
||||||
if parser is None:
|
if required and parser is None:
|
||||||
raise ValidationException(
|
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"Did you mean one of the following: "
|
||||||
f"{', '.join(self.get_process_ids())}?")
|
f"{', '.join(self.get_process_ids())}?")
|
||||||
return parser.get_spec()
|
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 {}
|
used = specs or {}
|
||||||
wf_spec = self.get_spec(name)
|
wf_spec = self.get_spec(name)
|
||||||
for task_spec in wf_spec.task_specs.values():
|
for task_spec in wf_spec.task_specs.values():
|
||||||
if isinstance(task_spec, SubWorkflowTask) and task_spec.spec not in used:
|
if isinstance(task_spec, SubWorkflowTaskMixin) and task_spec.spec not in used:
|
||||||
used[task_spec.spec] = self.get_spec(task_spec.spec)
|
subprocess_spec = self.get_spec(task_spec.spec, required=require_call_activity_specs)
|
||||||
self.get_subprocess_specs(task_spec.spec, used)
|
used[task_spec.spec] = subprocess_spec
|
||||||
|
if subprocess_spec is not None:
|
||||||
|
self.get_subprocess_specs(task_spec.spec, used)
|
||||||
return used
|
return used
|
||||||
|
|
||||||
def find_all_specs(self):
|
def find_all_specs(self):
|
||||||
@ -332,16 +352,24 @@ class BpmnParser(object):
|
|||||||
self.find_all_specs()
|
self.find_all_specs()
|
||||||
spec = BpmnProcessSpec(name)
|
spec = BpmnProcessSpec(name)
|
||||||
subprocesses = {}
|
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)
|
spec.start.connect(start)
|
||||||
end = EndEvent(spec, 'End Collaboration', NoneEventDefinition())
|
end = end_type(spec, 'End Collaboration', NoneEventDefinition())
|
||||||
end.connect(spec.end)
|
end.connect(spec.end)
|
||||||
for process in self.collaborations[name]:
|
for process in self.collaborations[name]:
|
||||||
process_parser = self.get_process_parser(process)
|
process_parser = self.get_process_parser(process)
|
||||||
if process_parser and process_parser.process_executable:
|
if process_parser and process_parser.process_executable:
|
||||||
participant = CallActivity(spec, process, process)
|
sp_spec = self.get_spec(process)
|
||||||
start.connect(participant)
|
subprocesses[process] = sp_spec
|
||||||
participant.connect(end)
|
|
||||||
subprocesses[process] = self.get_spec(process)
|
|
||||||
subprocesses.update(self.get_subprocess_specs(process))
|
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)
|
||||||
return spec, subprocesses
|
return spec, subprocesses
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# Copyright (C) 2012 Matthew Hampton
|
# 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
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
# License as published by the Free Software Foundation; either
|
# 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
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
# Lesser General Public License for more details.
|
# Lesser General Public License for more details.
|
||||||
@ -18,7 +18,7 @@
|
|||||||
# 02110-1301 USA
|
# 02110-1301 USA
|
||||||
|
|
||||||
from .ValidationException import ValidationException
|
from .ValidationException import ValidationException
|
||||||
from ..specs.BpmnProcessSpec import BpmnProcessSpec
|
from ..specs.bpmn_process_spec import BpmnProcessSpec
|
||||||
from ..specs.data_spec import DataObject
|
from ..specs.data_spec import DataObject
|
||||||
from .node_parser import NodeParser
|
from .node_parser import NodeParser
|
||||||
from .util import first
|
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)
|
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:
|
def has_lanes(self) -> bool:
|
||||||
"""Returns true if this process has one or more named lanes """
|
"""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')
|
start_node_list = self.xpath('./bpmn:startEvent')
|
||||||
if not start_node_list and self.process_executable:
|
if not start_node_list and self.process_executable:
|
||||||
raise ValidationException("No start event found", node=self.node, file_name=self.filename)
|
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)
|
self.spec.data_objects.update(self.inherited_data_objects)
|
||||||
|
|
||||||
# Get the data objects
|
# Get the data objects
|
||||||
for obj in self.xpath('./bpmn:dataObject'):
|
for obj in self.xpath('./bpmn:dataObject'):
|
||||||
data_object = self.parse_data_object(obj)
|
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.
|
# Check for an IO Specification.
|
||||||
io_spec = first(self.xpath('./bpmn:ioSpecification'))
|
io_spec = first(self.xpath('./bpmn:ioSpecification'))
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# Copyright (C) 2012 Matthew Hampton, 2023 Sartography
|
||||||
|
|
||||||
# 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
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
# License as published by the Free Software Foundation; either
|
# 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
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
# Lesser General Public License for more details.
|
# Lesser General Public License for more details.
|
||||||
@ -17,17 +17,21 @@
|
|||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||||
# 02110-1301 USA
|
# 02110-1301 USA
|
||||||
|
|
||||||
from .ValidationException import ValidationException
|
from SpiffWorkflow.bpmn.specs.mixins.subworkflow_task import TransactionSubprocess
|
||||||
from ..specs.events.IntermediateEvent import _BoundaryEventParent
|
from SpiffWorkflow.bpmn.specs.mixins.exclusive_gateway import ExclusiveGateway
|
||||||
from ..specs.events.event_definitions import CancelEventDefinition
|
from SpiffWorkflow.bpmn.specs.mixins.inclusive_gateway import InclusiveGateway
|
||||||
from ..specs.MultiInstanceTask import StandardLoopTask, SequentialMultiInstanceTask, ParallelMultiInstanceTask
|
from SpiffWorkflow.bpmn.specs.defaults import (
|
||||||
from ..specs.SubWorkflowTask import TransactionSubprocess
|
StandardLoopTask,
|
||||||
from ..specs.ExclusiveGateway import ExclusiveGateway
|
SequentialMultiInstanceTask,
|
||||||
from ..specs.InclusiveGateway import InclusiveGateway
|
ParallelMultiInstanceTask
|
||||||
from ..specs.data_spec import TaskDataReference
|
)
|
||||||
|
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 .util import one
|
||||||
from .node_parser import NodeParser
|
from .node_parser import NodeParser
|
||||||
|
from .ValidationException import ValidationException
|
||||||
|
|
||||||
|
|
||||||
class TaskParser(NodeParser):
|
class TaskParser(NodeParser):
|
||||||
@ -94,15 +98,17 @@ class TaskParser(NodeParser):
|
|||||||
self._copy_task_attrs(original)
|
self._copy_task_attrs(original)
|
||||||
|
|
||||||
def _add_multiinstance_task(self, loop_characteristics):
|
def _add_multiinstance_task(self, loop_characteristics):
|
||||||
|
|
||||||
sequential = loop_characteristics.get('isSequential') == 'true'
|
sequential = loop_characteristics.get('isSequential') == 'true'
|
||||||
prefix = 'bpmn:multiInstanceLoopCharacteristics'
|
prefix = 'bpmn:multiInstanceLoopCharacteristics'
|
||||||
cardinality = self.xpath(f'./{prefix}/bpmn:loopCardinality')
|
cardinality = self.xpath(f'./{prefix}/bpmn:loopCardinality')
|
||||||
loop_input = self.xpath(f'./{prefix}/bpmn:loopDataInputRef')
|
loop_input = self.xpath(f'./{prefix}/bpmn:loopDataInputRef')
|
||||||
if len(cardinality) == 0 and len(loop_input) == 0:
|
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:
|
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
|
cardinality = cardinality[0].text if len(cardinality) > 0 else None
|
||||||
|
|
||||||
loop_input = loop_input[0].text if len(loop_input) > 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:
|
if self.task.io_specification is not None:
|
||||||
try:
|
try:
|
||||||
loop_input = [v for v in self.task.io_specification.data_inputs if v.name == loop_input][0]
|
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')
|
self.raise_validation_exception('The loop input data reference is missing from the IO specification')
|
||||||
else:
|
else:
|
||||||
loop_input = TaskDataReference(loop_input)
|
loop_input = TaskDataReference(loop_input)
|
||||||
@ -125,7 +131,7 @@ class TaskParser(NodeParser):
|
|||||||
try:
|
try:
|
||||||
refs = set(self.task.io_specification.data_inputs + self.task.io_specification.data_outputs)
|
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]
|
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')
|
self.raise_validation_exception('The loop output data reference is missing from the IO specification')
|
||||||
else:
|
else:
|
||||||
loop_output = TaskDataReference(loop_output)
|
loop_output = TaskDataReference(loop_output)
|
||||||
@ -138,8 +144,8 @@ class TaskParser(NodeParser):
|
|||||||
|
|
||||||
original = self.spec.task_specs.pop(self.task.name)
|
original = self.spec.task_specs.pop(self.task.name)
|
||||||
params = {
|
params = {
|
||||||
'task_spec': '',
|
'task_spec': '',
|
||||||
'cardinality': cardinality,
|
'cardinality': cardinality,
|
||||||
'data_input': loop_input,
|
'data_input': loop_input,
|
||||||
'data_output':loop_output,
|
'data_output':loop_output,
|
||||||
'input_item': input_item,
|
'input_item': input_item,
|
||||||
@ -155,7 +161,7 @@ class TaskParser(NodeParser):
|
|||||||
def _add_boundary_event(self, children):
|
def _add_boundary_event(self, children):
|
||||||
|
|
||||||
parent = _BoundaryEventParent(
|
parent = _BoundaryEventParent(
|
||||||
self.spec, '%s.BoundaryEventParent' % self.get_id(),
|
self.spec, '%s.BoundaryEventParent' % self.bpmn_id,
|
||||||
self.task, lane=self.task.lane)
|
self.task, lane=self.task.lane)
|
||||||
self.process_parser.parsed_nodes[self.node.get('id')] = parent
|
self.process_parser.parsed_nodes[self.node.get('id')] = parent
|
||||||
parent.connect(self.task)
|
parent.connect(self.task)
|
||||||
@ -176,10 +182,6 @@ class TaskParser(NodeParser):
|
|||||||
# Why do we just set random attributes willy nilly everywhere in the code????
|
# 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!
|
# And we still pass around a gigantic kwargs dict whenever we create anything!
|
||||||
self.task.extensions = self.parse_extensions()
|
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')
|
io_spec = self.xpath('./bpmn:ioSpecification')
|
||||||
if len(io_spec) > 0:
|
if len(io_spec) > 0:
|
||||||
@ -193,30 +195,32 @@ class TaskParser(NodeParser):
|
|||||||
if len(mi_loop_characteristics) > 0:
|
if len(mi_loop_characteristics) > 0:
|
||||||
self._add_multiinstance_task(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:
|
if boundary_event_nodes:
|
||||||
parent = self._add_boundary_event(boundary_event_nodes)
|
parent = self._add_boundary_event(boundary_event_nodes)
|
||||||
else:
|
else:
|
||||||
self.process_parser.parsed_nodes[self.node.get('id')] = self.task
|
self.process_parser.parsed_nodes[self.node.get('id')] = self.task
|
||||||
|
|
||||||
children = []
|
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():
|
if len(outgoing) > 1 and not self.handles_multiple_outgoing():
|
||||||
self.raise_validation_exception('Multiple outgoing flows are not supported for tasks of type')
|
self.raise_validation_exception('Multiple outgoing flows are not supported for tasks of type')
|
||||||
for sequence_flow in outgoing:
|
for sequence_flow in outgoing:
|
||||||
target_ref = sequence_flow.get('targetRef')
|
target_ref = sequence_flow.get('targetRef')
|
||||||
try:
|
try:
|
||||||
target_node = one(self.doc_xpath('.//bpmn:*[@id="%s"]'% target_ref))
|
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, '
|
self.raise_validation_exception('When looking for a task spec, we found two items, '
|
||||||
'perhaps a form has the same ID? (%s)' % target_ref)
|
'perhaps a form has the same ID? (%s)' % target_ref)
|
||||||
|
|
||||||
c = self.process_parser.parse_node(target_node)
|
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))
|
children.append((position, c, target_node, sequence_flow))
|
||||||
|
|
||||||
if children:
|
if children:
|
||||||
# Sort children by their y coordinate.
|
# 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"]))
|
children = sorted(children, key=lambda tup: float(tup[0]["y"]))
|
||||||
|
|
||||||
default_outgoing = self.node.get('default')
|
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)
|
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):
|
def create_task(self):
|
||||||
"""
|
"""
|
||||||
Create an instance of the task appropriately. A subclass can override
|
Create an instance of the task appropriately. A subclass can override
|
||||||
this method to get extra information from the node.
|
this method to get extra information from the node.
|
||||||
"""
|
"""
|
||||||
return self.spec_class(self.spec, self.get_task_spec_name(),
|
return self.spec_class(self.spec, self.bpmn_id, **self.bpmn_attributes)
|
||||||
lane=self.lane,
|
|
||||||
description=self.node.get('name', None),
|
|
||||||
position=self.position)
|
|
||||||
|
|
||||||
def connect_outgoing(self, outgoing_task, sequence_flow_node, is_default):
|
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
|
# 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
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
# License as published by the Free Software Foundation; either
|
# 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
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
# Lesser General Public License for more details.
|
# Lesser General Public License for more details.
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# Copyright (C) 2012 Matthew Hampton, 2023 Sartography
|
||||||
# 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
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
# License as published by the Free Software Foundation; either
|
# 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
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
# Lesser General Public License for more details.
|
# 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 .ValidationException import ValidationException
|
||||||
from .TaskParser import TaskParser
|
from .TaskParser import TaskParser
|
||||||
from .util import first, one
|
from .util import first, one
|
||||||
from ..specs.events.event_definitions import (
|
from ..specs.event_definitions import (
|
||||||
MultipleEventDefinition,
|
MultipleEventDefinition,
|
||||||
TimeDateEventDefinition,
|
TimeDateEventDefinition,
|
||||||
DurationTimerEventDefinition,
|
DurationTimerEventDefinition,
|
||||||
@ -16,7 +33,8 @@ from ..specs.events.event_definitions import (
|
|||||||
SignalEventDefinition,
|
SignalEventDefinition,
|
||||||
CancelEventDefinition,
|
CancelEventDefinition,
|
||||||
TerminateEventDefinition,
|
TerminateEventDefinition,
|
||||||
NoneEventDefinition
|
NoneEventDefinition,
|
||||||
|
CorrelationProperty
|
||||||
)
|
)
|
||||||
|
|
||||||
CANCEL_EVENT_XPATH = './/bpmn:cancelEventDefinition'
|
CANCEL_EVENT_XPATH = './/bpmn:cancelEventDefinition'
|
||||||
@ -31,8 +49,26 @@ TIMER_EVENT_XPATH = './/bpmn:timerEventDefinition'
|
|||||||
class EventDefinitionParser(TaskParser):
|
class EventDefinitionParser(TaskParser):
|
||||||
"""This class provvides methods for parsing different event definitions."""
|
"""This class provvides methods for parsing different event definitions."""
|
||||||
|
|
||||||
def parse_cancel_event(self):
|
def __init__(self, process_parser, spec_class, node, nsmap=None, lane=None):
|
||||||
return CancelEventDefinition()
|
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):
|
def parse_error_event(self, error_event):
|
||||||
"""Parse the errorEventDefinition node and return an instance of ErrorEventDefinition."""
|
"""Parse the errorEventDefinition node and return an instance of ErrorEventDefinition."""
|
||||||
@ -43,7 +79,7 @@ class EventDefinitionParser(TaskParser):
|
|||||||
name = error.get('name')
|
name = error.get('name')
|
||||||
else:
|
else:
|
||||||
name, error_code = 'None Error Event', None
|
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):
|
def parse_escalation_event(self, escalation_event):
|
||||||
"""Parse the escalationEventDefinition node and return an instance of EscalationEventDefinition."""
|
"""Parse the escalationEventDefinition node and return an instance of EscalationEventDefinition."""
|
||||||
@ -55,7 +91,7 @@ class EventDefinitionParser(TaskParser):
|
|||||||
name = escalation.get('name')
|
name = escalation.get('name')
|
||||||
else:
|
else:
|
||||||
name, escalation_code = 'None Escalation Event', None
|
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):
|
def parse_message_event(self, message_event):
|
||||||
|
|
||||||
@ -63,11 +99,13 @@ class EventDefinitionParser(TaskParser):
|
|||||||
if message_ref is not None:
|
if message_ref is not None:
|
||||||
message = one(self.doc_xpath('.//bpmn:message[@id="%s"]' % message_ref))
|
message = one(self.doc_xpath('.//bpmn:message[@id="%s"]' % message_ref))
|
||||||
name = message.get('name')
|
name = message.get('name')
|
||||||
|
description = self.get_event_description(message_event)
|
||||||
correlations = self.get_message_correlations(message_ref)
|
correlations = self.get_message_correlations(message_ref)
|
||||||
else:
|
else:
|
||||||
name = message_event.getparent().get('name')
|
name = message_event.getparent().get('name')
|
||||||
|
description = 'Message'
|
||||||
correlations = {}
|
correlations = {}
|
||||||
return MessageEventDefinition(name, correlations)
|
return MessageEventDefinition(name, correlations, description=description)
|
||||||
|
|
||||||
def parse_signal_event(self, signal_event):
|
def parse_signal_event(self, signal_event):
|
||||||
"""Parse the signalEventDefinition node and return an instance of SignalEventDefinition."""
|
"""Parse the signalEventDefinition node and return an instance of SignalEventDefinition."""
|
||||||
@ -78,26 +116,26 @@ class EventDefinitionParser(TaskParser):
|
|||||||
name = signal.get('name')
|
name = signal.get('name')
|
||||||
else:
|
else:
|
||||||
name = signal_event.getparent().get('name')
|
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."""
|
"""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."""
|
"""Parse the timerEventDefinition node and return an instance of TimerEventDefinition."""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
description = self.get_event_description(event)
|
||||||
name = self.node.get('name', self.node.get('id'))
|
name = self.node.get('name', self.node.get('id'))
|
||||||
time_date = first(self.xpath('.//bpmn:timeDate'))
|
time_date = first(self.xpath('.//bpmn:timeDate'))
|
||||||
if time_date is not None:
|
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'))
|
time_duration = first(self.xpath('.//bpmn:timeDuration'))
|
||||||
if time_duration is not None:
|
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'))
|
time_cycle = first(self.xpath('.//bpmn:timeCycle'))
|
||||||
if time_cycle is not None:
|
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)
|
raise ValidationException("Unknown Time Specification", node=self.node, file_name=self.filename)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ValidationException("Time Specification Error. " + str(e), node=self.node, file_name=self.filename)
|
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]:
|
if prop.name not in self.spec.correlation_keys[key]:
|
||||||
self.spec.correlation_keys[key].append(prop.name)
|
self.spec.correlation_keys[key].append(prop.name)
|
||||||
|
|
||||||
kwargs = {
|
kwargs = self.bpmn_attributes
|
||||||
'lane': self.lane,
|
|
||||||
'description': self.node.get('name', None),
|
|
||||||
'position': self.position,
|
|
||||||
}
|
|
||||||
if cancel_activity is not None:
|
if cancel_activity is not None:
|
||||||
kwargs['cancel_activity'] = cancel_activity
|
kwargs['cancel_activity'] = cancel_activity
|
||||||
|
interrupt = 'Interrupting' if cancel_activity else 'Non-Interrupting'
|
||||||
|
kwargs['description'] = interrupt + ' ' + kwargs['description']
|
||||||
if parallel is not None:
|
if parallel is not None:
|
||||||
kwargs['parallel'] = parallel
|
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):
|
def get_event_definition(self, xpaths):
|
||||||
"""Returns all event definitions it can find in given list of xpaths"""
|
"""Returns all event definitions it can find in given list of xpaths"""
|
||||||
@ -142,29 +178,31 @@ class EventDefinitionParser(TaskParser):
|
|||||||
event_definitions = []
|
event_definitions = []
|
||||||
for path in xpaths:
|
for path in xpaths:
|
||||||
for event in self.xpath(path):
|
for event in self.xpath(path):
|
||||||
|
if event is not None:
|
||||||
|
self.event_nodes.append(event)
|
||||||
if path == MESSAGE_EVENT_XPATH:
|
if path == MESSAGE_EVENT_XPATH:
|
||||||
event_definitions.append(self.parse_message_event(event))
|
event_definitions.append(self.parse_message_event(event))
|
||||||
elif path == SIGNAL_EVENT_XPATH:
|
elif path == SIGNAL_EVENT_XPATH:
|
||||||
event_definitions.append(self.parse_signal_event(event))
|
event_definitions.append(self.parse_signal_event(event))
|
||||||
elif path == TIMER_EVENT_XPATH:
|
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:
|
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:
|
elif path == ERROR_EVENT_XPATH:
|
||||||
event_definitions.append(self.parse_error_event(event))
|
event_definitions.append(self.parse_error_event(event))
|
||||||
elif path == ESCALATION_EVENT_XPATH:
|
elif path == ESCALATION_EVENT_XPATH:
|
||||||
event_definitions.append(self.parse_escalation_event(event))
|
event_definitions.append(self.parse_escalation_event(event))
|
||||||
elif path == TERMINATION_EVENT_XPATH:
|
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'
|
parallel = self.node.get('parallelMultiple') == 'true'
|
||||||
|
|
||||||
if len(event_definitions) == 0:
|
if len(event_definitions) == 0:
|
||||||
return NoneEventDefinition()
|
return NoneEventDefinition(description='Default')
|
||||||
elif len(event_definitions) == 1:
|
elif len(event_definitions) == 1:
|
||||||
return event_definitions[0]
|
return event_definitions[0]
|
||||||
else:
|
else:
|
||||||
return MultipleEventDefinition(event_definitions, parallel)
|
return MultipleEventDefinition(event_definitions, parallel, description='Multiple')
|
||||||
|
|
||||||
|
|
||||||
class StartEventParser(EventDefinitionParser):
|
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.parser.ValidationException import ValidationException
|
||||||
from SpiffWorkflow.bpmn.specs.data_spec import TaskDataReference, BpmnIoSpecification
|
from SpiffWorkflow.bpmn.specs.data_spec import TaskDataReference, BpmnIoSpecification
|
||||||
from .util import first
|
from .util import first
|
||||||
@ -17,11 +36,25 @@ class NodeParser:
|
|||||||
self.nsmap = nsmap or DEFAULT_NSMAP
|
self.nsmap = nsmap or DEFAULT_NSMAP
|
||||||
self.filename = filename
|
self.filename = filename
|
||||||
self.lane = self._get_lane() or lane
|
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')
|
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):
|
def xpath(self, xpath, extra_ns=None):
|
||||||
return self._xpath(self.node, xpath, extra_ns)
|
return self._xpath(self.node, xpath, extra_ns)
|
||||||
|
|
||||||
@ -76,10 +109,10 @@ class NodeParser:
|
|||||||
data_refs = {}
|
data_refs = {}
|
||||||
for elem in self.xpath('./bpmn:ioSpecification/bpmn:dataInput'):
|
for elem in self.xpath('./bpmn:ioSpecification/bpmn:dataInput'):
|
||||||
ref = self.create_data_spec(elem, TaskDataReference)
|
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'):
|
for elem in self.xpath('./bpmn:ioSpecification/bpmn:dataOutput'):
|
||||||
ref = self.create_data_spec(elem, TaskDataReference)
|
ref = self.create_data_spec(elem, TaskDataReference)
|
||||||
data_refs[ref.name] = ref
|
data_refs[ref.bpmn_id] = ref
|
||||||
|
|
||||||
inputs, outputs = [], []
|
inputs, outputs = [], []
|
||||||
for ref in self.xpath('./bpmn:ioSpecification/bpmn:inputSet/bpmn:dataInputRefs'):
|
for ref in self.xpath('./bpmn:ioSpecification/bpmn:inputSet/bpmn:dataInputRefs'):
|
||||||
@ -96,16 +129,20 @@ class NodeParser:
|
|||||||
def parse_extensions(self, node=None):
|
def parse_extensions(self, node=None):
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
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):
|
def _get_lane(self):
|
||||||
noderef = first(self.doc_xpath(f".//bpmn:flowNodeRef[text()='{self.get_id()}']"))
|
noderef = first(self.doc_xpath(f".//bpmn:flowNodeRef[text()='{self.bpmn_id}']"))
|
||||||
if noderef is not None:
|
if noderef is not None:
|
||||||
return noderef.getparent().get('name')
|
return noderef.getparent().get('name')
|
||||||
|
|
||||||
def _get_position(self):
|
|
||||||
bounds = first(self.doc_xpath(f".//bpmndi:BPMNShape[@bpmnElement='{self.get_id()}']//dc:Bounds"))
|
|
||||||
if bounds is not None:
|
|
||||||
return {'x': float(bounds.get('x', 0)), 'y': float(bounds.get('y', 0))}
|
|
||||||
|
|
||||||
def _xpath(self, node, xpath, extra_ns=None):
|
def _xpath(self, node, xpath, extra_ns=None):
|
||||||
if extra_ns is not None:
|
if extra_ns is not None:
|
||||||
nsmap = self.nsmap.copy()
|
nsmap = self.nsmap.copy()
|
||||||
|
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, 2023 Sartography
|
||||||
# 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
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
# License as published by the Free Software Foundation; either
|
# 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
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
# Lesser General Public License for more details.
|
# Lesser General Public License for more details.
|
||||||
@ -85,13 +86,6 @@ class SubprocessParser:
|
|||||||
'No "calledElement" attribute for Call Activity.',
|
'No "calledElement" attribute for Call Activity.',
|
||||||
node=task_parser.node,
|
node=task_parser.node,
|
||||||
file_name=task_parser.filename)
|
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
|
return called_element
|
||||||
|
|
||||||
|
|
||||||
@ -99,10 +93,7 @@ class SubWorkflowParser(TaskParser):
|
|||||||
|
|
||||||
def create_task(self):
|
def create_task(self):
|
||||||
subworkflow_spec = SubprocessParser.get_subprocess_spec(self)
|
subworkflow_spec = SubprocessParser.get_subprocess_spec(self)
|
||||||
return self.spec_class(
|
return self.spec_class(self.spec, self.bpmn_id, subworkflow_spec=subworkflow_spec, **self.bpmn_attributes)
|
||||||
self.spec, self.get_task_spec_name(), subworkflow_spec,
|
|
||||||
lane=self.lane, position=self.position,
|
|
||||||
description=self.node.get('name', None))
|
|
||||||
|
|
||||||
|
|
||||||
class CallActivityParser(TaskParser):
|
class CallActivityParser(TaskParser):
|
||||||
@ -110,23 +101,14 @@ class CallActivityParser(TaskParser):
|
|||||||
|
|
||||||
def create_task(self):
|
def create_task(self):
|
||||||
subworkflow_spec = SubprocessParser.get_call_activity_spec(self)
|
subworkflow_spec = SubprocessParser.get_call_activity_spec(self)
|
||||||
return self.spec_class(
|
return self.spec_class(self.spec, self.bpmn_id, subworkflow_spec=subworkflow_spec, **self.bpmn_attributes)
|
||||||
self.spec, self.get_task_spec_name(), subworkflow_spec,
|
|
||||||
lane=self.lane, position=self.position,
|
|
||||||
description=self.node.get('name', None))
|
|
||||||
|
|
||||||
|
|
||||||
class ScriptTaskParser(TaskParser):
|
class ScriptTaskParser(TaskParser):
|
||||||
"""
|
"""Parses a script task"""
|
||||||
Parses a script task
|
|
||||||
"""
|
|
||||||
|
|
||||||
def create_task(self):
|
def create_task(self):
|
||||||
script = self.get_script()
|
return self.spec_class(self.spec, self.bpmn_id, script=self.get_script(), **self.bpmn_attributes)
|
||||||
return self.spec_class(self.spec, self.get_task_spec_name(), script,
|
|
||||||
lane=self.lane,
|
|
||||||
position=self.position,
|
|
||||||
description=self.node.get('name', None))
|
|
||||||
|
|
||||||
def get_script(self):
|
def get_script(self):
|
||||||
"""
|
"""
|
||||||
@ -138,6 +120,6 @@ class ScriptTaskParser(TaskParser):
|
|||||||
return one(self.xpath('.//bpmn:script')).text
|
return one(self.xpath('.//bpmn:script')).text
|
||||||
except AssertionError as ae:
|
except AssertionError as ae:
|
||||||
raise ValidationException(
|
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)
|
node=self.node, file_name=self.filename)
|
||||||
|
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright (C) 2012 Matthew Hampton
|
# 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
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
# License as published by the Free Software Foundation; either
|
# 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
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
# Lesser General Public License for more details.
|
# Lesser General Public License for more details.
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# Copyright (C) 2012 Matthew Hampton, 2023 Sartography
|
||||||
# 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
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
# License as published by the Free Software Foundation; either
|
# 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
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
# Lesser General Public License for more details.
|
# 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 ..specs.data_spec import DataObject, TaskDataReference, BpmnIoSpecification
|
||||||
from .helpers.spec import BpmnSpecConverter, BpmnDataSpecificationConverter
|
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,
|
CancelEventDefinition,
|
||||||
ErrorEventDefinition,
|
ErrorEventDefinition,
|
||||||
EscalationEventDefinition,
|
EscalationEventDefinition,
|
||||||
@ -13,6 +30,7 @@ from ..specs.events.event_definitions import (
|
|||||||
CycleTimerEventDefinition,
|
CycleTimerEventDefinition,
|
||||||
MultipleEventDefinition,
|
MultipleEventDefinition,
|
||||||
)
|
)
|
||||||
|
from .helpers.spec import EventDefinitionConverter
|
||||||
|
|
||||||
class CancelEventDefinitionConverter(EventDefinitionConverter):
|
class CancelEventDefinitionConverter(EventDefinitionConverter):
|
||||||
def __init__(self, registry):
|
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
|
from functools import partial
|
||||||
|
|
||||||
class DictionaryConverter:
|
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 uuid import UUID
|
||||||
from datetime import datetime, timedelta
|
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 functools import partial
|
||||||
|
|
||||||
from ...specs.BpmnSpecMixin import BpmnSpecMixin
|
from SpiffWorkflow.operators import Attrib, PathAttrib
|
||||||
from ...specs.events.event_definitions import NamedEventDefinition, TimerEventDefinition
|
|
||||||
from ...specs.events.event_definitions import CorrelationProperty
|
from SpiffWorkflow.bpmn.specs.mixins.bpmn_spec_mixin import BpmnSpecMixin
|
||||||
from ....operators import Attrib, PathAttrib
|
from SpiffWorkflow.bpmn.specs.event_definitions import (
|
||||||
|
NamedEventDefinition,
|
||||||
|
TimerEventDefinition,
|
||||||
|
CorrelationProperty
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class BpmnSpecConverter:
|
class BpmnSpecConverter:
|
||||||
@ -57,7 +80,7 @@ class BpmnDataSpecificationConverter(BpmnSpecConverter):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def to_dict(self, data_spec):
|
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):
|
def from_dict(self, dct):
|
||||||
return self.spec_class(**dct)
|
return self.spec_class(**dct)
|
||||||
@ -72,7 +95,11 @@ class EventDefinitionConverter(BpmnSpecConverter):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def to_dict(self, event_definition):
|
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)):
|
if isinstance(event_definition, (NamedEventDefinition, TimerEventDefinition)):
|
||||||
dct['name'] = event_definition.name
|
dct['name'] = event_definition.name
|
||||||
return dct
|
return dct
|
||||||
@ -105,7 +132,7 @@ class TaskSpecConverter(BpmnSpecConverter):
|
|||||||
modules of this package; the `camunda`,`dmn`, and `spiff` serialization packages contain other
|
modules of this package; the `camunda`,`dmn`, and `spiff` serialization packages contain other
|
||||||
examples.
|
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.
|
"""Extracts the default Spiff attributes from a task spec.
|
||||||
|
|
||||||
:param spec: the task spec to be converted
|
:param spec: the task spec to be converted
|
||||||
@ -113,38 +140,17 @@ class TaskSpecConverter(BpmnSpecConverter):
|
|||||||
Returns:
|
Returns:
|
||||||
a dictionary of standard task spec attributes
|
a dictionary of standard task spec attributes
|
||||||
"""
|
"""
|
||||||
dct = {
|
return {
|
||||||
'id': spec.id,
|
|
||||||
'name': spec.name,
|
'name': spec.name,
|
||||||
'description': spec.description,
|
'description': spec.description,
|
||||||
'manual': spec.manual,
|
'manual': spec.manual,
|
||||||
'internal': spec.internal,
|
|
||||||
'lookahead': spec.lookahead,
|
'lookahead': spec.lookahead,
|
||||||
'inputs': [task.name for task in spec.inputs],
|
'inputs': [task.name for task in spec.inputs],
|
||||||
'outputs': [task.name for task in spec.outputs],
|
'outputs': [task.name for task in spec.outputs],
|
||||||
}
|
'bpmn_id': spec.bpmn_id,
|
||||||
# This stuff is also all defined in the base task spec, but can contain data, so we need
|
'bpmn_name': spec.bpmn_name,
|
||||||
# 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 {
|
|
||||||
'lane': spec.lane,
|
'lane': spec.lane,
|
||||||
'documentation': spec.documentation,
|
'documentation': spec.documentation,
|
||||||
'position': spec.position,
|
|
||||||
'data_input_associations': [ self.registry.convert(obj) for obj in spec.data_input_associations ],
|
'data_input_associations': [ self.registry.convert(obj) for obj in spec.data_input_associations ],
|
||||||
'data_output_associations': [ self.registry.convert(obj) for obj in spec.data_output_associations ],
|
'data_output_associations': [ self.registry.convert(obj) for obj in spec.data_output_associations ],
|
||||||
'io_specification': self.registry.convert(spec.io_specification),
|
'io_specification': self.registry.convert(spec.io_specification),
|
||||||
@ -189,38 +195,36 @@ class TaskSpecConverter(BpmnSpecConverter):
|
|||||||
'test_before': spec.test_before,
|
'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
|
Creates a task spec based on the supplied dictionary. It handles setting the default
|
||||||
task spec attributes as well as attributes added by `BpmnSpecMixin`.
|
task spec attributes as well as attributes added by `BpmnSpecMixin`.
|
||||||
|
|
||||||
:param dct: the dictionary to create the task spec from
|
:param dct: the dictionary to create the task spec from
|
||||||
:param include_data: whether or not to include task spec data attributes
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
a restored task spec
|
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')
|
inputs = dct.pop('inputs')
|
||||||
outputs = dct.pop('outputs')
|
outputs = dct.pop('outputs')
|
||||||
|
|
||||||
spec = self.spec_class(**dct)
|
wf_spec = dct.pop('wf_spec')
|
||||||
spec.internal = internal
|
name = dct.pop('name')
|
||||||
|
bpmn_id = dct.pop('bpmn_id')
|
||||||
|
|
||||||
|
spec = self.spec_class(wf_spec, name, **dct)
|
||||||
spec.inputs = inputs
|
spec.inputs = inputs
|
||||||
spec.outputs = outputs
|
spec.outputs = outputs
|
||||||
spec.id = dct['id']
|
|
||||||
|
|
||||||
if include_data:
|
if issubclass(self.spec_class, BpmnSpecMixin) and bpmn_id != name:
|
||||||
spec.data = self.registry.restore(dct.get('data', {}))
|
# This is a hack for multiinstance tasks :( At least it is simple.
|
||||||
spec.defines = self.registry.restore(dct.get('defines', {}))
|
# Ideally I'd fix it in the parser, but I'm afraid of quickly running into a wall there
|
||||||
spec.pre_assign = self.registry.restore(dct.get('pre_assign', {}))
|
spec.bpmn_id = bpmn_id
|
||||||
spec.post_assign = self.registry.restore(dct.get('post_assign', {}))
|
|
||||||
|
|
||||||
if isinstance(spec, BpmnSpecMixin):
|
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))
|
spec.io_specification = self.registry.restore(dct.pop('io_specification', None))
|
||||||
|
|
||||||
return spec
|
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
|
from SpiffWorkflow.exceptions import WorkflowException
|
||||||
|
|
||||||
class VersionMigrationError(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):
|
def move_subprocesses_to_top(dct):
|
||||||
subprocesses = dict((sp, { 'tasks': {}, 'root': None, 'data': {}, 'success': True }) for sp in dct['subprocesses'])
|
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 datetime import datetime, timedelta
|
||||||
|
|
||||||
from SpiffWorkflow.task import TaskState
|
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
|
from .exceptions import VersionMigrationError
|
||||||
|
|
||||||
@ -29,7 +48,7 @@ def convert_timer_expressions(dct):
|
|||||||
elif isinstance(dt, timedelta):
|
elif isinstance(dt, timedelta):
|
||||||
spec['event_definition']['expression'] = f"'{td_to_iso(dt)}'"
|
spec['event_definition']['expression'] = f"'{td_to_iso(dt)}'"
|
||||||
spec['event_definition']['typename'] = 'DurationTimerEventDefinition'
|
spec['event_definition']['typename'] = 'DurationTimerEventDefinition'
|
||||||
except:
|
except Exception:
|
||||||
raise VersionMigrationError(message.format(spec=spec['name']))
|
raise VersionMigrationError(message.format(spec=spec['name']))
|
||||||
|
|
||||||
def convert_cycle(spec, task):
|
def convert_cycle(spec, task):
|
||||||
@ -47,7 +66,7 @@ def convert_timer_expressions(dct):
|
|||||||
'next': datetime.combine(dt.date(), dt.time(), LOCALTZ).isoformat(),
|
'next': datetime.combine(dt.date(), dt.time(), LOCALTZ).isoformat(),
|
||||||
'duration': duration.total_seconds(),
|
'duration': duration.total_seconds(),
|
||||||
}
|
}
|
||||||
except:
|
except Exception:
|
||||||
raise VersionMigrationError(message.format(spec=spec['name']))
|
raise VersionMigrationError(message.format(spec=spec['name']))
|
||||||
|
|
||||||
if spec['typename'] == 'StartEvent':
|
if spec['typename'] == 'StartEvent':
|
||||||
@ -65,7 +84,8 @@ def convert_timer_expressions(dct):
|
|||||||
task['children'].remove(remove['id'])
|
task['children'].remove(remove['id'])
|
||||||
dct['tasks'].pop(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) ]:
|
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')
|
spec['event_definition']['name'] = spec['event_definition'].pop('label')
|
||||||
if spec['event_definition']['typename'] == 'TimerEventDefinition':
|
if spec['event_definition']['typename'] == 'TimerEventDefinition':
|
||||||
@ -113,7 +133,7 @@ def create_data_objects_and_io_specs(dct):
|
|||||||
item['typename'] = 'DataObject'
|
item['typename'] = 'DataObject'
|
||||||
|
|
||||||
def check_multiinstance(dct):
|
def check_multiinstance(dct):
|
||||||
|
|
||||||
specs = [ spec for spec in dct['spec']['task_specs'].values() if 'prevtaskclass' in spec ]
|
specs = [ spec for spec in dct['spec']['task_specs'].values() if 'prevtaskclass' in spec ]
|
||||||
if len(specs) > 0:
|
if len(specs) > 0:
|
||||||
raise VersionMigrationError("This workflow cannot be migrated because it contains MultiInstance Tasks")
|
raise VersionMigrationError("This workflow cannot be migrated because it contains MultiInstance Tasks")
|
||||||
@ -143,3 +163,97 @@ def update_task_states(dct):
|
|||||||
update(dct)
|
update(dct)
|
||||||
for sp in dct['subprocesses'].values():
|
for sp in dct['subprocesses'].values():
|
||||||
update(sp)
|
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 copy import deepcopy
|
||||||
|
|
||||||
from .version_1_1 import move_subprocesses_to_top
|
from .version_1_1 import move_subprocesses_to_top
|
||||||
@ -8,6 +27,8 @@ from .version_1_2 import (
|
|||||||
check_multiinstance,
|
check_multiinstance,
|
||||||
remove_loop_reset,
|
remove_loop_reset,
|
||||||
update_task_states,
|
update_task_states,
|
||||||
|
convert_simple_tasks,
|
||||||
|
update_bpmn_attributes,
|
||||||
)
|
)
|
||||||
|
|
||||||
def from_version_1_1(old):
|
def from_version_1_1(old):
|
||||||
@ -38,6 +59,8 @@ def from_version_1_1(old):
|
|||||||
check_multiinstance(new)
|
check_multiinstance(new)
|
||||||
remove_loop_reset(new)
|
remove_loop_reset(new)
|
||||||
update_task_states(new)
|
update_task_states(new)
|
||||||
|
convert_simple_tasks(new)
|
||||||
|
update_bpmn_attributes(new)
|
||||||
new['VERSION'] = "1.2"
|
new['VERSION'] = "1.2"
|
||||||
return new
|
return new
|
||||||
|
|
||||||
|
@ -1,5 +1,24 @@
|
|||||||
from ..specs.BpmnProcessSpec import BpmnProcessSpec
|
# Copyright (C) 2023 Sartography
|
||||||
from ..specs.events.IntermediateEvent import _BoundaryEventParent
|
#
|
||||||
|
# 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
|
from .helpers.spec import WorkflowSpecConverter
|
||||||
|
|
||||||
@ -39,12 +58,9 @@ class BpmnProcessSpecConverter(WorkflowSpecConverter):
|
|||||||
def from_dict(self, dct):
|
def from_dict(self, dct):
|
||||||
|
|
||||||
spec = self.spec_class(name=dct['name'], description=dct['description'], filename=dct['file'])
|
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
|
# These are automatically created with a workflow and should be replaced
|
||||||
# the BPMN process spec doesn't pass it in, so we have to delete the auto generated Start task.
|
|
||||||
del spec.task_specs['Start']
|
del spec.task_specs['Start']
|
||||||
spec.start = None
|
spec.start = None
|
||||||
|
|
||||||
# These are also automatically created with a workflow and should be replaced
|
|
||||||
del spec.task_specs['End']
|
del spec.task_specs['End']
|
||||||
del spec.task_specs[f'{spec.name}.EndJoin']
|
del spec.task_specs[f'{spec.name}.EndJoin']
|
||||||
|
|
||||||
@ -60,6 +76,7 @@ class BpmnProcessSpecConverter(WorkflowSpecConverter):
|
|||||||
# Add messaging related stuff
|
# Add messaging related stuff
|
||||||
spec.correlation_keys = dct.pop('correlation_keys', {})
|
spec.correlation_keys = dct.pop('correlation_keys', {})
|
||||||
|
|
||||||
|
dct['task_specs'].pop('Root', None)
|
||||||
for name, task_dict in dct['task_specs'].items():
|
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.
|
# 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
|
# 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 SpiffWorkflow.bpmn.specs.control import BpmnStartTask, _EndJoin, _BoundaryEventParent, SimpleBpmnTask
|
||||||
from ...specs.Simple import Simple
|
from SpiffWorkflow.bpmn.specs.bpmn_task_spec import _BpmnCondition
|
||||||
|
from SpiffWorkflow.bpmn.specs.defaults import (
|
||||||
from ..specs.BpmnProcessSpec import _EndJoin
|
UserTask,
|
||||||
from ..specs.BpmnSpecMixin import _BpmnCondition
|
ManualTask,
|
||||||
from ..specs.NoneTask import NoneTask
|
NoneTask,
|
||||||
from ..specs.UserTask import UserTask
|
ScriptTask,
|
||||||
from ..specs.ManualTask import ManualTask
|
ExclusiveGateway,
|
||||||
from ..specs.ScriptTask import ScriptTask
|
InclusiveGateway,
|
||||||
from ..specs.MultiInstanceTask import StandardLoopTask, SequentialMultiInstanceTask, ParallelMultiInstanceTask
|
ParallelGateway,
|
||||||
from ..specs.SubWorkflowTask import CallActivity, TransactionSubprocess, SubWorkflowTask
|
StandardLoopTask,
|
||||||
from ..specs.ExclusiveGateway import ExclusiveGateway
|
SequentialMultiInstanceTask,
|
||||||
from ..specs.InclusiveGateway import InclusiveGateway
|
ParallelMultiInstanceTask,
|
||||||
from ..specs.ParallelGateway import ParallelGateway
|
CallActivity,
|
||||||
from ..specs.events.StartEvent import StartEvent
|
TransactionSubprocess,
|
||||||
from ..specs.events.EndEvent import EndEvent
|
SubWorkflowTask,
|
||||||
from ..specs.events.IntermediateEvent import (
|
StartEvent,
|
||||||
BoundaryEvent,
|
EndEvent,
|
||||||
_BoundaryEventParent,
|
|
||||||
EventBasedGateway,
|
|
||||||
IntermediateCatchEvent,
|
IntermediateCatchEvent,
|
||||||
IntermediateThrowEvent,
|
IntermediateThrowEvent,
|
||||||
|
BoundaryEvent,
|
||||||
|
EventBasedGateway,
|
||||||
SendTask,
|
SendTask,
|
||||||
ReceiveTask,
|
ReceiveTask,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from .helpers.spec import TaskSpecConverter
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
class BpmnTaskSpecConverter(TaskSpecConverter):
|
class BpmnTaskSpecConverter(TaskSpecConverter):
|
||||||
|
|
||||||
def to_dict(self, spec):
|
def to_dict(self, spec):
|
||||||
dct = self.get_default_attributes(spec)
|
dct = self.get_default_attributes(spec)
|
||||||
dct.update(self.get_bpmn_attributes(spec))
|
|
||||||
return dct
|
return dct
|
||||||
|
|
||||||
def from_dict(self, dct):
|
def from_dict(self, dct):
|
||||||
return self.task_spec_from_dict(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):
|
class NoneTaskConverter(BpmnTaskSpecConverter):
|
||||||
def __init__(self, registry):
|
def __init__(self, registry):
|
||||||
super().__init__(NoneTask, registry)
|
super().__init__(NoneTask, registry)
|
||||||
@ -85,7 +92,6 @@ class ScriptTaskConverter(BpmnTaskSpecConverter):
|
|||||||
|
|
||||||
def to_dict(self, spec):
|
def to_dict(self, spec):
|
||||||
dct = self.get_default_attributes(spec)
|
dct = self.get_default_attributes(spec)
|
||||||
dct.update(self.get_bpmn_attributes(spec))
|
|
||||||
dct['script'] = spec.script
|
dct['script'] = spec.script
|
||||||
return dct
|
return dct
|
||||||
|
|
||||||
@ -97,7 +103,6 @@ class StandardLoopTaskConverter(BpmnTaskSpecConverter):
|
|||||||
|
|
||||||
def to_dict(self, spec):
|
def to_dict(self, spec):
|
||||||
dct = self.get_default_attributes(spec)
|
dct = self.get_default_attributes(spec)
|
||||||
dct.update(self.get_bpmn_attributes(spec))
|
|
||||||
dct.update(self.get_standard_loop_attributes(spec))
|
dct.update(self.get_standard_loop_attributes(spec))
|
||||||
return dct
|
return dct
|
||||||
|
|
||||||
@ -106,7 +111,6 @@ class MultiInstanceTaskConverter(BpmnTaskSpecConverter):
|
|||||||
|
|
||||||
def to_dict(self, spec):
|
def to_dict(self, spec):
|
||||||
dct = self.get_default_attributes(spec)
|
dct = self.get_default_attributes(spec)
|
||||||
dct.update(self.get_bpmn_attributes(spec))
|
|
||||||
dct['task_spec'] = spec.task_spec
|
dct['task_spec'] = spec.task_spec
|
||||||
dct['cardinality'] = spec.cardinality
|
dct['cardinality'] = spec.cardinality
|
||||||
dct['data_input'] = self.registry.convert(spec.data_input)
|
dct['data_input'] = self.registry.convert(spec.data_input)
|
||||||
@ -294,8 +298,8 @@ class EventBasedGatewayConverter(EventConverter):
|
|||||||
|
|
||||||
|
|
||||||
DEFAULT_TASK_SPEC_CONVERTER_CLASSES = [
|
DEFAULT_TASK_SPEC_CONVERTER_CLASSES = [
|
||||||
SimpleTaskConverter,
|
SimpleBpmnTaskConverter,
|
||||||
StartTaskConverter,
|
BpmnStartTaskConverter,
|
||||||
EndJoinConverter,
|
EndJoinConverter,
|
||||||
NoneTaskConverter,
|
NoneTaskConverter,
|
||||||
UserTaskConverter,
|
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 json
|
||||||
import gzip
|
import gzip
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
from ..workflow import BpmnMessage, BpmnWorkflow
|
from SpiffWorkflow.task import Task
|
||||||
from ..specs.SubWorkflowTask import SubWorkflowTask
|
from SpiffWorkflow.bpmn.workflow import BpmnMessage, BpmnWorkflow
|
||||||
from ...task import Task
|
from SpiffWorkflow.bpmn.specs.mixins.subworkflow_task import SubWorkflowTask
|
||||||
|
|
||||||
from .migration.version_migration import MIGRATIONS
|
from .migration.version_migration import MIGRATIONS
|
||||||
from .helpers.registry import DefaultRegistry
|
from .helpers.registry import DefaultRegistry
|
||||||
@ -134,7 +153,7 @@ class BpmnWorkflowSerializer:
|
|||||||
dct = self.__get_dict(serialization, use_gzip)
|
dct = self.__get_dict(serialization, use_gzip)
|
||||||
if self.VERSION_KEY in dct:
|
if self.VERSION_KEY in dct:
|
||||||
return dct[self.VERSION_KEY]
|
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
|
return None
|
||||||
|
|
||||||
def workflow_to_dict(self, workflow):
|
def workflow_to_dict(self, workflow):
|
||||||
@ -260,7 +279,7 @@ class BpmnWorkflowSerializer:
|
|||||||
|
|
||||||
for child_task_id in task_dict['children']:
|
for child_task_id in task_dict['children']:
|
||||||
if child_task_id in process_dct['tasks']:
|
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)
|
self.task_tree_from_dict(process_dct, child_task_id, task, process, top, top_dct)
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"Task {task_id} ({task_spec.name}) has child {child_task_id}, but no such task exists")
|
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
|
# 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
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
# License as published by the Free Software Foundation; either
|
# 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
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
# Lesser General Public License for more details.
|
# 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
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
# License as published by the Free Software Foundation; either
|
# 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
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
# Lesser General Public License for more details.
|
# Lesser General Public License for more details.
|
||||||
@ -17,40 +18,20 @@
|
|||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||||
# 02110-1301 USA
|
# 02110-1301 USA
|
||||||
|
|
||||||
from .event_types import ThrowingEvent, CatchingEvent
|
from SpiffWorkflow.task import TaskState
|
||||||
from ..BpmnSpecMixin import BpmnSpecMixin
|
from SpiffWorkflow.specs.StartTask import StartTask
|
||||||
from ....specs.Simple import Simple
|
from SpiffWorkflow.bpmn.specs.bpmn_task_spec import BpmnTaskSpec
|
||||||
from ....task import TaskState
|
from SpiffWorkflow.bpmn.specs.mixins.unstructured_join import UnstructuredJoin
|
||||||
|
from SpiffWorkflow.bpmn.specs.mixins.events.intermediate_event import BoundaryEvent
|
||||||
class SendTask(ThrowingEvent):
|
|
||||||
|
|
||||||
@property
|
|
||||||
def spec_type(self):
|
|
||||||
return 'Send Task'
|
|
||||||
|
|
||||||
|
|
||||||
class ReceiveTask(CatchingEvent):
|
class BpmnStartTask(BpmnTaskSpec, StartTask):
|
||||||
|
pass
|
||||||
|
|
||||||
@property
|
class SimpleBpmnTask(BpmnTaskSpec):
|
||||||
def spec_type(self):
|
pass
|
||||||
return 'Receive Task'
|
|
||||||
|
|
||||||
|
class _BoundaryEventParent(BpmnTaskSpec):
|
||||||
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):
|
|
||||||
"""This task is inserted before a task with boundary events."""
|
"""This task is inserted before a task with boundary events."""
|
||||||
|
|
||||||
# I wonder if this would be better modelled as some type of join.
|
# 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.
|
# they're attached to be inputs rather than outputs.
|
||||||
|
|
||||||
def __init__(self, wf_spec, name, main_child_task_spec, **kwargs):
|
def __init__(self, wf_spec, name, main_child_task_spec, **kwargs):
|
||||||
|
super(_BoundaryEventParent, self).__init__(wf_spec, name, **kwargs)
|
||||||
super(_BoundaryEventParent, self).__init__(wf_spec, name)
|
|
||||||
self.main_child_task_spec = main_child_task_spec
|
self.main_child_task_spec = main_child_task_spec
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -93,39 +73,32 @@ class _BoundaryEventParent(Simple, BpmnSpecMixin):
|
|||||||
child._set_state(state)
|
child._set_state(state)
|
||||||
|
|
||||||
|
|
||||||
class BoundaryEvent(CatchingEvent):
|
class _EndJoin(UnstructuredJoin, BpmnTaskSpec):
|
||||||
"""Task Spec for a bpmn:boundaryEvent node."""
|
|
||||||
|
|
||||||
def __init__(self, wf_spec, name, event_definition, cancel_activity, **kwargs):
|
def _check_threshold_unstructured(self, my_task, force=False):
|
||||||
"""
|
# Look at the tree to find all ready and waiting tasks (excluding
|
||||||
Constructor.
|
# 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.
|
is_mine = False
|
||||||
"""
|
w = task.workflow
|
||||||
super(BoundaryEvent, self).__init__(wf_spec, name, event_definition, **kwargs)
|
if w == my_task.workflow:
|
||||||
self.cancel_activity = cancel_activity
|
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
|
return force or len(waiting_tasks) == 0, waiting_tasks
|
||||||
def spec_type(self):
|
|
||||||
interrupting = 'Interrupting' if self.cancel_activity else 'Non-Interrupting'
|
|
||||||
return f'{interrupting} {self.event_definition.event_type} Event'
|
|
||||||
|
|
||||||
def catches(self, my_task, event_definition, correlations=None):
|
def _run_hook(self, my_task):
|
||||||
# Boundary events should only be caught while waiting
|
result = super(_EndJoin, self)._run_hook(my_task)
|
||||||
return super(BoundaryEvent, self).catches(my_task, event_definition, correlations) and my_task.state == TaskState.WAITING
|
my_task.workflow.data.update(my_task.data)
|
||||||
|
return result
|
||||||
|
|
||||||
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()
|
|
@ -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
|
import logging
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
|
||||||
@ -8,13 +27,13 @@ data_log = logging.getLogger('spiff.data')
|
|||||||
|
|
||||||
class BpmnDataSpecification:
|
class BpmnDataSpecification:
|
||||||
|
|
||||||
def __init__(self, name, description=None):
|
def __init__(self, bpmn_id, bpmn_name=None):
|
||||||
"""
|
"""
|
||||||
:param name: the variable (the BPMN ID)
|
:param name: the variable (the BPMN ID)
|
||||||
:param description: a human readable name (the BPMN name)
|
:param description: a human readable name (the BPMN name)
|
||||||
"""
|
"""
|
||||||
self.name = name
|
self.bpmn_id = bpmn_id
|
||||||
self.description = description or name
|
self.bpmn_name = bpmn_name
|
||||||
# In the future, we can add schemas defining the objects here.
|
# In the future, we can add schemas defining the objects here.
|
||||||
|
|
||||||
def get(self, my_task, **kwargs):
|
def get(self, my_task, **kwargs):
|
||||||
@ -25,7 +44,7 @@ class BpmnDataSpecification:
|
|||||||
|
|
||||||
|
|
||||||
class BpmnDataStoreSpecification(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 name: the name of the task data variable and data store key (the BPMN ID)
|
||||||
:param description: the task description (the BPMN name)
|
:param description: the task description (the BPMN name)
|
||||||
@ -35,7 +54,7 @@ class BpmnDataStoreSpecification(BpmnDataSpecification):
|
|||||||
self.capacity = capacity or 0
|
self.capacity = capacity or 0
|
||||||
self.is_unlimited = is_unlimited or True
|
self.is_unlimited = is_unlimited or True
|
||||||
# In the future, we can add schemas defining the objects here.
|
# In the future, we can add schemas defining the objects here.
|
||||||
super().__init__(name, description)
|
super().__init__(bpmn_id, bpmn_name)
|
||||||
|
|
||||||
|
|
||||||
class BpmnIoSpecification:
|
class BpmnIoSpecification:
|
||||||
@ -50,20 +69,20 @@ class DataObject(BpmnDataSpecification):
|
|||||||
|
|
||||||
def get(self, my_task):
|
def get(self, my_task):
|
||||||
"""Copy a value form the workflow data to the task data."""
|
"""Copy a value form the workflow data to the task data."""
|
||||||
if self.name not in my_task.workflow.data:
|
if self.bpmn_id not in my_task.workflow.data:
|
||||||
message = f"The data object could not be read; '{self.name}' does not exist in the process."
|
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)
|
raise WorkflowDataException(message, my_task, data_input=self)
|
||||||
my_task.data[self.name] = deepcopy(my_task.workflow.data[self.name])
|
my_task.data[self.bpmn_id] = deepcopy(my_task.workflow.data[self.bpmn_id])
|
||||||
data_log.info(f'Read workflow variable {self.name}', extra=my_task.log_info())
|
data_log.info(f'Read workflow variable {self.bpmn_id}', extra=my_task.log_info())
|
||||||
|
|
||||||
def set(self, my_task):
|
def set(self, my_task):
|
||||||
"""Copy a value from the task data to the workflow data"""
|
"""Copy a value from the task data to the workflow data"""
|
||||||
if self.name not in my_task.data:
|
if self.bpmn_id not in my_task.data:
|
||||||
message = f"A data object could not be set; '{self.name}' not exist in the task."
|
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)
|
raise WorkflowDataException(message, my_task, data_output=self)
|
||||||
my_task.workflow.data[self.name] = deepcopy(my_task.data[self.name])
|
my_task.workflow.data[self.bpmn_id] = deepcopy(my_task.data[self.bpmn_id])
|
||||||
del my_task.data[self.name]
|
del my_task.data[self.bpmn_id]
|
||||||
data_log.info(f'Set workflow variable {self.name}', extra=my_task.log_info())
|
data_log.info(f'Set workflow variable {self.bpmn_id}', extra=my_task.log_info())
|
||||||
|
|
||||||
|
|
||||||
class TaskDataReference(BpmnDataSpecification):
|
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, 2023 Sartography
|
||||||
|
|
||||||
# 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
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
# License as published by the Free Software Foundation; either
|
# 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
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
# Lesser General Public License for more details.
|
# 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 copy import deepcopy
|
||||||
|
|
||||||
from SpiffWorkflow.exceptions import WorkflowException
|
from SpiffWorkflow.exceptions import WorkflowException
|
||||||
from SpiffWorkflow.task import TaskState
|
|
||||||
|
|
||||||
seconds_from_utc = dstoffset if isdst else tzoffset
|
seconds_from_utc = dstoffset if isdst else tzoffset
|
||||||
LOCALTZ = timezone(timedelta(seconds=-1 * seconds_from_utc))
|
LOCALTZ = timezone(timedelta(seconds=-1 * seconds_from_utc))
|
||||||
@ -42,11 +41,12 @@ class EventDefinition(object):
|
|||||||
and external flags.
|
and external flags.
|
||||||
Default catch behavior is to set the event to fired
|
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
|
# 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
|
# 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.
|
# I don't want to write a separate deserializer for every every type.
|
||||||
self.internal, self.external = True, True
|
self.internal, self.external = True, True
|
||||||
|
self.description = description
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def event_type(self):
|
def event_type(self):
|
||||||
@ -92,8 +92,8 @@ class NamedEventDefinition(EventDefinition):
|
|||||||
:param name: the name of this event
|
:param name: the name of this event
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, name):
|
def __init__(self, name, **kwargs):
|
||||||
super(NamedEventDefinition, self).__init__()
|
super(NamedEventDefinition, self).__init__(**kwargs)
|
||||||
self.name = name
|
self.name = name
|
||||||
|
|
||||||
def reset(self, my_task):
|
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
|
Cancel events are only handled by the outerworkflow, as they can only be used inside
|
||||||
of transaction subprocesses.
|
of transaction subprocesses.
|
||||||
"""
|
"""
|
||||||
def __init__(self):
|
def __init__(self, **kwargs):
|
||||||
super(CancelEventDefinition, self).__init__()
|
super(CancelEventDefinition, self).__init__(**kwargs)
|
||||||
self.internal = False
|
self.internal = False
|
||||||
|
|
||||||
@property
|
|
||||||
def event_type(self):
|
|
||||||
return 'Cancel'
|
|
||||||
|
|
||||||
|
|
||||||
class ErrorEventDefinition(NamedEventDefinition):
|
class ErrorEventDefinition(NamedEventDefinition):
|
||||||
"""
|
"""
|
||||||
@ -123,15 +119,11 @@ class ErrorEventDefinition(NamedEventDefinition):
|
|||||||
matched by code rather than name.
|
matched by code rather than name.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, name, error_code=None):
|
def __init__(self, name, error_code=None, **kwargs):
|
||||||
super(ErrorEventDefinition, self).__init__(name)
|
super(ErrorEventDefinition, self).__init__(name,**kwargs)
|
||||||
self.error_code = error_code
|
self.error_code = error_code
|
||||||
self.internal = False
|
self.internal = False
|
||||||
|
|
||||||
@property
|
|
||||||
def event_type(self):
|
|
||||||
return 'Error'
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
return self.__class__.__name__ == other.__class__.__name__ and self.error_code in [ None, other.error_code ]
|
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.
|
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.
|
Constructor.
|
||||||
|
|
||||||
:param escalation_code: The escalation code this event should
|
:param escalation_code: The escalation code this event should
|
||||||
react to. If None then all escalations will activate this event.
|
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
|
self.escalation_code = escalation_code
|
||||||
|
|
||||||
@property
|
|
||||||
def event_type(self):
|
|
||||||
return 'Escalation'
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
return self.__class__.__name__ == other.__class__.__name__ and self.escalation_code in [ None, other.escalation_code ]
|
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):
|
class MessageEventDefinition(NamedEventDefinition):
|
||||||
"""The default message event."""
|
"""The default message event."""
|
||||||
|
|
||||||
def __init__(self, name, correlation_properties=None):
|
def __init__(self, name, correlation_properties=None, **kwargs):
|
||||||
super().__init__(name)
|
super().__init__(name, **kwargs)
|
||||||
self.correlation_properties = correlation_properties or []
|
self.correlation_properties = correlation_properties or []
|
||||||
self.payload = None
|
self.payload = None
|
||||||
self.internal = False
|
self.internal = False
|
||||||
|
|
||||||
@property
|
def catch(self, my_task, event_definition=None):
|
||||||
def event_type(self):
|
|
||||||
return 'Message'
|
|
||||||
|
|
||||||
def catch(self, my_task, event_definition = None):
|
|
||||||
self.update_internal_data(my_task, event_definition)
|
self.update_internal_data(my_task, event_definition)
|
||||||
super(MessageEventDefinition, self).catch(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.
|
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
|
self.internal, self.external = False, False
|
||||||
|
|
||||||
@property
|
|
||||||
def event_type(self):
|
|
||||||
return 'Default'
|
|
||||||
|
|
||||||
def throw(self, my_task):
|
def throw(self, my_task):
|
||||||
"""It's a 'none' event, so nothing to throw."""
|
"""It's a 'none' event, so nothing to throw."""
|
||||||
pass
|
pass
|
||||||
@ -261,26 +242,21 @@ class NoneEventDefinition(EventDefinition):
|
|||||||
|
|
||||||
class SignalEventDefinition(NamedEventDefinition):
|
class SignalEventDefinition(NamedEventDefinition):
|
||||||
"""The SignalEventDefinition is the implementation of event definition used for Signal Events."""
|
"""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):
|
class TerminateEventDefinition(EventDefinition):
|
||||||
"""The TerminateEventDefinition is the implementation of event definition used for Termination Events."""
|
"""The TerminateEventDefinition is the implementation of event definition used for Termination Events."""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, **kwargs):
|
||||||
super(TerminateEventDefinition, self).__init__()
|
super(TerminateEventDefinition, self).__init__(**kwargs)
|
||||||
self.external = False
|
self.external = False
|
||||||
|
|
||||||
@property
|
|
||||||
def event_type(self):
|
|
||||||
return 'Terminate'
|
|
||||||
|
|
||||||
|
|
||||||
class TimerEventDefinition(EventDefinition):
|
class TimerEventDefinition(EventDefinition):
|
||||||
|
|
||||||
def __init__(self, name, expression):
|
def __init__(self, name, expression, **kwargs):
|
||||||
"""
|
"""
|
||||||
Constructor.
|
Constructor.
|
||||||
|
|
||||||
@ -288,7 +264,7 @@ class TimerEventDefinition(EventDefinition):
|
|||||||
|
|
||||||
:param expression: An ISO 8601 datetime or interval expression.
|
:param expression: An ISO 8601 datetime or interval expression.
|
||||||
"""
|
"""
|
||||||
super().__init__()
|
super().__init__(**kwargs)
|
||||||
self.name = name
|
self.name = name
|
||||||
self.expression = expression
|
self.expression = expression
|
||||||
|
|
||||||
@ -362,7 +338,7 @@ class TimerEventDefinition(EventDefinition):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_iso_week(expression):
|
def parse_iso_week(expression):
|
||||||
# https://en.wikipedia.org/wiki/ISO_8601#Week_dates
|
# 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()
|
year, month, day, ts = m.groups()
|
||||||
ds = datetime.fromisocalendar(int(year), int(month), int(day)).strftime('%Y-%m-%d')
|
ds = datetime.fromisocalendar(int(year), int(month), int(day)).strftime('%Y-%m-%d')
|
||||||
return TimerEventDefinition.get_datetime(ds + (ts or ''))
|
return TimerEventDefinition.get_datetime(ds + (ts or ''))
|
||||||
@ -409,10 +385,6 @@ class TimerEventDefinition(EventDefinition):
|
|||||||
class TimeDateEventDefinition(TimerEventDefinition):
|
class TimeDateEventDefinition(TimerEventDefinition):
|
||||||
"""A Timer event represented by a specific date/time."""
|
"""A Timer event represented by a specific date/time."""
|
||||||
|
|
||||||
@property
|
|
||||||
def event_type(self):
|
|
||||||
return 'Time Date Timer'
|
|
||||||
|
|
||||||
def has_fired(self, my_task):
|
def has_fired(self, my_task):
|
||||||
event_value = my_task._get_internal_data('event_value')
|
event_value = my_task._get_internal_data('event_value')
|
||||||
if event_value is None:
|
if event_value is None:
|
||||||
@ -429,10 +401,6 @@ class TimeDateEventDefinition(TimerEventDefinition):
|
|||||||
class DurationTimerEventDefinition(TimerEventDefinition):
|
class DurationTimerEventDefinition(TimerEventDefinition):
|
||||||
"""A timer event represented by a duration"""
|
"""A timer event represented by a duration"""
|
||||||
|
|
||||||
@property
|
|
||||||
def event_type(self):
|
|
||||||
return 'Duration Timer'
|
|
||||||
|
|
||||||
def has_fired(self, my_task):
|
def has_fired(self, my_task):
|
||||||
event_value = my_task._get_internal_data("event_value")
|
event_value = my_task._get_internal_data("event_value")
|
||||||
if event_value is None:
|
if event_value is None:
|
||||||
@ -450,10 +418,6 @@ class DurationTimerEventDefinition(TimerEventDefinition):
|
|||||||
|
|
||||||
class CycleTimerEventDefinition(TimerEventDefinition):
|
class CycleTimerEventDefinition(TimerEventDefinition):
|
||||||
|
|
||||||
@property
|
|
||||||
def event_type(self):
|
|
||||||
return 'Cycle Timer'
|
|
||||||
|
|
||||||
def cycle_complete(self, my_task):
|
def cycle_complete(self, my_task):
|
||||||
|
|
||||||
event_value = my_task._get_internal_data('event_value')
|
event_value = my_task._get_internal_data('event_value')
|
||||||
@ -489,15 +453,11 @@ class CycleTimerEventDefinition(TimerEventDefinition):
|
|||||||
|
|
||||||
class MultipleEventDefinition(EventDefinition):
|
class MultipleEventDefinition(EventDefinition):
|
||||||
|
|
||||||
def __init__(self, event_definitions=None, parallel=False):
|
def __init__(self, event_definitions=None, parallel=False, **kwargs):
|
||||||
super().__init__()
|
super().__init__(**kwargs)
|
||||||
self.event_definitions = event_definitions or []
|
self.event_definitions = event_definitions or []
|
||||||
self.parallel = parallel
|
self.parallel = parallel
|
||||||
|
|
||||||
@property
|
|
||||||
def event_type(self):
|
|
||||||
return 'Multiple'
|
|
||||||
|
|
||||||
def has_fired(self, my_task):
|
def has_fired(self, my_task):
|
||||||
|
|
||||||
seen_events = my_task.internal_data.get('seen_events', [])
|
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, 2023 Sartography
|
||||||
|
|
||||||
# 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
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
# License as published by the Free Software Foundation; either
|
# 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
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
# Lesser General Public License for more details.
|
# Lesser General Public License for more details.
|
||||||
@ -17,9 +17,9 @@
|
|||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||||
# 02110-1301 USA
|
# 02110-1301 USA
|
||||||
|
|
||||||
|
from SpiffWorkflow.task import TaskState
|
||||||
from .event_types import ThrowingEvent
|
from .event_types import ThrowingEvent
|
||||||
from .event_definitions import TerminateEventDefinition, CancelEventDefinition
|
from ...event_definitions import TerminateEventDefinition, CancelEventDefinition
|
||||||
from ....task import TaskState
|
|
||||||
|
|
||||||
|
|
||||||
class EndEvent(ThrowingEvent):
|
class EndEvent(ThrowingEvent):
|
||||||
@ -41,14 +41,6 @@ class EndEvent(ThrowingEvent):
|
|||||||
Gateways, one of the associated Events has been triggered.
|
Gateways, one of the associated Events has been triggered.
|
||||||
* There is no token remaining within the Process instance.
|
* 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):
|
def _on_complete_hook(self, my_task):
|
||||||
|
|
||||||
super(EndEvent, self)._on_complete_hook(my_task)
|
super(EndEvent, self)._on_complete_hook(my_task)
|
@ -1,13 +1,13 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# Copyright (C) 2012 Matthew Hampton, 2023 Sartography
|
||||||
|
|
||||||
# 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
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
# License as published by the Free Software Foundation; either
|
# 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
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
# Lesser General Public License for more details.
|
# Lesser General Public License for more details.
|
||||||
@ -16,22 +16,22 @@
|
|||||||
# License along with this library; if not, write to the Free Software
|
# License along with this library; if not, write to the Free Software
|
||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||||
# 02110-1301 USA
|
# 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."""
|
"""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.
|
Constructor.
|
||||||
|
|
||||||
:param event_definition: the EventDefinition that we must wait for.
|
: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
|
self.event_definition = event_definition
|
||||||
|
|
||||||
def catches(self, my_task, event_definition, correlations=None):
|
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.
|
definition, at which point we can update our task's state.
|
||||||
"""
|
"""
|
||||||
self.event_definition.catch(my_task, event_definition)
|
self.event_definition.catch(my_task, event_definition)
|
||||||
|
my_task.last_update_time = time.time()
|
||||||
my_task._set_state(TaskState.WAITING)
|
my_task._set_state(TaskState.WAITING)
|
||||||
|
|
||||||
def _update_hook(self, my_task):
|
def _update_hook(self, my_task):
|
||||||
@ -74,26 +75,17 @@ class CatchingEvent(Simple, BpmnSpecMixin):
|
|||||||
self.event_definition.reset(my_task)
|
self.event_definition.reset(my_task)
|
||||||
return super(CatchingEvent, self)._run_hook(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):
|
class ThrowingEvent(TaskSpec):
|
||||||
return True
|
|
||||||
|
|
||||||
def task_will_set_children_future(self, my_task):
|
|
||||||
my_task.internal_data = {}
|
|
||||||
|
|
||||||
|
|
||||||
class ThrowingEvent(Simple, BpmnSpecMixin):
|
|
||||||
"""Base Task Spec for Throwing Event nodes."""
|
"""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.
|
Constructor.
|
||||||
|
|
||||||
:param event_definition: the EventDefinition to be thrown.
|
: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
|
self.event_definition = event_definition
|
||||||
|
|
||||||
def _run_hook(self, my_task):
|
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, 2023 Sartography
|
||||||
|
|
||||||
# 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
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
# License as published by the Free Software Foundation; either
|
# 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
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
# Lesser General Public License for more details.
|
# Lesser General Public License for more details.
|
||||||
@ -17,27 +17,16 @@
|
|||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||||
# 02110-1301 USA
|
# 02110-1301 USA
|
||||||
|
|
||||||
|
from SpiffWorkflow.task import TaskState
|
||||||
from .event_types import CatchingEvent
|
from .event_types import CatchingEvent
|
||||||
from ....task import TaskState
|
|
||||||
|
|
||||||
|
|
||||||
class StartEvent(CatchingEvent):
|
class StartEvent(CatchingEvent):
|
||||||
"""Task Spec for a bpmn:startEvent node with an optional event definition."""
|
"""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):
|
def catch(self, my_task, event_definition):
|
||||||
|
|
||||||
# We might need to revisit a start event after it completes or
|
# 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 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:
|
if my_task.state == TaskState.COMPLETED or my_task.state == TaskState.CANCELLED:
|
||||||
my_task.set_children_future()
|
my_task.workflow.reset_from_task_id(my_task.id)
|
||||||
my_task._set_state(TaskState.WAITING)
|
|
||||||
|
|
||||||
super(StartEvent, self).catch(my_task, event_definition)
|
super(StartEvent, self).catch(my_task, event_definition)
|
||||||
|
|
@ -1,13 +1,13 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# Copyright (C) 2012 Matthew Hampton
|
# 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
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
# License as published by the Free Software Foundation; either
|
# 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
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
# Lesser General Public License for more details.
|
# Lesser General Public License for more details.
|
||||||
@ -17,12 +17,11 @@
|
|||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||||
# 02110-1301 USA
|
# 02110-1301 USA
|
||||||
|
|
||||||
from .BpmnSpecMixin import BpmnSpecMixin
|
from SpiffWorkflow.specs.ExclusiveChoice import ExclusiveChoice
|
||||||
from ...specs.ExclusiveChoice import ExclusiveChoice
|
from SpiffWorkflow.specs.MultiChoice import MultiChoice
|
||||||
from ...specs.MultiChoice import MultiChoice
|
|
||||||
|
|
||||||
|
|
||||||
class ExclusiveGateway(ExclusiveChoice, BpmnSpecMixin):
|
class ExclusiveGateway(ExclusiveChoice):
|
||||||
"""
|
"""
|
||||||
Task Spec for a bpmn:exclusiveGateway node.
|
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
|
# Bypass the check for no default output -- this is not required in BPMN
|
||||||
MultiChoice.test(self)
|
MultiChoice.test(self)
|
||||||
|
|
||||||
@property
|
|
||||||
def spec_type(self):
|
|
||||||
return 'Exclusive Gateway'
|
|
@ -1,13 +1,13 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# Copyright (C) 2012 Matthew Hampton, 2023 Sartography
|
||||||
|
|
||||||
# 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
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
# License as published by the Free Software Foundation; either
|
# 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
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
# Lesser General Public License for more details.
|
# Lesser General Public License for more details.
|
||||||
@ -17,10 +17,11 @@
|
|||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||||
# 02110-1301 USA
|
# 02110-1301 USA
|
||||||
|
|
||||||
from SpiffWorkflow.exceptions import WorkflowTaskException
|
from SpiffWorkflow.bpmn.exceptions import WorkflowTaskException
|
||||||
from ...task import TaskState
|
from SpiffWorkflow.task import TaskState
|
||||||
from .UnstructuredJoin import UnstructuredJoin
|
from SpiffWorkflow.specs.MultiChoice import MultiChoice
|
||||||
from ...specs.MultiChoice import MultiChoice
|
from .unstructured_join import UnstructuredJoin
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class InclusiveGateway(MultiChoice, UnstructuredJoin):
|
class InclusiveGateway(MultiChoice, UnstructuredJoin):
|
||||||
@ -113,10 +114,6 @@ class InclusiveGateway(MultiChoice, UnstructuredJoin):
|
|||||||
def _run_hook(self, my_task):
|
def _run_hook(self, my_task):
|
||||||
outputs = self._get_matching_outputs(my_task)
|
outputs = self._get_matching_outputs(my_task)
|
||||||
if len(outputs) == 0:
|
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)
|
my_task._sync_children(outputs, TaskState.FUTURE)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@property
|
|
||||||
def spec_type(self):
|
|
||||||
return 'Inclusive Gateway'
|
|
@ -1,13 +1,13 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# Copyright (C) 2012 Matthew Hampton
|
# 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
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
# License as published by the Free Software Foundation; either
|
# 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
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
# Lesser General Public License for more details.
|
# Lesser General Public License for more details.
|
||||||
@ -16,16 +16,13 @@
|
|||||||
# License along with this library; if not, write to the Free Software
|
# License along with this library; if not, write to the Free Software
|
||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||||
# 02110-1301 USA
|
# 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):
|
def __init__(self, wf_spec, bpmn_id, **kwargs):
|
||||||
return False
|
super().__init__(wf_spec, bpmn_id, **kwargs)
|
||||||
|
self.manual = True
|
||||||
@property
|
|
||||||
def spec_type(self):
|
|
||||||
return 'Manual Task'
|
|
@ -1,13 +1,13 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# Copyright (C) 2023 Sartography
|
||||||
|
|
||||||
# Copyright (C) 2020 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
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
# License as published by the Free Software Foundation; either
|
# 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
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
# Lesser General Public License for more details.
|
# Lesser General Public License for more details.
|
||||||
@ -20,17 +20,17 @@
|
|||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from collections.abc import Iterable, Sequence, Mapping, MutableSequence, MutableMapping
|
from collections.abc import Iterable, Sequence, Mapping, MutableSequence, MutableMapping
|
||||||
|
|
||||||
from ...task import TaskState
|
from SpiffWorkflow.task import TaskState
|
||||||
from ...util.deep_merge import DeepMerge
|
from SpiffWorkflow.specs.base import TaskSpec
|
||||||
from ..exceptions import WorkflowDataException
|
from SpiffWorkflow.util.deep_merge import DeepMerge
|
||||||
from .BpmnSpecMixin import BpmnSpecMixin
|
from SpiffWorkflow.bpmn.exceptions import WorkflowDataException
|
||||||
|
|
||||||
|
|
||||||
class LoopTask(BpmnSpecMixin):
|
class LoopTask(TaskSpec):
|
||||||
|
|
||||||
def process_children(self, my_task):
|
def process_children(self, my_task):
|
||||||
"""
|
"""
|
||||||
Handle any newly completed children and update merged tasks.
|
Handle any newly completed children and update merged tasks.
|
||||||
Returns a boolean indicating whether there is a child currently running
|
Returns a boolean indicating whether there is a child currently running
|
||||||
"""
|
"""
|
||||||
merged = my_task.internal_data.get('merged') or []
|
merged = my_task.internal_data.get('merged') or []
|
||||||
@ -42,7 +42,7 @@ class LoopTask(BpmnSpecMixin):
|
|||||||
elif not child._has_state(TaskState.FINISHED_MASK):
|
elif not child._has_state(TaskState.FINISHED_MASK):
|
||||||
child_running = True
|
child_running = True
|
||||||
my_task.internal_data['merged'] = merged
|
my_task.internal_data['merged'] = merged
|
||||||
return child_running
|
return child_running
|
||||||
|
|
||||||
def child_completed_action(self, my_task, child):
|
def child_completed_action(self, my_task, child):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
@ -50,8 +50,8 @@ class LoopTask(BpmnSpecMixin):
|
|||||||
|
|
||||||
class StandardLoopTask(LoopTask):
|
class StandardLoopTask(LoopTask):
|
||||||
|
|
||||||
def __init__(self, wf_spec, name, task_spec, maximum, condition, test_before, **kwargs):
|
def __init__(self, wf_spec, bpmn_id, task_spec, maximum, condition, test_before, **kwargs):
|
||||||
super().__init__(wf_spec, name, **kwargs)
|
super().__init__(wf_spec, bpmn_id, **kwargs)
|
||||||
self.task_spec = task_spec
|
self.task_spec = task_spec
|
||||||
self.maximum = maximum
|
self.maximum = maximum
|
||||||
self.condition = condition
|
self.condition = condition
|
||||||
@ -59,7 +59,9 @@ class StandardLoopTask(LoopTask):
|
|||||||
|
|
||||||
def _update_hook(self, my_task):
|
def _update_hook(self, my_task):
|
||||||
|
|
||||||
super()._update_hook(my_task)
|
if my_task.state != TaskState.WAITING:
|
||||||
|
super()._update_hook(my_task)
|
||||||
|
|
||||||
child_running = self.process_children(my_task)
|
child_running = self.process_children(my_task)
|
||||||
if child_running:
|
if child_running:
|
||||||
# We're in the middle of an iteration; we're not done and we can't create a new task
|
# 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:
|
if my_task.state != TaskState.WAITING:
|
||||||
my_task._set_state(TaskState.WAITING)
|
my_task._set_state(TaskState.WAITING)
|
||||||
task_spec = my_task.workflow.spec.task_specs[self.task_spec]
|
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)
|
child.data = deepcopy(my_task.data)
|
||||||
|
|
||||||
def child_completed_action(self, my_task, child):
|
def child_completed_action(self, my_task, child):
|
||||||
@ -92,11 +94,11 @@ class StandardLoopTask(LoopTask):
|
|||||||
|
|
||||||
class MultiInstanceTask(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,
|
data_output=None, input_item=None, output_item=None, condition=None,
|
||||||
**kwargs):
|
**kwargs):
|
||||||
|
|
||||||
super().__init__(wf_spec, name, **kwargs)
|
super().__init__(wf_spec, bpmn_id, **kwargs)
|
||||||
self.task_spec = task_spec
|
self.task_spec = task_spec
|
||||||
self.cardinality = cardinality
|
self.cardinality = cardinality
|
||||||
self.data_input = data_input
|
self.data_input = data_input
|
||||||
@ -109,13 +111,13 @@ class MultiInstanceTask(LoopTask):
|
|||||||
"""This merges child data into this task's data."""
|
"""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.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)
|
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')
|
key_or_index = child.internal_data.get('key_or_index')
|
||||||
data_output = my_task.data[self.data_output.name]
|
data_output = my_task.data[self.data_output.bpmn_id]
|
||||||
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 isinstance(data_output, Mapping) or data_input is data_output:
|
if key_or_index is not None and (isinstance(data_output, Mapping) or data_input is data_output):
|
||||||
data_output[key_or_index] = item
|
data_output[key_or_index] = item
|
||||||
else:
|
else:
|
||||||
data_output.append(item)
|
data_output.append(item)
|
||||||
@ -128,7 +130,7 @@ class MultiInstanceTask(LoopTask):
|
|||||||
child = my_task._add_child(task_spec, TaskState.WAITING)
|
child = my_task._add_child(task_spec, TaskState.WAITING)
|
||||||
child.data = deepcopy(my_task.data)
|
child.data = deepcopy(my_task.data)
|
||||||
if self.input_item is not None:
|
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:
|
if key_or_index is not None:
|
||||||
child.internal_data['key_or_index'] = key_or_index
|
child.internal_data['key_or_index'] = key_or_index
|
||||||
child.task_spec._update(child)
|
child.task_spec._update(child)
|
||||||
@ -142,7 +144,7 @@ class MultiInstanceTask(LoopTask):
|
|||||||
|
|
||||||
def init_data_output_with_input_data(self, my_task, input_data):
|
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 name not in my_task.data:
|
||||||
if isinstance(input_data, (MutableMapping, MutableSequence)):
|
if isinstance(input_data, (MutableMapping, MutableSequence)):
|
||||||
# We can use the same class if it implements __setitem__
|
# 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
|
# For all other types, we'll append to a list
|
||||||
my_task.data[name] = list()
|
my_task.data[name] = list()
|
||||||
else:
|
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)):
|
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)
|
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:
|
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):
|
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:
|
if name not in my_task.data:
|
||||||
my_task.data[name] = list()
|
my_task.data[name] = list()
|
||||||
elif not isinstance(my_task.data[name], MutableMapping) and len(my_task.data[name]) > 0:
|
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):
|
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')
|
remaining = my_task.internal_data.get('remaining')
|
||||||
|
|
||||||
if remaining is None:
|
if remaining is None:
|
||||||
@ -229,9 +231,9 @@ class SequentialMultiInstanceTask(MultiInstanceTask):
|
|||||||
|
|
||||||
def init_remaining_items(self, my_task):
|
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)
|
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
|
# 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):
|
if isinstance(input_data, Sequence):
|
||||||
@ -270,7 +272,7 @@ class SequentialMultiInstanceTask(MultiInstanceTask):
|
|||||||
|
|
||||||
|
|
||||||
class ParallelMultiInstanceTask(MultiInstanceTask):
|
class ParallelMultiInstanceTask(MultiInstanceTask):
|
||||||
|
|
||||||
def _update_hook(self, my_task):
|
def _update_hook(self, my_task):
|
||||||
|
|
||||||
if my_task.state != TaskState.WAITING:
|
if my_task.state != TaskState.WAITING:
|
||||||
@ -287,7 +289,7 @@ class ParallelMultiInstanceTask(MultiInstanceTask):
|
|||||||
|
|
||||||
def create_children(self, my_task):
|
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:
|
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
|
# 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):
|
if isinstance(data_input, Mapping):
|
||||||
@ -306,7 +308,7 @@ class ParallelMultiInstanceTask(MultiInstanceTask):
|
|||||||
|
|
||||||
if self.data_output is not None:
|
if self.data_output is not None:
|
||||||
if self.data_input 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:
|
else:
|
||||||
self.init_data_output_with_cardinality(my_task)
|
self.init_data_output_with_cardinality(my_task)
|
||||||
|
|
@ -1,13 +1,13 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# Copyright (C) 2012 Matthew Hampton, 2023 Sartography
|
||||||
|
|
||||||
# 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
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
# License as published by the Free Software Foundation; either
|
# 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
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
# Lesser General Public License for more details.
|
# Lesser General Public License for more details.
|
||||||
@ -16,16 +16,13 @@
|
|||||||
# License along with this library; if not, write to the Free Software
|
# License along with this library; if not, write to the Free Software
|
||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||||
# 02110-1301 USA
|
# 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):
|
def __init__(self, wf_spec, bpmn_id, **kwargs):
|
||||||
return False
|
super().__init__(wf_spec, bpmn_id, **kwargs)
|
||||||
|
self.manual = True
|
||||||
@property
|
|
||||||
def spec_type(self):
|
|
||||||
return 'Task'
|
|
@ -1,13 +1,13 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# Copyright (C) 2012 Matthew Hampton, 2023 Sartography
|
||||||
|
|
||||||
# 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
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
# License as published by the Free Software Foundation; either
|
# 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
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
# Lesser General Public License for more details.
|
# Lesser General Public License for more details.
|
||||||
@ -16,7 +16,8 @@
|
|||||||
# License along with this library; if not, write to the Free Software
|
# License along with this library; if not, write to the Free Software
|
||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||||
# 02110-1301 USA
|
# 02110-1301 USA
|
||||||
from .UnstructuredJoin import UnstructuredJoin
|
|
||||||
|
from .unstructured_join import UnstructuredJoin
|
||||||
|
|
||||||
|
|
||||||
class ParallelGateway(UnstructuredJoin):
|
class ParallelGateway(UnstructuredJoin):
|
||||||
@ -43,7 +44,3 @@ class ParallelGateway(UnstructuredJoin):
|
|||||||
def _check_threshold_unstructured(self, my_task, force=False):
|
def _check_threshold_unstructured(self, my_task, force=False):
|
||||||
completed_inputs, waiting_tasks = self._get_inputs_with_tokens(my_task)
|
completed_inputs, waiting_tasks = self._get_inputs_with_tokens(my_task)
|
||||||
return force or len(completed_inputs) >= len(self.inputs), waiting_tasks
|
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, 2023 Sartography
|
||||||
|
|
||||||
# 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
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
# License as published by the Free Software Foundation; either
|
# 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
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
# Lesser General Public License for more details.
|
# Lesser General Public License for more details.
|
||||||
@ -17,11 +17,10 @@
|
|||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||||
# 02110-1301 USA
|
# 02110-1301 USA
|
||||||
|
|
||||||
from .BpmnSpecMixin import BpmnSpecMixin
|
from SpiffWorkflow.specs.base import TaskSpec
|
||||||
from ...specs.Simple import Simple
|
|
||||||
|
|
||||||
|
|
||||||
class ScriptEngineTask(Simple, BpmnSpecMixin):
|
class ScriptEngineTask(TaskSpec):
|
||||||
"""Task Spec for a bpmn:scriptTask node"""
|
"""Task Spec for a bpmn:scriptTask node"""
|
||||||
|
|
||||||
def _execute(self, task):
|
def _execute(self, task):
|
||||||
@ -34,18 +33,14 @@ class ScriptEngineTask(Simple, BpmnSpecMixin):
|
|||||||
|
|
||||||
class ScriptTask(ScriptEngineTask):
|
class ScriptTask(ScriptEngineTask):
|
||||||
|
|
||||||
def __init__(self, wf_spec, name, script, **kwargs):
|
def __init__(self, wf_spec, bpmn_id, script, **kwargs):
|
||||||
"""
|
"""
|
||||||
Constructor.
|
Constructor.
|
||||||
|
|
||||||
:param script: the script that must be executed by the script engine.
|
: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
|
self.script = script
|
||||||
|
|
||||||
@property
|
|
||||||
def spec_type(self):
|
|
||||||
return 'Script Task'
|
|
||||||
|
|
||||||
def _execute(self, task):
|
def _execute(self, task):
|
||||||
return task.workflow.script_engine.execute(task, self.script)
|
return task.workflow.script_engine.execute(task, self.script)
|
@ -1,13 +1,13 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# Copyright (C) 2023 Sartography
|
||||||
|
|
||||||
# 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
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
# License as published by the Free Software Foundation; either
|
# 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
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
# Lesser General Public License for more details.
|
# Lesser General Public License for more details.
|
||||||
@ -17,19 +17,14 @@
|
|||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||||
# 02110-1301 USA
|
# 02110-1301 USA
|
||||||
|
|
||||||
from .BpmnSpecMixin import BpmnSpecMixin
|
from .script_task import ScriptEngineTask
|
||||||
from ...specs.Simple import Simple
|
|
||||||
|
|
||||||
|
|
||||||
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):
|
def __init__(self, wf_spec, bpmn_id, **kwargs):
|
||||||
return False
|
super(ServiceTask, self).__init__(wf_spec, bpmn_id, **kwargs)
|
||||||
|
|
||||||
@property
|
|
||||||
def spec_type(self):
|
|
||||||
return 'User Task'
|
|
@ -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 copy import deepcopy
|
||||||
|
|
||||||
from SpiffWorkflow.task import TaskState
|
from SpiffWorkflow.task import TaskState
|
||||||
from .BpmnSpecMixin import BpmnSpecMixin
|
from SpiffWorkflow.specs.base import TaskSpec
|
||||||
from ..exceptions import WorkflowDataException
|
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.
|
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.
|
Constructor.
|
||||||
|
|
||||||
:param bpmn_wf_spec: the BpmnProcessSpec for the sub process.
|
:param bpmn_wf_spec: the BpmnProcessSpec for the sub process.
|
||||||
:param bpmn_wf_class: the BpmnWorkflow class to instantiate
|
:param bpmn_wf_class: the BpmnWorkflow class to instantiate
|
||||||
"""
|
"""
|
||||||
super(SubWorkflowTask, self).__init__(wf_spec, name, **kwargs)
|
super(SubWorkflowTask, self).__init__(wf_spec, bpmn_id, **kwargs)
|
||||||
self.spec = subworkflow_spec
|
self.spec = subworkflow_spec
|
||||||
self.transaction = transaction
|
self.transaction = transaction
|
||||||
|
|
||||||
@property
|
|
||||||
def spec_type(self):
|
|
||||||
return 'Subprocess'
|
|
||||||
|
|
||||||
def _on_subworkflow_completed(self, subworkflow, my_task):
|
def _on_subworkflow_completed(self, subworkflow, my_task):
|
||||||
self.update_data(my_task, subworkflow)
|
self.update_data(my_task, subworkflow)
|
||||||
|
|
||||||
@ -46,7 +63,7 @@ class SubWorkflowTask(BpmnSpecMixin):
|
|||||||
|
|
||||||
def copy_data(self, my_task, subworkflow):
|
def copy_data(self, my_task, subworkflow):
|
||||||
# There is only one copy of any given data object, so it should be updated immediately
|
# There is only one copy of any given data object, so it should be updated immediately
|
||||||
# Doing this is actually a little problematic, because it gives parent processes access to
|
# Doing this is actually a little problematic, because it gives parent processes access to
|
||||||
# data objects defined in subprocesses.
|
# data objects defined in subprocesses.
|
||||||
# But our data management is already hopelessly messed up and in dire needs of reconsideration
|
# But our data management is already hopelessly messed up and in dire needs of reconsideration
|
||||||
if len(subworkflow.spec.data_objects) > 0:
|
if len(subworkflow.spec.data_objects) > 0:
|
||||||
@ -68,14 +85,11 @@ class SubWorkflowTask(BpmnSpecMixin):
|
|||||||
child.task_spec._update(child)
|
child.task_spec._update(child)
|
||||||
my_task._set_state(TaskState.WAITING)
|
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):
|
class CallActivity(SubWorkflowTask):
|
||||||
|
|
||||||
def __init__(self, wf_spec, name, subworkflow_spec, **kwargs):
|
def __init__(self, wf_spec, bpmn_id, subworkflow_spec, **kwargs):
|
||||||
super(CallActivity, self).__init__(wf_spec, name, subworkflow_spec, False, **kwargs)
|
super(CallActivity, self).__init__(wf_spec, bpmn_id, subworkflow_spec, False, **kwargs)
|
||||||
|
|
||||||
def copy_data(self, my_task, subworkflow):
|
def copy_data(self, my_task, subworkflow):
|
||||||
|
|
||||||
@ -86,13 +100,13 @@ class CallActivity(SubWorkflowTask):
|
|||||||
else:
|
else:
|
||||||
# Otherwise copy only task data with the specified names
|
# Otherwise copy only task data with the specified names
|
||||||
for var in subworkflow.spec.io_specification.data_inputs:
|
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(
|
raise WorkflowDataException(
|
||||||
"You are missing a required Data Input for a call activity.",
|
"You are missing a required Data Input for a call activity.",
|
||||||
task=my_task,
|
task=my_task,
|
||||||
data_input=var,
|
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):
|
def update_data(self, my_task, subworkflow):
|
||||||
|
|
||||||
@ -103,25 +117,36 @@ class CallActivity(SubWorkflowTask):
|
|||||||
end = subworkflow.get_tasks_from_spec_name('End', workflow=subworkflow)
|
end = subworkflow.get_tasks_from_spec_name('End', workflow=subworkflow)
|
||||||
# Otherwise only copy data with the specified names
|
# Otherwise only copy data with the specified names
|
||||||
for var in subworkflow.spec.io_specification.data_outputs:
|
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(
|
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,
|
task=my_task,
|
||||||
data_output=var,
|
data_output=var,
|
||||||
)
|
)
|
||||||
my_task.data[var.name] = end[0].data[var.name]
|
my_task.data[var.bpmn_id] = end[0].data[var.bpmn_id]
|
||||||
|
|
||||||
@property
|
|
||||||
def spec_type(self):
|
|
||||||
return 'Call Activity'
|
|
||||||
|
|
||||||
|
|
||||||
class TransactionSubprocess(SubWorkflowTask):
|
class TransactionSubprocess(SubWorkflowTask):
|
||||||
|
|
||||||
def __init__(self, wf_spec, name, subworkflow_spec, **kwargs):
|
def __init__(self, wf_spec, bpmn_id, subworkflow_spec, **kwargs):
|
||||||
super(TransactionSubprocess, self).__init__(wf_spec, name, subworkflow_spec, True, **kwargs)
|
super(TransactionSubprocess, self).__init__(wf_spec, bpmn_id, subworkflow_spec, True, **kwargs)
|
||||||
|
|
||||||
@property
|
|
||||||
def spec_type(self):
|
|
||||||
return 'Transactional Subprocess'
|
|
||||||
|
|
||||||
|
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, 2023 Sartography
|
||||||
|
|
||||||
# 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
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
# License as published by the Free Software Foundation; either
|
# 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
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
# Lesser General Public License for more details.
|
# Lesser General Public License for more details.
|
||||||
@ -17,12 +17,11 @@
|
|||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||||
# 02110-1301 USA
|
# 02110-1301 USA
|
||||||
|
|
||||||
from ...task import TaskState
|
from SpiffWorkflow.task import TaskState
|
||||||
from .BpmnSpecMixin import BpmnSpecMixin
|
from SpiffWorkflow.specs.Join import Join
|
||||||
from ...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
|
A helper subclass of Join that makes it work in a slightly friendlier way
|
||||||
for the BPMN style threading
|
for the BPMN style threading
|
||||||
@ -83,13 +82,3 @@ class UnstructuredJoin(Join, BpmnSpecMixin):
|
|||||||
task._drop_children()
|
task._drop_children()
|
||||||
else:
|
else:
|
||||||
task.data.update(collected_data)
|
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, 2023 Sartography
|
||||||
# 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
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
# License as published by the Free Software Foundation; either
|
# 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
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
# Lesser General Public License for more details.
|
# Lesser General Public License for more details.
|
||||||
@ -15,21 +16,26 @@
|
|||||||
# License along with this library; if not, write to the Free Software
|
# License along with this library; if not, write to the Free Software
|
||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||||
# 02110-1301 USA
|
# 02110-1301 USA
|
||||||
|
|
||||||
import copy
|
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,
|
MessageEventDefinition,
|
||||||
MultipleEventDefinition,
|
MultipleEventDefinition,
|
||||||
NamedEventDefinition,
|
NamedEventDefinition,
|
||||||
TimerEventDefinition,
|
TimerEventDefinition,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from SpiffWorkflow.bpmn.specs.control import _BoundaryEventParent
|
||||||
from .PythonScriptEngine import PythonScriptEngine
|
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:
|
class BpmnMessage:
|
||||||
@ -86,7 +92,8 @@ class BpmnWorkflow(Workflow):
|
|||||||
|
|
||||||
def delete_subprocess(self, my_task):
|
def delete_subprocess(self, my_task):
|
||||||
workflow = self._get_outermost_workflow(my_task)
|
workflow = self._get_outermost_workflow(my_task)
|
||||||
del workflow.subprocesses[my_task.id]
|
if my_task.id in workflow.subprocesses:
|
||||||
|
del workflow.subprocesses[my_task.id]
|
||||||
|
|
||||||
def get_subprocess(self, my_task):
|
def get_subprocess(self, my_task):
|
||||||
workflow = self._get_outermost_workflow(my_task)
|
workflow = self._get_outermost_workflow(my_task)
|
||||||
@ -94,7 +101,17 @@ class BpmnWorkflow(Workflow):
|
|||||||
|
|
||||||
def connect_subprocess(self, spec_name, name):
|
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
|
# 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)
|
self.spec.start.connect(new)
|
||||||
task = Task(self, new)
|
task = Task(self, new)
|
||||||
start = self.get_tasks_from_spec_name('Start', workflow=self)[0]
|
start = self.get_tasks_from_spec_name('Start', workflow=self)[0]
|
||||||
@ -135,7 +152,7 @@ class BpmnWorkflow(Workflow):
|
|||||||
:param event_definition: the thrown event
|
:param event_definition: the thrown event
|
||||||
"""
|
"""
|
||||||
# Start a subprocess for known specs with start events that catch this
|
# 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.
|
# be immutable, but I see no other way of doing this.
|
||||||
for name, spec in self.subprocess_specs.items():
|
for name, spec in self.subprocess_specs.items():
|
||||||
for task_spec in list(spec.task_specs.values()):
|
for task_spec in list(spec.task_specs.values()):
|
||||||
@ -183,8 +200,8 @@ class BpmnWorkflow(Workflow):
|
|||||||
conversation = task.task_spec.event_definition.conversation()
|
conversation = task.task_spec.event_definition.conversation()
|
||||||
if not conversation:
|
if not conversation:
|
||||||
raise WorkflowTaskException(
|
raise WorkflowTaskException(
|
||||||
f"The waiting task and message payload can not be matched to any correlation key (conversation topic). "
|
"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)
|
"And is therefor unable to respond to the given message.", task)
|
||||||
updated_props = self._correlate(conversation, payload, task)
|
updated_props = self._correlate(conversation, payload, task)
|
||||||
task.task_spec.catch(task, event_definition)
|
task.task_spec.catch(task, event_definition)
|
||||||
self.refresh_waiting_tasks()
|
self.refresh_waiting_tasks()
|
||||||
@ -229,7 +246,7 @@ class BpmnWorkflow(Workflow):
|
|||||||
elif isinstance(event_definition, MessageEventDefinition):
|
elif isinstance(event_definition, MessageEventDefinition):
|
||||||
value = event_definition.correlation_properties
|
value = event_definition.correlation_properties
|
||||||
events.append({
|
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,
|
'name': event_definition.name if isinstance(event_definition, NamedEventDefinition) else None,
|
||||||
'value': value
|
'value': value
|
||||||
})
|
})
|
||||||
@ -246,7 +263,7 @@ class BpmnWorkflow(Workflow):
|
|||||||
:param will_complete_task: Callback that will be called prior to completing a task
|
: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
|
: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:
|
while engine_steps:
|
||||||
for task in engine_steps:
|
for task in engine_steps:
|
||||||
if will_complete_task is not None:
|
if will_complete_task is not None:
|
||||||
@ -256,7 +273,7 @@ class BpmnWorkflow(Workflow):
|
|||||||
did_complete_task(task)
|
did_complete_task(task)
|
||||||
if task.task_spec.name == exit_at:
|
if task.task_spec.name == exit_at:
|
||||||
return task
|
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,
|
def refresh_waiting_tasks(self,
|
||||||
will_refresh_task=None,
|
will_refresh_task=None,
|
||||||
@ -292,10 +309,10 @@ class BpmnWorkflow(Workflow):
|
|||||||
# almost surely be in a different state than the tasks we want
|
# almost surely be in a different state than the tasks we want
|
||||||
for task in Workflow.get_tasks_iterator(wf):
|
for task in Workflow.get_tasks_iterator(wf):
|
||||||
subprocess = top.subprocesses.get(task.id)
|
subprocess = top.subprocesses.get(task.id)
|
||||||
if subprocess is not None:
|
|
||||||
tasks.extend(subprocess.get_tasks(state, subprocess))
|
|
||||||
if task._has_state(state):
|
if task._has_state(state):
|
||||||
tasks.append(task)
|
tasks.append(task)
|
||||||
|
if subprocess is not None:
|
||||||
|
tasks.extend(subprocess.get_tasks(state, subprocess))
|
||||||
return tasks
|
return tasks
|
||||||
|
|
||||||
def get_task_from_id(self, task_id, workflow=None):
|
def get_task_from_id(self, task_id, workflow=None):
|
||||||
@ -307,12 +324,10 @@ class BpmnWorkflow(Workflow):
|
|||||||
def get_ready_user_tasks(self, lane=None, workflow=None):
|
def get_ready_user_tasks(self, lane=None, workflow=None):
|
||||||
"""Returns a list of User Tasks that are READY for user action"""
|
"""Returns a list of User Tasks that are READY for user action"""
|
||||||
if lane is not None:
|
if lane is not None:
|
||||||
return [t for t in self.get_tasks(TaskState.READY, workflow)
|
return [t for t in self.get_tasks(TaskState.READY, workflow)
|
||||||
if (not self._is_engine_task(t.task_spec))
|
if t.task_spec.manual and t.task_spec.lane == lane]
|
||||||
and (t.task_spec.lane == lane)]
|
|
||||||
else:
|
else:
|
||||||
return [t for t in self.get_tasks(TaskState.READY, workflow)
|
return [t for t in self.get_tasks(TaskState.READY, workflow) if t.task_spec.manual]
|
||||||
if not self._is_engine_task(t.task_spec)]
|
|
||||||
|
|
||||||
def get_waiting_tasks(self, workflow=None):
|
def get_waiting_tasks(self, workflow=None):
|
||||||
"""Returns a list of all WAITING tasks"""
|
"""Returns a list of all WAITING tasks"""
|
||||||
@ -321,5 +336,58 @@ class BpmnWorkflow(Workflow):
|
|||||||
def get_catching_tasks(self, workflow=None):
|
def get_catching_tasks(self, workflow=None):
|
||||||
return [task for task in self.get_tasks(workflow=workflow) if isinstance(task.task_spec, CatchingEvent)]
|
return [task for task in self.get_tasks(workflow=workflow) if isinstance(task.task_spec, CatchingEvent)]
|
||||||
|
|
||||||
def _is_engine_task(self, task_spec):
|
def reset_from_task_id(self, task_id, data=None):
|
||||||
return (not hasattr(task_spec, 'is_engine_task') or task_spec.is_engine_task())
|
"""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 @@
|
|||||||
|
# Copyright (C) 2023 Sartography
|
||||||
from SpiffWorkflow.bpmn.parser.BpmnParser import full_tag, DEFAULT_NSMAP
|
#
|
||||||
|
# This file is part of SpiffWorkflow.
|
||||||
from SpiffWorkflow.bpmn.specs.ManualTask import ManualTask
|
#
|
||||||
from SpiffWorkflow.bpmn.specs.NoneTask import NoneTask
|
# SpiffWorkflow is free software; you can redistribute it and/or
|
||||||
from SpiffWorkflow.bpmn.specs.ScriptTask import ScriptTask
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
from SpiffWorkflow.bpmn.specs.SubWorkflowTask import CallActivity, TransactionSubprocess
|
# 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.parser.BpmnDmnParser import BpmnDmnParser
|
||||||
from SpiffWorkflow.dmn.specs.BusinessRuleTask import BusinessRuleTask
|
from SpiffWorkflow.bpmn.parser.BpmnParser import full_tag, DEFAULT_NSMAP
|
||||||
from SpiffWorkflow.camunda.specs.UserTask import UserTask
|
|
||||||
|
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 (
|
from SpiffWorkflow.camunda.parser.task_spec import (
|
||||||
CamundaTaskParser,
|
CamundaTaskParser,
|
||||||
BusinessRuleTaskParser,
|
BusinessRuleTaskParser,
|
||||||
@ -18,10 +44,6 @@ from SpiffWorkflow.camunda.parser.task_spec import (
|
|||||||
ScriptTaskParser,
|
ScriptTaskParser,
|
||||||
CAMUNDA_MODEL_NS
|
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 (
|
from .event_parsers import (
|
||||||
CamundaStartEventParser,
|
CamundaStartEventParser,
|
||||||
CamundaEndEventParser,
|
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
|
# Copyright (C) 2023 Sartography
|
||||||
from SpiffWorkflow.bpmn.parser.event_parsers import StartEventParser, EndEventParser, \
|
#
|
||||||
IntermediateCatchEventParser, IntermediateThrowEventParser, BoundaryEventParser
|
# This file is part of SpiffWorkflow.
|
||||||
from SpiffWorkflow.camunda.specs.events.event_definitions import MessageEventDefinition
|
#
|
||||||
|
# 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
|
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.specs.data_spec import TaskDataReference
|
||||||
from SpiffWorkflow.bpmn.parser.util import one
|
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.TaskParser import TaskParser
|
||||||
from SpiffWorkflow.bpmn.parser.task_parsers import SubprocessParser
|
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.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'
|
CAMUNDA_MODEL_NS = 'http://camunda.org/schema/1.0/bpmn'
|
||||||
|
|
||||||
@ -66,11 +83,9 @@ class BusinessRuleTaskParser(CamundaTaskParser):
|
|||||||
|
|
||||||
def create_task(self):
|
def create_task(self):
|
||||||
decision_ref = self.get_decision_ref(self.node)
|
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),
|
dmnEngine=self.process_parser.parser.get_engine(decision_ref, self.node),
|
||||||
lane=self.lane, position=self.position,
|
**self.bpmn_attributes)
|
||||||
description=self.node.get('name', None),
|
|
||||||
)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_decision_ref(node):
|
def get_decision_ref(node):
|
||||||
@ -82,10 +97,7 @@ class UserTaskParser(CamundaTaskParser):
|
|||||||
|
|
||||||
def create_task(self):
|
def create_task(self):
|
||||||
form = self.get_form()
|
form = self.get_form()
|
||||||
return self.spec_class(self.spec, self.get_task_spec_name(), form,
|
return self.spec_class(self.spec, self.bpmn_id, form=form, **self.bpmn_attributes)
|
||||||
lane=self.lane,
|
|
||||||
position=self.position,
|
|
||||||
description=self.node.get('name', None))
|
|
||||||
|
|
||||||
def get_form(self):
|
def get_form(self):
|
||||||
"""Camunda provides a simple form builder, this will extract the
|
"""Camunda provides a simple form builder, this will extract the
|
||||||
@ -138,10 +150,7 @@ class SubWorkflowParser(CamundaTaskParser):
|
|||||||
|
|
||||||
def create_task(self):
|
def create_task(self):
|
||||||
subworkflow_spec = SubprocessParser.get_subprocess_spec(self)
|
subworkflow_spec = SubprocessParser.get_subprocess_spec(self)
|
||||||
return self.spec_class(
|
return self.spec_class(self.spec, self.bpmn_id, subworkflow_spec=subworkflow_spec, **self.bpmn_attributes)
|
||||||
self.spec, self.get_task_spec_name(), subworkflow_spec,
|
|
||||||
lane=self.lane, position=self.position,
|
|
||||||
description=self.node.get('name', None))
|
|
||||||
|
|
||||||
|
|
||||||
class CallActivityParser(CamundaTaskParser):
|
class CallActivityParser(CamundaTaskParser):
|
||||||
@ -149,10 +158,7 @@ class CallActivityParser(CamundaTaskParser):
|
|||||||
|
|
||||||
def create_task(self):
|
def create_task(self):
|
||||||
subworkflow_spec = SubprocessParser.get_call_activity_spec(self)
|
subworkflow_spec = SubprocessParser.get_call_activity_spec(self)
|
||||||
return self.spec_class(
|
return self.spec_class(self.spec, self.bpmn_id, subworkflow_spec=subworkflow_spec, **self.bpmn_attributes)
|
||||||
self.spec, self.get_task_spec_name(), subworkflow_spec,
|
|
||||||
lane=self.lane, position=self.position,
|
|
||||||
description=self.node.get('name', None))
|
|
||||||
|
|
||||||
|
|
||||||
class ScriptTaskParser(TaskParser):
|
class ScriptTaskParser(TaskParser):
|
||||||
@ -162,10 +168,7 @@ class ScriptTaskParser(TaskParser):
|
|||||||
|
|
||||||
def create_task(self):
|
def create_task(self):
|
||||||
script = self.get_script()
|
script = self.get_script()
|
||||||
return self.spec_class(self.spec, self.get_task_spec_name(), script,
|
return self.spec_class(self.spec, self.bpmn_id, script=script, **self.bpmn_attributes)
|
||||||
lane=self.lane,
|
|
||||||
position=self.position,
|
|
||||||
description=self.node.get('name', None))
|
|
||||||
|
|
||||||
def get_script(self):
|
def get_script(self):
|
||||||
"""
|
"""
|
||||||
@ -177,5 +180,5 @@ class ScriptTaskParser(TaskParser):
|
|||||||
return one(self.xpath('.//bpmn:script')).text
|
return one(self.xpath('.//bpmn:script')).text
|
||||||
except AssertionError as ae:
|
except AssertionError as ae:
|
||||||
raise ValidationException(
|
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)
|
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 copy import deepcopy
|
||||||
|
|
||||||
from SpiffWorkflow.bpmn.serializer.workflow import DEFAULT_SPEC_CONFIG
|
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 SpiffWorkflow.bpmn.serializer.event_definition import MessageEventDefinitionConverter as DefaultMessageEventConverter
|
||||||
|
|
||||||
|
from .task_spec import (
|
||||||
from .task_spec import UserTaskConverter, ParallelMultiInstanceTaskConverter, SequentialMultiInstanceTaskConverter
|
UserTaskConverter,
|
||||||
|
BusinessRuleTaskConverter,
|
||||||
|
ParallelMultiInstanceTaskConverter,
|
||||||
|
SequentialMultiInstanceTaskConverter
|
||||||
|
)
|
||||||
from .event_definition import MessageEventDefinitionConverter
|
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'].append(ParallelMultiInstanceTaskConverter)
|
||||||
CAMUNDA_SPEC_CONFIG['task_specs'].remove(DefaultSequentialMIConverter)
|
CAMUNDA_SPEC_CONFIG['task_specs'].remove(DefaultSequentialMIConverter)
|
||||||
CAMUNDA_SPEC_CONFIG['task_specs'].append(SequentialMultiInstanceTaskConverter)
|
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'].remove(DefaultMessageEventConverter)
|
||||||
CAMUNDA_SPEC_CONFIG['event_definitions'].append(MessageEventDefinitionConverter)
|
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 SpiffWorkflow.bpmn.serializer.helpers.spec import EventDefinitionConverter
|
||||||
from ..specs.events.event_definitions import MessageEventDefinition
|
from ..specs.event_definitions import MessageEventDefinition
|
||||||
|
|
||||||
|
|
||||||
class MessageEventDefinitionConverter(EventDefinitionConverter):
|
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.helpers.spec import TaskSpecConverter
|
||||||
from SpiffWorkflow.bpmn.serializer.task_spec import MultiInstanceTaskConverter
|
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
|
from SpiffWorkflow.camunda.specs.multiinstance_task import ParallelMultiInstanceTask, SequentialMultiInstanceTask
|
||||||
|
|
||||||
class UserTaskConverter(TaskSpecConverter):
|
class UserTaskConverter(TaskSpecConverter):
|
||||||
@ -11,7 +32,6 @@ class UserTaskConverter(TaskSpecConverter):
|
|||||||
|
|
||||||
def to_dict(self, spec):
|
def to_dict(self, spec):
|
||||||
dct = self.get_default_attributes(spec)
|
dct = self.get_default_attributes(spec)
|
||||||
dct.update(self.get_bpmn_attributes(spec))
|
|
||||||
dct['form'] = self.form_to_dict(spec.form)
|
dct['form'] = self.form_to_dict(spec.form)
|
||||||
return dct
|
return dct
|
||||||
|
|
||||||
@ -36,6 +56,10 @@ class UserTaskConverter(TaskSpecConverter):
|
|||||||
return dct
|
return dct
|
||||||
|
|
||||||
|
|
||||||
|
class BusinessRuleTaskConverter(BaseBusinessRuleTaskConverter):
|
||||||
|
def __init__(self, registry):
|
||||||
|
super().__init__(BusinessRuleTask, registry)
|
||||||
|
|
||||||
class ParallelMultiInstanceTaskConverter(MultiInstanceTaskConverter):
|
class ParallelMultiInstanceTaskConverter(MultiInstanceTaskConverter):
|
||||||
def __init__(self, registry):
|
def __init__(self, registry):
|
||||||
super().__init__(ParallelMultiInstanceTask, 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):
|
class MessageEventDefinition(MessageEventDefinition):
|
||||||
"""
|
"""
|
||||||
@ -10,9 +29,9 @@ class MessageEventDefinition(MessageEventDefinition):
|
|||||||
# this should be revisited: for one thing, we're relying on some Camunda-specific
|
# this should be revisited: for one thing, we're relying on some Camunda-specific
|
||||||
# properties.
|
# 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.payload = payload
|
||||||
self.result_var = result_var
|
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.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.data_spec import TaskDataReference
|
||||||
|
|
||||||
from SpiffWorkflow.bpmn.specs.MultiInstanceTask import (
|
from SpiffWorkflow.bpmn.specs.defaults import (
|
||||||
SequentialMultiInstanceTask as BpmnSequentialMITask,
|
SequentialMultiInstanceTask as BpmnSequentialMITask,
|
||||||
ParallelMultiInstanceTask as BpmnParallelMITask,
|
ParallelMultiInstanceTask as BpmnParallelMITask,
|
||||||
)
|
)
|
||||||
@ -20,19 +39,19 @@ def update_task_spec(my_task):
|
|||||||
|
|
||||||
if task_spec.cardinality is None:
|
if task_spec.cardinality is None:
|
||||||
# Use the same collection for input and output
|
# Use the same collection for input and output
|
||||||
task_spec.data_input = TaskDataReference(task_spec.data_output.name)
|
task_spec.data_input = TaskDataReference(task_spec.data_output.bpmn_id)
|
||||||
task_spec.input_item = TaskDataReference(task_spec.output_item.name)
|
task_spec.input_item = TaskDataReference(task_spec.output_item.bpmn_id)
|
||||||
else:
|
else:
|
||||||
cardinality = my_task.workflow.script_engine.evaluate(my_task, task_spec.cardinality)
|
cardinality = my_task.workflow.script_engine.evaluate(my_task, task_spec.cardinality)
|
||||||
if not isinstance(cardinality, int):
|
if not isinstance(cardinality, int):
|
||||||
# The input data was supplied via "cardinality"
|
# The input data was supplied via "cardinality"
|
||||||
# We'll use the same reference for input and output item
|
# We'll use the same reference for input and output item
|
||||||
task_spec.data_input = TaskDataReference(task_spec.cardinality)
|
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
|
task_spec.cardinality = None
|
||||||
else:
|
else:
|
||||||
# This will be the index
|
# 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):
|
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
|
class UserTask(DefaultUserTask):
|
||||||
from ...bpmn.specs.BpmnSpecMixin import BpmnSpecMixin
|
|
||||||
|
|
||||||
|
|
||||||
class UserTask(UserTask, BpmnSpecMixin):
|
|
||||||
"""Task Spec for a bpmn:userTask node with Camunda forms."""
|
"""Task Spec for a bpmn:userTask node with Camunda forms."""
|
||||||
|
|
||||||
def __init__(self, wf_spec, name, form, **kwargs):
|
def __init__(self, wf_spec, name, form, **kwargs):
|
||||||
@ -17,12 +32,6 @@ class UserTask(UserTask, BpmnSpecMixin):
|
|||||||
super(UserTask, self).__init__(wf_spec, name, **kwargs)
|
super(UserTask, self).__init__(wf_spec, name, **kwargs)
|
||||||
self.form = form
|
self.form = form
|
||||||
|
|
||||||
def _on_trigger(self, my_task):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def is_engine_task(self):
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
class FormField(object):
|
class FormField(object):
|
||||||
def __init__(self, form_type="text"):
|
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 logging
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
from SpiffWorkflow.exceptions import SpiffWorkflowException
|
||||||
|
from SpiffWorkflow.bpmn.exceptions import WorkflowTaskException
|
||||||
|
|
||||||
from ..specs.model import HitPolicy
|
from ..specs.model import HitPolicy
|
||||||
from ...exceptions import SpiffWorkflowException, WorkflowTaskException
|
|
||||||
from ...util import levenshtein
|
|
||||||
from ...workflow import WorkflowException
|
|
||||||
|
|
||||||
logger = logging.getLogger('spiff.dmn')
|
logger = logging.getLogger('spiff.dmn')
|
||||||
|
|
||||||
@ -37,7 +56,7 @@ class DMNEngine:
|
|||||||
for rule in matched_rules:
|
for rule in matched_rules:
|
||||||
rule_output = rule.output_as_dict(task)
|
rule_output = rule.output_as_dict(task)
|
||||||
for key in rule_output.keys():
|
for key in rule_output.keys():
|
||||||
if not key in result:
|
if key not in result:
|
||||||
result[key] = []
|
result[key] = []
|
||||||
result[key].append(rule_output[key])
|
result[key].append(rule_output[key])
|
||||||
elif len(matched_rules) > 0:
|
elif len(matched_rules) > 0:
|
||||||
@ -98,7 +117,7 @@ class DMNEngine:
|
|||||||
# NOTE: It should only do this replacement outside of quotes.
|
# NOTE: It should only do this replacement outside of quotes.
|
||||||
# for example, provided "This thing?" in quotes, it should not
|
# for example, provided "This thing?" in quotes, it should not
|
||||||
# do the replacement.
|
# do the replacement.
|
||||||
match_expr = re.sub('(\?)(?=(?:[^\'"]|[\'"][^\'"]*[\'"])*$)', 'dmninputexpr', match_expr)
|
match_expr = re.sub(r'(\?)(?=(?:[^\'"]|[\'"][^\'"]*[\'"])*$)', 'dmninputexpr', match_expr)
|
||||||
if 'dmninputexpr' in match_expr:
|
if 'dmninputexpr' in match_expr:
|
||||||
external_methods = {
|
external_methods = {
|
||||||
'dmninputexpr': script_engine.evaluate(task, input_expr)
|
'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 glob
|
||||||
import os
|
import os
|
||||||
|
|
||||||
@ -53,7 +72,7 @@ class BpmnDmnParser(BpmnParser):
|
|||||||
validator.validate(node, filename)
|
validator.validate(node, filename)
|
||||||
|
|
||||||
dmn_parser = DMNParser(self, node, nsmap, filename=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
|
self.dmn_parsers_by_name[dmn_parser.get_name()] = dmn_parser
|
||||||
|
|
||||||
def add_dmn_file(self, filename):
|
def add_dmn_file(self, filename):
|
||||||
@ -75,7 +94,19 @@ class BpmnDmnParser(BpmnParser):
|
|||||||
"""
|
"""
|
||||||
for filename in filenames:
|
for filename in filenames:
|
||||||
with open(filename, 'r') as f:
|
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):
|
def get_dependencies(self):
|
||||||
return self.process_dependencies.union(self.dmn_dependencies)
|
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
|
import ast
|
||||||
|
|
||||||
from SpiffWorkflow.bpmn.parser.node_parser import NodeParser, DEFAULT_NSMAP
|
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, \
|
from SpiffWorkflow.dmn.specs.model import (
|
||||||
OutputEntry, Input, Output, Rule
|
Decision,
|
||||||
|
DecisionTable,
|
||||||
|
InputEntry,
|
||||||
|
OutputEntry,
|
||||||
|
Input,
|
||||||
|
Output,
|
||||||
|
Rule,
|
||||||
|
)
|
||||||
|
|
||||||
def get_dmn_ns(node):
|
def get_dmn_ns(node):
|
||||||
"""
|
"""
|
||||||
@ -55,7 +81,8 @@ class DMNParser(NodeParser):
|
|||||||
def parse(self):
|
def parse(self):
|
||||||
self.decision = self._parse_decision(self.node.findall('{*}decision'))
|
self.decision = self._parse_decision(self.node.findall('{*}decision'))
|
||||||
|
|
||||||
def get_id(self):
|
@property
|
||||||
|
def bpmn_id(self):
|
||||||
"""
|
"""
|
||||||
Returns the process ID
|
Returns the process ID
|
||||||
"""
|
"""
|
||||||
@ -172,9 +199,7 @@ class DMNParser(NodeParser):
|
|||||||
return rule
|
return rule
|
||||||
|
|
||||||
def _parse_input_output_element(self, decision_table, element, cls, idx):
|
def _parse_input_output_element(self, decision_table, element, cls, idx):
|
||||||
input_or_output = (
|
input_or_output = (decision_table.inputs if cls == InputEntry else decision_table.outputs)[idx]
|
||||||
decision_table.inputs if cls == InputEntry else decision_table.outputs if cls == OutputEntry else None)[
|
|
||||||
idx]
|
|
||||||
entry = cls(element.attrib['id'], input_or_output)
|
entry = cls(element.attrib['id'], input_or_output)
|
||||||
for child in element:
|
for child in element:
|
||||||
if child.tag.endswith('description'):
|
if child.tag.endswith('description'):
|
||||||
@ -182,7 +207,8 @@ class DMNParser(NodeParser):
|
|||||||
elif child.tag.endswith('text'):
|
elif child.tag.endswith('text'):
|
||||||
entry.text = child.text
|
entry.text = child.text
|
||||||
if cls == InputEntry:
|
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:
|
elif cls == OutputEntry:
|
||||||
if entry.text and entry.text != '':
|
if entry.text and entry.text != '':
|
||||||
try:
|
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 ...bpmn.serializer.helpers.spec import TaskSpecConverter
|
||||||
|
|
||||||
from ..specs.BusinessRuleTask import BusinessRuleTask
|
|
||||||
from ..specs.model import DecisionTable, Rule, HitPolicy
|
from ..specs.model import DecisionTable, Rule, HitPolicy
|
||||||
from ..specs.model import Input, InputEntry, Output, OutputEntry
|
from ..specs.model import Input, InputEntry, Output, OutputEntry
|
||||||
from ..engine.DMNEngine import DMNEngine
|
from ..engine.DMNEngine import DMNEngine
|
||||||
@ -9,7 +27,6 @@ class BaseBusinessRuleTaskConverter(TaskSpecConverter):
|
|||||||
|
|
||||||
def to_dict(self, spec):
|
def to_dict(self, spec):
|
||||||
dct = self.get_default_attributes(spec)
|
dct = self.get_default_attributes(spec)
|
||||||
dct.update(self.get_bpmn_attributes(spec))
|
|
||||||
# We only ever use one decision table
|
# We only ever use one decision table
|
||||||
dct['decision_table'] = self.decision_table_to_dict(spec.dmnEngine.decision_table)
|
dct['decision_table'] = self.decision_table_to_dict(spec.dmnEngine.decision_table)
|
||||||
return dct
|
return dct
|
||||||
@ -95,8 +112,3 @@ class BaseBusinessRuleTaskConverter(TaskSpecConverter):
|
|||||||
rule.outputEntries = [self.output_entry_from_dict(entry, outputs)
|
rule.outputEntries = [self.output_entry_from_dict(entry, outputs)
|
||||||
for entry in dct['output_entries']]
|
for entry in dct['output_entries']]
|
||||||
return rule
|
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 collections import OrderedDict
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# Copyright (C) 2007 Samuel Abels, 2023 Sartography
|
||||||
# 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
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
# License as published by the Free Software Foundation; either
|
# 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
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
# Lesser General Public License for more details.
|
# Lesser General Public License for more details.
|
||||||
@ -15,10 +16,6 @@
|
|||||||
# License along with this library; if not, write to the Free Software
|
# License along with this library; if not, write to the Free Software
|
||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||||
# 02110-1301 USA
|
# 02110-1301 USA
|
||||||
import re
|
|
||||||
|
|
||||||
from SpiffWorkflow.util import levenshtein
|
|
||||||
|
|
||||||
|
|
||||||
class SpiffWorkflowException(Exception):
|
class SpiffWorkflowException(Exception):
|
||||||
"""
|
"""
|
||||||
@ -55,81 +52,6 @@ class WorkflowException(SpiffWorkflowException):
|
|||||||
# Points to the TaskSpec that generated the exception.
|
# Points to the TaskSpec that generated the exception.
|
||||||
self.task_spec = task_spec
|
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):
|
class TaskNotFoundException(WorkflowException):
|
||||||
pass
|
pass
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# Copyright (C) 2007 Samuel Abels
|
# 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
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
# License as published by the Free Software Foundation; either
|
# 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
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
# Lesser General Public License for more details.
|
# Lesser General Public License for more details.
|
||||||
@ -16,6 +16,7 @@
|
|||||||
# License along with this library; if not, write to the Free Software
|
# License along with this library; if not, write to the Free Software
|
||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||||
# 02110-1301 USA
|
# 02110-1301 USA
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
|
|
||||||
@ -198,8 +199,8 @@ def valueof(scope, op, default=None):
|
|||||||
|
|
||||||
def is_number(text):
|
def is_number(text):
|
||||||
try:
|
try:
|
||||||
x = int(text)
|
int(text)
|
||||||
except:
|
except Exception:
|
||||||
return False
|
return False
|
||||||
return True
|
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 -*-
|
# This file is part of SpiffWorkflow.
|
||||||
|
#
|
||||||
from builtins import object
|
# SpiffWorkflow is free software; you can redistribute it and/or
|
||||||
# This library is free software; you can redistribute it and/or
|
|
||||||
# modify it under the terms of the GNU Lesser General Public
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
# License as published by the Free Software Foundation; either
|
# 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
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
# Lesser General Public License for more details.
|
# Lesser General Public License for more details.
|
||||||
@ -20,7 +19,6 @@ from .. import operators
|
|||||||
from ..specs.AcquireMutex import AcquireMutex
|
from ..specs.AcquireMutex import AcquireMutex
|
||||||
from ..specs.Cancel import Cancel
|
from ..specs.Cancel import Cancel
|
||||||
from ..specs.CancelTask import CancelTask
|
from ..specs.CancelTask import CancelTask
|
||||||
from ..specs.Celery import Celery
|
|
||||||
from ..specs.Choose import Choose
|
from ..specs.Choose import Choose
|
||||||
from ..specs.ExclusiveChoice import ExclusiveChoice
|
from ..specs.ExclusiveChoice import ExclusiveChoice
|
||||||
from ..specs.Execute import Execute
|
from ..specs.Execute import Execute
|
||||||
@ -46,7 +44,6 @@ def spec_map():
|
|||||||
'acquire-mutex': AcquireMutex,
|
'acquire-mutex': AcquireMutex,
|
||||||
'cancel': Cancel,
|
'cancel': Cancel,
|
||||||
'cancel-task': CancelTask,
|
'cancel-task': CancelTask,
|
||||||
'celery': Celery,
|
|
||||||
'choose': Choose,
|
'choose': Choose,
|
||||||
'exclusive-choice': ExclusiveChoice,
|
'exclusive-choice': ExclusiveChoice,
|
||||||
'execute': Execute,
|
'execute': Execute,
|
||||||
|
@ -1,14 +1,11 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# This file is part of SpiffWorkflow.
|
||||||
|
#
|
||||||
|
# SpiffWorkflow is free software; you can redistribute it and/or
|
||||||
import json
|
|
||||||
from builtins import str
|
|
||||||
# This library is free software; you can redistribute it and/or
|
|
||||||
# modify it under the terms of the GNU Lesser General Public
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
# License as published by the Free Software Foundation; either
|
# 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
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
# Lesser General Public License for more details.
|
# 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
|
# License along with this library; if not, write to the Free Software
|
||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||||
# 02110-1301 USA
|
# 02110-1301 USA
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
import pickle
|
import pickle
|
||||||
from base64 import b64encode, b64decode
|
from base64 import b64encode, b64decode
|
||||||
from ..workflow import Workflow
|
from ..workflow import Workflow
|
||||||
from ..util.impl import get_class
|
from ..util.impl import get_class
|
||||||
from ..task import Task, TaskState
|
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.base import TaskSpec
|
||||||
from ..specs.AcquireMutex import AcquireMutex
|
from ..specs.AcquireMutex import AcquireMutex
|
||||||
from ..specs.Cancel import Cancel
|
from ..specs.Cancel import Cancel
|
||||||
from ..specs.CancelTask import CancelTask
|
from ..specs.CancelTask import CancelTask
|
||||||
from ..specs.Celery import Celery
|
|
||||||
from ..specs.Choose import Choose
|
from ..specs.Choose import Choose
|
||||||
from ..specs.ExclusiveChoice import ExclusiveChoice
|
from ..specs.ExclusiveChoice import ExclusiveChoice
|
||||||
from ..specs.Execute import Execute
|
from ..specs.Execute import Execute
|
||||||
@ -51,25 +50,17 @@ import warnings
|
|||||||
|
|
||||||
class DictionarySerializer(Serializer):
|
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):
|
def serialize_dict(self, thedict):
|
||||||
return dict(
|
return dict(
|
||||||
(str(k), b64encode(pickle.dumps(v,
|
(str(k), b64encode(pickle.dumps(v, protocol=pickle.HIGHEST_PROTOCOL)))
|
||||||
protocol=pickle.HIGHEST_PROTOCOL)))
|
for k, v in list(thedict.items())
|
||||||
for k, v in list(thedict.items()))
|
)
|
||||||
|
|
||||||
def deserialize_dict(self, s_state):
|
def deserialize_dict(self, s_state):
|
||||||
return dict((k, pickle.loads(b64decode(v)))
|
return dict((k, pickle.loads(b64decode(v))) for k, v in list(s_state.items()))
|
||||||
for k, v in list(s_state.items()))
|
|
||||||
|
|
||||||
def serialize_list(self, thelist):
|
def serialize_list(self, thelist):
|
||||||
return [b64encode(pickle.dumps(v, protocol=pickle.HIGHEST_PROTOCOL))
|
return [b64encode(pickle.dumps(v, protocol=pickle.HIGHEST_PROTOCOL)) for v in thelist]
|
||||||
for v in thelist]
|
|
||||||
|
|
||||||
def deserialize_list(self, s_state):
|
def deserialize_list(self, s_state):
|
||||||
return [pickle.loads(b64decode(v)) for v in s_state]
|
return [pickle.loads(b64decode(v)) for v in s_state]
|
||||||
@ -149,48 +140,34 @@ class DictionarySerializer(Serializer):
|
|||||||
return ret
|
return ret
|
||||||
|
|
||||||
def serialize_task_spec(self, spec):
|
def serialize_task_spec(self, spec):
|
||||||
s_state = dict(id=spec.id,
|
s_state = dict(name=spec.name,
|
||||||
name=spec.name,
|
|
||||||
description=spec.description,
|
description=spec.description,
|
||||||
manual=spec.manual,
|
manual=spec.manual,
|
||||||
internal=spec.internal,
|
|
||||||
lookahead=spec.lookahead)
|
lookahead=spec.lookahead)
|
||||||
module_name = spec.__class__.__module__
|
module_name = spec.__class__.__module__
|
||||||
s_state['class'] = module_name + '.' + spec.__class__.__name__
|
s_state['class'] = module_name + '.' + spec.__class__.__name__
|
||||||
s_state['inputs'] = [t.id for t in spec.inputs]
|
s_state['inputs'] = [t.name for t in spec.inputs]
|
||||||
s_state['outputs'] = [t.id for t in spec.outputs]
|
s_state['outputs'] = [t.name for t in spec.outputs]
|
||||||
s_state['data'] = self.serialize_dict(spec.data)
|
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['defines'] = self.serialize_dict(spec.defines)
|
||||||
s_state['pre_assign'] = self.serialize_list(spec.pre_assign)
|
s_state['pre_assign'] = self.serialize_list(spec.pre_assign)
|
||||||
s_state['post_assign'] = self.serialize_list(spec.post_assign)
|
s_state['post_assign'] = self.serialize_list(spec.post_assign)
|
||||||
|
|
||||||
# Note: Events are not serialized; this is documented in
|
# Note: Events are not serialized; this is documented in
|
||||||
# the TaskSpec API docs.
|
# the TaskSpec API docs.
|
||||||
|
|
||||||
return s_state
|
return s_state
|
||||||
|
|
||||||
def deserialize_task_spec(self, wf_spec, s_state, spec):
|
def deserialize_task_spec(self, wf_spec, s_state, spec):
|
||||||
spec.id = s_state.get('id', None)
|
|
||||||
spec.description = s_state.get('description', '')
|
spec.description = s_state.get('description', '')
|
||||||
spec.manual = s_state.get('manual', False)
|
spec.manual = s_state.get('manual', False)
|
||||||
spec.internal = s_state.get('internal', False)
|
|
||||||
spec.lookahead = s_state.get('lookahead', 2)
|
spec.lookahead = s_state.get('lookahead', 2)
|
||||||
|
|
||||||
spec.data = self.deserialize_dict(s_state.get('data', {}))
|
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.defines = self.deserialize_dict(s_state.get('defines', {}))
|
||||||
spec.pre_assign = self.deserialize_list(s_state.get('pre_assign', []))
|
spec.pre_assign = self.deserialize_list(s_state.get('pre_assign', []))
|
||||||
spec.post_assign = self.deserialize_list(
|
spec.post_assign = self.deserialize_list(s_state.get('post_assign', []))
|
||||||
s_state.get('post_assign', []))
|
|
||||||
# We can't restore inputs and outputs yet because they may not be
|
# 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.
|
# deserialized yet. So keep the names, and resolve them in the end.
|
||||||
spec.inputs = s_state.get('inputs', [])[:]
|
spec.inputs = s_state.get('inputs', [])[:]
|
||||||
spec.outputs = s_state.get('outputs', [])[:]
|
spec.outputs = s_state.get('outputs', [])[:]
|
||||||
|
|
||||||
return spec
|
return spec
|
||||||
|
|
||||||
def serialize_acquire_mutex(self, spec):
|
def serialize_acquire_mutex(self, spec):
|
||||||
@ -210,8 +187,7 @@ class DictionarySerializer(Serializer):
|
|||||||
return s_state
|
return s_state
|
||||||
|
|
||||||
def deserialize_cancel(self, wf_spec, s_state):
|
def deserialize_cancel(self, wf_spec, s_state):
|
||||||
spec = Cancel(wf_spec, s_state['name'],
|
spec = Cancel(wf_spec, s_state['name'], success=s_state.get('cancel_successfully', False))
|
||||||
success=s_state.get('cancel_successfully', False))
|
|
||||||
self.deserialize_task_spec(wf_spec, s_state, spec=spec)
|
self.deserialize_task_spec(wf_spec, s_state, spec=spec)
|
||||||
return spec
|
return spec
|
||||||
|
|
||||||
@ -226,26 +202,6 @@ class DictionarySerializer(Serializer):
|
|||||||
self.deserialize_task_spec(wf_spec, s_state, spec=spec)
|
self.deserialize_task_spec(wf_spec, s_state, spec=spec)
|
||||||
return 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):
|
def serialize_choose(self, spec):
|
||||||
s_state = self.serialize_task_spec(spec)
|
s_state = self.serialize_task_spec(spec)
|
||||||
s_state['context'] = spec.context
|
s_state['context'] = spec.context
|
||||||
@ -305,12 +261,12 @@ class DictionarySerializer(Serializer):
|
|||||||
s_state['cancel_remaining'] = spec.cancel_remaining
|
s_state['cancel_remaining'] = spec.cancel_remaining
|
||||||
return s_state
|
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):
|
if isinstance(s_state['threshold'],dict):
|
||||||
byte_payload = s_state['threshold']['__bytes__']
|
byte_payload = s_state['threshold']['__bytes__']
|
||||||
else:
|
else:
|
||||||
byte_payload = s_state['threshold']
|
byte_payload = s_state['threshold']
|
||||||
spec = cls(wf_spec,
|
spec = Join(wf_spec,
|
||||||
s_state['name'],
|
s_state['name'],
|
||||||
split_task=s_state['split_task'],
|
split_task=s_state['split_task'],
|
||||||
threshold=pickle.loads(b64decode(byte_payload)),
|
threshold=pickle.loads(b64decode(byte_payload)),
|
||||||
@ -347,36 +303,25 @@ class DictionarySerializer(Serializer):
|
|||||||
s_state = self.serialize_task_spec(spec)
|
s_state = self.serialize_task_spec(spec)
|
||||||
# here we need to add in all of the things that would get serialized
|
# here we need to add in all of the things that would get serialized
|
||||||
# for other classes that the MultiInstance could be -
|
# for other classes that the MultiInstance could be -
|
||||||
#
|
|
||||||
|
|
||||||
if isinstance(spec, SubWorkflow):
|
if isinstance(spec, SubWorkflow):
|
||||||
br_state = self.serialize_sub_workflow(spec)
|
br_state = self.serialize_sub_workflow(spec)
|
||||||
s_state['file'] = br_state['file']
|
s_state['file'] = br_state['file']
|
||||||
s_state['in_assign'] = br_state['in_assign']
|
s_state['in_assign'] = br_state['in_assign']
|
||||||
s_state['out_assign'] = br_state['out_assign']
|
s_state['out_assign'] = br_state['out_assign']
|
||||||
|
|
||||||
s_state['times'] = self.serialize_arg(spec.times)
|
s_state['times'] = self.serialize_arg(spec.times)
|
||||||
s_state['prevtaskclass'] = spec.prevtaskclass
|
|
||||||
return s_state
|
return s_state
|
||||||
|
|
||||||
def deserialize_multi_instance(self, wf_spec, s_state, cls=None):
|
def deserialize_multi_instance(self, wf_spec, s_state):
|
||||||
if cls == None:
|
spec = MultiInstance(wf_spec, s_state['name'], times=self.deserialize_arg(s_state['times']))
|
||||||
cls = MultiInstance(wf_spec,
|
if isinstance(spec, SubWorkflow):
|
||||||
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):
|
|
||||||
if s_state.get('file'):
|
if s_state.get('file'):
|
||||||
cls.file = self.deserialize_arg(s_state['file'])
|
spec.file = self.deserialize_arg(s_state['file'])
|
||||||
else:
|
else:
|
||||||
cls.file = None
|
spec.file = None
|
||||||
cls.in_assign = self.deserialize_list(s_state['in_assign'])
|
spec.in_assign = self.deserialize_list(s_state['in_assign'])
|
||||||
cls.out_assign = self.deserialize_list(s_state['out_assign'])
|
spec.out_assign = self.deserialize_list(s_state['out_assign'])
|
||||||
|
self.deserialize_task_spec(wf_spec, s_state, spec=spec)
|
||||||
self.deserialize_task_spec(wf_spec, s_state, spec=cls)
|
return spec
|
||||||
return cls
|
|
||||||
|
|
||||||
def serialize_release_mutex(self, spec):
|
def serialize_release_mutex(self, spec):
|
||||||
s_state = self.serialize_task_spec(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)
|
self.deserialize_task_spec(wf_spec, s_state, spec=spec)
|
||||||
return spec
|
return spec
|
||||||
|
|
||||||
|
|
||||||
def deserialize_generic(self, wf_spec, s_state,newclass):
|
def deserialize_generic(self, wf_spec, s_state,newclass):
|
||||||
assert isinstance(wf_spec, WorkflowSpec)
|
assert isinstance(wf_spec, WorkflowSpec)
|
||||||
spec = newclass(wf_spec, s_state['name'])
|
spec = newclass(wf_spec, s_state['name'])
|
||||||
@ -486,71 +430,33 @@ class DictionarySerializer(Serializer):
|
|||||||
return spec
|
return spec
|
||||||
|
|
||||||
def serialize_workflow_spec(self, spec, **kwargs):
|
def serialize_workflow_spec(self, spec, **kwargs):
|
||||||
s_state = dict(name=spec.name,
|
s_state = dict(name=spec.name, description=spec.description, file=spec.file)
|
||||||
description=spec.description,
|
s_state['task_specs'] = dict(
|
||||||
file=spec.file)
|
(k, v.serialize(self))
|
||||||
|
for k, v in list(spec.task_specs.items())
|
||||||
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)
|
|
||||||
return s_state
|
return s_state
|
||||||
|
|
||||||
def _deserialize_workflow_spec_task_spec(self, spec, task_spec, name):
|
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.inputs = [spec.get_task_spec_from_name(t) for t in task_spec.inputs]
|
||||||
task_spec.outputs = [spec.get_task_spec_from_id(t) for t in task_spec.outputs]
|
task_spec.outputs = [spec.get_task_spec_from_name(t) for t in task_spec.outputs]
|
||||||
|
|
||||||
def _prevtaskclass_bases(self, oldtask):
|
|
||||||
return (oldtask)
|
|
||||||
|
|
||||||
def deserialize_workflow_spec(self, s_state, **kwargs):
|
def deserialize_workflow_spec(self, s_state, **kwargs):
|
||||||
spec = WorkflowSpec(s_state['name'], filename=s_state['file'])
|
spec = WorkflowSpec(s_state['name'], filename=s_state['file'])
|
||||||
spec.description = s_state['description']
|
spec.description = s_state['description']
|
||||||
|
|
||||||
# Handle Start Task
|
# Handle Start Task
|
||||||
spec.start = None
|
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']
|
del spec.task_specs['Start']
|
||||||
start_task_spec_state = s_state['task_specs']['Start']
|
start_task_spec_state = s_state['task_specs']['Start']
|
||||||
start_task_spec = StartTask.deserialize(self, spec, start_task_spec_state)
|
start_task_spec = StartTask.deserialize(self, spec, start_task_spec_state)
|
||||||
spec.start = start_task_spec
|
spec.start = start_task_spec
|
||||||
|
|
||||||
spec.task_specs['Start'] = start_task_spec
|
spec.task_specs['Start'] = start_task_spec
|
||||||
for name, task_spec_state in list(s_state['task_specs'].items()):
|
for name, task_spec_state in list(s_state['task_specs'].items()):
|
||||||
if name == 'Start':
|
if name == 'Start':
|
||||||
continue
|
continue
|
||||||
prevtask = task_spec_state.get('prevtaskclass', None)
|
task_spec_cls = get_class(task_spec_state['class'])
|
||||||
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)
|
task_spec = task_spec_cls.deserialize(self, spec, task_spec_state)
|
||||||
spec.task_specs[name] = task_spec
|
spec.task_specs[name] = task_spec
|
||||||
|
|
||||||
@ -558,7 +464,7 @@ class DictionarySerializer(Serializer):
|
|||||||
self._deserialize_workflow_spec_task_spec(spec, task_spec, name)
|
self._deserialize_workflow_spec_task_spec(spec, task_spec, name)
|
||||||
|
|
||||||
if s_state.get('end', None):
|
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')
|
assert spec.start is spec.get_task_spec_from_name('Start')
|
||||||
return spec
|
return spec
|
||||||
@ -578,25 +484,19 @@ class DictionarySerializer(Serializer):
|
|||||||
|
|
||||||
return s_state
|
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
|
"""It is possible to override the workflow class, and specify a
|
||||||
workflow_spec, otherwise the spec is assumed to be serialized in the
|
workflow_spec, otherwise the spec is assumed to be serialized in the
|
||||||
s_state['wf_spec']"""
|
s_state['wf_spec']"""
|
||||||
|
|
||||||
if wf_spec is None:
|
if isinstance(s_state['wf_spec'], str):
|
||||||
# The json serializer serializes the spec as a string and then serializes it again, hence this check
|
spec_dct = json.loads(s_state['wf_spec'])
|
||||||
# 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:
|
|
||||||
spec_dct = s_state['wf_spec']
|
|
||||||
reset_specs = [spec['name'] for spec in spec_dct['task_specs'].values() if spec['class'].endswith('LoopResetTask')]
|
|
||||||
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:
|
else:
|
||||||
reset_specs = []
|
spec_dct = s_state['wf_spec']
|
||||||
|
reset_specs = [spec['name'] for spec in spec_dct['task_specs'].values() if spec['class'].endswith('LoopResetTask')]
|
||||||
|
for name in reset_specs:
|
||||||
|
s_state['wf_spec']['task_specs'].pop(name)
|
||||||
|
wf_spec = self.deserialize_workflow_spec(s_state['wf_spec'], **kwargs)
|
||||||
|
|
||||||
workflow = wf_class(wf_spec)
|
workflow = wf_class(wf_spec)
|
||||||
workflow.data = self.deserialize_dict(s_state['data'])
|
workflow.data = self.deserialize_dict(s_state['data'])
|
||||||
@ -623,35 +523,24 @@ class DictionarySerializer(Serializer):
|
|||||||
|
|
||||||
return workflow
|
return workflow
|
||||||
|
|
||||||
def serialize_task(self, task, skip_children=False, allow_subs=False):
|
def serialize_task(self, task, skip_children=False):
|
||||||
"""
|
|
||||||
:param allow_subs: Allows sub-serialization to take place, otherwise
|
|
||||||
assumes that the subworkflow is stored in internal data and raises an error.
|
|
||||||
"""
|
|
||||||
|
|
||||||
assert isinstance(task, Task)
|
assert isinstance(task, Task)
|
||||||
|
if isinstance(task.task_spec, SubWorkflow):
|
||||||
# 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):
|
|
||||||
raise TaskNotSupportedError(
|
raise TaskNotSupportedError(
|
||||||
"Subworkflow tasks cannot be serialized (due to their use of" +
|
"Subworkflow tasks cannot be serialized (due to their use of" +
|
||||||
" internal_data to store the subworkflow).")
|
" internal_data to store the subworkflow).")
|
||||||
|
|
||||||
s_state = dict()
|
s_state = dict()
|
||||||
s_state['id'] = task.id
|
s_state['id'] = task.id
|
||||||
s_state['workflow_name'] = task.workflow.name
|
s_state['workflow_name'] = task.workflow.name
|
||||||
s_state['parent'] = task.parent.id if task.parent is not None else None
|
s_state['parent'] = task.parent.id if task.parent is not None else None
|
||||||
if not skip_children:
|
if not skip_children:
|
||||||
s_state['children'] = [
|
s_state['children'] = [self.serialize_task(child) for child in task.children]
|
||||||
self.serialize_task(child) for child in task.children]
|
|
||||||
s_state['state'] = task.state
|
s_state['state'] = task.state
|
||||||
s_state['triggered'] = task.triggered
|
s_state['triggered'] = task.triggered
|
||||||
s_state['task_spec'] = task.task_spec.name
|
s_state['task_spec'] = task.task_spec.name
|
||||||
s_state['last_state_change'] = task.last_state_change
|
s_state['last_state_change'] = task.last_state_change
|
||||||
s_state['data'] = self.serialize_dict(task.data)
|
s_state['data'] = self.serialize_dict(task.data)
|
||||||
s_state['internal_data'] = task.internal_data
|
s_state['internal_data'] = task.internal_data
|
||||||
|
|
||||||
return s_state
|
return s_state
|
||||||
|
|
||||||
def deserialize_task(self, workflow, s_state, ignored_specs=None):
|
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)
|
raise MissingSpecError("Unknown task spec: " + old_spec_name)
|
||||||
task = Task(workflow, task_spec)
|
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']
|
task.id = s_state['id']
|
||||||
# as the task_tree might not be complete yet
|
# as the task_tree might not be complete yet
|
||||||
# keep the ids so they can be processed at the end
|
# keep the ids so they can be processed at the end
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# This file is part of SpiffWorkflow.
|
||||||
|
#
|
||||||
# This library is free software; you can redistribute it and/or
|
# SpiffWorkflow is free software; you can redistribute it and/or
|
||||||
# modify it under the terms of the GNU Lesser General Public
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
# License as published by the Free Software Foundation; either
|
# 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
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
# Lesser General Public License for more details.
|
# 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):
|
class TaskSpecNotSupportedError(ValueError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# This file is part of SpiffWorkflow.
|
||||||
|
#
|
||||||
# This library is free software; you can redistribute it and/or
|
# SpiffWorkflow is free software; you can redistribute it and/or
|
||||||
# modify it under the terms of the GNU Lesser General Public
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
# License as published by the Free Software Foundation; either
|
# 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
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
# Lesser General Public License for more details.
|
# Lesser General Public License for more details.
|
||||||
@ -14,6 +14,7 @@
|
|||||||
# License along with this library; if not, write to the Free Software
|
# License along with this library; if not, write to the Free Software
|
||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||||
# 02110-1301 USA
|
# 02110-1301 USA
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import uuid
|
import uuid
|
||||||
from ..operators import Attrib
|
from ..operators import Attrib
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# Copyright (C) 2007-2012 Samuel Abels, 2023 Sartography
|
||||||
|
|
||||||
# Copyright (C) 2007-2012 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
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
# License as published by the Free Software Foundation; either
|
# 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
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
# Lesser General Public License for more details.
|
# Lesser General Public License for more details.
|
||||||
@ -16,6 +16,7 @@
|
|||||||
# License along with this library; if not, write to the Free Software
|
# License along with this library; if not, write to the Free Software
|
||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||||
# 02110-1301 USA
|
# 02110-1301 USA
|
||||||
|
|
||||||
from .. import operators
|
from .. import operators
|
||||||
from ..specs.Simple import Simple
|
from ..specs.Simple import Simple
|
||||||
from ..specs.WorkflowSpec import WorkflowSpec
|
from ..specs.WorkflowSpec import WorkflowSpec
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# This file is part of SpiffWorkflow.
|
||||||
|
#
|
||||||
from builtins import str
|
# SpiffWorkflow is free software; you can redistribute it and/or
|
||||||
# This library is free software; you can redistribute it and/or
|
|
||||||
# modify it under the terms of the GNU Lesser General Public
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
# License as published by the Free Software Foundation; either
|
# 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
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
# Lesser General Public License for more details.
|
# 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
|
# License along with this library; if not, write to the Free Software
|
||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||||
# 02110-1301 USA
|
# 02110-1301 USA
|
||||||
|
|
||||||
import warnings
|
import warnings
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
from lxml.etree import SubElement
|
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.AcquireMutex import AcquireMutex
|
||||||
from ..specs.Cancel import Cancel
|
from ..specs.Cancel import Cancel
|
||||||
from ..specs.CancelTask import CancelTask
|
from ..specs.CancelTask import CancelTask
|
||||||
from ..specs.Celery import Celery
|
|
||||||
from ..specs.Choose import Choose
|
from ..specs.Choose import Choose
|
||||||
from ..specs.ExclusiveChoice import ExclusiveChoice
|
from ..specs.ExclusiveChoice import ExclusiveChoice
|
||||||
from ..specs.Execute import Execute
|
from ..specs.Execute import Execute
|
||||||
@ -287,15 +286,11 @@ class XmlSerializer(Serializer):
|
|||||||
"""
|
"""
|
||||||
Serializes common attributes of :meth:`SpiffWorkflow.specs.TaskSpec`.
|
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
|
SubElement(elem, 'name').text = spec.name
|
||||||
if spec.description:
|
if spec.description:
|
||||||
SubElement(elem, 'description').text = spec.description
|
SubElement(elem, 'description').text = spec.description
|
||||||
if spec.manual:
|
if spec.manual:
|
||||||
SubElement(elem, 'manual')
|
SubElement(elem, 'manual')
|
||||||
if spec.internal:
|
|
||||||
SubElement(elem, 'internal')
|
|
||||||
SubElement(elem, 'lookahead').text = str(spec.lookahead)
|
SubElement(elem, 'lookahead').text = str(spec.lookahead)
|
||||||
inputs = [t.name for t in spec.inputs]
|
inputs = [t.name for t in spec.inputs]
|
||||||
outputs = [t.name for t in spec.outputs]
|
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):
|
def deserialize_task_spec(self, wf_spec, elem, spec_cls, **kwargs):
|
||||||
name = elem.findtext('name')
|
name = elem.findtext('name')
|
||||||
spec = spec_cls(wf_spec, name, **kwargs)
|
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.description = elem.findtext('description', spec.description)
|
||||||
spec.manual = elem.findtext('manual', spec.manual)
|
spec.manual = elem.findtext('manual', spec.manual)
|
||||||
spec.internal = elem.find('internal') is not None
|
|
||||||
spec.lookahead = int(elem.findtext('lookahead', spec.lookahead))
|
spec.lookahead = int(elem.findtext('lookahead', spec.lookahead))
|
||||||
|
|
||||||
data_elem = elem.find('data')
|
data_elem = elem.find('data')
|
||||||
@ -384,37 +376,6 @@ class XmlSerializer(Serializer):
|
|||||||
def deserialize_cancel_task(self, wf_spec, elem, cls=CancelTask, **kwargs):
|
def deserialize_cancel_task(self, wf_spec, elem, cls=CancelTask, **kwargs):
|
||||||
return self.deserialize_trigger(wf_spec, elem, cls, **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):
|
def serialize_choose(self, spec, elem=None):
|
||||||
if elem is None:
|
if elem is None:
|
||||||
elem = etree.Element('choose')
|
elem = etree.Element('choose')
|
||||||
@ -526,7 +487,7 @@ class XmlSerializer(Serializer):
|
|||||||
|
|
||||||
def deserialize_multi_instance(self, wf_spec, elem, cls=None,
|
def deserialize_multi_instance(self, wf_spec, elem, cls=None,
|
||||||
**kwargs):
|
**kwargs):
|
||||||
if cls == None:
|
if cls is None:
|
||||||
cls = MultiInstance
|
cls = MultiInstance
|
||||||
#cls = MultiInstance(wf_spec,elem.find('name'),elem.find('times'))
|
#cls = MultiInstance(wf_spec,elem.find('name'),elem.find('times'))
|
||||||
times = self.deserialize_value(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