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,214 @@
1
+ """Mixin support methods for the GUI application shell."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from time import monotonic
7
+ from typing import TYPE_CHECKING
8
+
9
+ from PySide6.QtWidgets import QWidget
10
+
11
+ from data_engine.platform.workspace_models import authored_workspace_is_available
12
+ from data_engine.ui.gui.dialogs import show_message_box, structured_error_content
13
+ from data_engine.ui.gui.helpers import (
14
+ is_last_process_ui_window as helper_is_last_process_ui_window,
15
+ )
16
+ from data_engine.ui.gui.presenters import (
17
+ browse_workspace_collection_root_override as present_browse_workspace_collection_root_override,
18
+ create_docs_browser as present_create_docs_browser,
19
+ docs_build_dir as present_docs_build_dir,
20
+ force_shutdown_daemon as present_force_shutdown_daemon,
21
+ finish_docs_build as present_finish_docs_build,
22
+ initialize_docs_view as present_initialize_docs_view,
23
+ load_docs_page as present_load_docs_page,
24
+ provision_selected_workspace as present_provision_selected_workspace,
25
+ rebind_workspace_context as present_rebind_workspace_context,
26
+ refresh_workspace_visibility_panel as present_refresh_workspace_visibility_panel,
27
+ refresh_workspace_root_controls as present_refresh_workspace_root_controls,
28
+ reset_workspace_collection_root_override as present_reset_workspace_collection_root_override,
29
+ run_docs_build_worker as present_run_docs_build_worker,
30
+ save_workspace_collection_root_override as present_save_workspace_collection_root_override,
31
+ start_docs_build as present_start_docs_build,
32
+ )
33
+ from data_engine.ui.gui.surface import show_message_box_later as surface_show_message_box_later
34
+ from data_engine.views.state import build_flow_summary
35
+
36
+ if TYPE_CHECKING:
37
+ from data_engine.ui.gui.app import DataEngineWindow
38
+ from data_engine.views.models import QtFlowCard
39
+
40
+
41
+ class GuiWindowSupportMixin:
42
+ """Shared window-support methods kept separate from the main GUI app shell."""
43
+
44
+ def _resolve_workspace_paths(
45
+ self: "DataEngineWindow",
46
+ *,
47
+ workspace_id: str | None = None,
48
+ workspace_collection_root: Path | None | object = ...,
49
+ ):
50
+ """Resolve workspace paths using the current machine-local collection-root override."""
51
+ if workspace_collection_root is ...:
52
+ workspace_collection_root = self.workspace_collection_root_override
53
+ return self.workspace_service.resolve_paths(
54
+ workspace_id=workspace_id,
55
+ workspace_collection_root=workspace_collection_root,
56
+ )
57
+
58
+ def _daemon_request(self: "DataEngineWindow", paths, payload, *, timeout: float = 0.0):
59
+ """Send one request to the local workspace daemon."""
60
+ return self.daemon_service.request(paths, payload, timeout=timeout)
61
+
62
+ def _is_daemon_live(self: "DataEngineWindow", paths) -> bool:
63
+ """Return whether the local workspace daemon is currently reachable."""
64
+ return self.daemon_service.is_live(paths)
65
+
66
+ def _daemon_client_error_type(self: "DataEngineWindow"):
67
+ """Return the daemon client error type used by this module."""
68
+ return self.daemon_service.client_error_type
69
+
70
+ def _unregister_client_session_and_check_for_shutdown(
71
+ self: "DataEngineWindow", *, purge_process_ui_sessions: bool
72
+ ) -> bool:
73
+ """Unregister this window's client session and report whether daemon shutdown is needed."""
74
+ from data_engine.ui.gui.helpers import unregister_client_session_and_check_for_shutdown as helper_unregister
75
+
76
+ return helper_unregister(self, purge_process_ui_sessions=purge_process_ui_sessions)
77
+
78
+ def _is_last_process_ui_window(self: "DataEngineWindow") -> bool:
79
+ """Return whether this is the last live GUI window in the current process."""
80
+ return helper_is_last_process_ui_window(self)
81
+
82
+ def _shutdown_daemon_on_close(self: "DataEngineWindow") -> None:
83
+ """Shut down the daemon as part of final GUI process teardown."""
84
+ from data_engine.ui.gui.helpers import shutdown_daemon_on_close as helper_shutdown
85
+
86
+ helper_shutdown(self)
87
+
88
+ def _wait_for_worker_threads(self: "DataEngineWindow", *, timeout_seconds: float) -> None:
89
+ """Join outstanding GUI worker threads during shutdown."""
90
+ from data_engine.ui.gui.helpers import wait_for_worker_threads as helper_wait
91
+
92
+ helper_wait(self, timeout_seconds=timeout_seconds)
93
+
94
+ def _register_worker_thread(self: "DataEngineWindow", thread) -> None:
95
+ """Track one GUI worker thread under the window-local lock."""
96
+ if not hasattr(self, "_worker_threads_lock") or not hasattr(self, "_worker_threads"):
97
+ return
98
+ with self._worker_threads_lock:
99
+ self._worker_threads.add(thread)
100
+
101
+ def _discard_worker_thread(self: "DataEngineWindow", thread) -> None:
102
+ """Stop tracking one GUI worker thread under the window-local lock."""
103
+ if not hasattr(self, "_worker_threads_lock") or not hasattr(self, "_worker_threads"):
104
+ return
105
+ with self._worker_threads_lock:
106
+ self._worker_threads.discard(thread)
107
+
108
+ def _worker_threads_snapshot(self: "DataEngineWindow") -> tuple:
109
+ """Return a stable snapshot of tracked GUI worker threads."""
110
+ if not hasattr(self, "_worker_threads_lock") or not hasattr(self, "_worker_threads"):
111
+ return ()
112
+ with self._worker_threads_lock:
113
+ return tuple(self._worker_threads)
114
+
115
+ def _switch_view(self: "DataEngineWindow", index: int) -> None:
116
+ self.view_stack.setCurrentIndex(index)
117
+ if hasattr(self, "workspace_counts_footer_label"):
118
+ self.workspace_counts_footer_label.setVisible(index == 0)
119
+
120
+ def _monotonic(self: "DataEngineWindow") -> float:
121
+ """Return the current monotonic clock value."""
122
+ return monotonic()
123
+
124
+ def _structured_error_content(self: "DataEngineWindow", text: str):
125
+ """Parse one developer-facing flow-module error into dialog sections when possible."""
126
+ return structured_error_content(text)
127
+
128
+ def _show_message_box(self: "DataEngineWindow", *, title: str, text: str, tone: str) -> None:
129
+ """Show one simple application dialog for info/error messages."""
130
+ show_message_box(self, title=title, text=text, tone=tone)
131
+
132
+ def _show_message_box_later(self: "DataEngineWindow", *, title: str, text: str, tone: str) -> None:
133
+ """Defer one application dialog until the current UI update cycle completes."""
134
+ surface_show_message_box_later(self, title=title, text=text, tone=tone)
135
+
136
+ def _docs_source_dir(self: "DataEngineWindow") -> Path:
137
+ """Return the authored Sphinx source directory."""
138
+ return self.workspace_paths.sphinx_source_dir
139
+
140
+ def _docs_build_dir(self: "DataEngineWindow") -> Path:
141
+ """Return the generated Sphinx HTML output directory."""
142
+ return present_docs_build_dir(self)
143
+
144
+ def _refresh_workspace_root_controls(self: "DataEngineWindow") -> None:
145
+ """Refresh local workspace-root override copy in the Settings view."""
146
+ present_refresh_workspace_root_controls(self)
147
+
148
+ def _rebind_workspace_context(self: "DataEngineWindow", *, workspace_id: str | None = None) -> None:
149
+ """Re-resolve paths and rebuild local UI state after workspace settings change."""
150
+ present_rebind_workspace_context(self, workspace_id=workspace_id)
151
+
152
+ def _save_workspace_collection_root_override(self: "DataEngineWindow") -> None:
153
+ """Persist one machine-local workspace collection root override."""
154
+ present_save_workspace_collection_root_override(self)
155
+
156
+ def _reset_workspace_collection_root_override(self: "DataEngineWindow") -> None:
157
+ """Clear the machine-local workspace collection root override."""
158
+ present_reset_workspace_collection_root_override(self)
159
+
160
+ def _browse_workspace_collection_root_override(self: "DataEngineWindow") -> None:
161
+ """Open a folder picker for the local workspace collection root override."""
162
+ present_browse_workspace_collection_root_override(self)
163
+
164
+ def _provision_selected_workspace(self: "DataEngineWindow") -> None:
165
+ """Provision the selected authored workspace without overwriting existing files."""
166
+ present_provision_selected_workspace(self)
167
+
168
+ def _force_shutdown_daemon(self: "DataEngineWindow") -> None:
169
+ """Force-stop the local workspace daemon for the selected workspace."""
170
+ present_force_shutdown_daemon(self)
171
+
172
+ def _refresh_workspace_visibility_panel(self: "DataEngineWindow") -> None:
173
+ """Refresh the read-only workspace visibility stats shown in Settings."""
174
+ present_refresh_workspace_visibility_panel(self)
175
+
176
+ def _create_docs_browser(self: "DataEngineWindow") -> QWidget:
177
+ return present_create_docs_browser(self)
178
+
179
+ def _initialize_docs_view(self: "DataEngineWindow") -> None:
180
+ present_initialize_docs_view(self)
181
+
182
+ def _start_docs_build(self: "DataEngineWindow") -> None:
183
+ """Launch the Sphinx HTML build in a background thread."""
184
+ present_start_docs_build(self)
185
+
186
+ def _run_docs_build_worker(self: "DataEngineWindow") -> None:
187
+ """Build the authored Sphinx site and report completion back to the UI thread."""
188
+ present_run_docs_build_worker(self)
189
+
190
+ def _finish_docs_build(self: "DataEngineWindow", succeeded: bool, message: str) -> None:
191
+ """Refresh the Docs tab after a build completes."""
192
+ present_finish_docs_build(self, succeeded, message)
193
+
194
+ def _load_docs_page(self: "DataEngineWindow", file_name: str) -> None:
195
+ present_load_docs_page(self, file_name)
196
+
197
+ def _config_summary(self: "DataEngineWindow", card: "QtFlowCard | None"):
198
+ from data_engine.domain import ConfigPreviewState
199
+
200
+ return ConfigPreviewState.from_flow(card, self.flow_states)
201
+
202
+ def _has_authored_workspace(self: "DataEngineWindow") -> bool:
203
+ """Return whether the selected workspace currently has authored flow modules."""
204
+ return authored_workspace_is_available(self.workspace_paths)
205
+
206
+ def _is_bootstrap_ready_error(self: "DataEngineWindow", message: str) -> bool:
207
+ """Return whether one flow-load error is expected before bootstrap."""
208
+ return "No flow modules discovered" in message or ("Flow module" in message and "is not available" in message)
209
+
210
+ def _empty_flow_message_for_error(self: "DataEngineWindow", message: str) -> str:
211
+ """Return the UI empty-state copy for one flow-load failure."""
212
+ if self._is_bootstrap_ready_error(message):
213
+ return "No discoverable flows were found yet in this workspace folder."
214
+ return message
@@ -0,0 +1,209 @@
1
+ """Surface-level helper functions for the GUI application shell."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from queue import Empty
7
+ from typing import TYPE_CHECKING
8
+
9
+ from PySide6.QtCore import QTimer
10
+
11
+ from data_engine.ui.gui.bootstrap import GuiServices, build_gui_services, default_gui_service_kwargs
12
+
13
+ if TYPE_CHECKING:
14
+ from PySide6.QtWidgets import QCloseEvent, QShowEvent
15
+
16
+ from data_engine.domain import FlowLogEntry
17
+ from data_engine.ui.gui.app import DataEngineWindow
18
+ def build_default_gui_services(theme_name: str) -> GuiServices:
19
+ """Build the default GUI service bundle for the main window."""
20
+ return build_gui_services(
21
+ **default_gui_service_kwargs(theme_name),
22
+ )
23
+
24
+
25
+ def handle_show_event(window: "DataEngineWindow", event: "QShowEvent") -> None:
26
+ """Run the GUI show-event side effects."""
27
+ super(type(window), window).showEvent(event)
28
+ if not window._auto_daemon_enabled:
29
+ window._auto_daemon_enabled = True
30
+ QTimer.singleShot(0, window._ensure_daemon_started)
31
+
32
+
33
+ def handle_close_event(window: "DataEngineWindow", event: "QCloseEvent") -> None:
34
+ """Run the GUI close-event shutdown path."""
35
+ window.ui_closing = True
36
+ log_handler = getattr(window, "log_handler", None)
37
+ if log_handler is not None:
38
+ logging.getLogger("data_engine").removeHandler(log_handler)
39
+ runtime_stop_event = getattr(window, "engine_runtime_stop_event", None)
40
+ if runtime_stop_event is not None:
41
+ runtime_stop_event.set()
42
+ flow_stop_event = getattr(window, "engine_flow_stop_event", None)
43
+ if flow_stop_event is not None:
44
+ flow_stop_event.set()
45
+ for stop_event in getattr(window, "manual_flow_stop_events", {}).values():
46
+ stop_event.set()
47
+ if hasattr(window, "log_timer"):
48
+ window.log_timer.stop()
49
+ if hasattr(window, "ui_refresh_timer"):
50
+ window.ui_refresh_timer.stop()
51
+ if hasattr(window, "operation_timer"):
52
+ window.operation_timer.stop()
53
+ if hasattr(window, "daemon_timer"):
54
+ window.daemon_timer.stop()
55
+ if hasattr(window, "_unregister_client_session_and_check_for_shutdown") and hasattr(window, "_is_last_process_ui_window"):
56
+ should_shutdown_daemon = window._unregister_client_session_and_check_for_shutdown(
57
+ purge_process_ui_sessions=window._is_last_process_ui_window(),
58
+ )
59
+ if should_shutdown_daemon and hasattr(window, "_shutdown_daemon_on_close"):
60
+ window._shutdown_daemon_on_close()
61
+ if hasattr(window, "_wait_for_worker_threads"):
62
+ window._wait_for_worker_threads(timeout_seconds=1.5)
63
+ if hasattr(window, "runtime_binding_service") and hasattr(window, "runtime_binding"):
64
+ window.runtime_binding_service.close_binding(window.runtime_binding)
65
+ super(type(window), window).closeEvent(event)
66
+
67
+
68
+ def show_message_box_later(window: "DataEngineWindow", *, title: str, text: str, tone: str) -> None:
69
+ """Defer one application dialog until the current UI update cycle completes."""
70
+ if window.ui_closing:
71
+ return
72
+ if not str(text).strip():
73
+ return
74
+ window._pending_message_box = (title, text, tone)
75
+ if window._message_box_scheduled or window._message_box_open:
76
+ return
77
+ generation = window._message_box_generation
78
+
79
+ def _show_pending_message_box() -> None:
80
+ if generation != window._message_box_generation:
81
+ return
82
+ window._message_box_scheduled = False
83
+ if window.ui_closing:
84
+ return
85
+ payload = window._pending_message_box
86
+ window._pending_message_box = None
87
+ if payload is None:
88
+ return
89
+ next_title, next_text, next_tone = payload
90
+ window._message_box_open = True
91
+ try:
92
+ window._show_message_box(title=next_title, text=next_text, tone=next_tone)
93
+ finally:
94
+ window._message_box_open = False
95
+ if (
96
+ generation == window._message_box_generation
97
+ and window._pending_message_box is not None
98
+ and not window.ui_closing
99
+ ):
100
+ show_message_box_later(
101
+ window,
102
+ title=window._pending_message_box[0],
103
+ text=window._pending_message_box[1],
104
+ tone=window._pending_message_box[2],
105
+ )
106
+
107
+ window._message_box_scheduled = True
108
+ QTimer.singleShot(0, _show_pending_message_box)
109
+
110
+
111
+ def log_matches_selection(window: "DataEngineWindow", entry: "FlowLogEntry") -> bool:
112
+ """Return whether one log entry belongs to the currently selected flow."""
113
+ return entry.kind == "flow" and window.selected_flow_name is not None and entry.flow_name == window.selected_flow_name
114
+
115
+
116
+ def append_log_entry(window: "DataEngineWindow", entry: "FlowLogEntry") -> None:
117
+ """Append one log entry and schedule affected UI refresh work."""
118
+ window.log_service.append_entry(window.runtime_binding.log_store, entry)
119
+ if log_matches_selection(window, entry):
120
+ schedule_ui_refresh(window, log_view=True, action_buttons=True)
121
+
122
+
123
+ def schedule_ui_refresh(window: "DataEngineWindow", *, log_view: bool = False, action_buttons: bool = False) -> None:
124
+ """Schedule one deferred GUI refresh cycle."""
125
+ if log_view:
126
+ window._log_view_refresh_pending = True
127
+ if action_buttons:
128
+ window._action_buttons_refresh_pending = True
129
+ if not window.ui_refresh_timer.isActive():
130
+ window.ui_refresh_timer.start(0)
131
+
132
+
133
+ def flush_deferred_ui_updates(window: "DataEngineWindow") -> None:
134
+ """Flush any deferred GUI refresh work."""
135
+ if window._log_view_refresh_pending:
136
+ window._log_view_refresh_pending = False
137
+ window._refresh_log_view()
138
+ if window._action_buttons_refresh_pending:
139
+ window._action_buttons_refresh_pending = False
140
+ window._refresh_action_buttons()
141
+
142
+
143
+ def append_log_line(window: "DataEngineWindow", line: str, *, flow_name: str | None = None) -> None:
144
+ """Append one simple textual log line."""
145
+ from data_engine.domain import FlowLogEntry
146
+
147
+ kind = "flow" if flow_name is not None else "system"
148
+ append_log_entry(window, FlowLogEntry(line=line, kind=kind, flow_name=flow_name))
149
+
150
+
151
+ def poll_log_queue(window: "DataEngineWindow") -> None:
152
+ """Drain queued runtime log entries and schedule the minimum UI refresh work."""
153
+ selected_flow_dirty = False
154
+ action_buttons_dirty = False
155
+ processed = 0
156
+ while processed < window._MAX_LOG_EVENTS_PER_TICK:
157
+ try:
158
+ entry = window.log_queue.get_nowait()
159
+ except Empty:
160
+ break
161
+ window.log_service.append_entry(window.runtime_binding.log_store, entry)
162
+ if log_matches_selection(window, entry):
163
+ selected_flow_dirty = True
164
+ action_buttons_dirty = True
165
+ elif entry.kind == "flow":
166
+ action_buttons_dirty = True
167
+ if entry.event is not None:
168
+ window._apply_runtime_event(entry.event)
169
+ processed += 1
170
+ if selected_flow_dirty or action_buttons_dirty:
171
+ schedule_ui_refresh(window, log_view=selected_flow_dirty, action_buttons=action_buttons_dirty)
172
+ if not window.log_queue.empty() and not window.ui_closing:
173
+ QTimer.singleShot(0, window._poll_log_queue)
174
+
175
+
176
+ def safe_emit_run_finished(window: "DataEngineWindow", flow_name: str, results: object, error: object) -> None:
177
+ """Emit the queued run-finished signal unless the GUI is closing."""
178
+ if window.ui_closing:
179
+ return
180
+ try:
181
+ window.signals.run_finished.emit(flow_name, results, error)
182
+ except RuntimeError:
183
+ pass
184
+
185
+
186
+ def safe_emit_runtime_finished(window: "DataEngineWindow", flow_names: tuple[str, ...], results: object, error: object) -> None:
187
+ """Emit the queued runtime-finished signal unless the GUI is closing."""
188
+ if window.ui_closing:
189
+ return
190
+ try:
191
+ window.signals.runtime_finished.emit(flow_names, results, error)
192
+ except RuntimeError:
193
+ pass
194
+
195
+
196
+ __all__ = [
197
+ "append_log_entry",
198
+ "append_log_line",
199
+ "build_default_gui_services",
200
+ "flush_deferred_ui_updates",
201
+ "handle_close_event",
202
+ "handle_show_event",
203
+ "log_matches_selection",
204
+ "poll_log_queue",
205
+ "safe_emit_run_finished",
206
+ "safe_emit_runtime_finished",
207
+ "schedule_ui_refresh",
208
+ "show_message_box_later",
209
+ ]