stageflow-framework 0.1.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.
- stageflow_framework-0.1.0/PKG-INFO +167 -0
- stageflow_framework-0.1.0/README.md +157 -0
- stageflow_framework-0.1.0/pyproject.toml +24 -0
- stageflow_framework-0.1.0/setup.cfg +4 -0
- stageflow_framework-0.1.0/stageflow/__init__.py +25 -0
- stageflow_framework-0.1.0/stageflow/builtins/__init__.py +34 -0
- stageflow_framework-0.1.0/stageflow/builtins/dicts.py +59 -0
- stageflow_framework-0.1.0/stageflow/builtins/lists.py +71 -0
- stageflow_framework-0.1.0/stageflow/builtins/lists_extra.py +110 -0
- stageflow_framework-0.1.0/stageflow/builtins/logic.py +84 -0
- stageflow_framework-0.1.0/stageflow/builtins/strings.py +81 -0
- stageflow_framework-0.1.0/stageflow/builtins/vars.py +118 -0
- stageflow_framework-0.1.0/stageflow/core/__init__.py +7 -0
- stageflow_framework-0.1.0/stageflow/core/context.py +86 -0
- stageflow_framework-0.1.0/stageflow/core/event.py +71 -0
- stageflow_framework-0.1.0/stageflow/core/jsonlogic.py +43 -0
- stageflow_framework-0.1.0/stageflow/core/node.py +256 -0
- stageflow_framework-0.1.0/stageflow/core/pipeline.py +108 -0
- stageflow_framework-0.1.0/stageflow/core/session.py +398 -0
- stageflow_framework-0.1.0/stageflow/core/stage.py +152 -0
- stageflow_framework-0.1.0/stageflow/core/utils.py +62 -0
- stageflow_framework-0.1.0/stageflow/docs/__init__.py +4 -0
- stageflow_framework-0.1.0/stageflow/docs/html.py +479 -0
- stageflow_framework-0.1.0/stageflow/docs/schema.py +38 -0
- stageflow_framework-0.1.0/stageflow/docs/schemas/pipeline.json +134 -0
- stageflow_framework-0.1.0/stageflow/py.typed +1 -0
- stageflow_framework-0.1.0/stageflow/testing.py +59 -0
- stageflow_framework-0.1.0/stageflow_framework.egg-info/PKG-INFO +167 -0
- stageflow_framework-0.1.0/stageflow_framework.egg-info/SOURCES.txt +39 -0
- stageflow_framework-0.1.0/stageflow_framework.egg-info/dependency_links.txt +1 -0
- stageflow_framework-0.1.0/stageflow_framework.egg-info/requires.txt +2 -0
- stageflow_framework-0.1.0/stageflow_framework.egg-info/top_level.txt +1 -0
- stageflow_framework-0.1.0/tests/test_core_flow.py +79 -0
- stageflow_framework-0.1.0/tests/test_docs_schema.py +82 -0
- stageflow_framework-0.1.0/tests/test_full_pipeline.py +175 -0
- stageflow_framework-0.1.0/tests/test_jsonlogic.py +34 -0
- stageflow_framework-0.1.0/tests/test_payload_validation.py +114 -0
- stageflow_framework-0.1.0/tests/test_pipeline_tester.py +46 -0
- stageflow_framework-0.1.0/tests/test_snapshot.py +51 -0
- stageflow_framework-0.1.0/tests/test_std_stages.py +53 -0
- stageflow_framework-0.1.0/tests/test_subpipeline.py +187 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: stageflow-framework
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: StageFlow: Pipeline framework for stages
|
|
5
|
+
Author: lethargy
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: pyyaml
|
|
9
|
+
Requires-Dist: jsonschema>=4.0.0
|
|
10
|
+
|
|
11
|
+
# StageFlow
|
|
12
|
+
|
|
13
|
+
StageFlow is a lightweight framework for describing and running JSON pipelines with nodes, stages, and schema validation.
|
|
14
|
+
|
|
15
|
+
## Features
|
|
16
|
+
- Node types: `stage`, `condition`, `parallel`, `map`, `subpipeline`, `terminal`.
|
|
17
|
+
- Custom stages (async `run`) with documented arguments/config/outputs.
|
|
18
|
+
- Input waiting (`wait_input`), event history, session snapshots.
|
|
19
|
+
- Built-in stages for dict/list/string/logic utilities.
|
|
20
|
+
- Pipeline JSON Schema (stage enum injected on the fly) and HTML doc generator.
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
```bash
|
|
24
|
+
python -m venv .venv
|
|
25
|
+
source .venv/bin/activate # Windows: .venv\Scripts\activate
|
|
26
|
+
pip install stageflow-framework
|
|
27
|
+
# or for local dev:
|
|
28
|
+
# pip install -e .
|
|
29
|
+
```
|
|
30
|
+
Requires Python 3.10+.
|
|
31
|
+
|
|
32
|
+
## Quickstart
|
|
33
|
+
1) Register your stage:
|
|
34
|
+
```python
|
|
35
|
+
from stageflow.core.stage import BaseStage, register_stage
|
|
36
|
+
|
|
37
|
+
@register_stage("HelloStage")
|
|
38
|
+
class HelloStage(BaseStage):
|
|
39
|
+
"""
|
|
40
|
+
description: "Custom stage example"
|
|
41
|
+
arguments:
|
|
42
|
+
name: string
|
|
43
|
+
outputs:
|
|
44
|
+
greeting: string
|
|
45
|
+
"""
|
|
46
|
+
async def run(self):
|
|
47
|
+
name = self.get_arguments().get("name", "world")
|
|
48
|
+
self.set_outputs({"greeting": f"Hello, {name}!"})
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
2) Describe a pipeline in JSON:
|
|
52
|
+
```python
|
|
53
|
+
pipeline_dict = {
|
|
54
|
+
"entry": "start",
|
|
55
|
+
"nodes": [
|
|
56
|
+
{
|
|
57
|
+
"id": "start",
|
|
58
|
+
"type": "stage",
|
|
59
|
+
"stage": "HelloStage",
|
|
60
|
+
"arguments": {"name": "user_name"},
|
|
61
|
+
"outputs": {"greeting": "greeting"},
|
|
62
|
+
"next": "finish",
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"id": "finish",
|
|
66
|
+
"type": "terminal",
|
|
67
|
+
"result": {"status": "ok"},
|
|
68
|
+
"artifacts": ["greeting"],
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
3) Run:
|
|
75
|
+
```python
|
|
76
|
+
import asyncio
|
|
77
|
+
from stageflow.core.pipeline import Pipeline
|
|
78
|
+
from stageflow.core.session import Session
|
|
79
|
+
from stageflow.core.context import Context
|
|
80
|
+
|
|
81
|
+
async def main():
|
|
82
|
+
pipe = Pipeline.from_dict(pipeline_dict)
|
|
83
|
+
session = Session(id="demo", pipeline=pipe, context=Context(payload={"user_name": "Alice"}))
|
|
84
|
+
result = await session.run()
|
|
85
|
+
print(result.result) # {'status': 'ok'}
|
|
86
|
+
print(result.artifacts) # {'greeting': 'Hello, Alice!'}
|
|
87
|
+
|
|
88
|
+
asyncio.run(main())
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Docs and schema generation
|
|
92
|
+
- Programmatic:
|
|
93
|
+
```python
|
|
94
|
+
from stageflow.docs.html import generate_docs_assets
|
|
95
|
+
|
|
96
|
+
html_page, pipeline_schema, stages_json = generate_docs_assets()
|
|
97
|
+
# html_page — ready-to-serve HTML
|
|
98
|
+
# pipeline_schema — dict with JSON Schema (stage enum injected)
|
|
99
|
+
# stages_json — raw JSON with stage specs
|
|
100
|
+
```
|
|
101
|
+
- CLI (prints JSON payload with `html`, `pipeline_schema`, `stages_json` to stdout):
|
|
102
|
+
```bash
|
|
103
|
+
python scripts/generate_docs_html.py > docs_assets.json
|
|
104
|
+
```
|
|
105
|
+
If needed, persist the `html` from the JSON payload yourself.
|
|
106
|
+
|
|
107
|
+
## Tests
|
|
108
|
+
```bash
|
|
109
|
+
python -m unittest
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Node types overview
|
|
113
|
+
- `stage`: Executes a registered Stage. Fields: `stage` (name), `config`/`arguments`/`outputs` mappings, `next`, optional `fallback`.
|
|
114
|
+
- `condition`: List of `{if, then}` branches (JsonLogic conditions), optional `else`.
|
|
115
|
+
- `parallel`: Runs several children; `policy` = `all|any`, `cancel_on_error` flag, optional `next`.
|
|
116
|
+
- `map`: Iterates over items path, runs body node for each item (sequential/parallel), collects mapped outputs.
|
|
117
|
+
- `subpipeline`: Invokes nested pipeline by id, with `inputs` mapping, `artifact_outputs`, optional `result_output`, `next`.
|
|
118
|
+
- `terminal`: Ends execution, returns `result` and selects `artifacts`.
|
|
119
|
+
|
|
120
|
+
### Node types in detail
|
|
121
|
+
- **Stage node**
|
|
122
|
+
- Required: `id`, `type="stage"`, `stage` (registered stage name).
|
|
123
|
+
- Data flow: `arguments` map context paths -> stage inputs; `outputs` map stage return keys -> context paths.
|
|
124
|
+
- Control flow: `next` points to following node; optional `fallback` node is used on stage exceptions.
|
|
125
|
+
- Stage class declares `skipable`, `allowed_events`, `allowed_inputs`, `timeout`, `retries`.
|
|
126
|
+
- **Condition node**
|
|
127
|
+
- Required: `id`, `type="condition"`, non-empty `conditions` list.
|
|
128
|
+
- Each condition: `{ "if": <JsonLogic>, "then": "<node id>" }`; first matched branch wins.
|
|
129
|
+
- Optional `else` target if no branch matches.
|
|
130
|
+
- **Parallel node**
|
|
131
|
+
- Required: `id`, `type="parallel"`, non-empty `children` (node ids).
|
|
132
|
+
- `policy`: `all` (default) waits all children; `any` completes on first success.
|
|
133
|
+
- `cancel_on_error`: whether to cancel siblings on failure.
|
|
134
|
+
- `next`: node id to continue after parallel finishes.
|
|
135
|
+
- **Map node**
|
|
136
|
+
- Required: `id`, `type="map"`, `items` (context path with iterable), `body` (node id).
|
|
137
|
+
- `mode`: `sequential` (default) or `parallel` for body executions.
|
|
138
|
+
- `output_map`: map of body outputs -> target context paths for aggregated results.
|
|
139
|
+
- `item_path`/`index_path`: where to inject current item/index into context for body runs.
|
|
140
|
+
- `cancel_on_error`: abort remaining iterations on error.
|
|
141
|
+
- `next`: node id after all iterations finish.
|
|
142
|
+
- **Subpipeline node**
|
|
143
|
+
- Required: `id`, `type="subpipeline"`, `subpipeline_id` (key in `subpipelines` dict of pipeline).
|
|
144
|
+
- `inputs`: context mapping passed into subpipeline root context.
|
|
145
|
+
- `artifact_outputs`: map subpipeline artifacts -> parent context paths.
|
|
146
|
+
- `result_output`: path in parent context to store subpipeline `result`.
|
|
147
|
+
- `next`: next node in parent pipeline.
|
|
148
|
+
- **Terminal node**
|
|
149
|
+
- Required: `id`, `type="terminal"`.
|
|
150
|
+
- `result`: arbitrary object returned as final result.
|
|
151
|
+
- `artifacts`: list of context paths that will be returned as artifacts map.
|
|
152
|
+
|
|
153
|
+
## Built-in stages
|
|
154
|
+
- Vars: `SetValueStage`, `CopyValueStage`, `IncrementStage`, `MergeDictStage`.
|
|
155
|
+
- Dicts: `PickKeysStage`, `DropKeysStage`.
|
|
156
|
+
- Lists: `AppendListStage`, `ExtendListStage`, `FilterListStage`, `UniqueListStage`, `PopListStage`.
|
|
157
|
+
- Strings: `ConcatStage`, `TemplateStage`.
|
|
158
|
+
- Logic: `AssertStage`, `FailStage`, `LogStage`, `SleepStage`.
|
|
159
|
+
|
|
160
|
+
Each stage docstring describes arguments/config/outputs; they are also available via `generate_docs_assets()` / `stages_json`.
|
|
161
|
+
|
|
162
|
+
## Useful refs
|
|
163
|
+
- `stageflow/core` — core (pipeline, session, nodes, context).
|
|
164
|
+
- `stageflow/builtins` — built-in stages.
|
|
165
|
+
- `stageflow/docs` — schema/HTML generation helpers.
|
|
166
|
+
- `scripts/` — helpers (docs generator wrapper).
|
|
167
|
+
- `tests/` — unit tests.
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# StageFlow
|
|
2
|
+
|
|
3
|
+
StageFlow is a lightweight framework for describing and running JSON pipelines with nodes, stages, and schema validation.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
- Node types: `stage`, `condition`, `parallel`, `map`, `subpipeline`, `terminal`.
|
|
7
|
+
- Custom stages (async `run`) with documented arguments/config/outputs.
|
|
8
|
+
- Input waiting (`wait_input`), event history, session snapshots.
|
|
9
|
+
- Built-in stages for dict/list/string/logic utilities.
|
|
10
|
+
- Pipeline JSON Schema (stage enum injected on the fly) and HTML doc generator.
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
```bash
|
|
14
|
+
python -m venv .venv
|
|
15
|
+
source .venv/bin/activate # Windows: .venv\Scripts\activate
|
|
16
|
+
pip install stageflow-framework
|
|
17
|
+
# or for local dev:
|
|
18
|
+
# pip install -e .
|
|
19
|
+
```
|
|
20
|
+
Requires Python 3.10+.
|
|
21
|
+
|
|
22
|
+
## Quickstart
|
|
23
|
+
1) Register your stage:
|
|
24
|
+
```python
|
|
25
|
+
from stageflow.core.stage import BaseStage, register_stage
|
|
26
|
+
|
|
27
|
+
@register_stage("HelloStage")
|
|
28
|
+
class HelloStage(BaseStage):
|
|
29
|
+
"""
|
|
30
|
+
description: "Custom stage example"
|
|
31
|
+
arguments:
|
|
32
|
+
name: string
|
|
33
|
+
outputs:
|
|
34
|
+
greeting: string
|
|
35
|
+
"""
|
|
36
|
+
async def run(self):
|
|
37
|
+
name = self.get_arguments().get("name", "world")
|
|
38
|
+
self.set_outputs({"greeting": f"Hello, {name}!"})
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
2) Describe a pipeline in JSON:
|
|
42
|
+
```python
|
|
43
|
+
pipeline_dict = {
|
|
44
|
+
"entry": "start",
|
|
45
|
+
"nodes": [
|
|
46
|
+
{
|
|
47
|
+
"id": "start",
|
|
48
|
+
"type": "stage",
|
|
49
|
+
"stage": "HelloStage",
|
|
50
|
+
"arguments": {"name": "user_name"},
|
|
51
|
+
"outputs": {"greeting": "greeting"},
|
|
52
|
+
"next": "finish",
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"id": "finish",
|
|
56
|
+
"type": "terminal",
|
|
57
|
+
"result": {"status": "ok"},
|
|
58
|
+
"artifacts": ["greeting"],
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
3) Run:
|
|
65
|
+
```python
|
|
66
|
+
import asyncio
|
|
67
|
+
from stageflow.core.pipeline import Pipeline
|
|
68
|
+
from stageflow.core.session import Session
|
|
69
|
+
from stageflow.core.context import Context
|
|
70
|
+
|
|
71
|
+
async def main():
|
|
72
|
+
pipe = Pipeline.from_dict(pipeline_dict)
|
|
73
|
+
session = Session(id="demo", pipeline=pipe, context=Context(payload={"user_name": "Alice"}))
|
|
74
|
+
result = await session.run()
|
|
75
|
+
print(result.result) # {'status': 'ok'}
|
|
76
|
+
print(result.artifacts) # {'greeting': 'Hello, Alice!'}
|
|
77
|
+
|
|
78
|
+
asyncio.run(main())
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Docs and schema generation
|
|
82
|
+
- Programmatic:
|
|
83
|
+
```python
|
|
84
|
+
from stageflow.docs.html import generate_docs_assets
|
|
85
|
+
|
|
86
|
+
html_page, pipeline_schema, stages_json = generate_docs_assets()
|
|
87
|
+
# html_page — ready-to-serve HTML
|
|
88
|
+
# pipeline_schema — dict with JSON Schema (stage enum injected)
|
|
89
|
+
# stages_json — raw JSON with stage specs
|
|
90
|
+
```
|
|
91
|
+
- CLI (prints JSON payload with `html`, `pipeline_schema`, `stages_json` to stdout):
|
|
92
|
+
```bash
|
|
93
|
+
python scripts/generate_docs_html.py > docs_assets.json
|
|
94
|
+
```
|
|
95
|
+
If needed, persist the `html` from the JSON payload yourself.
|
|
96
|
+
|
|
97
|
+
## Tests
|
|
98
|
+
```bash
|
|
99
|
+
python -m unittest
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Node types overview
|
|
103
|
+
- `stage`: Executes a registered Stage. Fields: `stage` (name), `config`/`arguments`/`outputs` mappings, `next`, optional `fallback`.
|
|
104
|
+
- `condition`: List of `{if, then}` branches (JsonLogic conditions), optional `else`.
|
|
105
|
+
- `parallel`: Runs several children; `policy` = `all|any`, `cancel_on_error` flag, optional `next`.
|
|
106
|
+
- `map`: Iterates over items path, runs body node for each item (sequential/parallel), collects mapped outputs.
|
|
107
|
+
- `subpipeline`: Invokes nested pipeline by id, with `inputs` mapping, `artifact_outputs`, optional `result_output`, `next`.
|
|
108
|
+
- `terminal`: Ends execution, returns `result` and selects `artifacts`.
|
|
109
|
+
|
|
110
|
+
### Node types in detail
|
|
111
|
+
- **Stage node**
|
|
112
|
+
- Required: `id`, `type="stage"`, `stage` (registered stage name).
|
|
113
|
+
- Data flow: `arguments` map context paths -> stage inputs; `outputs` map stage return keys -> context paths.
|
|
114
|
+
- Control flow: `next` points to following node; optional `fallback` node is used on stage exceptions.
|
|
115
|
+
- Stage class declares `skipable`, `allowed_events`, `allowed_inputs`, `timeout`, `retries`.
|
|
116
|
+
- **Condition node**
|
|
117
|
+
- Required: `id`, `type="condition"`, non-empty `conditions` list.
|
|
118
|
+
- Each condition: `{ "if": <JsonLogic>, "then": "<node id>" }`; first matched branch wins.
|
|
119
|
+
- Optional `else` target if no branch matches.
|
|
120
|
+
- **Parallel node**
|
|
121
|
+
- Required: `id`, `type="parallel"`, non-empty `children` (node ids).
|
|
122
|
+
- `policy`: `all` (default) waits all children; `any` completes on first success.
|
|
123
|
+
- `cancel_on_error`: whether to cancel siblings on failure.
|
|
124
|
+
- `next`: node id to continue after parallel finishes.
|
|
125
|
+
- **Map node**
|
|
126
|
+
- Required: `id`, `type="map"`, `items` (context path with iterable), `body` (node id).
|
|
127
|
+
- `mode`: `sequential` (default) or `parallel` for body executions.
|
|
128
|
+
- `output_map`: map of body outputs -> target context paths for aggregated results.
|
|
129
|
+
- `item_path`/`index_path`: where to inject current item/index into context for body runs.
|
|
130
|
+
- `cancel_on_error`: abort remaining iterations on error.
|
|
131
|
+
- `next`: node id after all iterations finish.
|
|
132
|
+
- **Subpipeline node**
|
|
133
|
+
- Required: `id`, `type="subpipeline"`, `subpipeline_id` (key in `subpipelines` dict of pipeline).
|
|
134
|
+
- `inputs`: context mapping passed into subpipeline root context.
|
|
135
|
+
- `artifact_outputs`: map subpipeline artifacts -> parent context paths.
|
|
136
|
+
- `result_output`: path in parent context to store subpipeline `result`.
|
|
137
|
+
- `next`: next node in parent pipeline.
|
|
138
|
+
- **Terminal node**
|
|
139
|
+
- Required: `id`, `type="terminal"`.
|
|
140
|
+
- `result`: arbitrary object returned as final result.
|
|
141
|
+
- `artifacts`: list of context paths that will be returned as artifacts map.
|
|
142
|
+
|
|
143
|
+
## Built-in stages
|
|
144
|
+
- Vars: `SetValueStage`, `CopyValueStage`, `IncrementStage`, `MergeDictStage`.
|
|
145
|
+
- Dicts: `PickKeysStage`, `DropKeysStage`.
|
|
146
|
+
- Lists: `AppendListStage`, `ExtendListStage`, `FilterListStage`, `UniqueListStage`, `PopListStage`.
|
|
147
|
+
- Strings: `ConcatStage`, `TemplateStage`.
|
|
148
|
+
- Logic: `AssertStage`, `FailStage`, `LogStage`, `SleepStage`.
|
|
149
|
+
|
|
150
|
+
Each stage docstring describes arguments/config/outputs; they are also available via `generate_docs_assets()` / `stages_json`.
|
|
151
|
+
|
|
152
|
+
## Useful refs
|
|
153
|
+
- `stageflow/core` — core (pipeline, session, nodes, context).
|
|
154
|
+
- `stageflow/builtins` — built-in stages.
|
|
155
|
+
- `stageflow/docs` — schema/HTML generation helpers.
|
|
156
|
+
- `scripts/` — helpers (docs generator wrapper).
|
|
157
|
+
- `tests/` — unit tests.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "stageflow-framework"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "StageFlow: Pipeline framework for stages"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
authors = [
|
|
11
|
+
{ name="lethargy" }
|
|
12
|
+
]
|
|
13
|
+
dependencies = [
|
|
14
|
+
"pyyaml",
|
|
15
|
+
"jsonschema>=4.0.0"
|
|
16
|
+
]
|
|
17
|
+
requires-python = ">=3.10"
|
|
18
|
+
|
|
19
|
+
[tool.setuptools.packages.find]
|
|
20
|
+
where = ["."]
|
|
21
|
+
include = ["stageflow*"]
|
|
22
|
+
|
|
23
|
+
[tool.setuptools.package-data]
|
|
24
|
+
"stageflow" = ["py.typed", "docs/schemas/pipeline.json"]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from .core import (
|
|
2
|
+
Event, EventSpec, InputSpec,
|
|
3
|
+
JsonLogic,
|
|
4
|
+
Node, ConditionNode, Condition, ParallelNode, TerminalNode, StageNode,
|
|
5
|
+
Session, SessionResult,
|
|
6
|
+
BaseStage, get_stage, register_stage, get_stages, get_stages_by_category,
|
|
7
|
+
Context, DotDict,
|
|
8
|
+
Pipeline,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
from .docs import generate_stages_yaml, generate_stages_json
|
|
12
|
+
from . import builtins
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"Event", "EventSpec", "InputSpec",
|
|
17
|
+
"JsonLogic",
|
|
18
|
+
"Node", "ConditionNode", "Condition", "ParallelNode", "TerminalNode", "StageNode",
|
|
19
|
+
"Session", "SessionResult",
|
|
20
|
+
"BaseStage", "get_stage", "register_stage", "get_stages", "get_stages_by_category",
|
|
21
|
+
"Context", "DotDict",
|
|
22
|
+
"Pipeline",
|
|
23
|
+
"generate_stages_yaml", "generate_stages_json",
|
|
24
|
+
"builtins",
|
|
25
|
+
]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from .vars import (
|
|
2
|
+
SetValueStage,
|
|
3
|
+
CopyValueStage,
|
|
4
|
+
IncrementStage,
|
|
5
|
+
MergeDictStage,
|
|
6
|
+
)
|
|
7
|
+
from .lists import (
|
|
8
|
+
AppendListStage,
|
|
9
|
+
ExtendListStage,
|
|
10
|
+
)
|
|
11
|
+
from .logic import AssertStage, FailStage, LogStage, SleepStage
|
|
12
|
+
from .strings import ConcatStage, TemplateStage
|
|
13
|
+
from .dicts import PickKeysStage, DropKeysStage
|
|
14
|
+
from .lists_extra import FilterListStage, UniqueListStage, PopListStage
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"SetValueStage",
|
|
18
|
+
"CopyValueStage",
|
|
19
|
+
"IncrementStage",
|
|
20
|
+
"MergeDictStage",
|
|
21
|
+
"AppendListStage",
|
|
22
|
+
"ExtendListStage",
|
|
23
|
+
"AssertStage",
|
|
24
|
+
"FailStage",
|
|
25
|
+
"LogStage",
|
|
26
|
+
"SleepStage",
|
|
27
|
+
"ConcatStage",
|
|
28
|
+
"TemplateStage",
|
|
29
|
+
"PickKeysStage",
|
|
30
|
+
"DropKeysStage",
|
|
31
|
+
"FilterListStage",
|
|
32
|
+
"UniqueListStage",
|
|
33
|
+
"PopListStage",
|
|
34
|
+
]
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from stageflow.core.stage import BaseStage, register_stage
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
@register_stage("PickKeysStage")
|
|
5
|
+
class PickKeysStage(BaseStage):
|
|
6
|
+
"""
|
|
7
|
+
description: "Pick only specified keys from a dict and return new object"
|
|
8
|
+
arguments:
|
|
9
|
+
src:
|
|
10
|
+
type: object
|
|
11
|
+
description: "Source dict"
|
|
12
|
+
config:
|
|
13
|
+
keys:
|
|
14
|
+
type: list
|
|
15
|
+
description: "Keys to keep in the result"
|
|
16
|
+
outputs:
|
|
17
|
+
result:
|
|
18
|
+
type: object
|
|
19
|
+
description: "Dict containing only picked keys"
|
|
20
|
+
"""
|
|
21
|
+
category = "builtin.dicts"
|
|
22
|
+
|
|
23
|
+
async def run(self):
|
|
24
|
+
args = self.get_arguments()
|
|
25
|
+
src_val = args.get("src") or {}
|
|
26
|
+
keys = self.config.get("keys", [])
|
|
27
|
+
if not isinstance(src_val, dict):
|
|
28
|
+
raise ValueError("PickKeysStage expects dict in arguments.src")
|
|
29
|
+
picked = {k: src_val[k] for k in keys if k in src_val}
|
|
30
|
+
self.set_outputs({"result": picked})
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@register_stage("DropKeysStage")
|
|
34
|
+
class DropKeysStage(BaseStage):
|
|
35
|
+
"""
|
|
36
|
+
description: "Remove specified keys from dict and return cleaned object"
|
|
37
|
+
arguments:
|
|
38
|
+
src:
|
|
39
|
+
type: object
|
|
40
|
+
description: "Source dict"
|
|
41
|
+
config:
|
|
42
|
+
keys:
|
|
43
|
+
type: list
|
|
44
|
+
description: "Keys to remove from the dict"
|
|
45
|
+
outputs:
|
|
46
|
+
result:
|
|
47
|
+
type: object
|
|
48
|
+
description: "Dict without removed keys"
|
|
49
|
+
"""
|
|
50
|
+
category = "builtin.dicts"
|
|
51
|
+
|
|
52
|
+
async def run(self):
|
|
53
|
+
args = self.get_arguments()
|
|
54
|
+
keys = set(self.config.get("keys", []))
|
|
55
|
+
src_val = args.get("src") or {}
|
|
56
|
+
if not isinstance(src_val, dict):
|
|
57
|
+
raise ValueError("DropKeysStage expects dict in arguments.src")
|
|
58
|
+
cleaned = {k: v for k, v in src_val.items() if k not in keys}
|
|
59
|
+
self.set_outputs({"result": cleaned})
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
from stageflow.core.stage import BaseStage, register_stage
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
@register_stage("AppendListStage")
|
|
5
|
+
class AppendListStage(BaseStage):
|
|
6
|
+
"""
|
|
7
|
+
description: "Append value from args/config to list (creates list if missing)"
|
|
8
|
+
arguments:
|
|
9
|
+
list:
|
|
10
|
+
type: list
|
|
11
|
+
description: "List to append to (from context)"
|
|
12
|
+
value:
|
|
13
|
+
type: any
|
|
14
|
+
description: "Value to append (overrides config)"
|
|
15
|
+
config:
|
|
16
|
+
value:
|
|
17
|
+
type: any
|
|
18
|
+
description: "Fallback value when argument is missing"
|
|
19
|
+
outputs:
|
|
20
|
+
list:
|
|
21
|
+
type: list
|
|
22
|
+
description: "Resulting list after append"
|
|
23
|
+
"""
|
|
24
|
+
category = "builtin.lists"
|
|
25
|
+
|
|
26
|
+
async def run(self):
|
|
27
|
+
args = self.get_arguments()
|
|
28
|
+
value = args.get("value", self.config.get("value"))
|
|
29
|
+
lst = args.get("list", None)
|
|
30
|
+
if lst is None:
|
|
31
|
+
lst = []
|
|
32
|
+
if not isinstance(lst, list):
|
|
33
|
+
raise ValueError("AppendListStage expects list")
|
|
34
|
+
lst.append(value)
|
|
35
|
+
self.set_outputs({"list": lst})
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@register_stage("ExtendListStage")
|
|
39
|
+
class ExtendListStage(BaseStage):
|
|
40
|
+
"""
|
|
41
|
+
description: "Extend list with items from arguments (or default list)"
|
|
42
|
+
arguments:
|
|
43
|
+
list:
|
|
44
|
+
type: list
|
|
45
|
+
description: "Base list to extend"
|
|
46
|
+
items:
|
|
47
|
+
type: list
|
|
48
|
+
description: "Items to extend the list with"
|
|
49
|
+
config:
|
|
50
|
+
default_list:
|
|
51
|
+
type: list
|
|
52
|
+
description: "Fallback list when argument is missing"
|
|
53
|
+
outputs:
|
|
54
|
+
list:
|
|
55
|
+
type: list
|
|
56
|
+
description: "Resulting list after extend"
|
|
57
|
+
"""
|
|
58
|
+
category = "builtin.lists"
|
|
59
|
+
|
|
60
|
+
async def run(self):
|
|
61
|
+
args = self.get_arguments()
|
|
62
|
+
lst = args.get("list", self.config.get("default_list", []))
|
|
63
|
+
if lst is None:
|
|
64
|
+
lst = []
|
|
65
|
+
if not isinstance(lst, list):
|
|
66
|
+
raise ValueError("ExtendListStage expects list")
|
|
67
|
+
src_val = args.get("items", [])
|
|
68
|
+
if not isinstance(src_val, (list, tuple)):
|
|
69
|
+
raise ValueError("Source for extend is not a list/tuple")
|
|
70
|
+
lst.extend(src_val)
|
|
71
|
+
self.set_outputs({"list": lst})
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
from stageflow.core.stage import BaseStage, register_stage
|
|
2
|
+
from stageflow.core.jsonlogic import JsonLogic
|
|
3
|
+
from stageflow.core.context import Context
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@register_stage("FilterListStage")
|
|
7
|
+
class FilterListStage(BaseStage):
|
|
8
|
+
"""
|
|
9
|
+
description: "Filter list items by JsonLogic condition using item_path binding"
|
|
10
|
+
arguments:
|
|
11
|
+
items:
|
|
12
|
+
type: list
|
|
13
|
+
description: "List to filter"
|
|
14
|
+
config:
|
|
15
|
+
condition:
|
|
16
|
+
type: object
|
|
17
|
+
description: "JsonLogic condition evaluated for each item"
|
|
18
|
+
item_path:
|
|
19
|
+
type: string
|
|
20
|
+
description: "Context key to bind current item during evaluation"
|
|
21
|
+
outputs:
|
|
22
|
+
list:
|
|
23
|
+
type: list
|
|
24
|
+
description: "Filtered list"
|
|
25
|
+
"""
|
|
26
|
+
category = "builtin.lists"
|
|
27
|
+
|
|
28
|
+
async def run(self):
|
|
29
|
+
args = self.get_arguments()
|
|
30
|
+
condition = self.config.get("condition")
|
|
31
|
+
item_path = self.config.get("item_path", "item")
|
|
32
|
+
if condition is None:
|
|
33
|
+
raise ValueError("FilterListStage requires config.condition")
|
|
34
|
+
items = args.get("items", [])
|
|
35
|
+
if not isinstance(items, list):
|
|
36
|
+
raise ValueError("FilterListStage expects list in arguments.items")
|
|
37
|
+
base_payload = self.session.context.payload
|
|
38
|
+
result = []
|
|
39
|
+
for item in items:
|
|
40
|
+
temp_ctx = Context(payload=dict(base_payload))
|
|
41
|
+
temp_ctx.set(item_path, item)
|
|
42
|
+
if JsonLogic(condition).evaluate(temp_ctx):
|
|
43
|
+
result.append(item)
|
|
44
|
+
self.set_outputs({"list": result})
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@register_stage("UniqueListStage")
|
|
48
|
+
class UniqueListStage(BaseStage):
|
|
49
|
+
"""
|
|
50
|
+
description: "Deduplicate list while preserving original order"
|
|
51
|
+
arguments:
|
|
52
|
+
items:
|
|
53
|
+
type: list
|
|
54
|
+
description: "List to deduplicate"
|
|
55
|
+
outputs:
|
|
56
|
+
list:
|
|
57
|
+
type: list
|
|
58
|
+
description: "List with unique items"
|
|
59
|
+
"""
|
|
60
|
+
category = "builtin.lists"
|
|
61
|
+
|
|
62
|
+
async def run(self):
|
|
63
|
+
args = self.get_arguments()
|
|
64
|
+
items = args.get("items", [])
|
|
65
|
+
if not isinstance(items, list):
|
|
66
|
+
raise ValueError("UniqueListStage expects list in arguments.items")
|
|
67
|
+
seen = set()
|
|
68
|
+
out = []
|
|
69
|
+
for item in items:
|
|
70
|
+
if item not in seen:
|
|
71
|
+
seen.add(item)
|
|
72
|
+
out.append(item)
|
|
73
|
+
self.set_outputs({"list": out})
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@register_stage("PopListStage")
|
|
77
|
+
class PopListStage(BaseStage):
|
|
78
|
+
"""
|
|
79
|
+
description: "Pop element from list (default last) and return list+popped value"
|
|
80
|
+
arguments:
|
|
81
|
+
items:
|
|
82
|
+
type: list
|
|
83
|
+
description: "List to pop from"
|
|
84
|
+
index:
|
|
85
|
+
type: int
|
|
86
|
+
description: "Index to pop (overrides config)"
|
|
87
|
+
config:
|
|
88
|
+
index:
|
|
89
|
+
type: int
|
|
90
|
+
description: "Default index to pop, -1 means last element"
|
|
91
|
+
outputs:
|
|
92
|
+
list:
|
|
93
|
+
type: list
|
|
94
|
+
description: "List after pop"
|
|
95
|
+
popped:
|
|
96
|
+
type: any
|
|
97
|
+
description: "Popped value"
|
|
98
|
+
"""
|
|
99
|
+
category = "builtin.lists"
|
|
100
|
+
|
|
101
|
+
async def run(self):
|
|
102
|
+
args = self.get_arguments()
|
|
103
|
+
index = args.get("index", self.config.get("index", -1))
|
|
104
|
+
lst = args.get("items", [])
|
|
105
|
+
if not isinstance(lst, list):
|
|
106
|
+
raise ValueError("PopListStage expects list in arguments.items")
|
|
107
|
+
if not lst:
|
|
108
|
+
return
|
|
109
|
+
value = lst.pop(index)
|
|
110
|
+
self.set_outputs({"list": lst, "popped": value})
|