Skip to content

Commit 238efd0

Browse files
authored
Merge pull request #362 from sartography/improvement/misc-cleanup-and-bugfixes
Improvement/misc cleanup and bugfixes
2 parents 220de40 + e6a5e5a commit 238efd0

21 files changed

+91
-141
lines changed

SpiffWorkflow/bpmn/FeelLikeScriptEngine.py

+17-10
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import re
2121
import datetime
2222
import operator
23+
import warnings
24+
2325
from datetime import timedelta
2426
from decimal import Decimal
2527
from .PythonScriptEngine import PythonScriptEngine
@@ -30,6 +32,11 @@ def feelConvertTime(datestr,parsestr):
3032

3133
class FeelInterval():
3234
def __init__(self, begin, end, leftOpen=False, rightOpen=False):
35+
warnings.warn(
36+
'The FEEL script engine is deprecated and will be removed in the next release',
37+
DeprecationWarning,
38+
stacklevel=2,
39+
)
3340
# pesky thing with python floats and Decimal comparison
3441
if isinstance(begin,float):
3542
begin = Decimal("%0.5f"%begin)
@@ -287,26 +294,26 @@ def patch_expression(self, invalid_python, lhs=''):
287294
proposed_python = lhs + proposed_python
288295
return proposed_python
289296

290-
def _evaluate(self, expression, context, task=None, external_methods=None):
297+
def _evaluate(self, expression, context, task=None, external_context=None):
291298
"""
292299
Evaluate the given expression, within the context of the given task and
293300
return the result.
294301
"""
295-
if external_methods is None:
296-
external_methods = {}
302+
if external_context is None:
303+
external_context = {}
297304

298305
revised = self.patch_expression(expression)
299-
external_methods.update(externalFuncs)
300-
return super()._evaluate(revised, context, external_methods=external_methods)
306+
external_context.update(externalFuncs)
307+
return super()._evaluate(revised, context, external_context=external_context)
301308

302-
def execute(self, task, script, data, external_methods=None):
309+
def execute(self, task, script, data, external_context=None):
303310
"""
304311
Execute the script, within the context of the specified task
305312
"""
306-
if external_methods is None:
307-
external_methods = {}
308-
external_methods.update(externalFuncs)
309-
super().execute(task, script, external_methods)
313+
if external_context is None:
314+
external_context = {}
315+
external_context.update(externalFuncs)
316+
super().execute(task, script, external_context)
310317

311318

312319

SpiffWorkflow/bpmn/PythonScriptEngine.py

+10-23
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import ast
2121
import sys
2222
import traceback
23-
import warnings
2423

