py-data-engine 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (200) hide show
  1. data_engine/__init__.py +37 -0
  2. data_engine/application/__init__.py +39 -0
  3. data_engine/application/actions.py +42 -0
  4. data_engine/application/catalog.py +151 -0
  5. data_engine/application/control.py +213 -0
  6. data_engine/application/details.py +73 -0
  7. data_engine/application/runtime.py +449 -0
  8. data_engine/application/workspace.py +62 -0
  9. data_engine/authoring/__init__.py +14 -0
  10. data_engine/authoring/builder.py +31 -0
  11. data_engine/authoring/execution/__init__.py +6 -0
  12. data_engine/authoring/execution/app.py +6 -0
  13. data_engine/authoring/execution/context.py +82 -0
  14. data_engine/authoring/execution/continuous.py +176 -0
  15. data_engine/authoring/execution/grouped.py +106 -0
  16. data_engine/authoring/execution/logging.py +83 -0
  17. data_engine/authoring/execution/polling.py +135 -0
  18. data_engine/authoring/execution/runner.py +210 -0
  19. data_engine/authoring/execution/single.py +171 -0
  20. data_engine/authoring/flow.py +361 -0
  21. data_engine/authoring/helpers.py +160 -0
  22. data_engine/authoring/model.py +59 -0
  23. data_engine/authoring/primitives.py +430 -0
  24. data_engine/authoring/services.py +42 -0
  25. data_engine/devtools/__init__.py +3 -0
  26. data_engine/devtools/project_ast_map.py +503 -0
  27. data_engine/docs/__init__.py +1 -0
  28. data_engine/docs/sphinx_source/_static/custom.css +13 -0
  29. data_engine/docs/sphinx_source/api.rst +42 -0
  30. data_engine/docs/sphinx_source/conf.py +37 -0
  31. data_engine/docs/sphinx_source/guides/app-runtime-and-workspaces.md +397 -0
  32. data_engine/docs/sphinx_source/guides/authoring-flow-modules.md +215 -0
  33. data_engine/docs/sphinx_source/guides/configuring-flows.md +185 -0
  34. data_engine/docs/sphinx_source/guides/core-concepts.md +208 -0
  35. data_engine/docs/sphinx_source/guides/database-methods.md +107 -0
  36. data_engine/docs/sphinx_source/guides/duckdb-helpers.md +462 -0
  37. data_engine/docs/sphinx_source/guides/flow-context.md +538 -0
  38. data_engine/docs/sphinx_source/guides/flow-methods.md +206 -0
  39. data_engine/docs/sphinx_source/guides/getting-started.md +271 -0
  40. data_engine/docs/sphinx_source/guides/project-inventory.md +5683 -0
  41. data_engine/docs/sphinx_source/guides/project-map.md +118 -0
  42. data_engine/docs/sphinx_source/guides/recipes.md +268 -0
  43. data_engine/docs/sphinx_source/index.rst +22 -0
  44. data_engine/domain/__init__.py +92 -0
  45. data_engine/domain/actions.py +69 -0
  46. data_engine/domain/catalog.py +128 -0
  47. data_engine/domain/details.py +214 -0
  48. data_engine/domain/diagnostics.py +56 -0
  49. data_engine/domain/errors.py +104 -0
  50. data_engine/domain/inspection.py +99 -0
  51. data_engine/domain/logs.py +118 -0
  52. data_engine/domain/operations.py +172 -0
  53. data_engine/domain/operator.py +72 -0
  54. data_engine/domain/runs.py +155 -0
  55. data_engine/domain/runtime.py +279 -0
  56. data_engine/domain/source_state.py +17 -0
  57. data_engine/domain/support.py +54 -0
  58. data_engine/domain/time.py +23 -0
  59. data_engine/domain/workspace.py +159 -0
  60. data_engine/flow_modules/__init__.py +1 -0
  61. data_engine/flow_modules/flow_module_compiler.py +179 -0
  62. data_engine/flow_modules/flow_module_loader.py +201 -0
  63. data_engine/helpers/__init__.py +25 -0
  64. data_engine/helpers/duckdb.py +705 -0
  65. data_engine/hosts/__init__.py +1 -0
  66. data_engine/hosts/daemon/__init__.py +23 -0
  67. data_engine/hosts/daemon/app.py +221 -0
  68. data_engine/hosts/daemon/bootstrap.py +69 -0
  69. data_engine/hosts/daemon/client.py +465 -0
  70. data_engine/hosts/daemon/commands.py +64 -0
  71. data_engine/hosts/daemon/composition.py +310 -0
  72. data_engine/hosts/daemon/constants.py +15 -0
  73. data_engine/hosts/daemon/entrypoints.py +97 -0
  74. data_engine/hosts/daemon/lifecycle.py +191 -0
  75. data_engine/hosts/daemon/manager.py +272 -0
  76. data_engine/hosts/daemon/ownership.py +126 -0
  77. data_engine/hosts/daemon/runtime_commands.py +188 -0
  78. data_engine/hosts/daemon/runtime_control.py +31 -0
  79. data_engine/hosts/daemon/server.py +84 -0
  80. data_engine/hosts/daemon/shared_state.py +147 -0
  81. data_engine/hosts/daemon/state_sync.py +101 -0
  82. data_engine/platform/__init__.py +1 -0
  83. data_engine/platform/identity.py +35 -0
  84. data_engine/platform/local_settings.py +146 -0
  85. data_engine/platform/theme.py +259 -0
  86. data_engine/platform/workspace_models.py +190 -0
  87. data_engine/platform/workspace_policy.py +333 -0
  88. data_engine/runtime/__init__.py +1 -0
  89. data_engine/runtime/file_watch.py +185 -0
  90. data_engine/runtime/ledger_models.py +116 -0
  91. data_engine/runtime/runtime_db.py +938 -0
  92. data_engine/runtime/shared_state.py +523 -0
  93. data_engine/services/__init__.py +49 -0
  94. data_engine/services/daemon.py +64 -0
  95. data_engine/services/daemon_state.py +40 -0
  96. data_engine/services/flow_catalog.py +102 -0
  97. data_engine/services/flow_execution.py +48 -0
  98. data_engine/services/ledger.py +85 -0
  99. data_engine/services/logs.py +65 -0
  100. data_engine/services/runtime_binding.py +105 -0
  101. data_engine/services/runtime_execution.py +126 -0
  102. data_engine/services/runtime_history.py +62 -0
  103. data_engine/services/settings.py +58 -0
  104. data_engine/services/shared_state.py +28 -0
  105. data_engine/services/theme.py +59 -0
  106. data_engine/services/workspace_provisioning.py +224 -0
  107. data_engine/services/workspaces.py +74 -0
  108. data_engine/ui/__init__.py +3 -0
  109. data_engine/ui/cli/__init__.py +19 -0
  110. data_engine/ui/cli/app.py +161 -0
  111. data_engine/ui/cli/commands_doctor.py +178 -0
  112. data_engine/ui/cli/commands_run.py +80 -0
  113. data_engine/ui/cli/commands_start.py +100 -0
  114. data_engine/ui/cli/commands_workspace.py +97 -0
  115. data_engine/ui/cli/dependencies.py +44 -0
  116. data_engine/ui/cli/parser.py +56 -0
  117. data_engine/ui/gui/__init__.py +25 -0
  118. data_engine/ui/gui/app.py +116 -0
  119. data_engine/ui/gui/bootstrap.py +487 -0
  120. data_engine/ui/gui/bootstrapper.py +140 -0
  121. data_engine/ui/gui/cache_models.py +23 -0
  122. data_engine/ui/gui/control_support.py +185 -0
  123. data_engine/ui/gui/controllers/__init__.py +6 -0
  124. data_engine/ui/gui/controllers/flows.py +439 -0
  125. data_engine/ui/gui/controllers/runtime.py +245 -0
  126. data_engine/ui/gui/dialogs/__init__.py +12 -0
  127. data_engine/ui/gui/dialogs/messages.py +88 -0
  128. data_engine/ui/gui/dialogs/previews.py +222 -0
  129. data_engine/ui/gui/helpers/__init__.py +62 -0
  130. data_engine/ui/gui/helpers/inspection.py +81 -0
  131. data_engine/ui/gui/helpers/lifecycle.py +112 -0
  132. data_engine/ui/gui/helpers/scroll.py +28 -0
  133. data_engine/ui/gui/helpers/theming.py +87 -0
  134. data_engine/ui/gui/icons/dark_light.svg +12 -0
  135. data_engine/ui/gui/icons/documentation.svg +1 -0
  136. data_engine/ui/gui/icons/failed.svg +3 -0
  137. data_engine/ui/gui/icons/group.svg +4 -0
  138. data_engine/ui/gui/icons/home.svg +2 -0
  139. data_engine/ui/gui/icons/manual.svg +2 -0
  140. data_engine/ui/gui/icons/poll.svg +2 -0
  141. data_engine/ui/gui/icons/schedule.svg +4 -0
  142. data_engine/ui/gui/icons/settings.svg +2 -0
  143. data_engine/ui/gui/icons/started.svg +3 -0
  144. data_engine/ui/gui/icons/success.svg +3 -0
  145. data_engine/ui/gui/icons/view-log.svg +3 -0
  146. data_engine/ui/gui/icons.py +50 -0
  147. data_engine/ui/gui/launcher.py +48 -0
  148. data_engine/ui/gui/presenters/__init__.py +72 -0
  149. data_engine/ui/gui/presenters/docs.py +140 -0
  150. data_engine/ui/gui/presenters/logs.py +58 -0
  151. data_engine/ui/gui/presenters/runtime_projection.py +29 -0
  152. data_engine/ui/gui/presenters/sidebar.py +88 -0
  153. data_engine/ui/gui/presenters/steps.py +148 -0
  154. data_engine/ui/gui/presenters/workspace.py +39 -0
  155. data_engine/ui/gui/presenters/workspace_binding.py +75 -0
  156. data_engine/ui/gui/presenters/workspace_settings.py +182 -0
  157. data_engine/ui/gui/preview_models.py +37 -0
  158. data_engine/ui/gui/render_support.py +241 -0
  159. data_engine/ui/gui/rendering/__init__.py +12 -0
  160. data_engine/ui/gui/rendering/artifacts.py +95 -0
  161. data_engine/ui/gui/rendering/icons.py +50 -0
  162. data_engine/ui/gui/runtime.py +47 -0
  163. data_engine/ui/gui/state_support.py +193 -0
  164. data_engine/ui/gui/support.py +214 -0
  165. data_engine/ui/gui/surface.py +209 -0
  166. data_engine/ui/gui/theme.py +720 -0
  167. data_engine/ui/gui/widgets/__init__.py +34 -0
  168. data_engine/ui/gui/widgets/config.py +41 -0
  169. data_engine/ui/gui/widgets/logs.py +62 -0
  170. data_engine/ui/gui/widgets/panels.py +507 -0
  171. data_engine/ui/gui/widgets/sidebar.py +130 -0
  172. data_engine/ui/gui/widgets/steps.py +84 -0
  173. data_engine/ui/tui/__init__.py +5 -0
  174. data_engine/ui/tui/app.py +222 -0
  175. data_engine/ui/tui/bootstrap.py +475 -0
  176. data_engine/ui/tui/bootstrapper.py +117 -0
  177. data_engine/ui/tui/controllers/__init__.py +6 -0
  178. data_engine/ui/tui/controllers/flows.py +349 -0
  179. data_engine/ui/tui/controllers/runtime.py +167 -0
  180. data_engine/ui/tui/runtime.py +34 -0
  181. data_engine/ui/tui/state_support.py +141 -0
  182. data_engine/ui/tui/support.py +63 -0
  183. data_engine/ui/tui/theme.py +204 -0
  184. data_engine/ui/tui/widgets.py +123 -0
  185. data_engine/views/__init__.py +109 -0
  186. data_engine/views/actions.py +80 -0
  187. data_engine/views/artifacts.py +58 -0
  188. data_engine/views/flow_display.py +69 -0
  189. data_engine/views/logs.py +54 -0
  190. data_engine/views/models.py +96 -0
  191. data_engine/views/presentation.py +133 -0
  192. data_engine/views/runs.py +62 -0
  193. data_engine/views/state.py +39 -0
  194. data_engine/views/status.py +13 -0
  195. data_engine/views/text.py +109 -0
  196. py_data_engine-0.1.0.dist-info/METADATA +330 -0
  197. py_data_engine-0.1.0.dist-info/RECORD +200 -0
  198. py_data_engine-0.1.0.dist-info/WHEEL +5 -0
  199. py_data_engine-0.1.0.dist-info/entry_points.txt +2 -0
  200. py_data_engine-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,241 @@
