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/__init__.py +25 -0
- stageflow/builtins/__init__.py +34 -0
- stageflow/builtins/dicts.py +59 -0
- stageflow/builtins/lists.py +71 -0
- stageflow/builtins/lists_extra.py +110 -0
- stageflow/builtins/logic.py +84 -0
- stageflow/builtins/strings.py +81 -0
- stageflow/builtins/vars.py +118 -0
- stageflow/core/__init__.py +7 -0
- stageflow/core/context.py +86 -0
- stageflow/core/event.py +71 -0
- stageflow/core/jsonlogic.py +43 -0
- stageflow/core/node.py +256 -0
- stageflow/core/pipeline.py +108 -0
- stageflow/core/session.py +398 -0
- stageflow/core/stage.py +152 -0
- stageflow/core/utils.py +62 -0
- stageflow/docs/__init__.py +4 -0
- stageflow/docs/html.py +479 -0
- stageflow/docs/schema.py +38 -0
- stageflow/docs/schemas/pipeline.json +134 -0
- stageflow/py.typed +1 -0
- stageflow/testing.py +59 -0
- stageflow_framework-0.1.0.dist-info/METADATA +167 -0
- stageflow_framework-0.1.0.dist-info/RECORD +27 -0
- stageflow_framework-0.1.0.dist-info/WHEEL +5 -0
- stageflow_framework-0.1.0.dist-info/top_level.txt +1 -0
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 @@
|
|
|
1
|
+
stageflow
|