2524
from SpiffWorkflow.exceptions import SpiffWorkflowException
2625
from SpiffWorkflow.bpmn.exceptions import WorkflowTaskException
@@ -38,41 +37,29 @@ class PythonScriptEngine(object):
3837
expressions in a different way.
3938
"""
4039

41-
def __init__(self, default_globals=None, scripting_additions=None, environment=None):
42-
43-
if default_globals is not None or scripting_additions is not None:
44-
warnings.warn('default_globals and scripting_additions are deprecated. '
45-
'Please provide an environment such as TaskDataEnvrionment',
46-
DeprecationWarning, stacklevel=2)
47-
48-
if environment is None:
49-
environment_globals = {}
50-
environment_globals.update(default_globals or {})
51-
environment_globals.update(scripting_additions or {})
52-
self.environment = TaskDataEnvironment(environment_globals)
53-
else:
54-
self.environment = environment
40+
def __init__(self, environment=None):
41+
self.environment = environment or TaskDataEnvironment()
5542

5643
def validate(self, expression):
5744
ast.parse(expression)
5845

59-
def evaluate(self, task, expression, external_methods=None):
46+
def evaluate(self, task, expression, external_context=None):
6047
"""
6148
Evaluate the given expression, within the context of the given task and
6249
return the result.
6350
"""
6451
try:
65-
return self._evaluate(expression, task.data, external_methods)
52+
return self._evaluate(expression, task.data, external_context)
6653
except SpiffWorkflowException as se:
6754
se.add_note(f"Error evaluating expression '{expression}'")
6855
raise se
6956
except Exception as e:
7057
raise WorkflowTaskException(f"Error evaluating expression '{expression}'", task=task, exception=e)
7158

72-
def execute(self, task, script, external_methods=None):
59+
def execute(self, task, script, external_context=None):
7360
"""Execute the script, within the context of the specified task."""
7461
try:
75-
return self._execute(script, task.data, external_methods or {})
62+
return self._execute(script, task.data, external_context or {})
7663
except Exception as err:
7764
wte = self.create_task_exec_exception(task, script, err)
7865
raise wte
@@ -111,8 +98,8 @@ def get_error_line_number_and_content(self, script, err):
11198
error_line = script.splitlines()[line_number - 1]
11299
return line_number, error_line
113100

114-
def _evaluate(self, expression, context, external_methods=None):
115-
return self.environment.evaluate(expression, context, external_methods)
101+
def _evaluate(self, expression, context, external_context=None):
102+
return self.environment.evaluate(expression, context, external_context)
116103

117-
def _execute(self, script, context, external_methods=None):
118-
return self.environment.execute(script, context, external_methods)
104+
def _execute(self, script, context, external_context=None):
105+
return self.environment.execute(script, context, external_context)

SpiffWorkflow/bpmn/PythonScriptEngineEnvironment.py

+12-12
Original file line numberDiff line numberDiff line change
@@ -24,54 +24,54 @@ class BasePythonScriptEngineEnvironment:
2424
def __init__(self, environment_globals=None):
2525
self.globals = environment_globals or {}
2626

27-
def evaluate(self, expression, context, external_methods=None):
27+
def evaluate(self, expression, context, external_context=None):
2828
raise NotImplementedError("Subclass must implement this method")
2929

30-
def execute(self, script, context, external_methods=None):
30+
def execute(self, script, context, external_context=None):
3131
raise NotImplementedError("Subclass must implement this method")
3232

3333

3434
class TaskDataEnvironment(BasePythonScriptEngineEnvironment):
3535

36-
def evaluate(self, expression, context, external_methods=None):
36+
def evaluate(self, expression, context, external_context=None):
3737
my_globals = copy.copy(self.globals) # else we pollute all later evals.
3838
self._prepare_context(context)
39-
my_globals.update(external_methods or {})
39+
my_globals.update(external_context or {})
4040
my_globals.update(context)
4141
return eval(expression, my_globals)
4242

43-
def execute(self, script, context, external_methods=None):
44-
self.check_for_overwrite(context, external_methods or {})
43+
def execute(self, script, context, external_context=None):
44+
self.check_for_overwrite(context, external_context or {})
4545
my_globals = copy.copy(self.globals)
4646
self._prepare_context(context)
47-
my_globals.update(external_methods or {})
47+
my_globals.update(external_context or {})
4848
context.update(my_globals)
4949
try:
5050
exec(script, context)
5151
finally:
52-
self._remove_globals_and_functions_from_context(context, external_methods)
52+
self._remove_globals_and_functions_from_context(context, external_context)
5353
return True
5454

5555
def _prepare_context(self, context):
5656
pass
5757

58-
def _remove_globals_and_functions_from_context(self, context, external_methods=None):
58+
def _remove_globals_and_functions_from_context(self, context, external_context=None):
5959
"""When executing a script, don't leave the globals, functions
6060
and external methods in the context that we have modified."""
6161
for k in list(context):
6262
if k == "__builtins__" or \
6363
hasattr(context[k], '__call__') or \
6464
k in self.globals or \
65-
external_methods and k in external_methods:
65+
external_context and k in external_context:
6666
context.pop(k)
6767

68-
def check_for_overwrite(self, context, external_methods):
68+
def check_for_overwrite(self, context, external_context):
6969
"""It's possible that someone will define a variable with the
7070
same name as a pre-defined script, rendering the script un-callable.
7171
This results in a nearly indecipherable error. Better to fail
7272
fast with a sensible error message."""
7373
func_overwrites = set(self.globals).intersection(context)
74-
func_overwrites.update(set(external_methods).intersection(context))
74+
func_overwrites.update(set(external_context).intersection(context))
7575
if len(func_overwrites) > 0:
7676
msg = f"You have task data that overwrites a predefined " \
7777
f"function(s). Please change the following variable or " \

SpiffWorkflow/bpmn/serializer/default/workflow.py

+15-13
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,12 @@ def to_dict(self, workflow):
8080
'root': str(workflow.task_tree.id),
8181
}
8282

83+
def set_default_attributes(self, workflow, dct):
84+
workflow.success = dct['success']
85+
workflow.correlations = dct.pop('correlations', {})
86+
if isinstance(dct['last_task'], str):
87+
workflow.last_task = workflow.tasks.get(UUID(dct['last_task']))
88+
workflow.data = self.registry.restore(dct.pop('data', {}))
8389

8490
class BpmnSubWorkflowConverter(WorkflowConverter):
8591

@@ -92,13 +98,9 @@ def to_dict(self, workflow):
9298
def from_dict(self, dct, task, top_workflow):
9399
spec = top_workflow.subprocess_specs.get(task.task_spec.spec)
94100
subprocess = self.target_class(spec, task.id, top_workflow, deserializing=True)
95-
subprocess.correlations = dct.pop('correlations', {})
96101
subprocess.tasks = self.mapping_from_dict(dct['tasks'], UUID, workflow=subprocess)
97102
subprocess.task_tree = subprocess.tasks.get(UUID(dct['root']))
98-
if isinstance(dct['last_task'], str):
99-
subprocess.last_task = subprocess.tasks.get(UUID(dct['last_task']))
100-
subprocess.success = dct['success']
101-
subprocess.data = self.registry.restore(dct['data'])
103+
self.set_default_attributes(subprocess, dct)
102104
return subprocess
103105

104106

@@ -127,24 +129,24 @@ def from_dict(self, dct):
127129
Returns:
128130
a BPMN Workflow object
129131
"""
130-
# Restore the top level spec and the subprocess specs
132+
# Restore the specs
131133
spec = self.registry.restore(dct.pop('spec'))
132134
subprocess_specs = self.mapping_from_dict(dct.pop('subprocess_specs', {}))
133135

