stageflow-framework 0.1.0__py3-none-any.whl

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/testing.py ADDED
@@ -0,0 +1,59 @@
1
+ import asyncio
2
+ from dataclasses import dataclass
3
+ from typing import Sequence
4
+
5
+ from stageflow.core.context import Context
6
+ from stageflow.core.pipeline import Pipeline
7
+ from stageflow.core.session import Session, SessionResult
8
+ from stageflow.core.event import Event
9
+
10
+
11
+ @dataclass
12
+ class PipelineTestSpec:
13
+ pipeline: dict | Pipeline
14
+ inputs: Sequence[dict]
15
+ payload: dict | None = None
16
+ expected_result: dict | None = None
17
+ expected_artifacts: dict | None = None
18
+ expected_history: list[str] | None = None # sequence of stage_id/node_id seen in events
19
+
20
+
21
+ async def run_pipeline_test(spec: PipelineTestSpec) -> tuple[SessionResult, list[Event]]:
22
+ pipeline = spec.pipeline if isinstance(spec.pipeline, Pipeline) else Pipeline.from_dict(spec.pipeline)
23
+ events: list[Event] = []
24
+
25
+ def handler(ev: Event):
26
+ events.append(ev)
27
+
28
+ session = Session(id="test", pipeline=pipeline, context=Context(payload=spec.payload or {}), event_handler=handler)
29
+
30
+ async def feed_inputs():
31
+ for inp in spec.inputs:
32
+ delay = inp.get("delay", 0)
33
+ if delay:
34
+ await asyncio.sleep(delay)
35
+ wait_for_listener = inp.get("wait_for_listener", True)
36
+ if wait_for_listener:
37
+ waited = 0.0
38
+ while inp["type"] not in session._waiting and waited < 1.0:
39
+ await asyncio.sleep(0.01)
40
+ waited += 0.01
41
+ await session.input(inp["type"], inp.get("payload", {}))
42
+
43
+ run_task = asyncio.create_task(session.run())
44
+ await asyncio.gather(run_task, feed_inputs())
45
+ result = run_task.result()
46
+
47
+ if spec.expected_result is not None:
48
+ if result.result != spec.expected_result:
49
+ raise AssertionError(f"Expected result {spec.expected_result}, got {result.result}")
50
+ if spec.expected_artifacts is not None:
51
+ for k, v in spec.expected_artifacts.items():
52
+ if result.artifacts.get(k) != v:
53
+ raise AssertionError(f"Expected artifact {k}={v}, got {result.artifacts.get(k)}")
54
+ if spec.expected_history is not None:
55
+ seen = [ev.stage_id or ev.node_id for ev in events if (ev.stage_id or ev.node_id)]
56
+ if seen != spec.expected_history:
57
+ raise AssertionError(f"Expected history {spec.expected_history}, got {seen}")
58
+
59
+ return result, events
@@ -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,27 @@
1
+ stageflow/__init__.py,sha256=c_CM46gP_3mau3XAcV8J1LZIs2KTcavBqdk4B6zdvvo,761
2
+ stageflow/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
3
+ stageflow/testing.py,sha256=VYrSOx_5uuQ5dMsV18KF4JpvVIjQ0nrtCo7tqQi6Ak0,2345
4
+ stageflow/builtins/__init__.py,sha256=PItXwajwpwwFX5LK8h6rnPCTtPv0L4HNnyJ3YPZIpE0,764
5
+ stageflow/builtins/dicts.py,sha256=SeuuxCBV8S2Tn84HlkKjaBN1ysE81qv1NEV4xmr9puA,1750
6
+ stageflow/builtins/lists.py,sha256=JLlYnkvk_HQukDLj2XyfaNJxIG0vItmLeXlVs1DtBy8,2099
7
+ stageflow/builtins/lists_extra.py,sha256=vwKYbbacXfG1UjlW2RegQL4EPIxm6EDdkCxYIfH35Ps,3333
8
+ stageflow/builtins/logic.py,sha256=EnWKjHBx4sDnehDNTWx6psQR0vztCS3JGRcOihMKQcs,2302
9
+ stageflow/builtins/strings.py,sha256=ifIhGBu6WTLAwsNJ0FUj8hxdBeZVqJeYg05P7Kq9BeU,2520
10
+ stageflow/builtins/vars.py,sha256=3ChqXZuX0tIj5mZr5INny7IFMu2bROEHvvS8KI0JnPc,3411
11
+ stageflow/core/__init__.py,sha256=dWxd8eOZ8HxR66bQ0yqvWxVij6rnrbc68OBUG3AchMw,471
12
+ stageflow/core/context.py,sha256=Yql-6YH-1l4GtD70RS-jzcontD0mlA-owSZGCvuRKNA,2606
13
+ stageflow/core/event.py,sha256=xTwCicmAuWoxsYrDXAVl7JgSQ3hVh47MolBVf_K_jC8,1892
14
+ stageflow/core/jsonlogic.py,sha256=J8gqzD1-r0bJflseVdmB_JQB0GebnAUMJoZE8u52xmY,1543
15
+ stageflow/core/node.py,sha256=y03NTPcPvufNd-khjSigXXkT_cBGNSdJe2eVGGuKW3E,7117
16
+ stageflow/core/pipeline.py,sha256=efb1lL2jkZOWsBue3-e_ubyz7kLFEbXaNBg-7SFJ-A8,4867
17
+ stageflow/core/session.py,sha256=fk_WUuy9gDlcB_CPlbumjaqfR-PKLX-QAX_VxnZ-yZg,15835
18
+ stageflow/core/stage.py,sha256=rnJSvbFKY7J06LN-CvciElhbxZK7uMUT5xeaZpGAyJo,5884
19
+ stageflow/core/utils.py,sha256=aemH1oakRQxRWREjzjWZ28CSlvp75320vwAtv3eYXx4,2207
20
+ stageflow/docs/__init__.py,sha256=0-9v4s_MgVOR9Z2jNPspbPf4SWORJ0HDxdPN04Qa7RY,132
21
+ stageflow/docs/html.py,sha256=DDtvCsjwAIEwg1MVUJ1p6jQM1uIQ2-QWK7Y-hXGUbLE,19319
22
+ stageflow/docs/schema.py,sha256=1Hg_U7-OWVSn3y4Wp4TSbuKSxb0Hz8SHZBjLSmZ2JSc,1153
23
+ stageflow/docs/schemas/pipeline.json,sha256=ZnLjk9jcVI8olq7oo23MPbDnWa0ouj5kZrAl-zK3Z4M,4073
24
+ stageflow_framework-0.1.0.dist-info/METADATA,sha256=7fNsfkFtFxWpvAmOblyYb12JGg75YUl6RinNFJS3S8c,6369
25
+ stageflow_framework-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
26
+ stageflow_framework-0.1.0.dist-info/top_level.txt,sha256=byrtG44gmksw0XJXpaoPbYl38Px2_9Ww2BcRDmjBVZ0,10
27
+ stageflow_framework-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ stageflow