1
+ """Rendering and widget-presentation helpers for the GUI application shell."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from typing import TYPE_CHECKING
7
+
8
+ from PySide6.QtCore import Qt
9
+ from PySide6.QtGui import QColor, QIcon, QPixmap
10
+ from PySide6.QtWidgets import QFrame, QHBoxLayout, QLabel, QPushButton, QStyle, QWidget
11
+
12
+ from data_engine.domain import FlowLogEntry, RuntimeStepEvent
13
+ from data_engine.ui.gui.helpers import (
14
+ action_bar_icon as helper_action_bar_icon,
15
+ apply_theme as helper_apply_theme,
16
+ artifact_key_for_operation as helper_artifact_key_for_operation,
17
+ capture_step_outputs as helper_capture_step_outputs,
18
+ group_icon as helper_group_icon,
19
+ group_icon_color as helper_group_icon_color,
20
+ inspect_step_output as helper_inspect_step_output,
21
+ is_inspectable_operation as helper_is_inspectable_operation,
22
+ log_icon as helper_log_icon,
23
+ refresh_operation_buttons as helper_refresh_operation_buttons,
24
+ rehydrate_step_outputs_from_ledger as helper_rehydrate_step_outputs_from_ledger,
25
+ render_group_icon_pixmap as helper_render_group_icon_pixmap,
26
+ render_svg_icon_pixmap as helper_render_svg_icon_pixmap,
27
+ show_config_preview as helper_show_config_preview,
28
+ show_output_preview as helper_show_output_preview,
29
+ sync_theme_to_system as helper_sync_theme_to_system,
30
+ toggle_theme as helper_toggle_theme,
31
+ update_operation_scroll_cues as helper_update_operation_scroll_cues,
32
+ update_sidebar_scroll_cues as helper_update_sidebar_scroll_cues,
33
+ view_rail_icon as helper_view_rail_icon,
34
+ )
35
+ from data_engine.ui.gui.presenters import refresh_sidebar_selection, refresh_sidebar_state_views, repolish_widget_tree, set_hovered
36
+ from data_engine.ui.gui.presenters import (
37
+ apply_runtime_event as present_runtime_event,
38
+ duration_text as present_duration_text,
39
+ format_raw_log_message as present_format_raw_log_message,
40
+ format_seconds as present_format_seconds,
41
+ normalize_completed_operation_rows as present_normalize_completed_operation_rows,
42
+ refresh_live_operation_durations as present_refresh_live_operation_durations,
43
+ render_operation_durations as present_render_operation_durations,
44
+ reset_operation_state as present_reset_operation_state,
45
+ )
46
+ from data_engine.ui.gui.widgets import (
47
+ build_flow_row_widget,
48
+ build_group_row_widget,
49
+ format_operation_title as view_format_operation_title,
50
+ set_operation_cards as view_set_operation_cards,
51
+ )
52
+
53
+ if TYPE_CHECKING:
54
+ from data_engine.ui.gui.app import DataEngineWindow
55
+ from data_engine.views.models import QtFlowCard
56
+
57
+
58
+ class GuiRenderingMixin:
59
+ """Widget rendering and visual-state helpers for the GUI shell."""
60
+
61
+ def _format_raw_log_message(self: "DataEngineWindow", entry: FlowLogEntry) -> str:
62
+ return present_format_raw_log_message(entry)
63
+
64
+ def _set_operation_cards(self: "DataEngineWindow", operation_items: tuple[str, ...]) -> None:
65
+ view_set_operation_cards(self, operation_items)
66
+
67
+ def _update_operation_scroll_cues(self: "DataEngineWindow", *args) -> None:
68
+ helper_update_operation_scroll_cues(self, *args)
69
+
70
+ def _update_sidebar_scroll_cues(self: "DataEngineWindow", *args) -> None:
71
+ helper_update_sidebar_scroll_cues(self, *args)
72
+
73
+ def _format_operation_title(self: "DataEngineWindow", operation_name: str) -> str:
74
+ return view_format_operation_title(operation_name)
75
+
76
+ def _reset_operation_state(self: "DataEngineWindow", flow_name: str) -> None:
77
+ present_reset_operation_state(self, flow_name)
78
+
79
+ def _apply_runtime_event(self: "DataEngineWindow", event: RuntimeStepEvent) -> None:
80
+ present_runtime_event(self, event)
81
+
82
+ def _render_operation_durations(self: "DataEngineWindow", flow_name: str) -> None:
83
+ present_render_operation_durations(self, flow_name)
84
+
85
+ def _duration_text(self: "DataEngineWindow", flow_name: str, operation_name: str) -> str:
86
+ return present_duration_text(self, flow_name, operation_name)
87
+
88
+ def _refresh_live_operation_durations(self: "DataEngineWindow") -> None:
89
+ present_refresh_live_operation_durations(self)
90
+
91
+ def _apply_operation_row_state(self: "DataEngineWindow", row_card: QFrame, row_state) -> None:
92
+ status = row_state.status if row_state is not None else "idle"
93
+ if row_card.property("stepState") == status:
94
+ return
95
+ row_card.setProperty("stepState", status)
96
+ style = row_card.style()
97
+ style.unpolish(row_card)
98
+ style.polish(row_card)
99
+ row_card.update()
100
+
101
+ def _flash_operation_row(self: "DataEngineWindow", index: int) -> None:
102
+ from data_engine.ui.gui.presenters.steps import flash_operation_row
103
+
104
+ flash_operation_row(self, index)
105
+
106
+ def _normalize_completed_operation_rows(self: "DataEngineWindow", flow_name: str) -> None:
107
+ present_normalize_completed_operation_rows(self, flow_name)
108
+
109
+ def _format_seconds(self: "DataEngineWindow", seconds: float) -> str:
110
+ return present_format_seconds(seconds)
111
+
112
+ def _group_icon(self: "DataEngineWindow", group_name: str) -> QIcon:
113
+ return helper_group_icon(self, group_name)
114
+
115
+ def _group_icon_color(self: "DataEngineWindow") -> QColor:
116
+ return helper_group_icon_color(self)
117
+
118
+ def _render_svg_icon_pixmap(
119
+ self: "DataEngineWindow", icon_name: str, size: int, *, fill_color: str | None = None
120
+ ) -> QPixmap:
121
+ return helper_render_svg_icon_pixmap(self, icon_name, size, fill_color=fill_color)
122
+
123
+ def _view_rail_icon(self: "DataEngineWindow", view_name: str) -> QIcon:
124
+ return helper_view_rail_icon(self, view_name)
125
+
126
+ def _action_bar_icon(self: "DataEngineWindow", action_name: str) -> QIcon:
127
+ return helper_action_bar_icon(self, action_name)
128
+
129
+ def _log_icon(self: "DataEngineWindow", icon_name: str, size: int = 16) -> QIcon:
130
+ return helper_log_icon(self, icon_name, size)
131
+
132
+ def _render_group_icon_pixmap(self: "DataEngineWindow", group_name: str, size: int) -> QPixmap:
133
+ return helper_render_group_icon_pixmap(self, group_name, size)
134
+
135
+ def _build_group_row_widget(self: "DataEngineWindow", group_name: str, entries: list["QtFlowCard"]) -> QFrame:
136
+ return build_group_row_widget(self, group_name, entries)
137
+
138
+ def _build_flow_row_widget(self: "DataEngineWindow", card: "QtFlowCard") -> QFrame:
139
+ return build_flow_row_widget(self, card)
140
+
141
+ def _build_log_run_widget(self: "DataEngineWindow", run_group) -> QFrame:
142
+ frame = QFrame()
143
+ frame.setObjectName("logRunRow")
144
+ layout = QHBoxLayout(frame)
145
+ layout.setContentsMargins(8, 6, 8, 6)
146
+ layout.setSpacing(10)
147
+
148
+ title_row = QHBoxLayout()
149
+ title_row.setContentsMargins(0, 0, 0, 0)
150
+ title_row.setSpacing(8)
151
+ title = QLabel(run_group.display_label)
152
+ title.setObjectName("logPrimary")
153
+ title_row.addWidget(title, 0, Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter)
154
+ if run_group.elapsed_seconds is not None:
155
+ duration = QLabel(self._format_seconds(run_group.elapsed_seconds))
156
+ duration.setObjectName("logDuration")
157
+ title_row.addWidget(duration, 0, Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter)
158
+ title_row.addStretch(1)
159
+ layout.addLayout(title_row, 1)
160
+
161
+ status_name = "failed" if run_group.status in {"failed", "stopped"} else "started" if run_group.status == "started" else "finished"
162
+ status_icon = QLabel()
163
+ status_icon.setObjectName("logStatusIcon")
164
+ status_icon.setPixmap(self._render_svg_icon_pixmap(self._LOG_ICON_NAMES[status_name], 16, fill_color=self._LOG_ICON_COLORS[status_name]))
165
+ status_icon.setToolTip(run_group.status.title())
166
+ layout.addWidget(status_icon, 0, Qt.AlignmentFlag.AlignVCenter)
167
+
168
+ view_button = QPushButton()
169
+ view_button.setObjectName("logIconButton")
170
+ view_button.setIcon(self._log_icon("view_log"))
171
+ view_button.setIconSize(QPixmap(16, 16).size())
172
+ view_button.setToolTip("View Log")
173
+ view_button.clicked.connect(lambda _checked=False, group=run_group: self._show_run_log_preview(group))
174
+ layout.addWidget(view_button, 0, Qt.AlignmentFlag.AlignVCenter)
175
+
176
+ return frame
177
+
178
+ def _refresh_sidebar_selection(self: "DataEngineWindow") -> None:
179
+ refresh_sidebar_selection(self)
180
+
181
+ def _refresh_sidebar_state_views(self: "DataEngineWindow", changed_flow_names: set[str]) -> None:
182
+ """Refresh sidebar labels/colors in place for state-only changes."""
183
+ if refresh_sidebar_state_views(self, changed_flow_names):
184
+ self._populate_flow_tree()
185
+
186
+ def _set_hovered(self: "DataEngineWindow", widget: QFrame, hovered: bool) -> None:
187
+ """Update one sidebar row hover property and repolish it."""
188
+ set_hovered(widget, hovered)
189
+
190
+ def _repolish_widget_tree(self: "DataEngineWindow", widget: QWidget) -> None:
191
+ """Reapply stylesheet state to one widget and its child widgets."""
192
+ repolish_widget_tree(widget)
193
+
194
+ def _flow_icon(self: "DataEngineWindow", card: "QtFlowCard") -> QIcon:
195
+ """Return a left-side icon for one flow row."""
196
+ style = self.style()
197
+ state = self.flow_states.get(card.name, card.state)
198
+ if state == "failed":
199
+ return style.standardIcon(QStyle.StandardPixmap.SP_MessageBoxCritical)
200
+ if state in {"running", "polling", "scheduled"}:
201
+ return style.standardIcon(QStyle.StandardPixmap.SP_DialogApplyButton)
202
+ if state in {"stopping flow", "stopping runtime"}:
203
+ return style.standardIcon(QStyle.StandardPixmap.SP_BrowserStop)
204
+ if card.mode == "schedule":
205
+ return style.standardIcon(QStyle.StandardPixmap.SP_FileDialogDetailedView)
206
+ if card.mode == "poll":
207
+ return style.standardIcon(QStyle.StandardPixmap.SP_BrowserReload)
208
+ return style.standardIcon(QStyle.StandardPixmap.SP_FileIcon)
209
+
210
+ def _toggle_theme(self: "DataEngineWindow") -> None:
211
+ helper_toggle_theme(self)
212
+
213
+ def _sync_theme_to_system(self: "DataEngineWindow", *args) -> None:
214
+ helper_sync_theme_to_system(self, *args)
215
+
216
+ def _apply_theme(self: "DataEngineWindow") -> None:
217
+ helper_apply_theme(self)
218
+
219
+ def _is_inspectable_operation(self: "DataEngineWindow", operation_name: str) -> bool:
220
+ return helper_is_inspectable_operation(operation_name)
221
+
222
+ def _artifact_key_for_operation(self: "DataEngineWindow", operation_name: str) -> str | None:
223
+ return helper_artifact_key_for_operation(operation_name)
224
+
225
+ def _capture_step_outputs(self: "DataEngineWindow", flow_name: str, results: object) -> None:
226
+ helper_capture_step_outputs(self, flow_name, results)
227
+
228
+ def _rehydrate_step_outputs_from_ledger(self: "DataEngineWindow") -> None:
229
+ helper_rehydrate_step_outputs_from_ledger(self)
230
+
231
+ def _refresh_operation_buttons(self: "DataEngineWindow", flow_name: str) -> None:
232
+ helper_refresh_operation_buttons(self, flow_name)
233
+
234
+ def _inspect_step_output(self: "DataEngineWindow", operation_name: str) -> None:
235
+ helper_inspect_step_output(self, operation_name)
236
+
237
+ def _show_output_preview(self: "DataEngineWindow", operation_name: str, output_path: Path) -> None:
238
+ helper_show_output_preview(self, operation_name, output_path)
239
+
240
+ def _show_config_preview(self: "DataEngineWindow") -> None:
241
+ helper_show_config_preview(self)
@@ -0,0 +1,12 @@
1
+ """Rendering helpers for the desktop UI."""
2
+
3
+ from data_engine.ui.gui.rendering.artifacts import ArtifactPreviewSpec, classify_artifact_preview, populate_output_preview
4
+ from data_engine.ui.gui.rendering.icons import render_svg_icon_pixmap, theme_svg_paths
5
+
6
+ __all__ = [
7
+ "ArtifactPreviewSpec",
8
+ "classify_artifact_preview",
9
+ "populate_output_preview",
10
+ "render_svg_icon_pixmap",
11
+ "theme_svg_paths",
12
+ ]
@@ -0,0 +1,95 @@
1
+ """Artifact classification and preview rendering helpers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ import polars as pl
8
+ from PySide6.QtWidgets import (
9
+ QAbstractItemView,
10
+ QHeaderView,
11
+ QLabel,
12
+ QTableWidget,
13
+ QTableWidgetItem,
14
+ QTextEdit,
15
+ QVBoxLayout,
16
+ )
17
+
18
+ from data_engine.views import ArtifactPreviewSpec, classify_artifact_preview
19
+
20
+
21
+ def populate_output_preview(layout: QVBoxLayout, output_path: Path, preview_spec: ArtifactPreviewSpec | None = None) -> None:
22
+ """Populate one dialog layout with the appropriate artifact preview widgets."""
23
+ preview_spec = preview_spec or classify_artifact_preview(output_path)
24
+ if preview_spec.kind == "parquet":
25
+ _add_tabular_preview(layout, pl.read_parquet(output_path), preview_spec.label)
26
+ return
27
+ if preview_spec.kind == "excel":
28
+ _add_tabular_preview(layout, pl.read_excel(output_path, sheet_id=1, engine="calamine"), preview_spec.label)
29
+ return
30
+ if preview_spec.kind == "text":
31
+ _add_text_preview(layout, output_path, preview_spec.label)
32
+ return
33
+ if preview_spec.kind == "pdf":
34
+ _add_placeholder_preview(
35
+ layout,
36
+ heading=preview_spec.label,
37
+ message=preview_spec.placeholder_message or "PDF artifacts are recognized, but in-app PDF text inspection is not available yet.",
38
+ output_path=output_path,
39
+ )
40
+ return
41
+ _add_placeholder_preview(
42
+ layout,
43
+ heading=preview_spec.label,
44
+ message=preview_spec.placeholder_message or "This artifact type is not previewable in the UI yet.",
45
+ output_path=output_path,
46
+ )
47
+
48
+
49
+ def _add_tabular_preview(layout: QVBoxLayout, frame: pl.DataFrame, heading: str) -> None:
50
+ meta_label = QLabel(f"{heading} {frame.height} row(s) x {len(frame.columns)} column(s) Previewing up to 200 rows")
51
+ meta_label.setObjectName("sectionMeta")
52
+ layout.addWidget(meta_label)
53
+
54
+ table = QTableWidget()
55
+ table.setObjectName("outputPreviewTable")
56
+ table.setColumnCount(len(frame.columns))
57
+ table.setHorizontalHeaderLabels(frame.columns)
58
+ preview = frame.head(200)
59
+ table.setRowCount(preview.height)
60
+ table.setAlternatingRowColors(True)
61
+ table.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
62
+ table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
63
+ table.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
64
+ table.setShowGrid(False)
65
+ table.verticalHeader().setVisible(False)
66
+ table.horizontalHeader().setStretchLastSection(True)
67
+ table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.ResizeToContents)
68
+ for row_index in range(preview.height):
69
+ for column_index, column_name in enumerate(preview.columns):
70
+ table.setItem(row_index, column_index, QTableWidgetItem(str(preview[row_index, column_name])))
71
+ layout.addWidget(table, 1)
72
+
73
+
74
+ def _add_text_preview(layout: QVBoxLayout, output_path: Path, heading: str) -> None:
75
+ meta_label = QLabel(heading)
76
+ meta_label.setObjectName("sectionMeta")
77
+ layout.addWidget(meta_label)
78
+ body = QTextEdit()
79
+ body.setObjectName("outputPreviewText")
80
+ body.setReadOnly(True)
81
+ body.setPlainText(output_path.read_text(encoding="utf-8"))
82
+ layout.addWidget(body, 1)
83
+
84
+
85
+ def _add_placeholder_preview(layout: QVBoxLayout, *, heading: str, message: str, output_path: Path) -> None:
86
+ size_bytes = output_path.stat().st_size if output_path.exists() else 0
87
+ meta_label = QLabel(f"{heading} {size_bytes:,} bytes")
88
+ meta_label.setObjectName("sectionMeta")
89
+ layout.addWidget(meta_label)
90
+ body = QTextEdit()
91
+ body.setObjectName("outputPreviewText")
92
+ body.setReadOnly(True)
93
+ body.setPlainText(message)
94
+ layout.addWidget(body, 1)
95
+ __all__ = ["ArtifactPreviewSpec", "classify_artifact_preview", "populate_output_preview"]
@@ -0,0 +1,50 @@
1
+ """Theme-aware SVG icon rendering helpers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+
7
+ from PySide6.QtCore import QRectF, Qt
8
+ from PySide6.QtGui import QColor, QPainter, QPixmap
9
+ from PySide6.QtSvg import QSvgRenderer
10
+
11
+ from data_engine.ui.gui.icons import load_svg_icon_text
12
+
13
+
14
+ def theme_svg_paths(svg_text: str, fill: str) -> str:
15
+ """Apply one fill color to every SVG path element in one icon."""
16
+
17
+ def _replace(match: re.Match[str]) -> str:
18
+ attributes = re.sub(r'\sfill="[^"]*"', "", match.group(1))
19
+ return f'<path fill="{fill}"{attributes}>'
20
+
21
+ return re.sub(r"<path\b([^>]*)>", _replace, svg_text)
22
+
23
+
24
+ def render_svg_icon_pixmap(
25
+ *,
26
+ icon_name: str,
27
+ size: int,
28
+ device_pixel_ratio: float,
29
+ fill_color: str | None = None,
30
+ default_fill_color: QColor | str,
31
+ ) -> QPixmap:
32
+ """Render one registered SVG icon to one theme-aware pixmap."""
33
+ svg_text = load_svg_icon_text(icon_name)
34
+ if isinstance(default_fill_color, QColor):
35
+ default_fill = default_fill_color.name()
36
+ else:
37
+ default_fill = str(default_fill_color)
38
+ themed_svg = theme_svg_paths(svg_text, fill_color or default_fill)
39
+ renderer = QSvgRenderer(themed_svg.encode("utf-8"))
40
+ dpr = max(1.0, float(device_pixel_ratio))
41
+ pixmap = QPixmap(int(size * dpr), int(size * dpr))
42
+ pixmap.setDevicePixelRatio(dpr)
43
+ pixmap.fill(Qt.GlobalColor.transparent)
44
+ painter = QPainter(pixmap)
45
+ renderer.render(painter, QRectF(0, 0, size, size))
46
+ painter.end()
47
+ return pixmap
48
+
49
+
50
+ __all__ = ["render_svg_icon_pixmap", "theme_svg_paths"]
@@ -0,0 +1,47 @@
1
+ """Runtime wiring helpers for the Data Engine desktop UI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from queue import Queue
7
+
8
+ from PySide6.QtCore import QObject, Signal
9
+
10
+ from data_engine.domain import FlowLogEntry, format_log_line, parse_runtime_event
11
+
12
+
13
+ class QueueLogHandler(logging.Handler):
14
+ """Logging handler that forwards formatted runtime lines into a queue."""
15
+
16
+ def __init__(self, queue: Queue[FlowLogEntry]) -> None:
17
+ """Initialize the handler with the queue consumed by the UI thread."""
18
+ super().__init__(level=logging.INFO)
19
+ self.queue = queue
20
+
21
+ def emit(self, record: logging.LogRecord) -> None:
22
+ """Convert one log record into a UI entry and enqueue it."""
23
+ try:
24
+ event = parse_runtime_event(record)
25
+ kind = "flow" if event is not None and event.flow_name is not None else "system"
26
+ self.queue.put_nowait(
27
+ FlowLogEntry(
28
+ line=format_log_line(record),
29
+ kind=kind,
30
+ event=event,
31
+ flow_name=event.flow_name if event is not None else None,
32
+ )
33
+ )
34
+ except Exception:
35
+ self.handleError(record)
36
+
37
+
38
+ class UiSignals(QObject):
39
+ """Cross-thread Qt signals used by background runtime workers."""
40
+
41
+ run_finished = Signal(object, object, object)
42
+ runtime_finished = Signal(object, object, object)
43
+ docs_build_finished = Signal(bool, str)
44
+ daemon_startup_finished = Signal(bool, str)
45
+
46
+
47
+ __all__ = ["FlowLogEntry", "QueueLogHandler", "UiSignals"]
@@ -0,0 +1,193 @@
1
+ """Operator-session and log plumbing helpers for the GUI shell."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from typing import TYPE_CHECKING
7
+
8
+ from data_engine.domain import (
9
+ DaemonStatusState,
10
+ FlowLogEntry,
11
+ FlowCatalogState,
12
+ OperationSessionState,
13
+ OperatorSessionState,
14
+ RuntimeSessionState,
15
+ WorkspaceControlState,
16
+ WorkspaceSessionState,
17
+ )
18
+ from data_engine.ui.gui.surface import (
19
+ append_log_entry as surface_append_log_entry,
20
+ append_log_line as surface_append_log_line,
21
+ flush_deferred_ui_updates as surface_flush_deferred_ui_updates,
22
+ log_matches_selection as surface_log_matches_selection,
23
+ schedule_ui_refresh as surface_schedule_ui_refresh,
24
+ )
25
+ from data_engine.views.models import QtFlowCard, flow_catalog_entry_from_qt_card, qt_flow_cards_from_entries
26
+
27
+ if TYPE_CHECKING:
28
+ from data_engine.ui.gui.app import DataEngineWindow
29
+
30
+
31
+ class GuiStateMixin:
32
+ """Session-state and log-queue helpers separated from the main GUI shell."""
33
+
34
+ @property
35
+ def runtime_ledger(self: "DataEngineWindow"):
36
+ """Compatibility facade over the current runtime binding ledger."""
37
+ return self.runtime_binding.runtime_ledger
38
+
39
+ @property
40
+ def log_store(self: "DataEngineWindow"):
41
+ """Compatibility facade over the current runtime binding log store."""
42
+ return self.runtime_binding.log_store
43
+
44
+ @property
45
+ def _daemon_manager(self: "DataEngineWindow"):
46
+ """Compatibility facade over the current runtime binding daemon manager."""
47
+ return self.runtime_binding.daemon_manager
48
+
49
+ @property
50
+ def runtime_session(self: "DataEngineWindow") -> RuntimeSessionState:
51
+ """Return the current operator runtime/control session state."""
52
+ return self._operator_session_state.runtime
53
+
54
+ @runtime_session.setter
55
+ def runtime_session(self: "DataEngineWindow", value: RuntimeSessionState) -> None:
56
+ self._operator_session_state = self._operator_session_state.with_runtime(value)
57
+
58
+ @property
59
+ def flow_catalog_state(self: "DataEngineWindow") -> FlowCatalogState:
60
+ """Return the current discovered flow catalog state."""
61
+ return self._operator_session_state.catalog
62
+
63
+ @flow_catalog_state.setter
64
+ def flow_catalog_state(self: "DataEngineWindow", value: FlowCatalogState) -> None:
65
+ self._operator_session_state = self._operator_session_state.with_catalog(value)
66
+
67
+ @property
68
+ def flow_cards(self: "DataEngineWindow") -> dict[str, QtFlowCard]:
69
+ return {card.name: card for card in qt_flow_cards_from_entries(self.flow_catalog_state.entries)}
70
+
71
+ @flow_cards.setter
72
+ def flow_cards(self: "DataEngineWindow", value: dict[str, QtFlowCard] | tuple[QtFlowCard, ...]) -> None:
73
+ cards = tuple(value.values()) if isinstance(value, dict) else tuple(value)
74
+ self.flow_catalog_state = self.flow_catalog_state.with_entries(
75
+ tuple(flow_catalog_entry_from_qt_card(card) for card in cards)
76
+ )
77
+
78
+ @property
79
+ def flow_states(self: "DataEngineWindow") -> dict[str, str]:
80
+ return self.flow_catalog_state.flow_states or {}
81
+
82
+ @flow_states.setter
83
+ def flow_states(self: "DataEngineWindow", value: dict[str, str]) -> None:
84
+ self.flow_catalog_state = self.flow_catalog_state.with_flow_states(value)
85
+
86
+ @property
87
+ def selected_flow_name(self: "DataEngineWindow") -> str | None:
88
+ return self.flow_catalog_state.selected_flow_name
89
+
90
+ @selected_flow_name.setter
91
+ def selected_flow_name(self: "DataEngineWindow", value: str | None) -> None:
92
+ self.flow_catalog_state = self.flow_catalog_state.with_selected_flow_name(value)
93
+
94
+ @property
95
+ def empty_flow_message(self: "DataEngineWindow") -> str:
96
+ return self.flow_catalog_state.empty_message
97
+
98
+ @empty_flow_message.setter
99
+ def empty_flow_message(self: "DataEngineWindow", value: str) -> None:
100
+ self.flow_catalog_state = self.flow_catalog_state.with_empty_message(value)
101
+
102
+ @property
103
+ def daemon_status(self: "DataEngineWindow") -> DaemonStatusState:
104
+ """Return the current daemon status domain model."""
105
+ return self._daemon_status
106
+
107
+ @daemon_status.setter
108
+ def daemon_status(self: "DataEngineWindow", value: DaemonStatusState) -> None:
109
+ self._daemon_status = value
110
+
111
+ @property
112
+ def workspace_control_state(self: "DataEngineWindow") -> WorkspaceControlState:
113
+ """Return the current structured workspace control state."""
114
+ return self._operator_session_state.workspace_control
115
+
116
+ @workspace_control_state.setter
117
+ def workspace_control_state(self: "DataEngineWindow", value: WorkspaceControlState) -> None:
118
+ self._operator_session_state = self._operator_session_state.with_workspace_control(value)
119
+
120
+ @property
121
+ def workspace_session_state(self: "DataEngineWindow") -> WorkspaceSessionState:
122
+ """Return the current workspace selection/root session state."""
123
+ return self._operator_session_state.workspace
124
+
125
+ @workspace_session_state.setter
126
+ def workspace_session_state(self: "DataEngineWindow", value: WorkspaceSessionState) -> None:
127
+ self._operator_session_state = self._operator_session_state.with_workspace(value)
128
+
129
+ @property
130
+ def operator_session_state(self: "DataEngineWindow") -> OperatorSessionState:
131
+ """Return the top-level operator session state for this surface."""
132
+ return self._operator_session_state
133
+
134
+ @property
135
+ def operation_tracker(self: "DataEngineWindow") -> OperationSessionState:
136
+ """Return the current operation/step session state."""
137
+ return self._operator_session_state.operations
138
+
139
+ @operation_tracker.setter
140
+ def operation_tracker(self: "DataEngineWindow", value: OperationSessionState) -> None:
141
+ self._operator_session_state = self._operator_session_state.with_operations(value)
142
+
143
+ @property
144
+ def docs_build_running(self: "DataEngineWindow") -> bool:
145
+ """Return whether the documentation build is currently active for this GUI window."""
146
+ return self._docs_build_running
147
+
148
+ @docs_build_running.setter
149
+ def docs_build_running(self: "DataEngineWindow", value: bool) -> None:
150
+ self._docs_build_running = bool(value)
151
+
152
+ @property
153
+ def docs_root_dir(self: "DataEngineWindow") -> Path | None:
154
+ """Return the current built-docs root directory for this GUI window, if available."""
155
+ return self._docs_root_dir
156
+
157
+ @docs_root_dir.setter
158
+ def docs_root_dir(self: "DataEngineWindow", value: Path | None) -> None:
159
+ self._docs_root_dir = value
160
+
161
+ @property
162
+ def workspace_collection_root_override(self: "DataEngineWindow") -> Path | None:
163
+ return self.workspace_session_state.workspace_collection_root_override
164
+
165
+ @workspace_collection_root_override.setter
166
+ def workspace_collection_root_override(self: "DataEngineWindow", value: Path | None) -> None:
167
+ self.workspace_session_state = self.workspace_session_state.with_override_root(value)
168
+
169
+ @property
170
+ def discovered_workspace_ids(self: "DataEngineWindow") -> tuple[str, ...]:
171
+ return self.workspace_session_state.discovered_workspace_ids
172
+
173
+ @discovered_workspace_ids.setter
174
+ def discovered_workspace_ids(self: "DataEngineWindow", value: tuple[str, ...]) -> None:
175
+ self.workspace_session_state = self.workspace_session_state.with_discovered_workspace_ids(value)
176
+
177
+ def _log_matches_selection(self: "DataEngineWindow", entry: FlowLogEntry) -> bool:
178
+ return surface_log_matches_selection(self, entry)
179
+
180
+ def _append_log_entry(self: "DataEngineWindow", entry: FlowLogEntry) -> None:
181
+ surface_append_log_entry(self, entry)
182
+
183
+ def _schedule_ui_refresh(self: "DataEngineWindow", *, log_view: bool = False, action_buttons: bool = False) -> None:
184
+ surface_schedule_ui_refresh(self, log_view=log_view, action_buttons=action_buttons)
185
+
186
+ def _flush_deferred_ui_updates(self: "DataEngineWindow") -> None:
187
+ surface_flush_deferred_ui_updates(self)
188
+
189
+ def _append_log_line(self: "DataEngineWindow", line: str, *, flow_name: str | None = None) -> None:
190
+ surface_append_log_line(self, line, flow_name=flow_name)
191
+
192
+
193
+ __all__ = ["GuiStateMixin"]