134136
# Create the top-level workflow
135137
workflow = self.target_class(spec, subprocess_specs, deserializing=True)
136138

137-
# Restore the remainder of the workflow
138-
workflow.bpmn_events = self.registry.restore(dct.pop('bpmn_events', []))
139-
workflow.correlations = dct.pop('correlations', {})
140-
workflow.data = self.registry.restore(dct.pop('data', {}))
141-
workflow.success = dct.pop('success')
139+
# Restore the task tree
142140
workflow.tasks = self.mapping_from_dict(dct['tasks'], UUID, workflow=workflow)
143141
workflow.task_tree = workflow.tasks.get(UUID(dct['root']))
144-
if dct['last_task'] is not None:
145-
workflow.last_task = workflow.tasks.get(UUID(dct['last_task']))
146142

143+
# Restore other default attributes
144+
self.set_default_attributes(workflow, dct)
145+
146+
# Handle the remaining top workflow attributes
147147
self.subprocesses_from_dict(dct['subprocesses'], workflow)
148+
workflow.bpmn_events = self.registry.restore(dct.pop('bpmn_events', []))
149+
148150
return workflow
149151

150152
def subprocesses_from_dict(self, dct, workflow, top_workflow=None):

SpiffWorkflow/bpmn/specs/bpmn_task_spec.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ def __init__(self, *args):
1212
super(_BpmnCondition, self).__init__(*args)
1313

1414
def _matches(self, task):
15-
return task.workflow.script_engine.evaluate(task, self.args[0], external_methods=task.workflow.data)
15+
return task.workflow.script_engine.evaluate(task, self.args[0], external_context=task.workflow.data)
1616

1717

1818
class BpmnTaskSpec(TaskSpec):

SpiffWorkflow/bpmn/specs/event_definitions/conditional.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,6 @@ def __init__(self, expression, **kwargs):
99

1010
def has_fired(self, my_task):
1111
my_task._set_internal_data(
12-
has_fired=my_task.workflow.script_engine.evaluate(my_task, self.expression, external_methods=my_task.workflow.data)
12+
has_fired=my_task.workflow.script_engine.evaluate(my_task, self.expression, external_context=my_task.workflow.data)
1313
)
1414
return my_task._get_internal_data('has_fired', False)

SpiffWorkflow/bpmn/specs/event_definitions/timer.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import re
22
from datetime import datetime, timedelta, timezone
33
from calendar import monthrange
4-
from time import timezone as tzoffset, altzone as dstoffset, daylight as isdst
4+
from time import timezone as tzoffset, altzone as dstoffset, struct_time, localtime
55

66
from SpiffWorkflow.bpmn.event import PendingBpmnEvent
77
from .base import EventDefinition
88

9-
seconds_from_utc = dstoffset if isdst else tzoffset
9+
seconds_from_utc = dstoffset if struct_time(localtime()).tm_isdst else tzoffset
1010
LOCALTZ = timezone(timedelta(seconds=-1 * seconds_from_utc))
1111

1212
class TimerEventDefinition(EventDefinition):

