ipydagflow 0.0.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.
- ipydagflow-0.0.1/.gitignore +12 -0
- ipydagflow-0.0.1/PKG-INFO +268 -0
- ipydagflow-0.0.1/README.md +255 -0
- ipydagflow-0.0.1/pyproject.toml +84 -0
- ipydagflow-0.0.1/src/ipydagflow/__init__.py +47 -0
- ipydagflow-0.0.1/src/ipydagflow/models/__init__.py +5 -0
- ipydagflow-0.0.1/src/ipydagflow/models/step.py +115 -0
- ipydagflow-0.0.1/src/ipydagflow/static/dynamic_dag.css +1 -0
- ipydagflow-0.0.1/src/ipydagflow/static/dynamic_dag.js +82 -0
- ipydagflow-0.0.1/src/ipydagflow/utils/__init__.py +5 -0
- ipydagflow-0.0.1/src/ipydagflow/utils/layout.py +90 -0
- ipydagflow-0.0.1/src/ipydagflow/widgets/__init__.py +6 -0
- ipydagflow-0.0.1/src/ipydagflow/widgets/dynamic_dag.py +56 -0
- ipydagflow-0.0.1/src/ipydagflow/widgets/step_dag.py +205 -0
- ipydagflow-0.0.1/tests/__init__.py +1 -0
- ipydagflow-0.0.1/tests/test_dynamic_dag.py +209 -0
- ipydagflow-0.0.1/tests/test_integration.py +292 -0
- ipydagflow-0.0.1/tests/test_layout.py +251 -0
- ipydagflow-0.0.1/tests/test_step.py +253 -0
- ipydagflow-0.0.1/tests/test_step_dag.py +423 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ipydagflow
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Requires-Python: >=3.9
|
|
5
|
+
Requires-Dist: anywidget
|
|
6
|
+
Provides-Extra: dev
|
|
7
|
+
Requires-Dist: jupyterlab; extra == 'dev'
|
|
8
|
+
Requires-Dist: pytest-cov; extra == 'dev'
|
|
9
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
10
|
+
Requires-Dist: ruff>=0.6.0; extra == 'dev'
|
|
11
|
+
Requires-Dist: watchfiles; extra == 'dev'
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# ipydagflow
|
|
15
|
+
|
|
16
|
+
Interactive DAG (Directed Acyclic Graph) visualization widgets for Jupyter notebooks using React Flow.
|
|
17
|
+
|
|
18
|
+
## Features
|
|
19
|
+
|
|
20
|
+
- 🎨 **Beautiful Interactive Visualizations** - Smooth, modern DAG rendering with React Flow
|
|
21
|
+
- 🔧 **Two APIs** - Low-level (DynamicDAG) and high-level (StepDAG) interfaces
|
|
22
|
+
- 🎯 **Automatic Layout** - No need to specify positions - nodes are laid out automatically
|
|
23
|
+
- 🎨 **Customizable Styles** - Full control over colors, borders, and appearance
|
|
24
|
+
- 📊 **Built for Data Pipelines** - Perfect for ETL workflows, ML pipelines, and task dependencies
|
|
25
|
+
- ✅ **Validation** - Automatic cycle detection and DAG validation
|
|
26
|
+
- 🔍 **Interactive** - Click nodes to see details, drag to rearrange
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install ipydagflow
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
For development:
|
|
35
|
+
```bash
|
|
36
|
+
git clone https://github.com/yourusername/ipydagflow
|
|
37
|
+
cd ipydagflow
|
|
38
|
+
pip install -e .
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Quick Start
|
|
42
|
+
|
|
43
|
+
### High-Level API (StepDAG)
|
|
44
|
+
|
|
45
|
+
Build DAGs programmatically using Step objects:
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from ipydagflow import StepDAG, Step
|
|
49
|
+
|
|
50
|
+
# Create steps
|
|
51
|
+
extract = Step(id="extract", label="Extract Data", step_type="datasource")
|
|
52
|
+
transform = Step(id="transform", label="Transform", step_type="box")
|
|
53
|
+
load = Step(id="load", label="Load to DB", step_type="datasource")
|
|
54
|
+
|
|
55
|
+
# Connect steps
|
|
56
|
+
extract.add_child(transform)
|
|
57
|
+
transform.add_child(load)
|
|
58
|
+
|
|
59
|
+
# Create and display DAG
|
|
60
|
+
dag = StepDAG()
|
|
61
|
+
dag.add_steps(extract, transform, load)
|
|
62
|
+
dag.render() # Returns a widget to display in Jupyter
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Low-Level API (DynamicDAG)
|
|
66
|
+
|
|
67
|
+
For direct control over nodes and edges:
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from ipydagflow import DynamicDAG
|
|
71
|
+
|
|
72
|
+
nodes = [
|
|
73
|
+
{"id": "1", "type": "datasource", "data": {"label": "Input"}},
|
|
74
|
+
{"id": "2", "type": "box", "data": {"label": "Process"}},
|
|
75
|
+
{"id": "3", "type": "datasource", "data": {"label": "Output"}},
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
edges = [
|
|
79
|
+
{"id": "e1", "source": "1", "target": "2"},
|
|
80
|
+
{"id": "e2", "source": "2", "target": "3"},
|
|
81
|
+
]
|
|
82
|
+
|
|
83
|
+
dag = DynamicDAG(nodes=nodes, edges=edges)
|
|
84
|
+
dag # Display in Jupyter
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Examples
|
|
88
|
+
|
|
89
|
+
### Complex Pipeline
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
from ipydagflow import StepDAG, Step
|
|
93
|
+
|
|
94
|
+
# Build a data processing pipeline
|
|
95
|
+
raw = Step(id="raw", label="Raw Data", step_type="datasource")
|
|
96
|
+
clean = Step(id="clean", label="Clean", step_type="box")
|
|
97
|
+
feature_a = Step(id="feat_a", label="Feature A", step_type="box")
|
|
98
|
+
feature_b = Step(id="feat_b", label="Feature B", step_type="box")
|
|
99
|
+
combine = Step(id="combine", label="Combine", step_type="box")
|
|
100
|
+
output = Step(id="output", label="Output", step_type="datasource")
|
|
101
|
+
|
|
102
|
+
# Connect: raw -> clean -> [feature_a, feature_b] -> combine -> output
|
|
103
|
+
raw.add_child(clean)
|
|
104
|
+
clean.add_children(feature_a, feature_b)
|
|
105
|
+
feature_a.add_child(combine)
|
|
106
|
+
feature_b.add_child(combine)
|
|
107
|
+
combine.add_child(output)
|
|
108
|
+
|
|
109
|
+
# Render
|
|
110
|
+
dag = StepDAG()
|
|
111
|
+
dag.add_steps(raw, clean, feature_a, feature_b, combine, output)
|
|
112
|
+
dag.render()
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Custom Styling
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
custom_styles = {
|
|
119
|
+
"datasource": {
|
|
120
|
+
"background": "rgba(255, 99, 132, 0.8)",
|
|
121
|
+
"color": "white",
|
|
122
|
+
"borderColor": "#ff6384",
|
|
123
|
+
"borderWidth": 3,
|
|
124
|
+
},
|
|
125
|
+
"box": {
|
|
126
|
+
"background": "rgba(54, 162, 235, 0.8)",
|
|
127
|
+
"color": "white",
|
|
128
|
+
"borderColor": "#36a2eb",
|
|
129
|
+
"borderWidth": 2,
|
|
130
|
+
},
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
dag = StepDAG(styles=custom_styles)
|
|
134
|
+
dag.add_steps(step1, step2, step3)
|
|
135
|
+
dag.render()
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Adding Metadata
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
step = Step(
|
|
142
|
+
id="process",
|
|
143
|
+
label="Calculate Metrics",
|
|
144
|
+
step_type="box",
|
|
145
|
+
data={
|
|
146
|
+
"owner": "data-team",
|
|
147
|
+
"schedule": "0 0 * * *",
|
|
148
|
+
"retry": 3,
|
|
149
|
+
"timeout": "1h"
|
|
150
|
+
}
|
|
151
|
+
)
|
|
152
|
+
# Click the node in the widget to see the metadata!
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## API Reference
|
|
156
|
+
|
|
157
|
+
### Step
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
Step(
|
|
161
|
+
id: str, # Unique identifier
|
|
162
|
+
label: str, # Display label
|
|
163
|
+
step_type: str, # Node type: "datasource", "box", or custom
|
|
164
|
+
data: dict # Additional metadata
|
|
165
|
+
)
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
**Methods:**
|
|
169
|
+
- `add_child(step)` - Add a child step
|
|
170
|
+
- `add_children(*steps)` - Add multiple children
|
|
171
|
+
- `add_parent(step)` - Add a parent step
|
|
172
|
+
- `get_all_descendants()` - Get all descendant steps
|
|
173
|
+
- `get_all_ancestors()` - Get all ancestor steps
|
|
174
|
+
|
|
175
|
+
### StepDAG
|
|
176
|
+
|
|
177
|
+
```python
|
|
178
|
+
StepDAG(styles: dict = None)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**Methods:**
|
|
182
|
+
- `add_step(step)` - Add a step
|
|
183
|
+
- `add_steps(*steps)` - Add multiple steps
|
|
184
|
+
- `get_step(step_id)` - Get step by ID
|
|
185
|
+
- `get_all_steps()` - Get all steps
|
|
186
|
+
- `get_root_steps()` - Get steps with no parents
|
|
187
|
+
- `get_leaf_steps()` - Get steps with no children
|
|
188
|
+
- `validate()` - Validate DAG (returns list of errors)
|
|
189
|
+
- `render()` - Create and return DynamicDAG widget
|
|
190
|
+
|
|
191
|
+
### DynamicDAG
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
DynamicDAG(
|
|
195
|
+
nodes: list, # List of node dicts
|
|
196
|
+
edges: list, # List of edge dicts
|
|
197
|
+
styles: dict = None # Custom styling
|
|
198
|
+
)
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
**Node format:**
|
|
202
|
+
```python
|
|
203
|
+
{
|
|
204
|
+
"id": "unique_id",
|
|
205
|
+
"type": "datasource", # or "box"
|
|
206
|
+
"data": {"label": "Display Name", ...},
|
|
207
|
+
"position": {"x": 100, "y": 100} # Optional - auto-layout if omitted
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
**Edge format:**
|
|
212
|
+
```python
|
|
213
|
+
{
|
|
214
|
+
"id": "edge_id",
|
|
215
|
+
"source": "source_node_id",
|
|
216
|
+
"target": "target_node_id",
|
|
217
|
+
"animated": True # Optional
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Development
|
|
222
|
+
|
|
223
|
+
### Setup
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
# Clone the repo
|
|
227
|
+
git clone https://github.com/yourusername/ipydagflow
|
|
228
|
+
cd ipydagflow
|
|
229
|
+
|
|
230
|
+
# Install dependencies
|
|
231
|
+
npm install
|
|
232
|
+
pip install -e .
|
|
233
|
+
|
|
234
|
+
# Build JavaScript
|
|
235
|
+
npm run build
|
|
236
|
+
|
|
237
|
+
# Run in dev mode (auto-rebuild on changes)
|
|
238
|
+
npm run dev
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Project Structure
|
|
242
|
+
|
|
243
|
+
```
|
|
244
|
+
ipydagflow/
|
|
245
|
+
├── src/
|
|
246
|
+
│ └── ipydagflow/
|
|
247
|
+
│ ├── __init__.py # Main exports
|
|
248
|
+
│ ├── widgets/ # Widget classes
|
|
249
|
+
│ │ ├── dynamic_dag.py # Low-level widget
|
|
250
|
+
│ │ └── step_dag.py # High-level builder
|
|
251
|
+
│ ├── models/ # Data models
|
|
252
|
+
│ │ └── step.py # Step class
|
|
253
|
+
│ ├── utils/ # Utilities
|
|
254
|
+
│ │ └── layout.py # Layout algorithms
|
|
255
|
+
│ └── static/ # Built JS/CSS
|
|
256
|
+
├── ui/ # TypeScript/React source
|
|
257
|
+
│ ├── dynamic_dag.tsx # React Flow component
|
|
258
|
+
│ └── dynamic_dag.css # Styles
|
|
259
|
+
└── examples/ # Example notebooks
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## License
|
|
263
|
+
|
|
264
|
+
MIT
|
|
265
|
+
|
|
266
|
+
## Contributing
|
|
267
|
+
|
|
268
|
+
Contributions welcome! Please open an issue or PR on GitHub.
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
# ipydagflow
|
|
2
|
+
|
|
3
|
+
Interactive DAG (Directed Acyclic Graph) visualization widgets for Jupyter notebooks using React Flow.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🎨 **Beautiful Interactive Visualizations** - Smooth, modern DAG rendering with React Flow
|
|
8
|
+
- 🔧 **Two APIs** - Low-level (DynamicDAG) and high-level (StepDAG) interfaces
|
|
9
|
+
- 🎯 **Automatic Layout** - No need to specify positions - nodes are laid out automatically
|
|
10
|
+
- 🎨 **Customizable Styles** - Full control over colors, borders, and appearance
|
|
11
|
+
- 📊 **Built for Data Pipelines** - Perfect for ETL workflows, ML pipelines, and task dependencies
|
|
12
|
+
- ✅ **Validation** - Automatic cycle detection and DAG validation
|
|
13
|
+
- 🔍 **Interactive** - Click nodes to see details, drag to rearrange
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install ipydagflow
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
For development:
|
|
22
|
+
```bash
|
|
23
|
+
git clone https://github.com/yourusername/ipydagflow
|
|
24
|
+
cd ipydagflow
|
|
25
|
+
pip install -e .
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Quick Start
|
|
29
|
+
|
|
30
|
+
### High-Level API (StepDAG)
|
|
31
|
+
|
|
32
|
+
Build DAGs programmatically using Step objects:
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from ipydagflow import StepDAG, Step
|
|
36
|
+
|
|
37
|
+
# Create steps
|
|
38
|
+
extract = Step(id="extract", label="Extract Data", step_type="datasource")
|
|
39
|
+
transform = Step(id="transform", label="Transform", step_type="box")
|
|
40
|
+
load = Step(id="load", label="Load to DB", step_type="datasource")
|
|
41
|
+
|
|
42
|
+
# Connect steps
|
|
43
|
+
extract.add_child(transform)
|
|
44
|
+
transform.add_child(load)
|
|
45
|
+
|
|
46
|
+
# Create and display DAG
|
|
47
|
+
dag = StepDAG()
|
|
48
|
+
dag.add_steps(extract, transform, load)
|
|
49
|
+
dag.render() # Returns a widget to display in Jupyter
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Low-Level API (DynamicDAG)
|
|
53
|
+
|
|
54
|
+
For direct control over nodes and edges:
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
from ipydagflow import DynamicDAG
|
|
58
|
+
|
|
59
|
+
nodes = [
|
|
60
|
+
{"id": "1", "type": "datasource", "data": {"label": "Input"}},
|
|
61
|
+
{"id": "2", "type": "box", "data": {"label": "Process"}},
|
|
62
|
+
{"id": "3", "type": "datasource", "data": {"label": "Output"}},
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
edges = [
|
|
66
|
+
{"id": "e1", "source": "1", "target": "2"},
|
|
67
|
+
{"id": "e2", "source": "2", "target": "3"},
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
dag = DynamicDAG(nodes=nodes, edges=edges)
|
|
71
|
+
dag # Display in Jupyter
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Examples
|
|
75
|
+
|
|
76
|
+
### Complex Pipeline
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
from ipydagflow import StepDAG, Step
|
|
80
|
+
|
|
81
|
+
# Build a data processing pipeline
|
|
82
|
+
raw = Step(id="raw", label="Raw Data", step_type="datasource")
|
|
83
|
+
clean = Step(id="clean", label="Clean", step_type="box")
|
|
84
|
+
feature_a = Step(id="feat_a", label="Feature A", step_type="box")
|
|
85
|
+
feature_b = Step(id="feat_b", label="Feature B", step_type="box")
|
|
86
|
+
combine = Step(id="combine", label="Combine", step_type="box")
|
|
87
|
+
output = Step(id="output", label="Output", step_type="datasource")
|
|
88
|
+
|
|
89
|
+
# Connect: raw -> clean -> [feature_a, feature_b] -> combine -> output
|
|
90
|
+
raw.add_child(clean)
|
|
91
|
+
clean.add_children(feature_a, feature_b)
|
|
92
|
+
feature_a.add_child(combine)
|
|
93
|
+
feature_b.add_child(combine)
|
|
94
|
+
combine.add_child(output)
|
|
95
|
+
|
|
96
|
+
# Render
|
|
97
|
+
dag = StepDAG()
|
|
98
|
+
dag.add_steps(raw, clean, feature_a, feature_b, combine, output)
|
|
99
|
+
dag.render()
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Custom Styling
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
custom_styles = {
|
|
106
|
+
"datasource": {
|
|
107
|
+
"background": "rgba(255, 99, 132, 0.8)",
|
|
108
|
+
"color": "white",
|
|
109
|
+
"borderColor": "#ff6384",
|
|
110
|
+
"borderWidth": 3,
|
|
111
|
+
},
|
|
112
|
+
"box": {
|
|
113
|
+
"background": "rgba(54, 162, 235, 0.8)",
|
|
114
|
+
"color": "white",
|
|
115
|
+
"borderColor": "#36a2eb",
|
|
116
|
+
"borderWidth": 2,
|
|
117
|
+
},
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
dag = StepDAG(styles=custom_styles)
|
|
121
|
+
dag.add_steps(step1, step2, step3)
|
|
122
|
+
dag.render()
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Adding Metadata
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
step = Step(
|
|
129
|
+
id="process",
|
|
130
|
+
label="Calculate Metrics",
|
|
131
|
+
step_type="box",
|
|
132
|
+
data={
|
|
133
|
+
"owner": "data-team",
|
|
134
|
+
"schedule": "0 0 * * *",
|
|
135
|
+
"retry": 3,
|
|
136
|
+
"timeout": "1h"
|
|
137
|
+
}
|
|
138
|
+
)
|
|
139
|
+
# Click the node in the widget to see the metadata!
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## API Reference
|
|
143
|
+
|
|
144
|
+
### Step
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
Step(
|
|
148
|
+
id: str, # Unique identifier
|
|
149
|
+
label: str, # Display label
|
|
150
|
+
step_type: str, # Node type: "datasource", "box", or custom
|
|
151
|
+
data: dict # Additional metadata
|
|
152
|
+
)
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
**Methods:**
|
|
156
|
+
- `add_child(step)` - Add a child step
|
|
157
|
+
- `add_children(*steps)` - Add multiple children
|
|
158
|
+
- `add_parent(step)` - Add a parent step
|
|
159
|
+
- `get_all_descendants()` - Get all descendant steps
|
|
160
|
+
- `get_all_ancestors()` - Get all ancestor steps
|
|
161
|
+
|
|
162
|
+
### StepDAG
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
StepDAG(styles: dict = None)
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
**Methods:**
|
|
169
|
+
- `add_step(step)` - Add a step
|
|
170
|
+
- `add_steps(*steps)` - Add multiple steps
|
|
171
|
+
- `get_step(step_id)` - Get step by ID
|
|
172
|
+
- `get_all_steps()` - Get all steps
|
|
173
|
+
- `get_root_steps()` - Get steps with no parents
|
|
174
|
+
- `get_leaf_steps()` - Get steps with no children
|
|
175
|
+
- `validate()` - Validate DAG (returns list of errors)
|
|
176
|
+
- `render()` - Create and return DynamicDAG widget
|
|
177
|
+
|
|
178
|
+
### DynamicDAG
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
DynamicDAG(
|
|
182
|
+
nodes: list, # List of node dicts
|
|
183
|
+
edges: list, # List of edge dicts
|
|
184
|
+
styles: dict = None # Custom styling
|
|
185
|
+
)
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
**Node format:**
|
|
189
|
+
```python
|
|
190
|
+
{
|
|
191
|
+
"id": "unique_id",
|
|
192
|
+
"type": "datasource", # or "box"
|
|
193
|
+
"data": {"label": "Display Name", ...},
|
|
194
|
+
"position": {"x": 100, "y": 100} # Optional - auto-layout if omitted
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
**Edge format:**
|
|
199
|
+
```python
|
|
200
|
+
{
|
|
201
|
+
"id": "edge_id",
|
|
202
|
+
"source": "source_node_id",
|
|
203
|
+
"target": "target_node_id",
|
|
204
|
+
"animated": True # Optional
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## Development
|
|
209
|
+
|
|
210
|
+
### Setup
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
# Clone the repo
|
|
214
|
+
git clone https://github.com/yourusername/ipydagflow
|
|
215
|
+
cd ipydagflow
|
|
216
|
+
|
|
217
|
+
# Install dependencies
|
|
218
|
+
npm install
|
|
219
|
+
pip install -e .
|
|
220
|
+
|
|
221
|
+
# Build JavaScript
|
|
222
|
+
npm run build
|
|
223
|
+
|
|
224
|
+
# Run in dev mode (auto-rebuild on changes)
|
|
225
|
+
npm run dev
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Project Structure
|
|
229
|
+
|
|
230
|
+
```
|
|
231
|
+
ipydagflow/
|
|
232
|
+
├── src/
|
|
233
|
+
│ └── ipydagflow/
|
|
234
|
+
│ ├── __init__.py # Main exports
|
|
235
|
+
│ ├── widgets/ # Widget classes
|
|
236
|
+
│ │ ├── dynamic_dag.py # Low-level widget
|
|
237
|
+
│ │ └── step_dag.py # High-level builder
|
|
238
|
+
│ ├── models/ # Data models
|
|
239
|
+
│ │ └── step.py # Step class
|
|
240
|
+
│ ├── utils/ # Utilities
|
|
241
|
+
│ │ └── layout.py # Layout algorithms
|
|
242
|
+
│ └── static/ # Built JS/CSS
|
|
243
|
+
├── ui/ # TypeScript/React source
|
|
244
|
+
│ ├── dynamic_dag.tsx # React Flow component
|
|
245
|
+
│ └── dynamic_dag.css # Styles
|
|
246
|
+
└── examples/ # Example notebooks
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## License
|
|
250
|
+
|
|
251
|
+
MIT
|
|
252
|
+
|
|
253
|
+
## Contributing
|
|
254
|
+
|
|
255
|
+
Contributions welcome! Please open an issue or PR on GitHub.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ipydagflow"
|
|
7
|
+
version = "0.0.1"
|
|
8
|
+
dependencies = ["anywidget"]
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
|
|
12
|
+
[project.optional-dependencies]
|
|
13
|
+
dev = [
|
|
14
|
+
"ruff>=0.6.0",
|
|
15
|
+
"watchfiles",
|
|
16
|
+
"jupyterlab",
|
|
17
|
+
"pytest>=7.0",
|
|
18
|
+
"pytest-cov",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[tool.uv]
|
|
22
|
+
dev-dependencies = [
|
|
23
|
+
"pytest>=7.0",
|
|
24
|
+
"pytest-cov",
|
|
25
|
+
"ruff>=0.6.0",
|
|
26
|
+
"watchfiles",
|
|
27
|
+
"jupyterlab",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[tool.hatch.envs.default]
|
|
31
|
+
features = ["dev"]
|
|
32
|
+
|
|
33
|
+
[tool.hatch.build]
|
|
34
|
+
only-packages = true
|
|
35
|
+
artifacts = ["src/ipydagflow/static/*"]
|
|
36
|
+
|
|
37
|
+
[tool.hatch.build.hooks.jupyter-builder]
|
|
38
|
+
build-function = "hatch_jupyter_builder.npm_builder"
|
|
39
|
+
ensured-targets = ["src/ipydagflow/static/dynamic_dag.js"]
|
|
40
|
+
skip-if-exists = ["src/ipydagflow/static/dynamic_dag.js"]
|
|
41
|
+
dependencies = ["hatch-jupyter-builder>=0.5.0"]
|
|
42
|
+
|
|
43
|
+
[tool.hatch.build.hooks.jupyter-builder.build-kwargs]
|
|
44
|
+
npm = "npm"
|
|
45
|
+
build_cmd = "build"
|
|
46
|
+
|
|
47
|
+
# ───────────────────────────────────────
|
|
48
|
+
# Ruff configuration
|
|
49
|
+
# ───────────────────────────────────────
|
|
50
|
+
|
|
51
|
+
[tool.ruff]
|
|
52
|
+
target-version = "py39"
|
|
53
|
+
line-length = 88
|
|
54
|
+
src = ["src", "tests"]
|
|
55
|
+
extend-exclude = ["src/ipydagflow/static"]
|
|
56
|
+
|
|
57
|
+
[tool.ruff.lint]
|
|
58
|
+
select = [
|
|
59
|
+
"E", # pycodestyle errors
|
|
60
|
+
"W", # pycodestyle warnings
|
|
61
|
+
"F", # pyflakes
|
|
62
|
+
"I", # isort
|
|
63
|
+
"B", # bugbear
|
|
64
|
+
"C4", # comprehensions
|
|
65
|
+
"UP", # pyupgrade
|
|
66
|
+
"RUF", # ruff-specific rules
|
|
67
|
+
"PL", # pylint (selected rules)
|
|
68
|
+
"SIM", # flake8-simplify
|
|
69
|
+
]
|
|
70
|
+
ignore = [
|
|
71
|
+
"E501", # line too long → we use Ruff formatter
|
|
72
|
+
"PLR2004",# magic value comparison
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
[tool.ruff.format]
|
|
76
|
+
quote-style = "double"
|
|
77
|
+
indent-style = "space"
|
|
78
|
+
skip-magic-trailing-comma = false
|
|
79
|
+
|
|
80
|
+
[tool.ruff.lint.per-file-ignores]
|
|
81
|
+
"tests/*" = ["S101"] # allow assert in tests
|
|
82
|
+
|
|
83
|
+
[tool.ruff.lint.mccabe]
|
|
84
|
+
max-complexity = 12
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ipydagflow - Interactive DAG visualization widgets for Jupyter.
|
|
3
|
+
|
|
4
|
+
This library provides tools for creating and visualizing directed acyclic graphs (DAGs)
|
|
5
|
+
in Jupyter notebooks using React Flow.
|
|
6
|
+
|
|
7
|
+
Main classes:
|
|
8
|
+
- DynamicDAG: Low-level widget for rendering DAGs from nodes/edges
|
|
9
|
+
- StepDAG: High-level DAG builder using Step objects
|
|
10
|
+
- Step: Represents a single step/node in a workflow
|
|
11
|
+
|
|
12
|
+
Example (Low-level):
|
|
13
|
+
>>> from ipydagflow import DynamicDAG
|
|
14
|
+
>>> nodes = [
|
|
15
|
+
... {"id": "1", "type": "datasource", "data": {"label": "Input"}},
|
|
16
|
+
... {"id": "2", "type": "box", "data": {"label": "Process"}},
|
|
17
|
+
... ]
|
|
18
|
+
>>> edges = [{"id": "e1", "source": "1", "target": "2"}]
|
|
19
|
+
>>> DynamicDAG(nodes=nodes, edges=edges)
|
|
20
|
+
|
|
21
|
+
Example (High-level):
|
|
22
|
+
>>> from ipydagflow import StepDAG, Step
|
|
23
|
+
>>> extract = Step(id="extract", label="Extract", step_type="datasource")
|
|
24
|
+
>>> transform = Step(id="transform", label="Transform", step_type="box")
|
|
25
|
+
>>> extract.add_child(transform)
|
|
26
|
+
>>> dag = StepDAG()
|
|
27
|
+
>>> dag.add_steps(extract, transform)
|
|
28
|
+
>>> dag.render()
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
import importlib.metadata
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
__version__ = importlib.metadata.version("ipydagflow")
|
|
35
|
+
except importlib.metadata.PackageNotFoundError:
|
|
36
|
+
__version__ = "unknown"
|
|
37
|
+
|
|
38
|
+
# Main exports
|
|
39
|
+
from .models import Step
|
|
40
|
+
from .widgets import DynamicDAG, StepDAG
|
|
41
|
+
|
|
42
|
+
__all__ = [
|
|
43
|
+
"DynamicDAG",
|
|
44
|
+
"Step",
|
|
45
|
+
"StepDAG",
|
|
46
|
+
"__version__",
|
|
47
|
+
]
|