simpleflow 0.36.0__tar.gz → 0.37.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {simpleflow-0.36.0 → simpleflow-0.37.0}/PKG-INFO +4 -3
- {simpleflow-0.36.0 → simpleflow-0.37.0}/pyproject.toml +5 -2
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/format.py +24 -1
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/step/constants.py +4 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/step/submittable.py +23 -7
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/step/workflow.py +63 -3
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/executor.py +3 -1
- {simpleflow-0.36.0 → simpleflow-0.37.0}/.gitignore +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/LICENSE +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/README.md +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/__init__.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/__main__.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/_decorators.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/activity.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/base.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/boto3_utils.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/canvas.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/command.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/constants.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/dispatch/__init__.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/dispatch/by_module.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/dispatch/dry_run.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/dispatch/dynamic_dispatcher.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/dispatch/exceptions.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/dispatch/from_task_registry.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/download.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/exceptions.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/execute.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/executor.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/futures.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/history.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/job/__init__.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/local/__init__.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/local/executor.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/log.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/logging_context.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/marker.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/metrology.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/process/__init__.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/process/_named_mixin.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/process/_supervisor.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/registry.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/runtime/__init__.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/settings/__init__.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/settings/base.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/settings/default.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/signal.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/step/__init__.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/step/tasks.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/step/utils.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/storage.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/__init__.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/constants.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/helpers.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/mapper/__init__.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/mapper/actors/__init__.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/mapper/actors/core.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/mapper/actors/decider.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/mapper/actors/worker.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/mapper/constants.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/mapper/core.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/mapper/exceptions.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/mapper/models/__init__.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/mapper/models/activity.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/mapper/models/base.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/mapper/models/decision/__init__.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/mapper/models/decision/base.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/mapper/models/decision/marker.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/mapper/models/decision/task.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/mapper/models/decision/timer.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/mapper/models/decision/workflow.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/mapper/models/domain.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/mapper/models/event/__init__.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/mapper/models/event/base.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/mapper/models/event/compiler.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/mapper/models/event/factory.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/mapper/models/event/marker.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/mapper/models/event/task.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/mapper/models/event/timer.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/mapper/models/event/workflow.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/mapper/models/history/__init__.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/mapper/models/history/base.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/mapper/models/history/builder.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/mapper/models/workflow.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/mapper/querysets/__init__.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/mapper/querysets/activity.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/mapper/querysets/base.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/mapper/querysets/domain.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/mapper/querysets/history.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/mapper/querysets/workflow.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/mapper/responses/__init__.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/mapper/responses/_base.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/mapper/settings.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/mapper/utils.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/process/__init__.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/process/decider/__init__.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/process/decider/base.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/process/decider/command.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/process/decider/helpers.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/process/poller.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/process/worker/__init__.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/process/worker/base.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/process/worker/command.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/stats/__init__.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/stats/base.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/stats/pretty.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/task.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/swf/utils.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/task.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/utils/__init__.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/utils/_dict.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/utils/_json.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/utils/retry.py +0 -0
- {simpleflow-0.36.0 → simpleflow-0.37.0}/simpleflow/workflow.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: simpleflow
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.37.0
|
|
4
4
|
Summary: Python library for dataflow programming with Amazon SWF
|
|
5
5
|
Project-URL: documentation, https://botify-labs.github.io/simpleflow
|
|
6
6
|
Project-URL: repository, https://github.com/botify-labs/simpleflow
|
|
@@ -25,7 +25,6 @@ Requires-Dist: attrs
|
|
|
25
25
|
Requires-Dist: boto3>=1.28.20
|
|
26
26
|
Requires-Dist: click
|
|
27
27
|
Requires-Dist: dill>=0.3.6
|
|
28
|
-
Requires-Dist: diskcache>=4.1.0
|
|
29
28
|
Requires-Dist: jinja2
|
|
30
29
|
Requires-Dist: lazy-object-proxy
|
|
31
30
|
Requires-Dist: lockfile>=0.9.1
|
|
@@ -35,6 +34,8 @@ Requires-Dist: psutil
|
|
|
35
34
|
Requires-Dist: pytz
|
|
36
35
|
Requires-Dist: setproctitle
|
|
37
36
|
Requires-Dist: tabulate<1.0.0,>=0.8.2
|
|
37
|
+
Provides-Extra: diskcache
|
|
38
|
+
Requires-Dist: diskcache>=4.1.0; extra == 'diskcache'
|
|
38
39
|
Description-Content-Type: text/markdown
|
|
39
40
|
|
|
40
41
|
Simpleflow
|
|
@@ -35,7 +35,6 @@ dependencies = [
|
|
|
35
35
|
"attrs",
|
|
36
36
|
"boto3>=1.28.20",
|
|
37
37
|
"dill>=0.3.6",
|
|
38
|
-
"diskcache>=4.1.0",
|
|
39
38
|
"Jinja2",
|
|
40
39
|
"markupsafe",
|
|
41
40
|
"multiprocess>=0.70.14",
|
|
@@ -48,6 +47,11 @@ dependencies = [
|
|
|
48
47
|
"pytz",
|
|
49
48
|
]
|
|
50
49
|
|
|
50
|
+
[project.optional-dependencies]
|
|
51
|
+
# Jumbo fields disk cache, only used when SIMPLEFLOW_ENABLE_DISK_CACHE is set (deprecated).
|
|
52
|
+
# Optional because diskcache unpickles cache entries: https://osv.dev/vulnerability/CVE-2025-69872
|
|
53
|
+
diskcache = ["diskcache>=4.1.0"]
|
|
54
|
+
|
|
51
55
|
[project.urls]
|
|
52
56
|
documentation = "https://botify-labs.github.io/simpleflow"
|
|
53
57
|
repository = "https://github.com/botify-labs/simpleflow"
|
|
@@ -127,7 +131,6 @@ dev = [
|
|
|
127
131
|
"pytest",
|
|
128
132
|
"pytest-xdist>=3.2.0",
|
|
129
133
|
"ruff",
|
|
130
|
-
"setuptools<72", # See https://github.com/pypa/setuptools/issues/4519
|
|
131
134
|
"sphinx",
|
|
132
135
|
"sphinx_rtd_theme",
|
|
133
136
|
"typing-extensions",
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
|
+
import warnings
|
|
4
5
|
from sqlite3 import OperationalError
|
|
5
6
|
from typing import Any, cast
|
|
6
7
|
from uuid import uuid4
|
|
7
8
|
|
|
8
9
|
import lazy_object_proxy
|
|
9
|
-
from diskcache import Cache
|
|
10
10
|
|
|
11
11
|
from simpleflow import constants, logger, storage
|
|
12
12
|
from simpleflow.settings import SIMPLEFLOW_ENABLE_DISK_CACHE
|
|
@@ -78,6 +78,17 @@ def encode(message: str | None, max_length: int, allow_jumbo_fields: bool = True
|
|
|
78
78
|
return message
|
|
79
79
|
|
|
80
80
|
|
|
81
|
+
DISK_CACHE_DEPRECATION = (
|
|
82
|
+
"SIMPLEFLOW_ENABLE_DISK_CACHE is deprecated and will be removed: its only backend, diskcache, "
|
|
83
|
+
"unpickles cache entries (https://osv.dev/vulnerability/CVE-2025-69872), so anyone who can write to the cache "
|
|
84
|
+
"directory can run code in the process that reads it."
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _warn_disk_cache_deprecated() -> None:
|
|
89
|
+
warnings.warn(DISK_CACHE_DEPRECATION, DeprecationWarning, stacklevel=3)
|
|
90
|
+
|
|
91
|
+
|
|
81
92
|
def _get_cached(path: str) -> str | None:
|
|
82
93
|
# 1/ memory cache
|
|
83
94
|
if path in JUMBO_FIELDS_MEMORY_CACHE:
|
|
@@ -85,11 +96,15 @@ def _get_cached(path: str) -> str | None:
|
|
|
85
96
|
|
|
86
97
|
# 2/ disk cache
|
|
87
98
|
if SIMPLEFLOW_ENABLE_DISK_CACHE:
|
|
99
|
+
_warn_disk_cache_deprecated()
|
|
88
100
|
try:
|
|
89
101
|
# NB: this cache may also be triggered on activity workers, where it's not that
|
|
90
102
|
# useful. The performance hit should be minimal. To be improved later.
|
|
91
103
|
# NB2: cache has to be lazily instantiated here, cache objects do not survive forks,
|
|
92
104
|
# see DiskCache docs.
|
|
105
|
+
# Optional extra: simpleflow[diskcache]. Deprecated, see _warn_disk_cache_deprecated.
|
|
106
|
+
from diskcache import Cache
|
|
107
|
+
|
|
93
108
|
cache = Cache(constants.CACHE_DIR)
|
|
94
109
|
# generate a dedicated cache key because this cache may be shared with other
|
|
95
110
|
# features of simpleflow at some point
|
|
@@ -97,6 +112,8 @@ def _get_cached(path: str) -> str | None:
|
|
|
97
112
|
if cache_key in cache:
|
|
98
113
|
logger.debug(f"diskcache: getting key={cache_key} from cache_dir={constants.CACHE_DIR}")
|
|
99
114
|
return cache[cache_key]
|
|
115
|
+
except ImportError:
|
|
116
|
+
logger.warning("diskcache: not installed (pip install simpleflow[diskcache]), skipping cache usage")
|
|
100
117
|
except OperationalError:
|
|
101
118
|
logger.warning("diskcache: got an OperationalError, skipping cache usage")
|
|
102
119
|
|
|
@@ -110,11 +127,17 @@ def _set_cached(path: str, content: str) -> None:
|
|
|
110
127
|
|
|
111
128
|
# 2/ disk cache
|
|
112
129
|
if SIMPLEFLOW_ENABLE_DISK_CACHE:
|
|
130
|
+
_warn_disk_cache_deprecated()
|
|
113
131
|
try:
|
|
132
|
+
# Optional extra: simpleflow[diskcache]. Deprecated, see _warn_disk_cache_deprecated.
|
|
133
|
+
from diskcache import Cache
|
|
134
|
+
|
|
114
135
|
cache = Cache(constants.CACHE_DIR)
|
|
115
136
|
cache_key = "jumbo_fields/" + path.split("/")[-1]
|
|
116
137
|
logger.debug(f"diskcache: setting key={cache_key} on cache_dir={constants.CACHE_DIR}")
|
|
117
138
|
cache.set(cache_key, content, expire=3 * constants.HOUR)
|
|
139
|
+
except ImportError:
|
|
140
|
+
logger.warning("diskcache: not installed (pip install simpleflow[diskcache]), skipping cache write")
|
|
118
141
|
except OperationalError:
|
|
119
142
|
logger.warning("diskcache: got an OperationalError on write, skipping cache write")
|
|
120
143
|
|
|
@@ -25,3 +25,7 @@ STEP_ACTIVITY_PARAMS_DEFAULT: dict[str, Any] = {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
UNKNOWN_CONTEXT = {"run_id": "unknown", "workflow_id": "unknown", "version": "unknown"}
|
|
28
|
+
|
|
29
|
+
# Prefix of the marker memoizing, in the workflow history, the steps done at the
|
|
30
|
+
# beginning of an execution (only used with ``WorkflowStepMixin.steps_in_decider``).
|
|
31
|
+
STEPS_DONE_MARKER_PREFIX = "step.steps_done"
|
|
@@ -57,6 +57,15 @@ class Step(SubmittableContainer):
|
|
|
57
57
|
if workflow is None:
|
|
58
58
|
raise ValueError("Executor has no associated workflow")
|
|
59
59
|
|
|
60
|
+
steps_in_decider = getattr(workflow, "steps_in_decider", False)
|
|
61
|
+
|
|
62
|
+
def mark_step_done_in_decider(marker_done):
|
|
63
|
+
# The "completed" marker is recorded in the same decision as the S3 push:
|
|
64
|
+
# once it is in the history, the step is not pushed again on replays.
|
|
65
|
+
if not any(m.name == "log.step" and m.details == marker_done for m in workflow.list_markers(all=True)):
|
|
66
|
+
workflow._mark_step_done_in_decider(self.step_name)
|
|
67
|
+
return workflow.record_marker("log.step", marker_done)
|
|
68
|
+
|
|
60
69
|
def fn_steps_done(steps_done):
|
|
61
70
|
marker = {
|
|
62
71
|
"step": self.step_name,
|
|
@@ -81,14 +90,19 @@ class Step(SubmittableContainer):
|
|
|
81
90
|
chain += (
|
|
82
91
|
workflow.record_marker("log.step", marker),
|
|
83
92
|
self.activities,
|
|
84
|
-
(
|
|
85
|
-
activity.Activity(MarkStepDoneTask, **workflow._get_step_activity_params()),
|
|
86
|
-
workflow.get_step_bucket(),
|
|
87
|
-
workflow.get_step_path_prefix(),
|
|
88
|
-
self.step_name,
|
|
89
|
-
),
|
|
90
|
-
workflow.record_marker("log.step", marker_done),
|
|
91
93
|
)
|
|
94
|
+
if steps_in_decider:
|
|
95
|
+
chain.append(FuncGroup(mark_step_done_in_decider, marker_done))
|
|
96
|
+
else:
|
|
97
|
+
chain += (
|
|
98
|
+
(
|
|
99
|
+
activity.Activity(MarkStepDoneTask, **workflow._get_step_activity_params()),
|
|
100
|
+
workflow.get_step_bucket(),
|
|
101
|
+
workflow.get_step_path_prefix(),
|
|
102
|
+
self.step_name,
|
|
103
|
+
),
|
|
104
|
+
workflow.record_marker("log.step", marker_done),
|
|
105
|
+
)
|
|
92
106
|
else:
|
|
93
107
|
marker["status"] = "skipped"
|
|
94
108
|
if step_is_skipped_by_force(self.step_name, skipped_steps):
|
|
@@ -108,6 +122,8 @@ class Step(SubmittableContainer):
|
|
|
108
122
|
chain.bubbles_exception_on_failure = self.bubbles_exception_on_failure
|
|
109
123
|
return chain
|
|
110
124
|
|
|
125
|
+
if steps_in_decider:
|
|
126
|
+
return workflow.submit(fn_steps_done(workflow._get_steps_done_in_decider()))
|
|
111
127
|
return workflow.submit(
|
|
112
128
|
Chain(
|
|
113
129
|
workflow.get_steps_done_activity(),
|
|
@@ -2,15 +2,17 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import abc
|
|
4
4
|
import copy
|
|
5
|
+
import hashlib
|
|
5
6
|
import os
|
|
6
7
|
from collections import defaultdict
|
|
7
8
|
from typing import TYPE_CHECKING
|
|
8
9
|
|
|
9
|
-
from simpleflow import activity, settings, task
|
|
10
|
+
from simpleflow import activity, constants, settings, task
|
|
11
|
+
from simpleflow.utils import json_dumps, retry
|
|
10
12
|
|
|
11
|
-
from .constants import STEP_ACTIVITY_PARAMS_DEFAULT
|
|
13
|
+
from .constants import STEP_ACTIVITY_PARAMS_DEFAULT, STEPS_DONE_MARKER_PREFIX
|
|
12
14
|
from .submittable import Step
|
|
13
|
-
from .tasks import GetStepsDoneTask
|
|
15
|
+
from .tasks import GetStepsDoneTask, MarkStepDoneTask
|
|
14
16
|
|
|
15
17
|
if TYPE_CHECKING:
|
|
16
18
|
from collections.abc import Sequence
|
|
@@ -18,6 +20,11 @@ if TYPE_CHECKING:
|
|
|
18
20
|
|
|
19
21
|
|
|
20
22
|
class WorkflowStepMixin(metaclass=abc.ABCMeta):
|
|
23
|
+
# Opt-in: list and mark the steps done from the decider instead of
|
|
24
|
+
# GetStepsDoneTask/MarkStepDoneTask activities. The S3 listing is done once
|
|
25
|
+
# per execution and memoized in a marker so that replays stay deterministic.
|
|
26
|
+
steps_in_decider: bool = False
|
|
27
|
+
|
|
21
28
|
def get_step_bucket(self) -> str:
|
|
22
29
|
"""
|
|
23
30
|
Return the S3 bucket where to store the steps files.
|
|
@@ -110,3 +117,56 @@ class WorkflowStepMixin(metaclass=abc.ABCMeta):
|
|
|
110
117
|
Return the list of steps done.
|
|
111
118
|
"""
|
|
112
119
|
return self.submit(self.get_steps_done_activity()).result
|
|
120
|
+
|
|
121
|
+
def _get_steps_done_in_decider(self) -> list[str]:
|
|
122
|
+
"""
|
|
123
|
+
Return the list of steps done, as listed on S3 the first time it is
|
|
124
|
+
needed in this execution.
|
|
125
|
+
|
|
126
|
+
The listing is recorded in a marker, then read back from the history on
|
|
127
|
+
the next replays: the steps marked done later in this execution don't
|
|
128
|
+
change the decisions already taken.
|
|
129
|
+
"""
|
|
130
|
+
bucket = self.get_step_bucket()
|
|
131
|
+
path = self.get_step_path_prefix()
|
|
132
|
+
location_hash = hashlib.md5(f"{bucket}/{path}".encode()).hexdigest() # nosec
|
|
133
|
+
marker_name = f"{STEPS_DONE_MARKER_PREFIX}.{location_hash}"
|
|
134
|
+
|
|
135
|
+
# Cache for the current replay only: the workflow instance may be reused
|
|
136
|
+
# across replays, whereas the executor builds a new history for each one.
|
|
137
|
+
history = self.executor._history
|
|
138
|
+
if getattr(self, "_steps_done_snapshots", (None,))[0] is not history:
|
|
139
|
+
self._steps_done_snapshots: tuple[Any, dict[str, list[str]]] = (history, {})
|
|
140
|
+
snapshots = self._steps_done_snapshots[1]
|
|
141
|
+
if marker_name not in snapshots:
|
|
142
|
+
event = self.get_event_details("marker", marker_name)
|
|
143
|
+
if event is not None:
|
|
144
|
+
steps_done = event["details"]["steps_done"]
|
|
145
|
+
else:
|
|
146
|
+
steps_done = _execute_with_retry(GetStepsDoneTask(bucket, path))
|
|
147
|
+
details = {"bucket": bucket, "path": path, "steps_done": steps_done}
|
|
148
|
+
if len(json_dumps(details)) > constants.MAX_DETAILS_LENGTH:
|
|
149
|
+
raise ValueError(
|
|
150
|
+
f"Too many steps done in {bucket}/{path} to record them in a marker;"
|
|
151
|
+
" disable steps_in_decider for this workflow"
|
|
152
|
+
)
|
|
153
|
+
# Not waited for: the marker is recorded along with the other decisions.
|
|
154
|
+
self.submit(self.record_marker(marker_name, details))
|
|
155
|
+
snapshots[marker_name] = steps_done
|
|
156
|
+
return snapshots[marker_name]
|
|
157
|
+
|
|
158
|
+
def _mark_step_done_in_decider(self, step_name: str) -> None:
|
|
159
|
+
"""
|
|
160
|
+
Push the step file on S3 from the decider.
|
|
161
|
+
"""
|
|
162
|
+
mark_step_done = MarkStepDoneTask(self.get_step_bucket(), self.get_step_path_prefix(), step_name)
|
|
163
|
+
run_context = self.get_run_context()
|
|
164
|
+
if run_context.get("run_id"):
|
|
165
|
+
mark_step_done.context = run_context
|
|
166
|
+
_execute_with_retry(mark_step_done)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
@retry.with_delay(nb_times=3, delay=retry.exponential)
|
|
170
|
+
def _execute_with_retry(a_task: GetStepsDoneTask | MarkStepDoneTask) -> Any:
|
|
171
|
+
# An exception in the decider fails the workflow: retry transient S3 errors.
|
|
172
|
+
return a_task.execute()
|
|
@@ -1260,7 +1260,9 @@ class Executor(executor.Executor):
|
|
|
1260
1260
|
|
|
1261
1261
|
def list_markers(self, all=False):
|
|
1262
1262
|
if all:
|
|
1263
|
-
return [
|
|
1263
|
+
return [
|
|
1264
|
+
Marker(m["name"], format.decode(m.get("details"))) for ml in self._history.markers.values() for m in ml
|
|
1265
|
+
]
|
|
1264
1266
|
rc = []
|
|
1265
1267
|
for ml in self._history.markers.values():
|
|
1266
1268
|
m = ml[-1]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|