stepfunction 0.0.7__tar.gz → 0.2.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.
- {stepfunction-0.0.7/src/StepFunction.egg-info → stepfunction-0.2.0}/PKG-INFO +36 -8
- stepfunction-0.2.0/README.md +75 -0
- {stepfunction-0.0.7 → stepfunction-0.2.0}/pyproject.toml +3 -3
- stepfunction-0.2.0/src/stepfunction/constants/visualizer.py +53 -0
- stepfunction-0.2.0/src/stepfunction/core/serializer/__init__.py +3 -0
- stepfunction-0.2.0/src/stepfunction/core/serializer/serializer.py +170 -0
- stepfunction-0.2.0/src/stepfunction/core/step_function/step_function.py +743 -0
- stepfunction-0.2.0/src/stepfunction/core/visualizer/visualizer.py +370 -0
- stepfunction-0.2.0/src/stepfunction/exceptions/step_errors.py +50 -0
- stepfunction-0.2.0/src/stepfunction/hooks/__init__.py +4 -0
- stepfunction-0.2.0/src/stepfunction/hooks/events.py +73 -0
- stepfunction-0.2.0/src/stepfunction/hooks/step_function_hooks.py +51 -0
- stepfunction-0.2.0/src/stepfunction/registry/step_registry.py +97 -0
- stepfunction-0.2.0/src/stepfunction/types/__init__.py +0 -0
- {stepfunction-0.0.7 → stepfunction-0.2.0}/src/stepfunction/types/step_types.py +5 -1
- stepfunction-0.2.0/src/stepfunction/utils/__init__.py +0 -0
- {stepfunction-0.0.7 → stepfunction-0.2.0}/src/stepfunction/utils/utils.py +6 -0
- {stepfunction-0.0.7 → stepfunction-0.2.0/src/stepfunction.egg-info}/PKG-INFO +36 -8
- {stepfunction-0.0.7/src/StepFunction.egg-info → stepfunction-0.2.0/src/stepfunction.egg-info}/SOURCES.txt +8 -7
- stepfunction-0.0.7/README.md +0 -46
- stepfunction-0.0.7/src/StepFunction.egg-info/requires.txt +0 -1
- stepfunction-0.0.7/src/stepfunction/constants/visualizer.py +0 -47
- stepfunction-0.0.7/src/stepfunction/core/step_function/step_function.py +0 -434
- stepfunction-0.0.7/src/stepfunction/core/visualizer/visualizer.py +0 -146
- stepfunction-0.0.7/src/stepfunction/exceptions/step_errors.py +0 -14
- stepfunction-0.0.7/src/stepfunction/types/visualizer_types.py +0 -8
- {stepfunction-0.0.7 → stepfunction-0.2.0}/LICENSE +0 -0
- {stepfunction-0.0.7 → stepfunction-0.2.0}/setup.cfg +0 -0
- {stepfunction-0.0.7/src/stepfunction/constants → stepfunction-0.2.0/src/stepfunction}/__init__.py +0 -0
- {stepfunction-0.0.7/src/stepfunction/exceptions → stepfunction-0.2.0/src/stepfunction/constants}/__init__.py +0 -0
- {stepfunction-0.0.7 → stepfunction-0.2.0}/src/stepfunction/constants/enums.py +0 -0
- {stepfunction-0.0.7 → stepfunction-0.2.0}/src/stepfunction/core/step_function/__init__.py +0 -0
- {stepfunction-0.0.7 → stepfunction-0.2.0}/src/stepfunction/core/visualizer/__init__.py +0 -0
- {stepfunction-0.0.7/src/stepfunction/types → stepfunction-0.2.0/src/stepfunction/exceptions}/__init__.py +0 -0
- {stepfunction-0.0.7/src/stepfunction/utils → stepfunction-0.2.0/src/stepfunction/registry}/__init__.py +0 -0
- {stepfunction-0.0.7 → stepfunction-0.2.0}/src/stepfunction/steps/__init__.py +0 -0
- {stepfunction-0.0.7 → stepfunction-0.2.0}/src/stepfunction/steps/base/__init__.py +0 -0
- {stepfunction-0.0.7 → stepfunction-0.2.0}/src/stepfunction/steps/base/base_step.py +0 -0
- {stepfunction-0.0.7 → stepfunction-0.2.0}/src/stepfunction/steps/exceptions/__init__.py +0 -0
- {stepfunction-0.0.7 → stepfunction-0.2.0}/src/stepfunction/steps/exceptions/step_exceptions.py +0 -0
- {stepfunction-0.0.7 → stepfunction-0.2.0}/src/stepfunction/steps/retry_step.py +0 -0
- {stepfunction-0.0.7 → stepfunction-0.2.0}/src/stepfunction/steps/timeout_step.py +0 -0
- {stepfunction-0.0.7 → stepfunction-0.2.0}/src/stepfunction/steps/wait_step.py +0 -0
- {stepfunction-0.0.7 → stepfunction-0.2.0}/src/stepfunction/utils/constants.py +0 -0
- {stepfunction-0.0.7 → stepfunction-0.2.0}/src/stepfunction/utils/logger.py +0 -0
- {stepfunction-0.0.7/src/StepFunction.egg-info → stepfunction-0.2.0/src/stepfunction.egg-info}/dependency_links.txt +0 -0
- {stepfunction-0.0.7/src/StepFunction.egg-info → stepfunction-0.2.0/src/stepfunction.egg-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: stepfunction
|
|
3
|
-
Version: 0.0
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Step Function Workflow Orchestration Library
|
|
5
5
|
Author: Vineeth Penugonda
|
|
6
6
|
License-Expression: MIT
|
|
@@ -16,7 +16,6 @@ Classifier: Topic :: Software Development :: Libraries
|
|
|
16
16
|
Requires-Python: >=3.9
|
|
17
17
|
Description-Content-Type: text/markdown
|
|
18
18
|
License-File: LICENSE
|
|
19
|
-
Requires-Dist: graphviz===0.20.3
|
|
20
19
|
Dynamic: license-file
|
|
21
20
|
|
|
22
21
|
# StepFunction
|
|
@@ -33,7 +32,40 @@ Dynamic: license-file
|
|
|
33
32
|
- **Error Handling**: Define failure paths and handle exceptions gracefully.
|
|
34
33
|
- **Branching Logic**: Direct workflows based on conditions.
|
|
35
34
|
- **Sub-Step Functions**: Modularize workflows by embedding sub-step functions.
|
|
36
|
-
- **Visualization**: Integrated support for visualizing the workflow graph.
|
|
35
|
+
- **Visualization**: Integrated support for visualizing the workflow graph as a Mermaid flowchart.
|
|
36
|
+
- **Lifecycle Hooks**: Get notified as the workflow and each step start, succeed or fail — with inputs, outputs, errors, routing and timings — to record run history, metrics or traces.
|
|
37
|
+
|
|
38
|
+
## Lifecycle Hooks
|
|
39
|
+
|
|
40
|
+
Subclass `StepFunctionHooks`, override the events you need (sync or async), and pass an instance to the step function:
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
from stepfunction.core.step_function import StepFunction
|
|
44
|
+
from stepfunction.hooks import StepEvent, StepFunctionHooks
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class RunRecorder(StepFunctionHooks):
|
|
48
|
+
async def on_step_start(self, event: StepEvent):
|
|
49
|
+
print(f"{event.workflow}: {event.step} started with {event.input!r}")
|
|
50
|
+
|
|
51
|
+
async def on_step_success(self, event: StepEvent):
|
|
52
|
+
print(f"{event.step} -> {event.output!r} in {event.duration:.3f}s, next: {event.next_step}")
|
|
53
|
+
|
|
54
|
+
async def on_step_failure(self, event: StepEvent):
|
|
55
|
+
print(f"{event.step} failed: {event.error!r}, next: {event.next_step}")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
sf = StepFunction("MyStepFunction", hooks=RunRecorder())
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Events: `on_workflow_start`, `on_step_start`, `on_step_success`, `on_step_failure`, `on_workflow_end`.
|
|
62
|
+
|
|
63
|
+
- `StepEvent` carries the workflow path, step name, parallel `task` name (if any), `input`, `output`, the original `error`, the resolved `next_step`, UTC `started_at` / `finished_at` and `duration` in seconds.
|
|
64
|
+
- `WorkflowEvent` carries the workflow path, `status`, initial `input`, final `output`, the `error` that ended the run (if `execute()` raised) and timings.
|
|
65
|
+
- Each task of a parallel step is reported as its own step event, with `task` set.
|
|
66
|
+
- A sub-step function without hooks of its own reports to its parent's hooks; its events carry the nested path, e.g. `"FLOW -> SubStep (SUB_FLOW)"`.
|
|
67
|
+
- Hooks observe; they don't steer. An exception raised by a hook is logged and ignored.
|
|
68
|
+
- If the run is cancelled, the running step and the workflow are reported with the `CancelledError`, which is then re-raised.
|
|
37
69
|
|
|
38
70
|
## Installation
|
|
39
71
|
|
|
@@ -49,11 +81,7 @@ For more detailed usage examples and advanced use cases, please read the [blog p
|
|
|
49
81
|
|
|
50
82
|
## Dependencies
|
|
51
83
|
|
|
52
|
-
The StepFunction package
|
|
53
|
-
|
|
54
|
-
- `graphviz == 0.20.3`
|
|
55
|
-
|
|
56
|
-
These will be automatically installed when you install the package via pip.
|
|
84
|
+
The StepFunction package has no required runtime dependencies. Workflow visualization renders directly to [Mermaid](https://mermaid.js.org/) flowchart syntax (`.mmd`), which can be previewed in VS Code, GitHub/Bitbucket markdown, or https://mermaid.live without any additional tooling.
|
|
57
85
|
|
|
58
86
|
## Contributing
|
|
59
87
|
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# StepFunction
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
`StepFunction` is a Python library for orchestrating complex workflows through a step-by-step process. It allows for easy management of sequential and parallel tasks with robust error handling and branching. The library is inspired by AWS Step Functions but implemented as a Python-native solution to orchestrate workflows across any environment.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Sequential and Parallel Execution**: Run steps one after another or in parallel.
|
|
12
|
+
- **Error Handling**: Define failure paths and handle exceptions gracefully.
|
|
13
|
+
- **Branching Logic**: Direct workflows based on conditions.
|
|
14
|
+
- **Sub-Step Functions**: Modularize workflows by embedding sub-step functions.
|
|
15
|
+
- **Visualization**: Integrated support for visualizing the workflow graph as a Mermaid flowchart.
|
|
16
|
+
- **Lifecycle Hooks**: Get notified as the workflow and each step start, succeed or fail — with inputs, outputs, errors, routing and timings — to record run history, metrics or traces.
|
|
17
|
+
|
|
18
|
+
## Lifecycle Hooks
|
|
19
|
+
|
|
20
|
+
Subclass `StepFunctionHooks`, override the events you need (sync or async), and pass an instance to the step function:
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
from stepfunction.core.step_function import StepFunction
|
|
24
|
+
from stepfunction.hooks import StepEvent, StepFunctionHooks
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class RunRecorder(StepFunctionHooks):
|
|
28
|
+
async def on_step_start(self, event: StepEvent):
|
|
29
|
+
print(f"{event.workflow}: {event.step} started with {event.input!r}")
|
|
30
|
+
|
|
31
|
+
async def on_step_success(self, event: StepEvent):
|
|
32
|
+
print(f"{event.step} -> {event.output!r} in {event.duration:.3f}s, next: {event.next_step}")
|
|
33
|
+
|
|
34
|
+
async def on_step_failure(self, event: StepEvent):
|
|
35
|
+
print(f"{event.step} failed: {event.error!r}, next: {event.next_step}")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
sf = StepFunction("MyStepFunction", hooks=RunRecorder())
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Events: `on_workflow_start`, `on_step_start`, `on_step_success`, `on_step_failure`, `on_workflow_end`.
|
|
42
|
+
|
|
43
|
+
- `StepEvent` carries the workflow path, step name, parallel `task` name (if any), `input`, `output`, the original `error`, the resolved `next_step`, UTC `started_at` / `finished_at` and `duration` in seconds.
|
|
44
|
+
- `WorkflowEvent` carries the workflow path, `status`, initial `input`, final `output`, the `error` that ended the run (if `execute()` raised) and timings.
|
|
45
|
+
- Each task of a parallel step is reported as its own step event, with `task` set.
|
|
46
|
+
- A sub-step function without hooks of its own reports to its parent's hooks; its events carry the nested path, e.g. `"FLOW -> SubStep (SUB_FLOW)"`.
|
|
47
|
+
- Hooks observe; they don't steer. An exception raised by a hook is logged and ignored.
|
|
48
|
+
- If the run is cancelled, the running step and the workflow are reported with the `CancelledError`, which is then re-raised.
|
|
49
|
+
|
|
50
|
+
## Installation
|
|
51
|
+
|
|
52
|
+
You can install the `StepFunction` package using `pip`:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pip install stepfunction
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Documentation and Further Reading
|
|
59
|
+
|
|
60
|
+
For more detailed usage examples and advanced use cases, please read the [blog post](https://blog.vineethp.com/posts/introducingstepfunction/) that explains the design and usage of the library in-depth. This blog post covers advanced concepts like error handling, sub-step functions, and visualizing workflows in a real-world context.
|
|
61
|
+
|
|
62
|
+
## Dependencies
|
|
63
|
+
|
|
64
|
+
The StepFunction package has no required runtime dependencies. Workflow visualization renders directly to [Mermaid](https://mermaid.js.org/) flowchart syntax (`.mmd`), which can be previewed in VS Code, GitHub/Bitbucket markdown, or https://mermaid.live without any additional tooling.
|
|
65
|
+
|
|
66
|
+
## Contributing
|
|
67
|
+
|
|
68
|
+
If you'd like to contribute to StepFunction, feel free to submit issues or pull requests. Contributions are welcome!
|
|
69
|
+
|
|
70
|
+
## License
|
|
71
|
+
|
|
72
|
+
This project is licensed under the MIT License - see the LICENSE file for details.
|
|
73
|
+
|
|
74
|
+
## Author
|
|
75
|
+
Created and maintained by **Vineeth Penugonda**.
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "stepfunction"
|
|
3
|
-
version = "0.0
|
|
3
|
+
version = "0.2.0"
|
|
4
4
|
authors = [{ name = "Vineeth Penugonda" }]
|
|
5
5
|
description = "Step Function Workflow Orchestration Library"
|
|
6
6
|
readme = "README.md"
|
|
7
7
|
keywords = ["StepFunction", "Workflow", "Orchestration", "Library"]
|
|
8
8
|
requires-python = ">=3.9"
|
|
9
|
-
dependencies = [
|
|
9
|
+
dependencies = []
|
|
10
10
|
license = "MIT"
|
|
11
11
|
classifiers = [
|
|
12
12
|
"Programming Language :: Python :: 3",
|
|
@@ -26,7 +26,7 @@ ignore = ["E501"]
|
|
|
26
26
|
|
|
27
27
|
[tool.setuptools.packages.find]
|
|
28
28
|
where = ["src"]
|
|
29
|
-
include = ["stepfunction.*"]
|
|
29
|
+
include = ["stepfunction", "stepfunction.*"]
|
|
30
30
|
|
|
31
31
|
[project.urls]
|
|
32
32
|
Homepage = "https://github.com/vinecodes/stepfunction"
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Visualizer
|
|
2
|
+
|
|
3
|
+
# Render Configuration
|
|
4
|
+
|
|
5
|
+
DEFAULT_VISUALIZER_DIRECTION = "TD"
|
|
6
|
+
"""str: The default flowchart direction for the Mermaid visualizer (e.g. TD, LR)."""
|
|
7
|
+
|
|
8
|
+
DEFAULT_VISUALIZER_EXTENSION = "mmd"
|
|
9
|
+
"""str: The default file extension for the visualizer renderer output."""
|
|
10
|
+
|
|
11
|
+
DEFAULT_VISUALIZER_FOLDER = "workflow_renders"
|
|
12
|
+
"""str: The default folder where visualizer renders are stored."""
|
|
13
|
+
|
|
14
|
+
DEFAULT_VISUALIZER_STRING_ENCODING = "utf-8"
|
|
15
|
+
"""str: The default encoding for visualizer strings."""
|
|
16
|
+
|
|
17
|
+
# Edge labels and colors
|
|
18
|
+
|
|
19
|
+
DEFAULT_VISUALIZER_SUCCESS_EDGE_LABEL = "Success"
|
|
20
|
+
"""str: The default edge label for success transitions in the visualizer."""
|
|
21
|
+
|
|
22
|
+
DEFAULT_VISUALIZER_FAILURE_EDGE_LABEL = "Failure"
|
|
23
|
+
"""str: The default edge label for failure transitions in the visualizer."""
|
|
24
|
+
|
|
25
|
+
DEFAULT_VISUALIZER_STOP_ON_FAILURE_EDGE_LABEL = "Stop on Failure"
|
|
26
|
+
"""str: The default edge label for stop on failure transitions in the visualizer."""
|
|
27
|
+
|
|
28
|
+
DEFAULT_VISUALIZER_STOP_ON_FAILURE_EDGE_COLOR = "red"
|
|
29
|
+
"""str: The default edge color for stop on failure transitions in the visualizer."""
|
|
30
|
+
|
|
31
|
+
DEFAULT_VISUALIZER_BRANCH_EDGE_LABEL_PREFIX = "Branch"
|
|
32
|
+
"""str: The default edge label prefix for branch transitions in the visualizer."""
|
|
33
|
+
|
|
34
|
+
DEFAULT_VISUALIZER_BRANCH_DEFAULT_LABEL = "else"
|
|
35
|
+
"""str: The label used for an unconditional (fallback) branch return."""
|
|
36
|
+
|
|
37
|
+
DEFAULT_VISUALIZER_MAX_BRANCH_LABEL_LENGTH = 60
|
|
38
|
+
"""int: The maximum length of a branch condition label before it's truncated."""
|
|
39
|
+
|
|
40
|
+
# Node styling (Mermaid classDef names)
|
|
41
|
+
|
|
42
|
+
DEFAULT_VISUALIZER_SUB_STEP_FUNCTION_CLASS = "subStepFunction"
|
|
43
|
+
"""str: The Mermaid classDef name applied to sub-step function nodes."""
|
|
44
|
+
|
|
45
|
+
DEFAULT_VISUALIZER_SUB_STEP_FUNCTION_CLASS_STYLE = (
|
|
46
|
+
"fill:#f5f5f5,stroke:#333,stroke-dasharray: 5 5"
|
|
47
|
+
)
|
|
48
|
+
"""str: The Mermaid classDef style applied to sub-step function nodes."""
|
|
49
|
+
|
|
50
|
+
# Node ID sanitization
|
|
51
|
+
|
|
52
|
+
VISUALIZER_INVALID_NODE_ID_CHARS = r"[^0-9A-Za-z_]"
|
|
53
|
+
"""str: Regex matching characters not allowed in a Mermaid node ID; matches are replaced with "_"."""
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""Encode/decode logic for declarative (JSON-able dict) StepFunction specs.
|
|
2
|
+
|
|
3
|
+
Author: Vineeth Penugonda
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Any, Callable, Dict, Optional, cast
|
|
7
|
+
|
|
8
|
+
from stepfunction.core.step_function.step_function import StepFunction
|
|
9
|
+
from stepfunction.exceptions.step_errors import (
|
|
10
|
+
UnregisteredFunctionError,
|
|
11
|
+
UnserializableStepError,
|
|
12
|
+
)
|
|
13
|
+
from stepfunction.registry.step_registry import StepRegistry
|
|
14
|
+
from stepfunction.registry.step_registry import registry as default_registry
|
|
15
|
+
from stepfunction.types.step_types import StepParams
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _name_for_or_raise(
|
|
19
|
+
step_registry: StepRegistry, func: Callable[[Any], Any], step_name: str
|
|
20
|
+
) -> str:
|
|
21
|
+
name = step_registry.name_for(func)
|
|
22
|
+
if name is None:
|
|
23
|
+
raise UnregisteredFunctionError(
|
|
24
|
+
f"The function used in step '{step_name}' is not registered in the "
|
|
25
|
+
"given registry, so its name can't be determined for export. "
|
|
26
|
+
"Register it with stepfunction.registry.step_registry.register_step() first."
|
|
27
|
+
)
|
|
28
|
+
return name
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _encode_step(
|
|
32
|
+
step_name: str, step: StepParams, step_registry: StepRegistry
|
|
33
|
+
) -> Dict[str, Any]:
|
|
34
|
+
if step["step_type"] is not None:
|
|
35
|
+
raise UnserializableStepError(step_name, step["step_type"])
|
|
36
|
+
|
|
37
|
+
encoded: Dict[str, Any] = {
|
|
38
|
+
"next_step": step["next_step"],
|
|
39
|
+
"on_failure": step["on_failure"],
|
|
40
|
+
"parallel": step["parallel"],
|
|
41
|
+
"stop_on_failure": step["stop_on_failure"],
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if step["is_sub_step_function"]:
|
|
45
|
+
sub_step_function = cast(StepFunction, step["sub_step_function"])
|
|
46
|
+
encoded["sub_step_function"] = encode_step_function(
|
|
47
|
+
sub_step_function, step_registry
|
|
48
|
+
)
|
|
49
|
+
return encoded
|
|
50
|
+
|
|
51
|
+
if step["parallel"]:
|
|
52
|
+
func_map = cast(Dict[str, Callable[[Any], Any]], step["func"])
|
|
53
|
+
encoded["func"] = {
|
|
54
|
+
slot: _name_for_or_raise(step_registry, fn, step_name)
|
|
55
|
+
for slot, fn in func_map.items()
|
|
56
|
+
}
|
|
57
|
+
else:
|
|
58
|
+
func = cast(Callable[[Any], Any], step["func"])
|
|
59
|
+
encoded["func"] = _name_for_or_raise(step_registry, func, step_name)
|
|
60
|
+
|
|
61
|
+
branch = step["branch"]
|
|
62
|
+
if branch is not None:
|
|
63
|
+
if callable(branch):
|
|
64
|
+
encoded["branch"] = _name_for_or_raise(step_registry, branch, step_name)
|
|
65
|
+
else:
|
|
66
|
+
encoded["branch"] = {str(key): value for key, value in branch.items()}
|
|
67
|
+
|
|
68
|
+
return encoded
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def encode_step_function(
|
|
72
|
+
sf: StepFunction, step_registry: Optional[StepRegistry] = None
|
|
73
|
+
) -> Dict[str, Any]:
|
|
74
|
+
"""Export ``sf`` as a JSON-able dict.
|
|
75
|
+
|
|
76
|
+
Recurses into nested sub-step-functions by calling itself again, so
|
|
77
|
+
arbitrary nesting depth is handled without special-casing.
|
|
78
|
+
|
|
79
|
+
Raises:
|
|
80
|
+
UnserializableStepError: If any step was built from a BaseStep
|
|
81
|
+
instance (RetryStep, TimeoutStep, WaitStep, or a custom
|
|
82
|
+
BaseStep subclass) — not yet supported.
|
|
83
|
+
UnregisteredFunctionError: If a step or branch function used in
|
|
84
|
+
``sf`` has no registered name in ``step_registry``.
|
|
85
|
+
"""
|
|
86
|
+
step_registry = step_registry or default_registry
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
"name": sf.name,
|
|
90
|
+
"start_step": sf.current_step,
|
|
91
|
+
"steps": {
|
|
92
|
+
step_name: _encode_step(step_name, step, step_registry)
|
|
93
|
+
for step_name, step in sf.steps.items()
|
|
94
|
+
},
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def decode_step_function(
|
|
99
|
+
data: Dict[str, Any],
|
|
100
|
+
step_registry: Optional[StepRegistry] = None,
|
|
101
|
+
_validate: bool = True,
|
|
102
|
+
) -> StepFunction:
|
|
103
|
+
"""Reconstruct a StepFunction from a dict produced by ``encode_step_function``.
|
|
104
|
+
|
|
105
|
+
Rebuilds the workflow purely through ``add_step``/``add_sub_step_function``/
|
|
106
|
+
``set_start_step`` — the same public API a user would call by hand — and
|
|
107
|
+
recurses into nested "sub_step_function" entries by calling itself again.
|
|
108
|
+
Validates exactly once, at the very end, at the outermost level only:
|
|
109
|
+
``StepFunction.validate()`` already recurses into sub-step functions and
|
|
110
|
+
reports a readable breadcrumb across nesting levels, so a malformed
|
|
111
|
+
nested spec still fails fast without the decoder needing its own
|
|
112
|
+
recursive validation pass.
|
|
113
|
+
|
|
114
|
+
Raises:
|
|
115
|
+
UnregisteredFunctionError: If a referenced function name isn't
|
|
116
|
+
registered in ``step_registry`` (defaults to the package's
|
|
117
|
+
default singleton registry if not given).
|
|
118
|
+
ValueError: If the reconstructed workflow fails validate().
|
|
119
|
+
"""
|
|
120
|
+
step_registry = step_registry or default_registry
|
|
121
|
+
|
|
122
|
+
sf = StepFunction(data["name"])
|
|
123
|
+
|
|
124
|
+
for step_name, step_data in data["steps"].items():
|
|
125
|
+
if "sub_step_function" in step_data:
|
|
126
|
+
sub_step_function = decode_step_function(
|
|
127
|
+
step_data["sub_step_function"], step_registry, _validate=False
|
|
128
|
+
)
|
|
129
|
+
sf.add_sub_step_function(
|
|
130
|
+
step_name,
|
|
131
|
+
sub_step_function=sub_step_function,
|
|
132
|
+
next_step=step_data.get("next_step"),
|
|
133
|
+
on_failure=step_data.get("on_failure"),
|
|
134
|
+
)
|
|
135
|
+
continue
|
|
136
|
+
|
|
137
|
+
func_spec = step_data["func"]
|
|
138
|
+
if isinstance(func_spec, dict):
|
|
139
|
+
func: Any = {
|
|
140
|
+
slot: step_registry.get(ref) for slot, ref in func_spec.items()
|
|
141
|
+
}
|
|
142
|
+
else:
|
|
143
|
+
func = step_registry.get(func_spec)
|
|
144
|
+
|
|
145
|
+
branch_spec = step_data.get("branch")
|
|
146
|
+
if branch_spec is None:
|
|
147
|
+
branch: Any = None
|
|
148
|
+
elif isinstance(branch_spec, dict):
|
|
149
|
+
branch = dict(branch_spec)
|
|
150
|
+
else:
|
|
151
|
+
branch = step_registry.get(branch_spec)
|
|
152
|
+
|
|
153
|
+
sf.add_step(
|
|
154
|
+
step_name,
|
|
155
|
+
func,
|
|
156
|
+
next_step=step_data.get("next_step"),
|
|
157
|
+
on_failure=step_data.get("on_failure"),
|
|
158
|
+
branch=branch,
|
|
159
|
+
parallel=step_data.get("parallel", False),
|
|
160
|
+
stop_on_failure=step_data.get("stop_on_failure", False),
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
start_step = data.get("start_step")
|
|
164
|
+
if start_step is not None:
|
|
165
|
+
sf.set_start_step(start_step)
|
|
166
|
+
|
|
167
|
+
if _validate:
|
|
168
|
+
sf.validate()
|
|
169
|
+
|
|
170
|
+
return sf
|