haybale-graph-editor 0.0.2__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 (37) hide show
  1. haybale_graph_editor-0.0.2/.gitignore +205 -0
  2. haybale_graph_editor-0.0.2/PKG-INFO +8 -0
  3. haybale_graph_editor-0.0.2/README.md +9 -0
  4. haybale_graph_editor-0.0.2/haybale_graph_editor/__init__.py +59 -0
  5. haybale_graph_editor-0.0.2/haybale_graph_editor/editors/__init__.py +0 -0
  6. haybale_graph_editor-0.0.2/haybale_graph_editor/editors/graph_canvas/__init__.py +16 -0
  7. haybale_graph_editor-0.0.2/haybale_graph_editor/editors/graph_canvas/connection_info_popup.py +134 -0
  8. haybale_graph_editor-0.0.2/haybale_graph_editor/editors/graph_canvas/event_handlers.py +46 -0
  9. haybale_graph_editor-0.0.2/haybale_graph_editor/editors/graph_canvas/graph_canvas_manager.py +217 -0
  10. haybale_graph_editor-0.0.2/haybale_graph_editor/editors/graph_canvas/handlers/__init__.py +11 -0
  11. haybale_graph_editor-0.0.2/haybale_graph_editor/editors/graph_canvas/handlers/context_menu.py +543 -0
  12. haybale_graph_editor-0.0.2/haybale_graph_editor/editors/graph_canvas/handlers/context_menu_actions.py +58 -0
  13. haybale_graph_editor-0.0.2/haybale_graph_editor/editors/graph_canvas/handlers/interaction.py +56 -0
  14. haybale_graph_editor-0.0.2/haybale_graph_editor/editors/graph_canvas/handlers/selection.py +147 -0
  15. haybale_graph_editor-0.0.2/haybale_graph_editor/editors/graph_canvas/handlers/visual_layer.py +465 -0
  16. haybale_graph_editor-0.0.2/haybale_graph_editor/editors/graph_canvas/node_menu_builder.py +389 -0
  17. haybale_graph_editor-0.0.2/haybale_graph_editor/editors/graph_canvas/ui_edge.py +205 -0
  18. haybale_graph_editor-0.0.2/haybale_graph_editor/editors/graph_canvas/ui_node.py +300 -0
  19. haybale_graph_editor-0.0.2/haybale_graph_editor/editors/graph_editor.py +515 -0
  20. haybale_graph_editor-0.0.2/haybale_graph_editor/editors/node_status.py +66 -0
  21. haybale_graph_editor-0.0.2/haybale_graph_editor/focuses.py +70 -0
  22. haybale_graph_editor-0.0.2/haybale_graph_editor/panels/context_menu/create_node_panel.py +99 -0
  23. haybale_graph_editor-0.0.2/haybale_graph_editor/panels/context_menu/edge_actions.py +54 -0
  24. haybale_graph_editor-0.0.2/haybale_graph_editor/panels/context_menu/node_actions.py +171 -0
  25. haybale_graph_editor-0.0.2/haybale_graph_editor/panels/context_menu/node_errors.py +87 -0
  26. haybale_graph_editor-0.0.2/haybale_graph_editor/panels/context_menu/port_info.py +53 -0
  27. haybale_graph_editor-0.0.2/haybale_graph_editor/panels/context_menu/selection_actions.py +80 -0
  28. haybale_graph_editor-0.0.2/haybale_graph_editor/panels/edge_panels.py +283 -0
  29. haybale_graph_editor-0.0.2/haybale_graph_editor/panels/graph_info_panel.py +52 -0
  30. haybale_graph_editor-0.0.2/haybale_graph_editor/panels/node_ports_panel.py +80 -0
  31. haybale_graph_editor-0.0.2/haybale_graph_editor/panels/node_props_panel.py +83 -0
  32. haybale_graph_editor-0.0.2/haybale_graph_editor/panels/node_settings.py +71 -0
  33. haybale_graph_editor-0.0.2/haybale_graph_editor/protocols.py +54 -0
  34. haybale_graph_editor-0.0.2/haybale_graph_editor/state/__init__.py +0 -0
  35. haybale_graph_editor-0.0.2/haybale_graph_editor/state/edit_state.py +57 -0
  36. haybale_graph_editor-0.0.2/haybale_graph_editor/state/graph_app_state.py +69 -0
  37. haybale_graph_editor-0.0.2/pyproject.toml +27 -0
