flograph 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 (152) hide show
  1. flograph-0.1.0/.gitignore +10 -0
  2. flograph-0.1.0/LICENSE +21 -0
  3. flograph-0.1.0/PKG-INFO +211 -0
  4. flograph-0.1.0/README.md +172 -0
  5. flograph-0.1.0/ideas.md +7 -0
  6. flograph-0.1.0/issues.md +0 -0
  7. flograph-0.1.0/main.py +5 -0
  8. flograph-0.1.0/pyproject.toml +57 -0
  9. flograph-0.1.0/scripts/flopy_to_flograph.py +121 -0
  10. flograph-0.1.0/src/flograph/__init__.py +0 -0
  11. flograph-0.1.0/src/flograph/__main__.py +5 -0
  12. flograph-0.1.0/src/flograph/app.py +43 -0
  13. flograph-0.1.0/src/flograph/core/__init__.py +26 -0
  14. flograph-0.1.0/src/flograph/core/datatypes.py +88 -0
  15. flograph-0.1.0/src/flograph/core/events.py +54 -0
  16. flograph-0.1.0/src/flograph/core/graph.py +414 -0
  17. flograph-0.1.0/src/flograph/core/node.py +91 -0
  18. flograph-0.1.0/src/flograph/core/params.py +69 -0
  19. flograph-0.1.0/src/flograph/core/ports.py +19 -0
  20. flograph-0.1.0/src/flograph/core/registry.py +160 -0
  21. flograph-0.1.0/src/flograph/core/script.py +172 -0
  22. flograph-0.1.0/src/flograph/core/serialization.py +212 -0
  23. flograph-0.1.0/src/flograph/core/spec.py +35 -0
  24. flograph-0.1.0/src/flograph/core/user_nodes.py +201 -0
  25. flograph-0.1.0/src/flograph/engine/__init__.py +19 -0
  26. flograph-0.1.0/src/flograph/engine/cache.py +70 -0
  27. flograph-0.1.0/src/flograph/engine/cache_persistence.py +142 -0
  28. flograph-0.1.0/src/flograph/engine/context.py +51 -0
  29. flograph-0.1.0/src/flograph/engine/errors.py +54 -0
  30. flograph-0.1.0/src/flograph/engine/headless.py +63 -0
  31. flograph-0.1.0/src/flograph/engine/introspect.py +55 -0
  32. flograph-0.1.0/src/flograph/engine/scheduler.py +206 -0
  33. flograph-0.1.0/src/flograph/engine/worker.py +135 -0
  34. flograph-0.1.0/src/flograph/nodes/__init__.py +0 -0
  35. flograph-0.1.0/src/flograph/nodes/io/__init__.py +0 -0
  36. flograph-0.1.0/src/flograph/nodes/io/read_csv.py +31 -0
  37. flograph-0.1.0/src/flograph/nodes/io/read_excel.py +35 -0
  38. flograph-0.1.0/src/flograph/nodes/io/read_json.py +33 -0
  39. flograph-0.1.0/src/flograph/nodes/io/read_parquet.py +30 -0
  40. flograph-0.1.0/src/flograph/nodes/io/read_sqlite.py +33 -0
  41. flograph-0.1.0/src/flograph/nodes/io/table.py +68 -0
  42. flograph-0.1.0/src/flograph/nodes/io/write_csv.py +24 -0
  43. flograph-0.1.0/src/flograph/nodes/io/write_excel.py +27 -0
  44. flograph-0.1.0/src/flograph/nodes/io/write_json.py +31 -0
  45. flograph-0.1.0/src/flograph/nodes/io/write_parquet.py +24 -0
  46. flograph-0.1.0/src/flograph/nodes/io/write_sqlite.py +35 -0
  47. flograph-0.1.0/src/flograph/nodes/scripting/__init__.py +0 -0
  48. flograph-0.1.0/src/flograph/nodes/scripting/node_template.py +68 -0
  49. flograph-0.1.0/src/flograph/nodes/scripting/python_script.py +22 -0
  50. flograph-0.1.0/src/flograph/nodes/transform/__init__.py +0 -0
  51. flograph-0.1.0/src/flograph/nodes/transform/concatenate.py +28 -0
  52. flograph-0.1.0/src/flograph/nodes/transform/convert_types.py +54 -0
  53. flograph-0.1.0/src/flograph/nodes/transform/duplicate_filter.py +34 -0
  54. flograph-0.1.0/src/flograph/nodes/transform/expression.py +29 -0
  55. flograph-0.1.0/src/flograph/nodes/transform/filter_rows.py +24 -0
  56. flograph-0.1.0/src/flograph/nodes/transform/group_by.py +46 -0
  57. flograph-0.1.0/src/flograph/nodes/transform/join.py +31 -0
  58. flograph-0.1.0/src/flograph/nodes/transform/missing_values.py +63 -0
  59. flograph-0.1.0/src/flograph/nodes/transform/pivot.py +49 -0
  60. flograph-0.1.0/src/flograph/nodes/transform/rename_columns.py +35 -0
  61. flograph-0.1.0/src/flograph/nodes/transform/row_sampling.py +37 -0
  62. flograph-0.1.0/src/flograph/nodes/transform/select_columns.py +28 -0
  63. flograph-0.1.0/src/flograph/nodes/transform/sort.py +27 -0
  64. flograph-0.1.0/src/flograph/nodes/transform/statistics.py +26 -0
  65. flograph-0.1.0/src/flograph/nodes/transform/string_manipulation.py +52 -0
  66. flograph-0.1.0/src/flograph/nodes/transform/unpivot.py +40 -0
  67. flograph-0.1.0/src/flograph/nodes/util/__init__.py +0 -0
  68. flograph-0.1.0/src/flograph/nodes/util/action_button.py +36 -0
  69. flograph-0.1.0/src/flograph/nodes/util/constant.py +27 -0
  70. flograph-0.1.0/src/flograph/nodes/util/note.py +25 -0
  71. flograph-0.1.0/src/flograph/nodes/util/reroute.py +14 -0
  72. flograph-0.1.0/src/flograph/nodes/viz/__init__.py +0 -0
  73. flograph-0.1.0/src/flograph/nodes/viz/card.py +64 -0
  74. flograph-0.1.0/src/flograph/nodes/viz/show_plot.py +72 -0
  75. flograph-0.1.0/src/flograph/nodes/viz/show_plotly.py +68 -0
  76. flograph-0.1.0/src/flograph/nodes/viz/show_table.py +25 -0
  77. flograph-0.1.0/src/flograph/nodes/viz/slicer.py +61 -0
  78. flograph-0.1.0/src/flograph/nodes/viz/table_spec.py +26 -0
  79. flograph-0.1.0/src/flograph/packages.py +89 -0
  80. flograph-0.1.0/src/flograph/paths.py +31 -0
  81. flograph-0.1.0/src/flograph/ui/__init__.py +0 -0
  82. flograph-0.1.0/src/flograph/ui/canvas/__init__.py +13 -0
  83. flograph-0.1.0/src/flograph/ui/canvas/base_view.py +228 -0
  84. flograph-0.1.0/src/flograph/ui/canvas/connection_item.py +98 -0
  85. flograph-0.1.0/src/flograph/ui/canvas/frame_item.py +241 -0
  86. flograph-0.1.0/src/flograph/ui/canvas/grid.py +49 -0
  87. flograph-0.1.0/src/flograph/ui/canvas/minimap.py +115 -0
  88. flograph-0.1.0/src/flograph/ui/canvas/node_item.py +1475 -0
  89. flograph-0.1.0/src/flograph/ui/canvas/palette.py +228 -0
  90. flograph-0.1.0/src/flograph/ui/canvas/scene.py +415 -0
  91. flograph-0.1.0/src/flograph/ui/canvas/view.py +140 -0
  92. flograph-0.1.0/src/flograph/ui/commands.py +405 -0
  93. flograph-0.1.0/src/flograph/ui/console/log_dock.py +80 -0
  94. flograph-0.1.0/src/flograph/ui/dashboard/__init__.py +10 -0
  95. flograph-0.1.0/src/flograph/ui/dashboard/dashboard_page.py +53 -0
  96. flograph-0.1.0/src/flograph/ui/dashboard/dashboard_scene.py +147 -0
  97. flograph-0.1.0/src/flograph/ui/dashboard/dashboard_view.py +56 -0
  98. flograph-0.1.0/src/flograph/ui/dashboard/page_bar.py +117 -0
  99. flograph-0.1.0/src/flograph/ui/dashboard/tile_item.py +674 -0
  100. flograph-0.1.0/src/flograph/ui/dashboard/visuals_list.py +69 -0
  101. flograph-0.1.0/src/flograph/ui/editor/code_editor.py +213 -0
  102. flograph-0.1.0/src/flograph/ui/editor/completion.py +183 -0
  103. flograph-0.1.0/src/flograph/ui/editor/editor_dock.py +188 -0
  104. flograph-0.1.0/src/flograph/ui/editor/highlighter.py +123 -0
  105. flograph-0.1.0/src/flograph/ui/editor/save_user_node_dialog.py +64 -0
  106. flograph-0.1.0/src/flograph/ui/inspector/figure_view.py +194 -0
  107. flograph-0.1.0/src/flograph/ui/inspector/inspector_dock.py +127 -0
  108. flograph-0.1.0/src/flograph/ui/inspector/object_view.py +28 -0
  109. flograph-0.1.0/src/flograph/ui/inspector/pandas_model.py +87 -0
  110. flograph-0.1.0/src/flograph/ui/inspector/plotly_view.py +91 -0
  111. flograph-0.1.0/src/flograph/ui/inspector/popup_view.py +76 -0
  112. flograph-0.1.0/src/flograph/ui/inspector/spec_view.py +26 -0
  113. flograph-0.1.0/src/flograph/ui/inspector/view_for.py +34 -0
  114. flograph-0.1.0/src/flograph/ui/mainwindow.py +1240 -0
  115. flograph-0.1.0/src/flograph/ui/packages_dialog.py +216 -0
  116. flograph-0.1.0/src/flograph/ui/properties/params_panel.py +286 -0
  117. flograph-0.1.0/src/flograph/ui/slicer_list.py +86 -0
  118. flograph-0.1.0/src/flograph/ui/theme.py +85 -0
  119. flograph-0.1.0/tests/__init__.py +0 -0
  120. flograph-0.1.0/tests/conftest.py +86 -0
  121. flograph-0.1.0/tests/test_action_button_node.py +235 -0
  122. flograph-0.1.0/tests/test_cache_persistence.py +135 -0
  123. flograph-0.1.0/tests/test_canvas_polish.py +270 -0
  124. flograph-0.1.0/tests/test_column_suggestions.py +155 -0
  125. flograph-0.1.0/tests/test_commands.py +166 -0
  126. flograph-0.1.0/tests/test_dashboard_ui.py +245 -0
  127. flograph-0.1.0/tests/test_datatypes.py +64 -0
  128. flograph-0.1.0/tests/test_engine.py +235 -0
  129. flograph-0.1.0/tests/test_graph.py +160 -0
  130. flograph-0.1.0/tests/test_graph_pages.py +100 -0
  131. flograph-0.1.0/tests/test_grid_snap.py +311 -0
  132. flograph-0.1.0/tests/test_inspector.py +220 -0
  133. flograph-0.1.0/tests/test_migration_converter.py +102 -0
  134. flograph-0.1.0/tests/test_no_qt_in_core.py +24 -0
  135. flograph-0.1.0/tests/test_note_node.py +142 -0
  136. flograph-0.1.0/tests/test_packages.py +86 -0
  137. flograph-0.1.0/tests/test_page_commands.py +109 -0
  138. flograph-0.1.0/tests/test_plotly_node.py +150 -0
  139. flograph-0.1.0/tests/test_project_lifecycle.py +186 -0
  140. flograph-0.1.0/tests/test_registry.py +87 -0
  141. flograph-0.1.0/tests/test_script.py +92 -0
  142. flograph-0.1.0/tests/test_serialization.py +149 -0
  143. flograph-0.1.0/tests/test_show_plot_node.py +195 -0
  144. flograph-0.1.0/tests/test_show_table_node.py +138 -0
  145. flograph-0.1.0/tests/test_slicer_card_nodes.py +223 -0
  146. flograph-0.1.0/tests/test_stdlib_nodes.py +512 -0
  147. flograph-0.1.0/tests/test_table_node.py +157 -0
  148. flograph-0.1.0/tests/test_ui_editor.py +167 -0
  149. flograph-0.1.0/tests/test_user_nodes.py +208 -0
  150. flograph-0.1.0/tests/test_view_keyboard.py +84 -0
  151. flograph-0.1.0/tests/test_wheel_zoom.py +121 -0
  152. flograph-0.1.0/uv.lock +952 -0