SpiffWorkflow/bpmn/specs/mixins/events/event_types.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def catch(self, my_task, event):
4545
definition, at which point we can update our task's state.
4646
"""
4747
self.event_definition.catch(my_task, event)
48-
my_task.last_update_time = time.time()
48+
my_task.last_state_change = time.time()
4949
my_task._set_state(TaskState.WAITING)
5050

5151
def _update_hook(self, my_task):

SpiffWorkflow/bpmn/workflow.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -68,15 +68,15 @@ def _next(self):
6868
self.task_list = []
6969
elif all([
7070
len(task._children) > 0 or subprocess is not None,
71-
task.state >= self.min_state,
71+
task.state >= self.min_state or subprocess is not None,
7272
self.depth < self.max_depth,
7373
]):
7474
if subprocess is None:
7575
next_tasks = task.children
7676
elif self.depth_first:
7777
next_tasks = [subprocess.task_tree] + task.children
7878
else:
79-
next_tasks = task.children = [subprocess.task_tree]
79+
next_tasks = task.children + [subprocess.task_tree]
8080

8181
if self.depth_first:
8282
self.task_list = next_tasks + self.task_list

SpiffWorkflow/dmn/engine/DMNEngine.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -119,11 +119,11 @@ def evaluate(self, input_expr, match_expr, task):
119119
# do the replacement.
120120
match_expr = re.sub(r'(\?)(?=(?:[^\'"]|[\'"][^\'"]*[\'"])*$)', 'dmninputexpr', match_expr)
121121
if 'dmninputexpr' in match_expr:
122-
external_methods = {
122+
external_context = {
123123
'dmninputexpr': script_engine.evaluate(task, input_expr)
124124
}
125125
return script_engine.evaluate(task, match_expr,
126-
external_methods=external_methods)
126+
external_context=external_context)
127127

128128
# The input expression just has to be something that can be parsed as is by the engine.
129129
script_engine.validate(input_expr)

SpiffWorkflow/specs/base.py

-4
Original file line numberDiff line numberDiff line change
@@ -112,10 +112,6 @@ def __init__(self, wf_spec, name, **kwargs):
112112
self._wf_spec._add_notify(self)
113113
self.data.update(self.defines)
114114

115-
@property
116-
def spec_type(self):
117-
return f'{self.__class__.__module__}.{self.__class__.__name__}'
118-
119115
@property
120116
def inputs(self):
121117
return [self._wf_spec.task_specs.get(name) for name in self._inputs]

SpiffWorkflow/spiff/specs/mixins/service_task.py

-4
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,6 @@ def __init__(self, wf_spec, name, operation_name, operation_params, result_varia
3131
self.operation_params = operation_params
3232
self.result_variable = result_variable
3333

34-
@property
35-
def spec_type(self):
36-
return 'Service Task'
37-
3834
def _result_variable(self, task):
3935
if self.result_variable is not None and len(self.result_variable) > 0:
4036
return self.result_variable

SpiffWorkflow/spiff/specs/spiff_task.py

-13
Original file line numberDiff line numberDiff line change
@@ -31,26 +31,13 @@ def __init__(self, wf_spec, name, prescript=None, postscript=None, **kwargs):
3131
self.prescript = prescript
3232
self.postscript = postscript
3333

34-
@property
35-
def spec_type(self):
36-
return 'Spiff BPMN Task'
37-
3834
def execute_script(self, my_task, script):
3935
try:
4036
my_task.workflow.script_engine.execute(my_task, script)
4137
except Exception as exc:
4238
my_task._set_state(TaskState.ERROR)
4339
raise exc
4440

45-
def get_payload(self, my_task, script, expr):
46-
try:
47-
data = deepcopy(my_task.data)
48-
my_task.worklflow.script_engine.execute(my_task, script, data)
49-
return my_task.workflow.script_engine._evaluate(expr, data)
50-
except Exception as exc:
51-
my_task._set_state(TaskState.WAITING)
52-
raise exc
53-
5441
def _update_hook(self, my_task):
5542
super()._update_hook(my_task)
5643
if self.prescript is not None:

SpiffWorkflow/task.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -398,9 +398,8 @@ def log_info(self, dct=None):
398398
extra.update({
399399
'workflow_spec': self.workflow.spec.name,
400400
'task_spec': self.task_spec.name,
401-
'task_name': self.task_spec.description,
402401
'task_id': self.id,
403-
'task_type': self.task_spec.spec_type,
402+
'task_type': self.task_spec.__class__.__name__,
404403
'data': self.data if logger.level < 20 else None,
405404
'internal_data': self.internal_data if logger.level <= 10 else None,
406405
})

0 commit comments

Comments
 (0)