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.
Files changed (41) hide show
  1. stageflow_framework-0.1.0/PKG-INFO +167 -0
  2. stageflow_framework-0.1.0/README.md +157 -0
  3. stageflow_framework-0.1.0/pyproject.toml +24 -0
  4. stageflow_framework-0.1.0/setup.cfg +4 -0
  5. stageflow_framework-0.1.0/stageflow/__init__.py +25 -0
  6. stageflow_framework-0.1.0/stageflow/builtins/__init__.py +34 -0
  7. stageflow_framework-0.1.0/stageflow/builtins/dicts.py +59 -0
  8. stageflow_framework-0.1.0/stageflow/builtins/lists.py +71 -0
  9. stageflow_framework-0.1.0/stageflow/builtins/lists_extra.py +110 -0
  10. stageflow_framework-0.1.0/stageflow/builtins/logic.py +84 -0
  11. stageflow_framework-0.1.0/stageflow/builtins/strings.py +81 -0
  12. stageflow_framework-0.1.0/stageflow/builtins/vars.py +118 -0
  13. stageflow_framework-0.1.0/stageflow/core/__init__.py +7 -0
  14. stageflow_framework-0.1.0/stageflow/core/context.py +86 -0
  15. stageflow_framework-0.1.0/stageflow/core/event.py +71 -0
  16. stageflow_framework-0.1.0/stageflow/core/jsonlogic.py +43 -0
  17. stageflow_framework-0.1.0/stageflow/core/node.py +256 -0
  18. stageflow_framework-0.1.0/stageflow/core/pipeline.py +108 -0
  19. stageflow_framework-0.1.0/stageflow/core/session.py +398 -0
  20. stageflow_framework-0.1.0/stageflow/core/stage.py +152 -0
  21. stageflow_framework-0.1.0/stageflow/core/utils.py +62 -0
  22. stageflow_framework-0.1.0/stageflow/docs/__init__.py +4 -0
  23. stageflow_framework-0.1.0/stageflow/docs/html.py +479 -0
  24. stageflow_framework-0.1.0/stageflow/docs/schema.py +38 -0
  25. stageflow_framework-0.1.0/stageflow/docs/schemas/pipeline.json +134 -0
  26. stageflow_framework-0.1.0/stageflow/py.typed +1 -0
  27. stageflow_framework-0.1.0/stageflow/testing.py +59 -0
  28. stageflow_framework-0.1.0/stageflow_framework.egg-info/PKG-INFO +167 -0
  29. stageflow_framework-0.1.0/stageflow_framework.egg-info/SOURCES.txt +39 -0
  30. stageflow_framework-0.1.0/stageflow_framework.egg-info/dependency_links.txt +1 -0
  31. stageflow_framework-0.1.0/stageflow_framework.egg-info/requires.txt +2 -0
  32. stageflow_framework-0.1.0/stageflow_framework.egg-info/top_level.txt +1 -0
  33. stageflow_framework-0.1.0/tests/test_core_flow.py +79 -0
  34. stageflow_framework-0.1.0/tests/test_docs_schema.py +82 -0
  35. stageflow_framework-0.1.0/tests/test_full_pipeline.py +175 -0
  36. stageflow_framework-0.1.0/tests/test_jsonlogic.py +34 -0
  37. stageflow_framework-0.1.0/tests/test_payload_validation.py +114 -0
  38. stageflow_framework-0.1.0/tests/test_pipeline_tester.py +46 -0
  39. stageflow_framework-0.1.0/tests/test_snapshot.py +51 -0
  40. stageflow_framework-0.1.0/tests/test_std_stages.py +53 -0
  41. 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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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})