renderflow 0.1.1__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.
- renderflow-0.1.1/PKG-INFO +181 -0
- renderflow-0.1.1/README.md +168 -0
- renderflow-0.1.1/pyproject.toml +27 -0
- renderflow-0.1.1/renderflow/__init__.py +7 -0
- renderflow-0.1.1/renderflow/app.py +6 -0
- renderflow-0.1.1/renderflow/autodefine.py +227 -0
- renderflow-0.1.1/renderflow/cli.py +361 -0
- renderflow-0.1.1/renderflow/contracts.py +52 -0
- renderflow-0.1.1/renderflow/discovery.py +51 -0
- renderflow-0.1.1/renderflow/progress.py +56 -0
- renderflow-0.1.1/renderflow/results.py +279 -0
- renderflow-0.1.1/renderflow/streamlit_renderer.py +247 -0
- renderflow-0.1.1/renderflow/workflow.py +31 -0
- renderflow-0.1.1/renderflow.egg-info/PKG-INFO +181 -0
- renderflow-0.1.1/renderflow.egg-info/SOURCES.txt +19 -0
- renderflow-0.1.1/renderflow.egg-info/dependency_links.txt +1 -0
- renderflow-0.1.1/renderflow.egg-info/entry_points.txt +3 -0
- renderflow-0.1.1/renderflow.egg-info/requires.txt +4 -0
- renderflow-0.1.1/renderflow.egg-info/top_level.txt +1 -0
- renderflow-0.1.1/setup.cfg +4 -0
- renderflow-0.1.1/tests/test_execute_figure_export.py +155 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: renderflow
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Generic workflow app runtime and Streamlit renderer with provider plugins
|
|
5
|
+
Author: Brian Day
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.9
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: streamlit>=1.31.0
|
|
10
|
+
Requires-Dist: pandas>=2.0.0
|
|
11
|
+
Requires-Dist: plotly>=5.18.0
|
|
12
|
+
Requires-Dist: kaleido>=0.2.1
|
|
13
|
+
|
|
14
|
+
# renderflow
|
|
15
|
+
|
|
16
|
+
Workflow runtime and rendering API for:
|
|
17
|
+
- Streamlit UI
|
|
18
|
+
- CLI execution
|
|
19
|
+
- HTML report export
|
|
20
|
+
- Individual figure export
|
|
21
|
+
|
|
22
|
+
## Core Idea
|
|
23
|
+
|
|
24
|
+
`renderflow` owns the interface contract and rendering behavior.
|
|
25
|
+
Provider packages should mostly define workflows (`run_workflow` + params), not custom UI/CLI plumbing.
|
|
26
|
+
|
|
27
|
+
## Demo App
|
|
28
|
+
|
|
29
|
+
Live Streamlit demo:
|
|
30
|
+
|
|
31
|
+
https://demo-for-renderflow.streamlit.app/
|
|
32
|
+
|
|
33
|
+
## CLI
|
|
34
|
+
|
|
35
|
+
List installed providers:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
renderflow list-providers
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
List provider workflows:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
renderflow list-workflows --provider crsd-inspector
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Show interpreted workflow parameters:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
renderflow show-params --provider crsd-inspector --workflow signal_analysis
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Execute a workflow in terminal mode:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
renderflow execute \
|
|
57
|
+
--provider crsd-inspector \
|
|
58
|
+
--workflow signal_analysis \
|
|
59
|
+
--param crsd_directory=examples \
|
|
60
|
+
--param prf_hz=1000 \
|
|
61
|
+
--output terminal
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Execute and export both:
|
|
65
|
+
- one combined report file (`--html`)
|
|
66
|
+
- per-figure files (`--save-figures-dir` + `--figure-format`)
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
renderflow execute \
|
|
70
|
+
--provider crsd-inspector \
|
|
71
|
+
--workflow signal_analysis \
|
|
72
|
+
--param crsd_directory=examples \
|
|
73
|
+
--html output/report.html \
|
|
74
|
+
--save-figures-dir output/figures \
|
|
75
|
+
--figure-format html
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Add per-figure JSON in the same run:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
renderflow execute \
|
|
82
|
+
--provider crsd-inspector \
|
|
83
|
+
--workflow signal_analysis \
|
|
84
|
+
--param crsd_directory=examples \
|
|
85
|
+
--html output/report.html \
|
|
86
|
+
--save-figures-dir output/figures \
|
|
87
|
+
--figure-format html \
|
|
88
|
+
--figure-format json
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Export multiple figure formats in a single run:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
renderflow execute \
|
|
95
|
+
--provider crsd-inspector \
|
|
96
|
+
--workflow signal_analysis \
|
|
97
|
+
--param crsd_directory=examples \
|
|
98
|
+
--save-figures-dir output/figures \
|
|
99
|
+
--figure-format html \
|
|
100
|
+
--figure-format json
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Comma-separated format lists are also accepted:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
renderflow execute \
|
|
107
|
+
--provider crsd-inspector \
|
|
108
|
+
--workflow signal_analysis \
|
|
109
|
+
--param crsd_directory=examples \
|
|
110
|
+
--html output/report.html \
|
|
111
|
+
--save-figures-dir output/figures \
|
|
112
|
+
--figure-format html,json
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
If `--figure-format` is omitted, per-figure export defaults to `html`.
|
|
116
|
+
Image formats (`png`, `jpg`, `jpeg`, `svg`, `pdf`) require Kaleido. `renderflow` includes `kaleido` as a dependency.
|
|
117
|
+
|
|
118
|
+
Launch Streamlit:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
renderflow run --provider crsd-inspector
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Workflow Result Contract
|
|
125
|
+
|
|
126
|
+
Use `renderflow.workflow.Workflow` inside provider workflows:
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
from renderflow.workflow import Workflow
|
|
130
|
+
|
|
131
|
+
workflow = Workflow(name="My Workflow", description="...")
|
|
132
|
+
workflow.params = {
|
|
133
|
+
"threshold": {"type": "number", "default": 0.5, "label": "Threshold"},
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
workflow.add_text("Summary text")
|
|
137
|
+
workflow.add_table("Metrics", {"name": ["a"], "value": [1]})
|
|
138
|
+
workflow.add_plot(fig, title="Spectrum", figure_id="spectrum", save=True)
|
|
139
|
+
workflow.add_code("print('debug')", language="python")
|
|
140
|
+
return workflow.build()
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
`add_plot(..., save=False)` marks a plot as not exportable when using figure-save operations.
|
|
144
|
+
|
|
145
|
+
Minimum return contract from `run_workflow(...)`:
|
|
146
|
+
- must return a `dict`
|
|
147
|
+
- either:
|
|
148
|
+
- modern shape: `{"results": [ ... ]}`
|
|
149
|
+
- legacy shape: `{"text": [...], "tables": [...], "plots": [...]}`
|
|
150
|
+
- for modern shape, each item in `results` must be a dict with:
|
|
151
|
+
- `type` in `text | table | plot | code`
|
|
152
|
+
- if `type == "plot"`, item must include `figure`
|
|
153
|
+
|
|
154
|
+
## Provider Contract Options
|
|
155
|
+
|
|
156
|
+
### 1) Explicit `AppSpec` (fully explicit)
|
|
157
|
+
|
|
158
|
+
Entry point:
|
|
159
|
+
|
|
160
|
+
```toml
|
|
161
|
+
[project.entry-points."renderflow.providers"]
|
|
162
|
+
my-provider = "my_provider.app_definition:get_app_spec"
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
`get_app_spec()` returns `renderflow.contracts.AppSpec`.
|
|
166
|
+
|
|
167
|
+
### 2) Auto-Defined Provider (minimal)
|
|
168
|
+
|
|
169
|
+
If no `app_definition` exists, `renderflow` auto-builds from:
|
|
170
|
+
- `<provider>.workflows.*` modules with `run_workflow(...)`
|
|
171
|
+
- optional `<provider>.renderflow` module:
|
|
172
|
+
- `APP_NAME = "..."`
|
|
173
|
+
- `WORKFLOWS_PACKAGE = "provider.custom_workflows"` (optional)
|
|
174
|
+
- optional custom metadata constants for provider setup
|
|
175
|
+
|
|
176
|
+
Workflow parameters are pulled from:
|
|
177
|
+
1. `workflow.params` if a `workflow` object exists in the module
|
|
178
|
+
2. `PARAMS` module global
|
|
179
|
+
3. inferred function signature defaults
|
|
180
|
+
|
|
181
|
+
This lets packages like `crsd-inspector` keep only workflow definitions and optional init logic, while `renderflow` handles CLI + Streamlit parameter interpretation and rendering.
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# renderflow
|
|
2
|
+
|
|
3
|
+
Workflow runtime and rendering API for:
|
|
4
|
+
- Streamlit UI
|
|
5
|
+
- CLI execution
|
|
6
|
+
- HTML report export
|
|
7
|
+
- Individual figure export
|
|
8
|
+
|
|
9
|
+
## Core Idea
|
|
10
|
+
|
|
11
|
+
`renderflow` owns the interface contract and rendering behavior.
|
|
12
|
+
Provider packages should mostly define workflows (`run_workflow` + params), not custom UI/CLI plumbing.
|
|
13
|
+
|
|
14
|
+
## Demo App
|
|
15
|
+
|
|
16
|
+
Live Streamlit demo:
|
|
17
|
+
|
|
18
|
+
https://demo-for-renderflow.streamlit.app/
|
|
19
|
+
|
|
20
|
+
## CLI
|
|
21
|
+
|
|
22
|
+
List installed providers:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
renderflow list-providers
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
List provider workflows:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
renderflow list-workflows --provider crsd-inspector
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Show interpreted workflow parameters:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
renderflow show-params --provider crsd-inspector --workflow signal_analysis
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Execute a workflow in terminal mode:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
renderflow execute \
|
|
44
|
+
--provider crsd-inspector \
|
|
45
|
+
--workflow signal_analysis \
|
|
46
|
+
--param crsd_directory=examples \
|
|
47
|
+
--param prf_hz=1000 \
|
|
48
|
+
--output terminal
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Execute and export both:
|
|
52
|
+
- one combined report file (`--html`)
|
|
53
|
+
- per-figure files (`--save-figures-dir` + `--figure-format`)
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
renderflow execute \
|
|
57
|
+
--provider crsd-inspector \
|
|
58
|
+
--workflow signal_analysis \
|
|
59
|
+
--param crsd_directory=examples \
|
|
60
|
+
--html output/report.html \
|
|
61
|
+
--save-figures-dir output/figures \
|
|
62
|
+
--figure-format html
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Add per-figure JSON in the same run:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
renderflow execute \
|
|
69
|
+
--provider crsd-inspector \
|
|
70
|
+
--workflow signal_analysis \
|
|
71
|
+
--param crsd_directory=examples \
|
|
72
|
+
--html output/report.html \
|
|
73
|
+
--save-figures-dir output/figures \
|
|
74
|
+
--figure-format html \
|
|
75
|
+
--figure-format json
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Export multiple figure formats in a single run:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
renderflow execute \
|
|
82
|
+
--provider crsd-inspector \
|
|
83
|
+
--workflow signal_analysis \
|
|
84
|
+
--param crsd_directory=examples \
|
|
85
|
+
--save-figures-dir output/figures \
|
|
86
|
+
--figure-format html \
|
|
87
|
+
--figure-format json
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Comma-separated format lists are also accepted:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
renderflow execute \
|
|
94
|
+
--provider crsd-inspector \
|
|
95
|
+
--workflow signal_analysis \
|
|
96
|
+
--param crsd_directory=examples \
|
|
97
|
+
--html output/report.html \
|
|
98
|
+
--save-figures-dir output/figures \
|
|
99
|
+
--figure-format html,json
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
If `--figure-format` is omitted, per-figure export defaults to `html`.
|
|
103
|
+
Image formats (`png`, `jpg`, `jpeg`, `svg`, `pdf`) require Kaleido. `renderflow` includes `kaleido` as a dependency.
|
|
104
|
+
|
|
105
|
+
Launch Streamlit:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
renderflow run --provider crsd-inspector
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Workflow Result Contract
|
|
112
|
+
|
|
113
|
+
Use `renderflow.workflow.Workflow` inside provider workflows:
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
from renderflow.workflow import Workflow
|
|
117
|
+
|
|
118
|
+
workflow = Workflow(name="My Workflow", description="...")
|
|
119
|
+
workflow.params = {
|
|
120
|
+
"threshold": {"type": "number", "default": 0.5, "label": "Threshold"},
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
workflow.add_text("Summary text")
|
|
124
|
+
workflow.add_table("Metrics", {"name": ["a"], "value": [1]})
|
|
125
|
+
workflow.add_plot(fig, title="Spectrum", figure_id="spectrum", save=True)
|
|
126
|
+
workflow.add_code("print('debug')", language="python")
|
|
127
|
+
return workflow.build()
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
`add_plot(..., save=False)` marks a plot as not exportable when using figure-save operations.
|
|
131
|
+
|
|
132
|
+
Minimum return contract from `run_workflow(...)`:
|
|
133
|
+
- must return a `dict`
|
|
134
|
+
- either:
|
|
135
|
+
- modern shape: `{"results": [ ... ]}`
|
|
136
|
+
- legacy shape: `{"text": [...], "tables": [...], "plots": [...]}`
|
|
137
|
+
- for modern shape, each item in `results` must be a dict with:
|
|
138
|
+
- `type` in `text | table | plot | code`
|
|
139
|
+
- if `type == "plot"`, item must include `figure`
|
|
140
|
+
|
|
141
|
+
## Provider Contract Options
|
|
142
|
+
|
|
143
|
+
### 1) Explicit `AppSpec` (fully explicit)
|
|
144
|
+
|
|
145
|
+
Entry point:
|
|
146
|
+
|
|
147
|
+
```toml
|
|
148
|
+
[project.entry-points."renderflow.providers"]
|
|
149
|
+
my-provider = "my_provider.app_definition:get_app_spec"
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
`get_app_spec()` returns `renderflow.contracts.AppSpec`.
|
|
153
|
+
|
|
154
|
+
### 2) Auto-Defined Provider (minimal)
|
|
155
|
+
|
|
156
|
+
If no `app_definition` exists, `renderflow` auto-builds from:
|
|
157
|
+
- `<provider>.workflows.*` modules with `run_workflow(...)`
|
|
158
|
+
- optional `<provider>.renderflow` module:
|
|
159
|
+
- `APP_NAME = "..."`
|
|
160
|
+
- `WORKFLOWS_PACKAGE = "provider.custom_workflows"` (optional)
|
|
161
|
+
- optional custom metadata constants for provider setup
|
|
162
|
+
|
|
163
|
+
Workflow parameters are pulled from:
|
|
164
|
+
1. `workflow.params` if a `workflow` object exists in the module
|
|
165
|
+
2. `PARAMS` module global
|
|
166
|
+
3. inferred function signature defaults
|
|
167
|
+
|
|
168
|
+
This lets packages like `crsd-inspector` keep only workflow definitions and optional init logic, while `renderflow` handles CLI + Streamlit parameter interpretation and rendering.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "renderflow"
|
|
7
|
+
version = "0.1.1"
|
|
8
|
+
description = "Generic workflow app runtime and Streamlit renderer with provider plugins"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Brian Day" }
|
|
14
|
+
]
|
|
15
|
+
dependencies = [
|
|
16
|
+
"streamlit>=1.31.0",
|
|
17
|
+
"pandas>=2.0.0",
|
|
18
|
+
"plotly>=5.18.0",
|
|
19
|
+
"kaleido>=0.2.1"
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[project.scripts]
|
|
23
|
+
renderflow = "renderflow.cli:main"
|
|
24
|
+
workflow-renderer-streamlit = "renderflow.cli:main"
|
|
25
|
+
|
|
26
|
+
[tool.setuptools.packages.find]
|
|
27
|
+
include = ["renderflow*"]
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"""Automatic provider definition from workflow modules."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import importlib
|
|
6
|
+
import inspect
|
|
7
|
+
import pkgutil
|
|
8
|
+
from dataclasses import asdict, is_dataclass
|
|
9
|
+
from typing import Any, Callable
|
|
10
|
+
|
|
11
|
+
from renderflow.contracts import AppSpec, InitializerSpec, ParamSpec, WorkflowSpec
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _param_specs_from_mapping(params: dict[str, Any] | None) -> list[ParamSpec]:
|
|
15
|
+
specs: list[ParamSpec] = []
|
|
16
|
+
for key, cfg in (params or {}).items():
|
|
17
|
+
if isinstance(cfg, ParamSpec):
|
|
18
|
+
specs.append(cfg)
|
|
19
|
+
continue
|
|
20
|
+
cfg = cfg or {}
|
|
21
|
+
specs.append(
|
|
22
|
+
ParamSpec(
|
|
23
|
+
key=key,
|
|
24
|
+
label=cfg.get("label", key),
|
|
25
|
+
type=cfg.get("type", "text"),
|
|
26
|
+
default=cfg.get("default"),
|
|
27
|
+
min=cfg.get("min"),
|
|
28
|
+
max=cfg.get("max"),
|
|
29
|
+
step=cfg.get("step"),
|
|
30
|
+
options=cfg.get("options", []),
|
|
31
|
+
help=cfg.get("help", ""),
|
|
32
|
+
)
|
|
33
|
+
)
|
|
34
|
+
return specs
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _coerce_param_specs(value: Any) -> list[ParamSpec]:
|
|
38
|
+
if value is None:
|
|
39
|
+
return []
|
|
40
|
+
if isinstance(value, dict):
|
|
41
|
+
return _param_specs_from_mapping(value)
|
|
42
|
+
specs: list[ParamSpec] = []
|
|
43
|
+
for item in value:
|
|
44
|
+
if isinstance(item, ParamSpec):
|
|
45
|
+
specs.append(item)
|
|
46
|
+
elif isinstance(item, dict):
|
|
47
|
+
specs.append(ParamSpec(**item))
|
|
48
|
+
return specs
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _infer_params_from_signature(func: Callable[..., Any]) -> list[ParamSpec]:
|
|
52
|
+
reserved = {"signal_data", "metadata", "context", "kwargs", "args", "self"}
|
|
53
|
+
specs: list[ParamSpec] = []
|
|
54
|
+
for name, param in inspect.signature(func).parameters.items():
|
|
55
|
+
if name in reserved or param.kind in {inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD}:
|
|
56
|
+
continue
|
|
57
|
+
default = None if param.default is inspect.Parameter.empty else param.default
|
|
58
|
+
param_type = "text"
|
|
59
|
+
if isinstance(default, bool):
|
|
60
|
+
param_type = "checkbox"
|
|
61
|
+
elif isinstance(default, (int, float)):
|
|
62
|
+
param_type = "number"
|
|
63
|
+
specs.append(ParamSpec(key=name, label=name.replace("_", " ").title(), type=param_type, default=default))
|
|
64
|
+
return specs
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _invoke_workflow(func: Callable[..., dict[str, Any]], context: dict[str, Any], params: dict[str, Any]):
|
|
68
|
+
if not isinstance(context, dict):
|
|
69
|
+
raise TypeError(f"Workflow context must be a dict, got {type(context).__name__}")
|
|
70
|
+
if not isinstance(params, dict):
|
|
71
|
+
raise TypeError(f"Workflow params must be a dict, got {type(params).__name__}")
|
|
72
|
+
|
|
73
|
+
sig = inspect.signature(func)
|
|
74
|
+
kwargs: dict[str, Any] = {}
|
|
75
|
+
merged_metadata: dict[str, Any] = {}
|
|
76
|
+
base_metadata = context.get("metadata")
|
|
77
|
+
if isinstance(base_metadata, dict):
|
|
78
|
+
merged_metadata.update(base_metadata)
|
|
79
|
+
for key, value in context.items():
|
|
80
|
+
if key != "metadata":
|
|
81
|
+
merged_metadata[key] = value
|
|
82
|
+
merged_metadata.update(params)
|
|
83
|
+
|
|
84
|
+
if "signal_data" in sig.parameters and "signal_data" in context:
|
|
85
|
+
kwargs["signal_data"] = context["signal_data"]
|
|
86
|
+
if "metadata" in sig.parameters:
|
|
87
|
+
kwargs["metadata"] = merged_metadata
|
|
88
|
+
if "context" in sig.parameters:
|
|
89
|
+
kwargs["context"] = context
|
|
90
|
+
|
|
91
|
+
for key, value in params.items():
|
|
92
|
+
if key in sig.parameters and key not in kwargs:
|
|
93
|
+
kwargs[key] = value
|
|
94
|
+
|
|
95
|
+
result = func(**kwargs)
|
|
96
|
+
if not isinstance(result, dict):
|
|
97
|
+
raise TypeError(
|
|
98
|
+
f"Workflow '{func.__module__}.{func.__name__}' must return a dict, got {type(result).__name__}"
|
|
99
|
+
)
|
|
100
|
+
return result
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _discover_workflows(provider_name: str, package_name: str) -> list[WorkflowSpec]:
|
|
104
|
+
pkg = importlib.import_module(package_name)
|
|
105
|
+
specs: list[WorkflowSpec] = []
|
|
106
|
+
for module_info in sorted(pkgutil.iter_modules(pkg.__path__), key=lambda m: m.name):
|
|
107
|
+
if module_info.name.startswith("_"):
|
|
108
|
+
continue
|
|
109
|
+
module = importlib.import_module(f"{package_name}.{module_info.name}")
|
|
110
|
+
run_workflow = getattr(module, "run_workflow", None)
|
|
111
|
+
if not callable(run_workflow):
|
|
112
|
+
continue
|
|
113
|
+
|
|
114
|
+
workflow_obj = getattr(module, "workflow", None)
|
|
115
|
+
wf_id = module_info.name
|
|
116
|
+
wf_name = getattr(workflow_obj, "name", None) or getattr(module, "WORKFLOW_NAME", wf_id)
|
|
117
|
+
wf_desc = getattr(workflow_obj, "description", None) or getattr(module, "WORKFLOW_DESCRIPTION", "")
|
|
118
|
+
raw_params = getattr(workflow_obj, "params", None)
|
|
119
|
+
if raw_params is None:
|
|
120
|
+
raw_params = getattr(module, "PARAMS", None)
|
|
121
|
+
if raw_params is None:
|
|
122
|
+
param_specs = _infer_params_from_signature(run_workflow)
|
|
123
|
+
else:
|
|
124
|
+
param_specs = _coerce_param_specs(raw_params)
|
|
125
|
+
|
|
126
|
+
def _make_run(func):
|
|
127
|
+
def _run(context: dict[str, Any], params: dict[str, Any]) -> dict[str, Any]:
|
|
128
|
+
return _invoke_workflow(func, context, params)
|
|
129
|
+
|
|
130
|
+
return _run
|
|
131
|
+
|
|
132
|
+
specs.append(
|
|
133
|
+
WorkflowSpec(
|
|
134
|
+
id=wf_id,
|
|
135
|
+
name=wf_name,
|
|
136
|
+
description=wf_desc,
|
|
137
|
+
params=param_specs,
|
|
138
|
+
run=_make_run(run_workflow),
|
|
139
|
+
)
|
|
140
|
+
)
|
|
141
|
+
if not specs:
|
|
142
|
+
raise RuntimeError(f"No workflow modules with run_workflow() found under {package_name}")
|
|
143
|
+
return specs
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _dict_to_app_spec(raw: dict[str, Any]) -> AppSpec:
|
|
147
|
+
if {"app_name", "initializers", "workflows"} - set(raw.keys()):
|
|
148
|
+
raise TypeError("Dictionary provider spec missing one of: app_name, initializers, workflows")
|
|
149
|
+
initializers: list[InitializerSpec] = []
|
|
150
|
+
for item in raw["initializers"]:
|
|
151
|
+
if isinstance(item, InitializerSpec):
|
|
152
|
+
initializers.append(item)
|
|
153
|
+
else:
|
|
154
|
+
initializers.append(
|
|
155
|
+
InitializerSpec(
|
|
156
|
+
id=item["id"],
|
|
157
|
+
name=item["name"],
|
|
158
|
+
description=item.get("description", ""),
|
|
159
|
+
params=_coerce_param_specs(item.get("params")),
|
|
160
|
+
initialize=item["initialize"],
|
|
161
|
+
)
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
workflows: list[WorkflowSpec] = []
|
|
165
|
+
for item in raw["workflows"]:
|
|
166
|
+
if isinstance(item, WorkflowSpec):
|
|
167
|
+
workflows.append(item)
|
|
168
|
+
else:
|
|
169
|
+
workflows.append(
|
|
170
|
+
WorkflowSpec(
|
|
171
|
+
id=item["id"],
|
|
172
|
+
name=item["name"],
|
|
173
|
+
description=item.get("description", ""),
|
|
174
|
+
params=_coerce_param_specs(item.get("params")),
|
|
175
|
+
run=item["run"],
|
|
176
|
+
)
|
|
177
|
+
)
|
|
178
|
+
return AppSpec(app_name=raw["app_name"], initializers=initializers, workflows=workflows)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def coerce_to_app_spec(obj: Any, provider_name: str | None = None) -> AppSpec:
|
|
182
|
+
if callable(obj) and not isinstance(obj, type):
|
|
183
|
+
obj = obj()
|
|
184
|
+
if isinstance(obj, AppSpec):
|
|
185
|
+
return obj
|
|
186
|
+
if is_dataclass(obj):
|
|
187
|
+
return _dict_to_app_spec(asdict(obj))
|
|
188
|
+
if isinstance(obj, dict):
|
|
189
|
+
return _dict_to_app_spec(obj)
|
|
190
|
+
raise TypeError(f"Unsupported provider definition object for {provider_name or 'provider'}: {type(obj)}")
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def auto_build_app_spec(provider_name: str) -> AppSpec:
|
|
194
|
+
provider_pkg = importlib.import_module(provider_name)
|
|
195
|
+
|
|
196
|
+
cfg_module = None
|
|
197
|
+
try:
|
|
198
|
+
cfg_module = importlib.import_module(f"{provider_name}.renderflow")
|
|
199
|
+
except ModuleNotFoundError:
|
|
200
|
+
cfg_module = None
|
|
201
|
+
|
|
202
|
+
app_name = getattr(cfg_module, "APP_NAME", None) or getattr(provider_pkg, "APP_NAME", None) or provider_name
|
|
203
|
+
workflow_package = (
|
|
204
|
+
getattr(cfg_module, "WORKFLOWS_PACKAGE", None) or getattr(provider_pkg, "WORKFLOWS_PACKAGE", None)
|
|
205
|
+
)
|
|
206
|
+
if not workflow_package:
|
|
207
|
+
workflow_package = f"{provider_name}.workflows"
|
|
208
|
+
|
|
209
|
+
initializers: list[InitializerSpec] = []
|
|
210
|
+
init_func = getattr(cfg_module, "initialize", None) or getattr(provider_pkg, "initialize", None)
|
|
211
|
+
init_params = getattr(cfg_module, "INIT_PARAMS", None) or getattr(provider_pkg, "INIT_PARAMS", None)
|
|
212
|
+
if callable(init_func):
|
|
213
|
+
initializers.append(
|
|
214
|
+
InitializerSpec(
|
|
215
|
+
id="default_initializer",
|
|
216
|
+
name="Initialization",
|
|
217
|
+
description="Provider initialization context",
|
|
218
|
+
params=_coerce_param_specs(init_params),
|
|
219
|
+
initialize=init_func,
|
|
220
|
+
)
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
return AppSpec(
|
|
224
|
+
app_name=app_name,
|
|
225
|
+
initializers=initializers,
|
|
226
|
+
workflows=_discover_workflows(provider_name, workflow_package),
|
|
227
|
+
)
|