@@ -0,0 +1,10 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.pyc
4
+ .pytest_cache/
5
+ .idea/
6
+ .claude/
7
+ *.flograph.cache/
8
+ *.egg-info/
9
+ build/
10
+ dist/
flograph-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 redthista
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,211 @@
1
+ Metadata-Version: 2.4
2
+ Name: flograph
3
+ Version: 0.1.0
4
+ Summary: Visual node-based Python programming environment (KNIME-style dataflow, Blueprint-style canvas)
5
+ Project-URL: Homepage, https://github.com/redthista/flograph
6
+ Project-URL: Repository, https://github.com/redthista/flograph
7
+ Project-URL: Issues, https://github.com/redthista/flograph/issues
8
+ Author-email: redthista <dconrancpw@gmail.com>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: dataflow,etl,gui,knime,low-code,node-editor,pandas,pyside6,qt,visual-programming
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Environment :: X11 Applications :: Qt
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Intended Audience :: Science/Research
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Scientific/Engineering :: Visualization
22
+ Classifier: Topic :: Software Development :: Code Generators
23
+ Classifier: Topic :: Software Development :: User Interfaces
24
+ Requires-Python: >=3.11
25
+ Requires-Dist: jedi>=0.19
26
+ Requires-Dist: matplotlib>=3.8
27
+ Requires-Dist: pandas>=2.0
28
+ Requires-Dist: pyside6>=6.7
29
+ Provides-Extra: dev
30
+ Requires-Dist: pytest-qt>=4.4; extra == 'dev'
31
+ Requires-Dist: pytest>=8; extra == 'dev'
32
+ Provides-Extra: excel
33
+ Requires-Dist: openpyxl; extra == 'excel'
34
+ Provides-Extra: parquet
35
+ Requires-Dist: pyarrow; extra == 'parquet'
36
+ Provides-Extra: plotly
37
+ Requires-Dist: plotly; extra == 'plotly'
38
+ Description-Content-Type: text/markdown
39
+
40
+ # flograph
41
+
42
+ A visual node-based Python programming environment: KNIME-style dataflow on
43
+ an infinite Blueprint-style canvas, where every node is real, editable Python.
44
+
45
+ ![status](https://img.shields.io/badge/status-v0.1-blue)
46
+
47
+ ## Install
48
+
49
+ flograph is a standard pip-installable package (hatchling build backend):
50
+
51
+ ```bash
52
+ pip install . # or: uv pip install . / pip install -e .
53
+ ```
54
+
55
+ This puts a `flograph` command on your PATH and makes `python -m flograph`
56
+ work. To build a distributable wheel/sdist instead:
57
+
58
+ ```bash
59
+ python -m build # or: uv build -> dist/flograph-*.whl
60
+ ```
61
+
62
+ > The project was renamed from **flopy** to **flograph** because `flopy` is
63
+ > already taken on PyPI (USGS MODFLOW). To migrate projects saved by the old
64
+ > build, see [Migrating old `.flopy` projects](#migrating-old-flopy-projects).
65
+
66
+ ## Run it
67
+
68
+ ```bash
69
+ flograph # console entry point (after install)
70
+ python -m flograph # equivalent module entry point
71
+ python main.py project.flograph # open a project
72
+ python -m flograph.engine.headless project.flograph # run without GUI
73
+ ```
74
+
75
+ ## The idea
76
+
77
+ - **Nodes are Python scripts.** Every node — including the shipped library —
78
+ is a small module: a `NODE` dict declaring typed ports, an optional
79
+ `PARAMS` list that auto-generates its properties form, and a
80
+ `run(ctx, **inputs)` function. Double-click any node to read or fork its
81
+ code in the built-in editor (syntax highlighting, jedi completion,
82
+ error markers on the failing line).
83
+ - **KNIME semantics.** Data flows through typed ports; execution is a
84
+ topological walk of the dirty subgraph; every node's outputs are cached, so
85
+ re-runs only recompute what changed. Status LEDs: gray idle, yellow queued,
86
+ pulsing blue running, green done, red error.
87
+ - **Inspect everything.** Click any node or wire to see the data on it —
88
+ paged table view for DataFrames (millions of rows are fine), matplotlib
89
+ figures with a toolbar, pretty-printed objects. Per-node stdout/logs in the
90
+ console dock.
91
+
92
+ ## Canvas
93
+
94
+ | Action | Binding |
95
+ | --- | --- |
96
+ | Add node | `Tab` (search palette), right-click, or drag from the library |
97
+ | Connect | drag from a port; drop on empty canvas to pick a compatible node |
98
+ | Reroute dot | double-click a wire |
99
+ | Comment frame | `Ctrl+G` around the selection (frames move their contents) |
100
+ | Run all / selected / cancel | `F5` / `F6` / `Esc` |
101
+ | Pan / zoom | middle-drag or `Space`+drag / wheel |
102
+ | Frame view | `F` |
103
+ | Duplicate / delete | `Ctrl+D` / `Del` |
104
+ | Undo anything | `Ctrl+Z` — every graph mutation is on the undo stack |
105
+
106
+ Projects are plain JSON (`.flograph`); caches are never saved, so a reopened
107
+ project is fully reproducible with one `F5`.
108
+
109
+ ### Migrating old `.flopy` projects
110
+
111
+ Projects saved by the old **flopy** build use a `.flopy` extension and embed
112
+ `flopy.*` node type-ids, which this build no longer recognises. Convert them
113
+ in place with the bundled one-shot migrator (stdlib-only, no install needed):
114
+
115
+ ```bash
116
+ python scripts/flopy_to_flograph.py project.flopy # -> project.flograph
117
+ python scripts/flopy_to_flograph.py my/projects --recursive
118
+ ```
119
+
120
+ It rewrites builtin `flopy.*` type-ids to `flograph.*` (leaving `user.*`
121
+ nodes alone), renames the file and any `.flopy.cache/` side-car, and by
122
+ default keeps the original (`--delete-original` to remove it).
123
+
124
+ ## Node library
125
+
126
+ The shipped library covers the KNIME basics:
127
+
128
+ - **IO** — read/write CSV, Excel, Parquet, JSON (incl. JSONL), SQLite
129
+ (query in, table out), inline Table.
130
+ - **Transform** — Select Columns, Filter Rows, Sort, Join, Group By,
131
+ Expression, Concatenate, Missing Values, Duplicate Row Filter,
132
+ Rename Columns, Pivot, Unpivot, Row Sampling, Convert Types,
133
+ String Manipulation, Statistics.
134
+ - **Viz** — Show Table, Show Plot (live on-canvas cards), Show Plotly
135
+ (fully interactive plotly.js chart embedded on the canvas — hover, zoom
136
+ and pan in place; needs `pip install plotly`, e.g. via
137
+ Tools > Manage Packages).
138
+ - **Scripting / Util** — Python Script, Constant, Reroute, Note,
139
+ Action Button.
140
+
141
+ ## Packages
142
+
143
+ **Tools > Manage Packages** installs, upgrades and uninstalls pip packages
144
+ in flograph's own environment (the venv running the app). Nodes execute
145
+ in-process, so anything installed there is immediately importable from a
146
+ node's `run()` — no restart needed for new installs; upgrades of modules
147
+ the app has already imported take effect on the next launch. The dialog
148
+ uses `pip` when the interpreter has it and falls back to `uv pip` (uv-made
149
+ venvs ship without pip); flograph's own core dependencies are protected from
150
+ uninstall.
151
+
152
+ ## Writing a node
153
+
154
+ ```python
155
+ """My Node
156
+
157
+ Docstring first paragraph shows in the properties panel.
158
+ """
159
+ NODE = {
160
+ "label": "My Node",
161
+ "category": "Transform",
162
+ "inputs": [("table", "dataframe")],
163
+ "outputs": [("result", "dataframe")],
164
+ }
165
+ PARAMS = [
166
+ {"name": "factor", "type": "float", "default": 1.0},
167
+ ]
168
+
169
+ def run(ctx, table):
170
+ ctx.log(f"scaling by {ctx.params['factor']}")
171
+ ctx.check_cancelled() # cooperative cancellation
172
+ return {"result": table * ctx.params["factor"]}
173
+ ```
174
+
175
+ Port types: `any, dataframe, series, number, string, bool, object, figure`.
176
+ `columns`-typed params render with a ▾ picker listing the columns of the
177
+ DataFrames cached on the node's inputs (run upstream once to populate it);
178
+ add `"multi": False` for single-column params so picking replaces instead
179
+ of toggling a comma list.
180
+ Rules: treat inputs as read-only (outputs are cached by reference); heavy
181
+ imports go inside `run()`; matplotlib figures must use the OO API
182
+ (`matplotlib.figure.Figure()`), never pyplot.
183
+
184
+ Drop new `.py` files under `src/flograph/nodes/<category>/` and they appear in
185
+ the library on next launch.
186
+
187
+ ## Development
188
+
189
+ ```bash
190
+ uv pip install -p .venv/bin/python -e ".[dev]"
191
+ QT_QPA_PLATFORM=offscreen .venv/bin/python -m pytest tests/
192
+ ```
193
+
194
+ Architecture (src layout):
195
+
196
+ - `flograph/core` — Qt-free model: graph, typed ports, script contract,
197
+ registry, JSON serialization. Fully unit-testable; a poison test keeps Qt
198
+ and pandas out of its import graph.
199
+ - `flograph/engine` — background execution: plan builder, single-thread pool
200
+ worker, output cache, cancellation, per-node stdout capture, tracebacks
201
+ mapped to node script lines.
202
+ - `flograph/nodes` — the standard library; each node is a script file loaded as
203
+ text through the same contract as user code.
204
+ - `flograph/ui` — canvas (QGraphicsView from scratch), code editor, inspector,
205
+ properties, console. One rule everywhere: **QUndoCommands are the only
206
+ writers to the graph**; items react to graph events.
207
+
208
+ ## License
209
+
210
+ [MIT](LICENSE) — free for commercial and private use, modification, and
211
+ redistribution; just keep the copyright and license notice.
@@ -0,0 +1,172 @@
1
+ # flograph
2
+
3
+ A visual node-based Python programming environment: KNIME-style dataflow on
4
+ an infinite Blueprint-style canvas, where every node is real, editable Python.
5
+
6
+ ![status](https://img.shields.io/badge/status-v0.1-blue)
7
+
8
+ ## Install
9
+
10
+ flograph is a standard pip-installable package (hatchling build backend):
11
+
12
+ ```bash
13
+ pip install . # or: uv pip install . / pip install -e .
14
+ ```
15
+
16
+ This puts a `flograph` command on your PATH and makes `python -m flograph`
17
+ work. To build a distributable wheel/sdist instead:
18
+
19
+ ```bash
20
+ python -m build # or: uv build -> dist/flograph-*.whl
21
+ ```
22
+
23
+ > The project was renamed from **flopy** to **flograph** because `flopy` is
24
+ > already taken on PyPI (USGS MODFLOW). To migrate projects saved by the old
25
+ > build, see [Migrating old `.flopy` projects](#migrating-old-flopy-projects).
26
+
27
+ ## Run it
28
+
29
+ ```bash
30
+ flograph # console entry point (after install)
31
+ python -m flograph # equivalent module entry point
32
+ python main.py project.flograph # open a project
33
+ python -m flograph.engine.headless project.flograph # run without GUI
34
+ ```
35
+
36
+ ## The idea
37
+
38
+ - **Nodes are Python scripts.** Every node — including the shipped library —
39
+ is a small module: a `NODE` dict declaring typed ports, an optional
40
+ `PARAMS` list that auto-generates its properties form, and a
41
+ `run(ctx, **inputs)` function. Double-click any node to read or fork its
42
+ code in the built-in editor (syntax highlighting, jedi completion,
43
+ error markers on the failing line).
44
+ - **KNIME semantics.** Data flows through typed ports; execution is a
45
+ topological walk of the dirty subgraph; every node's outputs are cached, so
46
+ re-runs only recompute what changed. Status LEDs: gray idle, yellow queued,
47
+ pulsing blue running, green done, red error.
48
+ - **Inspect everything.** Click any node or wire to see the data on it —
49
+ paged table view for DataFrames (millions of rows are fine), matplotlib
50
+ figures with a toolbar, pretty-printed objects. Per-node stdout/logs in the
51
+ console dock.
52
+
53
+ ## Canvas
54
+
55
+ | Action | Binding |
56
+ | --- | --- |
57
+ | Add node | `Tab` (search palette), right-click, or drag from the library |
58
+ | Connect | drag from a port; drop on empty canvas to pick a compatible node |
59
+ | Reroute dot | double-click a wire |
60
+ | Comment frame | `Ctrl+G` around the selection (frames move their contents) |
61
+ | Run all / selected / cancel | `F5` / `F6` / `Esc` |
62
+ | Pan / zoom | middle-drag or `Space`+drag / wheel |
63
+ | Frame view | `F` |
64
+ | Duplicate / delete | `Ctrl+D` / `Del` |
65
+ | Undo anything | `Ctrl+Z` — every graph mutation is on the undo stack |
66
+
67
+ Projects are plain JSON (`.flograph`); caches are never saved, so a reopened
68
+ project is fully reproducible with one `F5`.
69
+
70
+ ### Migrating old `.flopy` projects
71
+
72
+ Projects saved by the old **flopy** build use a `.flopy` extension and embed
73
+ `flopy.*` node type-ids, which this build no longer recognises. Convert them
74
+ in place with the bundled one-shot migrator (stdlib-only, no install needed):
75
+
76
+ ```bash
77
+ python scripts/flopy_to_flograph.py project.flopy # -> project.flograph
78
+ python scripts/flopy_to_flograph.py my/projects --recursive
79
+ ```
80
+
81
+ It rewrites builtin `flopy.*` type-ids to `flograph.*` (leaving `user.*`
82
+ nodes alone), renames the file and any `.flopy.cache/` side-car, and by
83
+ default keeps the original (`--delete-original` to remove it).
84
+
85
+ ## Node library
86
+
87
+ The shipped library covers the KNIME basics:
88
+
89
+ - **IO** — read/write CSV, Excel, Parquet, JSON (incl. JSONL), SQLite
90
+ (query in, table out), inline Table.
91
+ - **Transform** — Select Columns, Filter Rows, Sort, Join, Group By,
92
+ Expression, Concatenate, Missing Values, Duplicate Row Filter,
93
+ Rename Columns, Pivot, Unpivot, Row Sampling, Convert Types,
94
+ String Manipulation, Statistics.
95
+ - **Viz** — Show Table, Show Plot (live on-canvas cards), Show Plotly
96
+ (fully interactive plotly.js chart embedded on the canvas — hover, zoom
97
+ and pan in place; needs `pip install plotly`, e.g. via
98
+ Tools > Manage Packages).
99
+ - **Scripting / Util** — Python Script, Constant, Reroute, Note,
100
+ Action Button.
101
+
102
+ ## Packages
103
+
104
+ **Tools > Manage Packages** installs, upgrades and uninstalls pip packages
105
+ in flograph's own environment (the venv running the app). Nodes execute
106
+ in-process, so anything installed there is immediately importable from a
107
+ node's `run()` — no restart needed for new installs; upgrades of modules
108
+ the app has already imported take effect on the next launch. The dialog
109
+ uses `pip` when the interpreter has it and falls back to `uv pip` (uv-made
110
+ venvs ship without pip); flograph's own core dependencies are protected from
111
+ uninstall.
112
+
113
+ ## Writing a node
114
+
115
+ ```python
116
+ """My Node
117
+
118
+ Docstring first paragraph shows in the properties panel.
119
+ """
120
+ NODE = {
121
+ "label": "My Node",
122
+ "category": "Transform",
123
+ "inputs": [("table", "dataframe")],
124
+ "outputs": [("result", "dataframe")],
125
+ }
126
+ PARAMS = [
127
+ {"name": "factor", "type": "float", "default": 1.0},
128
+ ]
129
+
130
+ def run(ctx, table):
131
+ ctx.log(f"scaling by {ctx.params['factor']}")
132
+ ctx.check_cancelled() # cooperative cancellation
133
+ return {"result": table * ctx.params["factor"]}
134
+ ```
135
+
136
+ Port types: `any, dataframe, series, number, string, bool, object, figure`.
137
+ `columns`-typed params render with a ▾ picker listing the columns of the
138
+ DataFrames cached on the node's inputs (run upstream once to populate it);
139
+ add `"multi": False` for single-column params so picking replaces instead
140
+ of toggling a comma list.
141
+ Rules: treat inputs as read-only (outputs are cached by reference); heavy
142
+ imports go inside `run()`; matplotlib figures must use the OO API
143
+ (`matplotlib.figure.Figure()`), never pyplot.
144
+
145
+ Drop new `.py` files under `src/flograph/nodes/<category>/` and they appear in
146
+ the library on next launch.
147
+
148
+ ## Development
149
+
150
+ ```bash
151
+ uv pip install -p .venv/bin/python -e ".[dev]"
152
+ QT_QPA_PLATFORM=offscreen .venv/bin/python -m pytest tests/
153
+ ```
154
+
155
+ Architecture (src layout):
156
+
157
+ - `flograph/core` — Qt-free model: graph, typed ports, script contract,
158
+ registry, JSON serialization. Fully unit-testable; a poison test keeps Qt
159
+ and pandas out of its import graph.
160
+ - `flograph/engine` — background execution: plan builder, single-thread pool
161
+ worker, output cache, cancellation, per-node stdout capture, tracebacks
162
+ mapped to node script lines.
163
+ - `flograph/nodes` — the standard library; each node is a script file loaded as
164
+ text through the same contract as user code.
165
+ - `flograph/ui` — canvas (QGraphicsView from scratch), code editor, inspector,
166
+ properties, console. One rule everywhere: **QUndoCommands are the only
167
+ writers to the graph**; items react to graph events.
168
+
169
+ ## License
170
+
171
+ [MIT](LICENSE) — free for commercial and private use, modification, and
172
+ redistribution; just keep the copyright and license notice.
@@ -0,0 +1,7 @@
1
+ 1. flow variables like in knime
2
+ 2. Export py script option, that creates a output script mirroring the flow. nice to use when a flograph workflow has matured enough to move the script to its own python script thats stands alone outside of the tool.
3
+ 3. moving nodes should happen from their top bar only, make resizing better and esp important on "show *" nodes, as they often need to be resized.
4
+ 4. add node: that similar to powerbis "Cards"
5
+ 5. add node: a slicer similar to powerbis, maybe it can fit inline and automatically run the visuals that follow?
6
+ 6. a way to turn a frame into a new "mega node" that is a node that contains nodes, i can config the initial wiring and end wiring. ie i input a df and it builds the table and graph.
7
+ 7.
File without changes
flograph-0.1.0/main.py ADDED
@@ -0,0 +1,5 @@
1
+ """Thin launcher for the flograph visual programming environment."""
2
+ from flograph.app import main
3
+
4
+ if __name__ == "__main__":
5
+ raise SystemExit(main())
@@ -0,0 +1,57 @@
1
+ [project]
2
+ name = "flograph"
3
+ version = "0.1.0"
4
+ description = "Visual node-based Python programming environment (KNIME-style dataflow, Blueprint-style canvas)"
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+ license = "MIT"
8
+ license-files = ["LICENSE"]
9
+ authors = [{ name = "redthista", email = "dconrancpw@gmail.com" }]
10
+ keywords = [
11
+ "dataflow", "visual-programming", "node-editor", "etl", "pandas",
12
+ "gui", "pyside6", "qt", "knime", "low-code",
13
+ ]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Environment :: X11 Applications :: Qt",
17
+ "Intended Audience :: Developers",
18
+ "Intended Audience :: Science/Research",
19
+ "Operating System :: OS Independent",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Programming Language :: Python :: 3.13",
24
+ "Topic :: Scientific/Engineering :: Visualization",
25
+ "Topic :: Software Development :: User Interfaces",
26
+ "Topic :: Software Development :: Code Generators",
27
+ ]
28
+ dependencies = [
29
+ "PySide6>=6.7",
30
+ "pandas>=2.0",
31
+ "matplotlib>=3.8",
32
+ "jedi>=0.19",
33
+ ]
34
+
35
+ [project.urls]
36
+ Homepage = "https://github.com/redthista/flograph"
37
+ Repository = "https://github.com/redthista/flograph"
38
+ Issues = "https://github.com/redthista/flograph/issues"
39
+
40
+ [project.optional-dependencies]
41
+ dev = ["pytest>=8", "pytest-qt>=4.4"]
42
+ excel = ["openpyxl"]
43
+ parquet = ["pyarrow"]
44
+ plotly = ["plotly"]
45
+
46
+ [project.scripts]
47
+ flograph = "flograph.app:main"
48
+
49
+ [build-system]
50
+ requires = ["hatchling"]
51
+ build-backend = "hatchling.build"
52
+
53
+ [tool.hatch.build.targets.wheel]
54
+ packages = ["src/flograph"]
55
+
56
+ [tool.pytest.ini_options]
57
+ testpaths = ["tests"]
@@ -0,0 +1,121 @@
1
+ #!/usr/bin/env python3
2
+ """Migrate legacy ``.flopy`` project files to the renamed ``.flograph`` format.
3
+
4
+ The project was renamed flopy -> flograph (the name ``flopy`` is taken on PyPI
5
+ by USGS MODFLOW). The rename is a clean break with no in-app backward
6
+ compatibility, so this one-shot tool upgrades any projects saved by the old
7
+ build:
8
+
9
+ * builtin node type-ids ``flopy.<sub>.<name>`` -> ``flograph.<sub>.<name>``
10
+ (``user.*`` and any other prefixes are left untouched);
11
+ * the informational ``flopy_version`` key -> ``flograph_version``;
12
+ * the file itself ``<name>.flopy`` -> ``<name>.flograph``;
13
+ * the side-car cache dir ``<name>.flopy.cache/`` -> ``<name>.flograph.cache/``.
14
+
15
+ Standalone and stdlib-only on purpose: it does not import the ``flograph``
16
+ package, so it runs against old files in any environment.
17
+
18
+ Usage::
19
+
20
+ python scripts/flopy_to_flograph.py project.flopy
21
+ python scripts/flopy_to_flograph.py path/to/dir --recursive
22
+ python scripts/flopy_to_flograph.py project.flopy --delete-original
23
+ """
24
+ from __future__ import annotations
25
+
26
+ import argparse
27
+ import json
28
+ import sys
29
+ from pathlib import Path
30
+
31
+ LEGACY_PREFIX = "flopy."
32
+ NEW_PREFIX = "flograph."
33
+
34
+
35
+ def convert_project_data(data: dict) -> tuple[dict, int]:
36
+ """Return a rewritten copy of a project dict and the number of builtin
37
+ node type-ids that were re-prefixed. Non-``flopy.*`` type-ids (notably
38
+ ``user.*``) are preserved verbatim."""
39
+ rewritten = 0
40
+ if "flopy_version" in data and "flograph_version" not in data:
41
+ data["flograph_version"] = data.pop("flopy_version")
42
+ graph = data.get("graph")
43
+ if isinstance(graph, dict):
44
+ for node in graph.get("nodes", []):
45
+ type_id = node.get("type")
46
+ if isinstance(type_id, str) and type_id.startswith(LEGACY_PREFIX):
47
+ node["type"] = NEW_PREFIX + type_id[len(LEGACY_PREFIX):]
48
+ rewritten += 1
49
+ return data, rewritten
50
+
51
+
52
+ def _new_path(path: Path) -> Path:
53
+ # only the trailing ".flopy" suffix is swapped; the stem is preserved even
54
+ # if it happens to contain the substring "flopy".
55
+ return path.with_name(path.name[: -len(".flopy")] + ".flograph")
56
+
57
+
58
+ def convert_file(path: Path, *, delete_original: bool = False) -> tuple[Path, int]:
59
+ """Convert one ``.flopy`` file, writing a sibling ``.flograph`` file and
60
+ renaming any ``.flopy.cache`` side-car. Returns (output_path, type_ids
61
+ rewritten)."""
62
+ data = json.loads(path.read_text())
63
+ data, rewritten = convert_project_data(data)
64
+ out_path = _new_path(path)
65
+ out_path.write_text(json.dumps(data, indent=2))
66
+
67
+ old_cache = path.with_name(path.name + ".cache")
68
+ if old_cache.is_dir():
69
+ new_cache = out_path.with_name(out_path.name + ".cache")
70
+ if not new_cache.exists():
71
+ old_cache.rename(new_cache)
72
+
73
+ if delete_original:
74
+ path.unlink()
75
+
76
+ return out_path, rewritten
77
+
78
+
79
+ def _iter_projects(paths: list[Path], recursive: bool):
80
+ for p in paths:
81
+ if p.is_dir():
82
+ yield from sorted(p.rglob("*.flopy") if recursive else p.glob("*.flopy"))
83
+ else:
84
+ yield p
85
+
86
+
87
+ def main(argv: list[str] | None = None) -> int:
88
+ parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
89
+ parser.add_argument("paths", nargs="+", type=Path,
90
+ help="`.flopy` files and/or directories to convert")
91
+ parser.add_argument("-r", "--recursive", action="store_true",
92
+ help="recurse into directories looking for `.flopy` files")
93
+ parser.add_argument("--delete-original", action="store_true",
94
+ help="delete each source `.flopy` after converting it")
95
+ args = parser.parse_args(argv)
96
+
97
+ files = 0
98
+ total_ids = 0
99
+ errors = 0
100
+ for project in _iter_projects(args.paths, args.recursive):
101
+ if project.suffix != ".flopy":
102
+ print(f"skip (not a .flopy file): {project}", file=sys.stderr)
103
+ continue
104
+ try:
105
+ out_path, rewritten = convert_file(
106
+ project, delete_original=args.delete_original)
107
+ except (OSError, json.JSONDecodeError) as exc:
108
+ print(f"error: {project}: {exc}", file=sys.stderr)
109
+ errors += 1
110
+ continue
111
+ files += 1
112
+ total_ids += rewritten
113
+ print(f"converted {project} -> {out_path} ({rewritten} type-ids rewritten)")
114
+
115
+ print(f"\n{files} file(s) converted, {total_ids} type-id(s) rewritten, "
116
+ f"{errors} error(s).")
117
+ return 1 if errors else 0
118
+
119
+
120
+ if __name__ == "__main__":
121
+ raise SystemExit(main())
File without changes
@@ -0,0 +1,5 @@
1
+ """Enable `python -m flograph` as an entry point (mirrors the console script)."""
2
+ from flograph.app import main
3
+
4
+ if __name__ == "__main__":
5
+ raise SystemExit(main())
@@ -0,0 +1,43 @@
1
+ """Application entry point: QApplication, theme, registry, main window."""
2
+ from __future__ import annotations
3
+
4
+ import sys
5
+
6
+
7
+ def main(argv: list[str] | None = None) -> int:
8
+ import matplotlib
9
+ matplotlib.use("QtAgg") # before any pyplot import, GUI-safe backend
10
+
11
+ from PySide6.QtCore import Qt
12
+ from PySide6.QtWidgets import QApplication
13
+
14
+ from flograph.core import NodeRegistry
15
+ from flograph.paths import user_nodes_dir
16
+ from flograph.ui.mainwindow import MainWindow
17
+ from flograph.ui.theme import apply_theme
18
+
19
+ # must be set before the QApplication exists: the Show Plotly card embeds
20
+ # Qt WebEngine, which needs shared GL contexts to composite
21
+ QApplication.setAttribute(Qt.AA_ShareOpenGLContexts)
22
+ app = QApplication.instance() or QApplication(sys.argv if argv is None else argv)
23
+ app.setApplicationName("flograph")
24
+ app.setOrganizationName("flograph")
25
+ apply_theme(app)
26
+
27
+ registry = NodeRegistry()
28
+ registry.load_builtins()
29
+ registry.load_user_nodes(user_nodes_dir())
30
+
31
+ window = MainWindow(registry)
32
+ window.resize(1400, 900)
33
+ window.show()
34
+
35
+ args = app.arguments()[1:]
36
+ project = next((a for a in args if a.endswith(".flograph")), None)
37
+ if project:
38
+ window.open_path(project, confirm=False)
39
+ return app.exec()
40
+
41
+
42
+ if __name__ == "__main__":
43
+ raise SystemExit(main())