Skip to content

Commit 9d3f516

Browse files
authored
add script globals to common to match backend (#3090)
1 parent f2aac8a commit 9d3f516

4 files changed

Lines changed: 89 additions & 3 deletions

File tree

‎spiff-arena-common/pyproject.toml‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
[project]
22
name = "spiff-arena-common"
3-
version = "0.1.49"
3+
version = "0.1.50"
44
description = "Shared Python utilities for Spiff Arena frontend and backend components."
55
readme = "README.md"
66
requires-python = ">=3.10"
77

88
dependencies = [
99
"Jinja2==3.1.6",
1010
"jsonschema==4.26.0",
11+
"pytz>=2025.2",
1112
"SpiffWorkflow==3.2.1a1788363675",
1213
]
1314

‎spiff-arena-common/src/spiff_arena_common/runner.py‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
import base64
22
import calendar
33
import datetime
4+
import decimal
45
import gzip
6+
import hashlib
57
import json
68
import logging
9+
import random
10+
import re
711
import time
812
import uuid
913
from types import ModuleType
14+
from urllib.parse import unquote
1015

1116
import jsonschema
17+
import pytz
1218

1319

1420
# This encoder/decoder replicates the type conversion behavior of
@@ -259,16 +265,32 @@ class CustomParser(SpiffBpmnParser):
259265
del SPIFF_CONFIG[SendTask]
260266
del SPIFF_CONFIG[StartEvent]
261267

268+
def _markdown_file_download_link(file_reference):
269+
# Arena passes "data:<mimetype>;name=<name>;base64,spifffiledatadigest+<sha256>";
270+
# Ed's forms keep the file contents inline instead of a digest.
271+
metadata, payload = file_reference.rsplit(";base64,", 1)
272+
match = re.fullmatch(r"spifffiledatadigest\+([0-9a-f]{64})", payload)
273+
digest = match.group(1) if match else hashlib.sha256(base64.b64decode(payload)).hexdigest()
274+
filename = unquote(metadata.split(";name=", 1)[1]) if ";name=" in metadata else digest
275+
label = re.sub(r"([\\\\`*\[\]_<>&])", r"\\\1", filename)
276+
return f"[{label}](http://local.spiff/process-data-file-download/local/0/{digest})"
277+
278+
262279
class CustomEnvironment(TaskDataEnvironment):
263280
def __init__(self):
264281
# TODO: would be nice to get these from the client so we don't have to code change for globals
265282
super().__init__(environment_globals={
266283
"calendar": calendar,
267284
"datetime": datetime.datetime,
285+
"decimal": decimal,
268286
"json": json,
269287
"jsonschema": jsonschema,
288+
"pytz": pytz,
289+
"random": random,
290+
"re": re,
270291
"time": time,
271292
"timedelta": datetime.timedelta,
293+
"uuid": uuid,
272294
})
273295

274296
def _build_external_context(self, context, external_context=None):
@@ -301,6 +323,8 @@ def _build_external_context(self, context, external_context=None):
301323
external_context["get_process_initiator_user"] = lambda: {
302324
key: value for key, value in process_initiator_user.items()
303325
}
326+
external_context["markdown_file_download_link"] = _markdown_file_download_link
327+
external_context["get_markdown_file_download_link"] = _markdown_file_download_link
304328
external_context["get_group_members"] = lambda group_name: [
305329
"group_member_1@example.com",
306330
"group_member_2@example.com",

‎spiff-arena-common/tests/spiff_arena_common/test_runner.py‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import hashlib
12
import json
23
from types import SimpleNamespace
34

@@ -1074,3 +1075,52 @@ def connector_404(*_args, **_kwargs):
10741075
assert result["status"] == "ok"
10751076
assert result["completed"] is True
10761077
assert result["result"]["route"] == "handled"
1078+
1079+
1080+
def test_custom_environment_provides_arena_script_modules():
1081+
context = {}
1082+
1083+
result = runner.CustomEnvironment().execute(
1084+
'tz = pytz.timezone("US/Central").zone\n'
1085+
'amount = str(decimal.Decimal("1.10") + decimal.Decimal("2.20"))\n'
1086+
'matched = re.match(r"\\d+", "42abc").group(0)\n'
1087+
'picked = random.choice(["only"])\n'
1088+
'is_uuid = isinstance(uuid.uuid4(), uuid.UUID)',
1089+
context,
1090+
)
1091+
1092+
assert result is True
1093+
assert context["tz"] == "US/Central"
1094+
assert context["amount"] == "3.30"
1095+
assert context["matched"] == "42"
1096+
assert context["picked"] == "only"
1097+
assert context["is_uuid"] is True
1098+
1099+
1100+
def test_custom_environment_markdown_file_download_link_handles_arena_digest_references():
1101+
digest = "7a2051ffefd1eaf475dbef9fda019cb3d4a10eb8aea4c2c2a84a50a797a541bf"
1102+
context = {"file": f"data:text/plain;name=my%20report_v1.txt;base64,spifffiledatadigest+{digest}"}
1103+
1104+
result = runner.CustomEnvironment().execute(
1105+
"link = markdown_file_download_link(file)\n"
1106+
"legacy_link = get_markdown_file_download_link(file)",
1107+
context,
1108+
)
1109+
1110+
assert result is True
1111+
expected = f"[my report\\_v1.txt](http://local.spiff/process-data-file-download/local/0/{digest})"
1112+
assert context["link"] == expected
1113+
assert context["legacy_link"] == expected
1114+
1115+
1116+
def test_custom_environment_markdown_file_download_link_handles_inline_file_data():
1117+
context = {"files": ["data:text/plain;name=notes.txt;base64,ZHVtbXk="]}
1118+
1119+
result = runner.CustomEnvironment().execute(
1120+
"links = [markdown_file_download_link(f) for f in files]",
1121+
context,
1122+
)
1123+
1124+
assert result is True
1125+
digest = hashlib.sha256(b"dummy").hexdigest()
1126+
assert context["links"] == [f"[notes.txt](http://local.spiff/process-data-file-download/local/0/{digest})"]

‎uv.lock‎

Lines changed: 13 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)