spiff lib upgrade (#1139)

* wip spiff lib upgrade

* unit and script tests are now passing w/ burnettk

* tests and pre-commit are passing

* pass None in as external_context as named param instead of creating a variable w/ burnettk

* some extra debugging in case bpmn_xml_file_contents is empty w/ burnettk

---------

Co-authored-by: burnettk <burnettk@users.noreply.github.com>
Co-authored-by: jasquat <jasquat@users.noreply.github.com>
This commit is contained in:
Kevin Burnett 2024-03-04 12:29:37 -08:00 committed by GitHub
parent 4cebddb6c2
commit 3416843329
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 89 additions and 251 deletions

View File

@ -2216,13 +2216,13 @@ pytest = ">=3.0.0"
[[package]]
name = "pytest-xdist"
version = "3.5.0"
version = "3.3.1"
description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs"
optional = false
python-versions = ">=3.7"
files = [
{file = "pytest-xdist-3.5.0.tar.gz", hash = "sha256:cbb36f3d67e0c478baa57fa4edc8843887e0f6cfc42d677530a36d7472b32d8a"},
{file = "pytest_xdist-3.5.0-py3-none-any.whl", hash = "sha256:d075629c7e00b611df89f490a5063944bee7a4362a5ff11c7cc7824a03dfce24"},
{file = "pytest-xdist-3.3.1.tar.gz", hash = "sha256:d5ee0520eb1b7bcca50a60a518ab7a7707992812c578198f8b44fdfac78e8c93"},
{file = "pytest_xdist-3.3.1-py3-none-any.whl", hash = "sha256:ff9daa7793569e6a68544850fd3927cd257cc03a7ef76c95e86915355e82b5f2"},
]
[package.dependencies]
@ -2974,8 +2974,8 @@ doc = ["sphinx", "sphinx_rtd_theme"]
[package.source]
type = "git"
url = "https://github.com/sartography/SpiffWorkflow"
reference = "633de80a722cf28f4a79df9de7be911130f1f5ad"
resolved_reference = "633de80a722cf28f4a79df9de7be911130f1f5ad"
reference = "main"
resolved_reference = "32c00e4a276b931aace9c9e9491cdb18011e6780"
[[package]]
name = "spiffworkflow-connector-command"
@ -3451,4 +3451,4 @@ tests-strict = ["pytest (==4.6.0)", "pytest (==4.6.0)", "pytest (==6.2.5)", "pyt
[metadata]
lock-version = "2.0"
python-versions = ">=3.10,<3.13"
content-hash = "7dbb421e285609232cf6d51884aea9acf3a5da6e79f75433124df954eb0cbd29"
content-hash = "17c71434c4fe464806f4cd43e6325269e062a9af9239c4c6202c1766389468a1"

View File

@ -26,7 +26,7 @@ flask-mail = "*"
flask-marshmallow = "*"
flask-migrate = "*"
flask-restful = "*"
SpiffWorkflow = {git = "https://github.com/sartography/SpiffWorkflow", rev = "633de80a722cf28f4a79df9de7be911130f1f5ad"}
SpiffWorkflow = {git = "https://github.com/sartography/SpiffWorkflow", rev = "main"}
# SpiffWorkflow = {develop = true, path = "../../spiffworkflow/" }
# SpiffWorkflow = {develop = true, path = "../../SpiffWorkflow/" }
sentry-sdk = "^1.10"
@ -100,7 +100,11 @@ ruff = "^0.1.7"
pytest-random-order = "^1.1.0"
pytest-flask = "^1.2.0"
pytest-flask-sqlalchemy = "^1.1.0"
pytest-xdist = "^3.3.1"
# 3.4+ broke existfirst option which we use
# https://stackoverflow.com/questions/77667559/pytest-xdist-3-40-and-higher-not-honoring-exitfirst
# https://github.com/pytest-dev/pytest-xdist/issues/1034
pytest-xdist = "3.3.1"
# 1.7.3 broke us. https://github.com/PyCQA/bandit/issues/841
bandit = "1.7.7"

View File

@ -114,7 +114,7 @@ class MessageInstanceModel(SpiffworkflowBaseDBModel):
if expected_value is None: # This key is not required for this instance to match.
continue
try:
result = expression_engine._evaluate(correlation_key.retrieval_expression, payload)
result = expression_engine.environment.evaluate(correlation_key.retrieval_expression, payload)
except Exception as e:
# the failure of a payload evaluation may not mean that matches for these
# message instances can't happen with other messages. So don't error up.

View File

@ -188,7 +188,8 @@ class FileSystemService:
@staticmethod
def full_path_to_process_model_file(process_model: ProcessModelInfo) -> str:
return os.path.join(
FileSystemService.process_model_full_path(process_model), process_model.primary_file_name # type: ignore
FileSystemService.process_model_full_path(process_model),
process_model.primary_file_name, # type: ignore
)
def next_display_order(self, process_model: ProcessModelInfo) -> int:

View File

@ -347,29 +347,14 @@ class CustomBpmnScriptEngine(PythonScriptEngine): # type: ignore
)
return Script.generate_augmented_list(script_attributes_context)
def evaluate(
self,
task: SpiffTask,
expression: str,
external_context: dict[str, Any] | None = None,
) -> Any:
return self._evaluate(expression, task.data, task, external_context)
def _evaluate(
self,
expression: str,
context: dict[str, Any],
task: SpiffTask | None = None,
external_context: dict[str, Any] | None = None,
) -> Any:
def evaluate(self, task: SpiffTask, expression: str, external_context: dict[str, Any] | None = None) -> Any:
"""Evaluate the given expression, within the context of the given task and return the result."""
methods = self.__get_augment_methods(task)
if external_context:
methods.update(external_context)
try:
return super()._evaluate(expression, context, external_context=methods)
return super().evaluate(task, expression, external_context=methods)
except Exception as exception:
if task is None:
raise WorkflowException(

View File

@ -37,7 +37,7 @@ class ScriptUnitTestRunner:
try:
cls._script_engine.environment.clear_state()
cls._script_engine._execute(context=context, script=script)
cls._script_engine.environment.execute(script, context, external_context=None)
except SyntaxError as ex:
return ScriptUnitTestResult(
result=False,

View File

@ -1,200 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_1gjhqt9" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.7.0">
<bpmn:process id="Process_SecondFact" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>SequenceFlow_0c7wlth</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:userTask id="Task_User_Select_Type" name="Set Type" camunda:formKey="Get A Random Fun Fact">
<bpmn:documentation># h1 Heading 8-)
## h2 Heading
### h3 Heading
#### h4 Heading
##### h5 Heading
###### h6 Heading
## Horizontal Rules
___
---
***
## Typographic replacements
"double quotes" and 'single quotes'
## Emphasis
**This is bold text**
__This is bold text__
*This is italic text*
_This is italic text_
~~Strikethrough~~
## Blockquotes
&gt; Blockquotes can also be nested...
&gt;&gt; ...by using additional greater-than signs right next to each other...
&gt; &gt; &gt; ...or with spaces between arrows.
## Lists
Unordered
+ Create a list by starting a line with `+`, `-`, or `*`
+ Sub-lists are made by indenting 2 spaces:
- Marker character change forces new list start:
* Ac tristique libero volutpat at
+ Facilisis in pretium nisl aliquet
- Nulla volutpat aliquam velit
+ Very easy!
Ordered
1. Lorem ipsum dolor sit amet
2. Consectetur adipiscing elit
3. Integer molestie lorem at massa
1. You can use sequential numbers...
1. ...or keep all the numbers as `1.`
Start numbering with offset:
57. foo
1. bar
## Tables
| Option | Description |
| ------ | ----------- |
| data | path to data files to supply the data that will be passed into templates. |
| engine | engine to be used for processing templates. Handlebars is the default. |
| ext | extension to be used for dest files. |
Right aligned columns
| Option | Description |
| ------:| -----------:|
| data | path to data files to supply the data that will be passed into templates. |
| engine | engine to be used for processing templates. Handlebars is the default. |
| ext | extension to be used for dest files. |
## Links
[link text](http://dev.nodeca.com)
[link with title](http://nodeca.github.io/pica/demo/ "title text!")
Autoconverted link https://github.com/nodeca/pica (enable linkify to see)
## Images
![Minion](https://octodex.github.com/images/minion.png)
![Stormtroopocat](https://octodex.github.com/images/stormtroopocat.jpg "The Stormtroopocat")</bpmn:documentation>
<bpmn:extensionElements>
<camunda:formData>
<camunda:formField id="type" label="'Type'" type="enum" defaultValue="cat">
<camunda:validation>
<camunda:constraint name="required" config="true" />
</camunda:validation>
<camunda:value id="norris" name="Chuck Norris" />
<camunda:value id="cat" name="Cat Fact" />
<camunda:value id="buzzword" name="Business Buzzword" />
</camunda:formField>
</camunda:formData>
<camunda:properties>
<camunda:property name="type" value="string" />
</camunda:properties>
</bpmn:extensionElements>
<bpmn:incoming>SequenceFlow_0c7wlth</bpmn:incoming>
<bpmn:outgoing>SequenceFlow_0641sh6</bpmn:outgoing>
</bpmn:userTask>
<bpmn:scriptTask id="Task_Get_Fact_From_API" name="Display Fact">
<bpmn:documentation />
<bpmn:extensionElements>
<camunda:inputOutput>
<camunda:inputParameter name="Fact.type" />
</camunda:inputOutput>
</bpmn:extensionElements>
<bpmn:incoming>SequenceFlow_0641sh6</bpmn:incoming>
<bpmn:outgoing>SequenceFlow_0t29gjo</bpmn:outgoing>
<bpmn:script>FactService = fact_service()</bpmn:script>
</bpmn:scriptTask>
<bpmn:endEvent id="EndEvent_0u1cgrf">
<bpmn:documentation># Great Job!
You have completed the random fact generator.
You chose to receive a random fact of the type: "{{type}}"
Your random fact is:
{{details}}</bpmn:documentation>
<bpmn:incoming>SequenceFlow_0t29gjo</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="SequenceFlow_0c7wlth" sourceRef="StartEvent_1" targetRef="Task_User_Select_Type" />
<bpmn:sequenceFlow id="SequenceFlow_0641sh6" sourceRef="Task_User_Select_Type" targetRef="Task_Get_Fact_From_API" />
<bpmn:sequenceFlow id="SequenceFlow_0t29gjo" sourceRef="Task_Get_Fact_From_API" targetRef="EndEvent_0u1cgrf" />
<bpmn:textAnnotation id="TextAnnotation_09fq7kh">
<bpmn:text>User sets the Fact.type to cat, norris, or buzzword</bpmn:text>
</bpmn:textAnnotation>
<bpmn:association id="Association_1cfasjp" sourceRef="Task_User_Select_Type" targetRef="TextAnnotation_09fq7kh" />
<bpmn:textAnnotation id="TextAnnotation_1234e5n">
<bpmn:text>Makes an API  call to get a fact of the required type.</bpmn:text>
</bpmn:textAnnotation>
<bpmn:association id="Association_1qirnyy" sourceRef="Task_Get_Fact_From_API" targetRef="TextAnnotation_1234e5n" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1ds61df">
<bpmndi:BPMNEdge id="SequenceFlow_0t29gjo_di" bpmnElement="SequenceFlow_0t29gjo">
<di:waypoint x="570" y="250" />
<di:waypoint x="692" y="250" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="SequenceFlow_0641sh6_di" bpmnElement="SequenceFlow_0641sh6">
<di:waypoint x="370" y="250" />
<di:waypoint x="470" y="250" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="SequenceFlow_0c7wlth_di" bpmnElement="SequenceFlow_0c7wlth">
<di:waypoint x="188" y="250" />
<di:waypoint x="270" y="250" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="152" y="232" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="UserTask_186s7tw_di" bpmnElement="Task_User_Select_Type">
<dc:Bounds x="270" y="210" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="EndEvent_0u1cgrf_di" bpmnElement="EndEvent_0u1cgrf">
<dc:Bounds x="692" y="232" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="TextAnnotation_09fq7kh_di" bpmnElement="TextAnnotation_09fq7kh">
<dc:Bounds x="330" y="116" width="99.99202297383536" height="68.28334396936822" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="TextAnnotation_1234e5n_di" bpmnElement="TextAnnotation_1234e5n">
<dc:Bounds x="570" y="120" width="100" height="68" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="ScriptTask_10keafb_di" bpmnElement="Task_Get_Fact_From_API">
<dc:Bounds x="470" y="210" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Association_1cfasjp_di" bpmnElement="Association_1cfasjp">
<di:waypoint x="344" y="210" />
<di:waypoint x="359" y="184" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Association_1qirnyy_di" bpmnElement="Association_1qirnyy">
<di:waypoint x="561" y="210" />
<di:waypoint x="584" y="188" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" id="Definitions_1gjhqt9" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.7.0">
<bpmn:process id="Process_SecondFact" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>Flow_1ctusgn</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:scriptTask id="Task_Get_Fact_From_API" name="Display Fact">
<bpmn:documentation />
<bpmn:extensionElements>
<camunda:inputOutput>
<camunda:inputParameter name="Fact.type" />
</camunda:inputOutput>
</bpmn:extensionElements>
<bpmn:incoming>Flow_1ctusgn</bpmn:incoming>
<bpmn:outgoing>SequenceFlow_0t29gjo</bpmn:outgoing>
<bpmn:script>FactService = fact_service(type='norris')</bpmn:script>
</bpmn:scriptTask>
<bpmn:endEvent id="EndEvent_0u1cgrf">
<bpmn:documentation># Great Job!
You have completed the random fact generator.
You chose to receive a random fact of the type: "{{type}}"
Your random fact is:
{{details}}</bpmn:documentation>
<bpmn:incoming>SequenceFlow_0t29gjo</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="SequenceFlow_0t29gjo" sourceRef="Task_Get_Fact_From_API" targetRef="EndEvent_0u1cgrf" />
<bpmn:sequenceFlow id="Flow_1ctusgn" sourceRef="StartEvent_1" targetRef="Task_Get_Fact_From_API" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_SecondFact">
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="152" y="232" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="ScriptTask_10keafb_di" bpmnElement="Task_Get_Fact_From_API">
<dc:Bounds x="350" y="210" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="EndEvent_0u1cgrf_di" bpmnElement="EndEvent_0u1cgrf">
<dc:Bounds x="582" y="232" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="SequenceFlow_0t29gjo_di" bpmnElement="SequenceFlow_0t29gjo">
<di:waypoint x="450" y="250" />
<di:waypoint x="582" y="250" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1ctusgn_di" bpmnElement="Flow_1ctusgn">
<di:waypoint x="188" y="250" />
<di:waypoint x="350" y="250" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -7,7 +7,7 @@ from tests.spiffworkflow_backend.helpers.base_test import BaseTest
from tests.spiffworkflow_backend.helpers.test_data import load_test_spec
class TestJSONFileDataStore(BaseTest):
class TestJsonFileDataStore(BaseTest):
def test_can_execute_diagram(
self,
app: Flask,

View File

@ -1378,6 +1378,7 @@ class TestProcessApi(BaseTest):
)
assert show_response.json is not None
assert show_response.status_code == 200
assert show_response.json["bpmn_xml_file_contents_retrieval_error"] is None
file_system_root = FileSystemService.root_path()
file_path = f"{file_system_root}/{process_model.id}/{process_model_id}.bpmn"
with open(file_path) as f_open:
@ -1418,6 +1419,7 @@ class TestProcessApi(BaseTest):
)
assert show_response.json is not None
assert show_response.status_code == 200
assert show_response.json["bpmn_xml_file_contents_retrieval_error"] is None
file_system_root = FileSystemService.root_path()
process_instance_file_path = f"{file_system_root}/{process_model.id}/{process_model_id}.bpmn"
with open(process_instance_file_path) as f_open:

View File

@ -27,33 +27,28 @@ from tests.spiffworkflow_backend.helpers.test_data import load_test_spec
class TestProcessInstanceProcessor(BaseTest):
# it's not totally obvious we want to keep this test/file
def test_script_engine_takes_data_and_returns_expected_results(
self,
app: Flask,
with_db_and_bpmn_file_cleanup: None,
) -> None:
app.config["THREAD_LOCAL_DATA"].process_model_identifier = "hey"
app.config["THREAD_LOCAL_DATA"].process_instance_id = 0
script_engine = ProcessInstanceProcessor._default_script_engine
result = script_engine._evaluate("a", {"a": 1})
assert result == 1
app.config["THREAD_LOCAL_DATA"].process_model_identifier = None
app.config["THREAD_LOCAL_DATA"].process_instance_id = None
def test_script_engine_can_use_custom_scripts(
self,
app: Flask,
with_db_and_bpmn_file_cleanup: None,
) -> None:
app.config["THREAD_LOCAL_DATA"].process_model_identifier = "hey"
app.config["THREAD_LOCAL_DATA"].process_instance_id = 0
script_engine = ProcessInstanceProcessor._default_script_engine
result = script_engine._evaluate("fact_service(type='norris')", {})
assert result == "Chuck Norris doesnt read books. He stares them down until he gets the information he wants."
app.config["THREAD_LOCAL_DATA"].process_model_identifier = None
app.config["THREAD_LOCAL_DATA"].process_instance_id = None
process_model = load_test_spec(
process_model_id="test_group/random_fact",
bpmn_file_name="random_fact_set.bpmn",
process_model_source_directory="random_fact",
)
process_instance = self.create_process_instance_from_process_model(process_model=process_model)
processor = ProcessInstanceProcessor(process_instance)
processor.do_engine_steps(save=True)
assert process_instance.status == ProcessInstanceStatus.complete.value
process_data = processor.get_data()
assert process_data is not None
assert "FactService" in process_data
assert (
process_data["FactService"]
== "Chuck Norris doesnt read books. He stares them down until he gets the information he wants."
)
def test_sets_permission_correctly_on_human_task(
self,