@@ -0,0 +1,205 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py,cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ #Pipfile.lock
96
+
97
+ # UV
98
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ #uv.lock
102
+
103
+ # poetry
104
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
+ #poetry.lock
109
+
110
+ # pdm
111
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
112
+ #pdm.lock
113
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
114
+ # in version control.
115
+ # https://pdm.fming.dev/latest/usage/project/#working-with-version-control
116
+ .pdm.toml
117
+ .pdm-python
118
+ .pdm-build/
119
+
120
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
121
+ __pypackages__/
122
+
123
+ # Celery stuff
124
+ celerybeat-schedule
125
+ celerybeat.pid
126
+
127
+ # SageMath parsed files
128
+ *.sage.py
129
+
130
+ # Environments
131
+ **/.env
132
+ .venv
133
+ env/
134
+ venv/
135
+ ENV/
136
+ env.bak/
137
+ venv.bak/
138
+
139
+ # Spyder project settings
140
+ .spyderproject
141
+ .spyproject
142
+
143
+ # Rope project settings
144
+ .ropeproject
145
+
146
+ # mkdocs documentation
147
+ /site
148
+
149
+ # mypy
150
+ .mypy_cache/
151
+ .dmypy.json
152
+ dmypy.json
153
+
154
+ # Pyre type checker
155
+ .pyre/
156
+
157
+ # pytype static type analyzer
158
+ .pytype/
159
+
160
+ # Cython debug symbols
161
+ cython_debug/
162
+
163
+ # PyCharm
164
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
165
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
166
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
167
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
168
+ .idea/
169
+
170
+ # Abstra
171
+ # Abstra is an AI-powered process automation framework.
172
+ # Ignore directories containing user credentials, local state, and settings.
173
+ # Learn more at https://abstra.io/docs
174
+ .abstra/
175
+
176
+ # Visual Studio Code
177
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
178
+ # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
179
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
180
+ # you could uncomment the following to ignore the enitre vscode folder
181
+ .vscode/
182
+
183
+ # Ruff stuff:
184
+ .ruff_cache/
185
+
186
+ # PyPI configuration file
187
+ .pypirc
188
+
189
+ # Cursor
190
+ # Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
191
+ # exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
192
+ # refer to https://docs.cursor.com/context/ignore-files
193
+ .cursorignore
194
+ .cursorindexingignore
195
+
196
+ .DS_Store
197
+ saves/*.*
198
+
199
+ # Dev-mode graphs (local testing only, not part of the repo)
200
+ graphs/*.json
201
+ # MkDocs build output
202
+ site/
203
+
204
+ # Local git worktrees (claude-code workflow)
205
+ .worktrees/
@@ -0,0 +1,8 @@
1
+ Metadata-Version: 2.4
2
+ Name: haybale-graph-editor
3
+ Version: 0.0.2
4
+ Summary: Graph editor library for Haywire — host-agnostic visual graph editing
5
+ License: MIT
6
+ Requires-Python: >=3.10
7
+ Requires-Dist: haywire-core~=0.0.2
8
+ Requires-Dist: haywire-studio~=0.0.2
@@ -0,0 +1,9 @@
1
+ # haybale-graph-editor
2
+
3
+ Visual graph editor library for Haywire. Provides:
4
+
5
+ - `GraphContainer` (Protocol) — what a graph-source library must implement
6
+ - `GraphAppState` (AppState) — shared registry of open graph containers, keyed by `binding_id`
7
+ - `GraphEditor` (BaseEditor) — the canvas-hosting editor surface
8
+
9
+ Source libraries (e.g. `haybale-haystack`) register containers; this library renders them.
@@ -0,0 +1,59 @@
1
+ """haybale-graph-editor: graph editor library for Haywire.
2
+
3
+ Provides the GraphContainer protocol, GraphAppState registry, and
4
+ GraphEditor surface. Decoupled from any specific graph source — source
5
+ libraries register their containers, this library renders them.
6
+ """
7
+
8
+ from importlib.metadata import version as _pkg_version
9
+ from pathlib import Path
10
+
11
+ from haywire.core.library.base import BaseLibrary
12
+ from haywire.core.library.decorator import library
13
+ from haywire.core.state import LibraryStateRegistry
14
+ from haywire.ui.editor.registry import EditorTypeRegistry
15
+
16
+ # Public API re-exports
17
+ from haybale_graph_editor.protocols import GraphContainer
18
+ from haybale_graph_editor.state.graph_app_state import GraphAppState
19
+ from haywire.ui.panel.registry import PanelRegistry
20
+
21
+ __all__ = ["GraphContainer", "GraphAppState", "Library"]
22
+
23
+
24
+ @library(
25
+ label="Graph Editor",
26
+ id="graph_editor",
27
+ version=_pkg_version("haybale-graph-editor"),
28
+ description="Visual graph editor library — host-agnostic",
29
+ url="",
30
+ help_url="",
31
+ author="",
32
+ author_url="",
33
+ dependencies=[],
34
+ tags=["graph-editor"],
35
+ file_watcher=True,
36
+ )
37
+ class Library(BaseLibrary):
38
+ """Graph Editor library."""
39
+
40
+ def register_components(self):
41
+ base_path = Path(__file__).parent
42
+
43
+ self.add_folder_to_registry(
44
+ folder_path=str(base_path / "state"),
45
+ registry_cls=LibraryStateRegistry,
46
+ )
47
+
48
+ self.add_folder_to_registry(
49
+ folder_path=str(base_path / "panels"),
50
+ registry_cls=PanelRegistry,
51
+ )
52
+
53
+ self.add_folder_to_registry(
54
+ folder_path=str(base_path / "editors"),
55
+ registry_cls=EditorTypeRegistry,
56
+ )
57
+
58
+ def validate(self) -> bool:
59
+ return True
@@ -0,0 +1,16 @@
1
+ from .connection_info_popup import EdgeInfoPopup
2
+ from .graph_canvas_manager import GraphCanvasManager
3
+ from .node_menu_builder import NodeMenuBuilder
4
+ from .ui_edge import EdgeVisualState
5
+ from .ui_edge import UIEdge
6
+ from .ui_node import UINode
7
+
8
+ __all__ = [
9
+ "EdgeInfoPopup",
10
+ "EdgeVisualState",
11
+ "GraphCanvasManager",
12
+ "GraphEventMetadata",
13
+ "NodeMenuBuilder",
14
+ "UIEdge",
15
+ "UINode",
16
+ ]
@@ -0,0 +1,134 @@
1
+ """
2
+ ConnectionInfoPopup - Detailed connection information display component
3
+
4
+ This component provides a dedicated popup for inspecting edge/connection details including:
5
+ - Connection path (nodes and ports)
6
+ - Validation status
7
+ - Error details with full error rendering
8
+ - Warning messages
9
+ - Adapter chain visualization and testing
10
+ - Execution statistics
11
+ """
12
+
13
+ from nicegui import ui
14
+ from typing import Optional
15
+
16
+ from haywire.core.edge.edge import Edge
17
+ from haywire.core.errors.haywire_exception import HaywireException
18
+ from haywire.core.edge.edge_wrapper import EdgeWrapperState
19
+
20
+ from haywire.ui import elements as hui
21
+ from haywire.ui.errors.error_info import error_render_detail
22
+
23
+ from haywire.ui.components.popup import Popup
24
+
25
+
26
+ class EdgeInfoPopup:
27
+ """Dedicated popup for displaying detailed connection/edge information."""
28
+
29
+ def __init__(self):
30
+ self._info_popup: Optional[Popup] = None
31
+
32
+ def show(self, x: float, y: float, edge_id: str, edge: Edge, state: EdgeWrapperState):
33
+ """Show detailed connection information in a dedicated popup."""
34
+ # Close any existing popup first
35
+ self.close()
36
+
37
+ # Create a larger popup for detailed information
38
+ popup = Popup.create_context_menu(
39
+ "Connection Details", x + 10, y + 10, width="400px", clamp_to_viewport=False
40
+ )
41
+
42
+ with popup:
43
+ with ui.column().classes("w-full gap-2 p-2"):
44
+ # Header with connection status
45
+ with ui.row().classes("w-full"):
46
+ ui.label(f"{edge.edge_type}").classes("text-xs hw-text-muted ml-2 mt-2")
47
+ is_valid = state.is_valid()
48
+ status_icon = "✓" if is_valid else "✗"
49
+ status_color = "hw-text-success" if is_valid else "hw-text-danger"
50
+ status_text = "Valid" if is_valid else "Invalid"
51
+ ui.label(f"{status_icon} {status_text}").classes(f"text-sm font-bold {status_color}")
52
+
53
+ ui.separator().classes("my-2")
54
+
55
+ # Error Section (if present, expandable, default open)
56
+ error = state.get_error()
57
+ if error and isinstance(error, HaywireException):
58
+ with hui.expansion_section("Error Details"):
59
+ with (
60
+ ui.card()
61
+ .classes("w-full p-3")
62
+ .style("background: var(--hw-danger-bg); border: 1px solid var(--hw-danger);")
63
+ ):
64
+ ui.label(f"Category: {error.category}").classes("text-xs hw-text-danger ml-2")
65
+ # Render the error detail with button to show full details
66
+ error_render_detail(error)
67
+
68
+ # Warning Section (if present, expandable, default open)
69
+ if state.has_warning():
70
+ with hui.expansion_section("Warning"):
71
+ with (
72
+ ui.card()
73
+ .classes("w-full p-3")
74
+ .style("background: var(--hw-bg-surface); border: 1px solid var(--hw-border);")
75
+ ):
76
+ with ui.column():
77
+ for warning in state.warnings:
78
+ ui.label(f"⚠ {warning}").classes("text-xs hw-text-warning ml-2")
79
+
80
+ # Adapter Chain Section (if available, expandable, default closed)
81
+ if edge.chain_adapter_keys:
82
+ with hui.expansion_section("Adapter Chain", default_open=False):
83
+ with (
84
+ ui.card()
85
+ .classes("w-full p-3")
86
+ .style("background: var(--hw-bg-surface); border: 1px solid var(--hw-border);")
87
+ ):
88
+ # Display each adapter in the chain
89
+ for i, adapter_key in enumerate(edge.chain_adapter_keys, 1):
90
+ ui.label(f"{i}. {adapter_key}").classes("text-xs hw-text-accent ml-2")
91
+
92
+ if state.execution_count > 0:
93
+ # Execution Statistics Section (expandable, default closed)
94
+ with hui.expansion_section("Execution Statistics", default_open=False):
95
+ exec_count = state.execution_count
96
+ ui.label(f"Execution Count: {exec_count}").classes("text-xs hw-text-muted ml-2")
97
+
98
+ avg_time = state.average_execution_time_us
99
+ if avg_time > 0:
100
+ ui.label(f"Average Time: {avg_time:.1f} μs").classes(
101
+ "text-xs hw-text-muted ml-2"
102
+ )
103
+ else:
104
+ ui.label("Average Time: Not measured").classes("text-xs hw-text-dim ml-2")
105
+
106
+ ui.label(f"Tested value: {state.example_test_value}").classes(
107
+ "text-xs hw-text-muted ml-2"
108
+ )
109
+ ui.label(f"Tested result: {state.example_test_result}").classes(
110
+ "text-xs hw-text-muted ml-2"
111
+ )
112
+
113
+ # Connection Path Section (Expandable, default closed)
114
+ with hui.expansion_section("Connection Path", default_open=False):
115
+ ui.label("Connection Path").classes("font-semibold text-sm")
116
+ ui.label(f"{edge.source_node_id} [{edge.outlet_port_id}]").classes("text-xs opacity-70")
117
+ ui.label("↓").classes("text-xs opacity-50 ml-2")
118
+ ui.label(f"{edge.sink_node_id} [{edge.inlet_port_id}]").classes("text-xs opacity-70")
119
+
120
+ # Close button
121
+ ui.separator().classes("my-2")
122
+ btn_close = ui.button("Close", on_click=lambda: self.close())
123
+ btn_close.props("flat")
124
+ btn_close.classes("w-full text-sm py-2")
125
+
126
+ popup.open()
127
+ self._info_popup = popup
128
+
129
+ def close(self):
130
+ """Close the connection info popup."""
131
+ if self._info_popup:
132
+ self._info_popup.close()
133
+ self._info_popup.delete()
134
+ self._info_popup = None
@@ -0,0 +1,46 @@
1
+ """
2
+ Event handler registration system for the enhanced graph canvas event system.
3
+
4
+ This module provides:
5
+ - Decorator for registering event handlers
6
+ - Type-safe handler registration
7
+ - Automatic handler discovery
8
+ - build_event_handler_map: scan multiple handler sources into a dispatch map
9
+ """
10
+
11
+ from typing import Callable, Dict, List, Type
12
+ from haywire.ui.components.graph.event_definitions import BaseGraphEvent
13
+
14
+
15
+ def handles_event(*event_classes: Type[BaseGraphEvent]):
16
+ """Decorator to register methods as handlers for specific event classes"""
17
+
18
+ def decorator(func: Callable):
19
+ setattr(func, "_handles_event_classes", event_classes)
20
+ return func
21
+
22
+ return decorator
23
+
24
+
25
+ def build_event_handler_map(sources: List[object]) -> Dict[str, Callable]:
26
+ """
27
+ Build an event-type → handler mapping by scanning a list of handler sources.
28
+
29
+ For each source object, scans all methods for the ``_handles_event_classes``
30
+ attribute set by the ``@handles_event`` decorator. When two sources register
31
+ the same event type the later source wins.
32
+
33
+ Args:
34
+ sources: Objects whose methods should be scanned for ``@handles_event``.
35
+
36
+ Returns:
37
+ Dict mapping event_type strings to bound handler methods.
38
+ """
39
+ handlers: Dict[str, Callable] = {}
40
+ for source in sources:
41
+ for method_name in dir(source):
42
+ method = getattr(source, method_name)
43
+ if hasattr(method, "_handles_event_classes"):
44
+ for event_class in method._handles_event_classes:
45
+ handlers[event_class.event_type] = method
46
+ return handlers