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.
- data_engine/__init__.py +37 -0
- data_engine/application/__init__.py +39 -0
- data_engine/application/actions.py +42 -0
- data_engine/application/catalog.py +151 -0
- data_engine/application/control.py +213 -0
- data_engine/application/details.py +73 -0
- data_engine/application/runtime.py +449 -0
- data_engine/application/workspace.py +62 -0
- data_engine/authoring/__init__.py +14 -0
- data_engine/authoring/builder.py +31 -0
- data_engine/authoring/execution/__init__.py +6 -0
- data_engine/authoring/execution/app.py +6 -0
- data_engine/authoring/execution/context.py +82 -0
- data_engine/authoring/execution/continuous.py +176 -0
- data_engine/authoring/execution/grouped.py +106 -0
- data_engine/authoring/execution/logging.py +83 -0
- data_engine/authoring/execution/polling.py +135 -0
- data_engine/authoring/execution/runner.py +210 -0
- data_engine/authoring/execution/single.py +171 -0
- data_engine/authoring/flow.py +361 -0
- data_engine/authoring/helpers.py +160 -0
- data_engine/authoring/model.py +59 -0
- data_engine/authoring/primitives.py +430 -0
- data_engine/authoring/services.py +42 -0
- data_engine/devtools/__init__.py +3 -0
- data_engine/devtools/project_ast_map.py +503 -0
- data_engine/docs/__init__.py +1 -0
- data_engine/docs/sphinx_source/_static/custom.css +13 -0
- data_engine/docs/sphinx_source/api.rst +42 -0
- data_engine/docs/sphinx_source/conf.py +37 -0
- data_engine/docs/sphinx_source/guides/app-runtime-and-workspaces.md +397 -0
- data_engine/docs/sphinx_source/guides/authoring-flow-modules.md +215 -0
- data_engine/docs/sphinx_source/guides/configuring-flows.md +185 -0
- data_engine/docs/sphinx_source/guides/core-concepts.md +208 -0
- data_engine/docs/sphinx_source/guides/database-methods.md +107 -0
- data_engine/docs/sphinx_source/guides/duckdb-helpers.md +462 -0
- data_engine/docs/sphinx_source/guides/flow-context.md +538 -0
- data_engine/docs/sphinx_source/guides/flow-methods.md +206 -0
- data_engine/docs/sphinx_source/guides/getting-started.md +271 -0
- data_engine/docs/sphinx_source/guides/project-inventory.md +5683 -0
- data_engine/docs/sphinx_source/guides/project-map.md +118 -0
- data_engine/docs/sphinx_source/guides/recipes.md +268 -0
- data_engine/docs/sphinx_source/index.rst +22 -0
- data_engine/domain/__init__.py +92 -0
- data_engine/domain/actions.py +69 -0
- data_engine/domain/catalog.py +128 -0
- data_engine/domain/details.py +214 -0
- data_engine/domain/diagnostics.py +56 -0
- data_engine/domain/errors.py +104 -0
- data_engine/domain/inspection.py +99 -0
- data_engine/domain/logs.py +118 -0
- data_engine/domain/operations.py +172 -0
- data_engine/domain/operator.py +72 -0
- data_engine/domain/runs.py +155 -0
- data_engine/domain/runtime.py +279 -0
- data_engine/domain/source_state.py +17 -0
- data_engine/domain/support.py +54 -0
- data_engine/domain/time.py +23 -0
- data_engine/domain/workspace.py +159 -0
- data_engine/flow_modules/__init__.py +1 -0
- data_engine/flow_modules/flow_module_compiler.py +179 -0
- data_engine/flow_modules/flow_module_loader.py +201 -0
- data_engine/helpers/__init__.py +25 -0
- data_engine/helpers/duckdb.py +705 -0
- data_engine/hosts/__init__.py +1 -0
- data_engine/hosts/daemon/__init__.py +23 -0
- data_engine/hosts/daemon/app.py +221 -0
- data_engine/hosts/daemon/bootstrap.py +69 -0
- data_engine/hosts/daemon/client.py +465 -0
- data_engine/hosts/daemon/commands.py +64 -0
- data_engine/hosts/daemon/composition.py +310 -0
- data_engine/hosts/daemon/constants.py +15 -0
- data_engine/hosts/daemon/entrypoints.py +97 -0
- data_engine/hosts/daemon/lifecycle.py +191 -0
- data_engine/hosts/daemon/manager.py +272 -0
- data_engine/hosts/daemon/ownership.py +126 -0
- data_engine/hosts/daemon/runtime_commands.py +188 -0
- data_engine/hosts/daemon/runtime_control.py +31 -0
- data_engine/hosts/daemon/server.py +84 -0
- data_engine/hosts/daemon/shared_state.py +147 -0
- data_engine/hosts/daemon/state_sync.py +101 -0
- data_engine/platform/__init__.py +1 -0
- data_engine/platform/identity.py +35 -0
- data_engine/platform/local_settings.py +146 -0
- data_engine/platform/theme.py +259 -0
- data_engine/platform/workspace_models.py +190 -0
- data_engine/platform/workspace_policy.py +333 -0
- data_engine/runtime/__init__.py +1 -0
- data_engine/runtime/file_watch.py +185 -0
- data_engine/runtime/ledger_models.py +116 -0
- data_engine/runtime/runtime_db.py +938 -0
- data_engine/runtime/shared_state.py +523 -0
- data_engine/services/__init__.py +49 -0
- data_engine/services/daemon.py +64 -0
- data_engine/services/daemon_state.py +40 -0
- data_engine/services/flow_catalog.py +102 -0
- data_engine/services/flow_execution.py +48 -0
- data_engine/services/ledger.py +85 -0
- data_engine/services/logs.py +65 -0
- data_engine/services/runtime_binding.py +105 -0
- data_engine/services/runtime_execution.py +126 -0
- data_engine/services/runtime_history.py +62 -0
- data_engine/services/settings.py +58 -0
- data_engine/services/shared_state.py +28 -0
- data_engine/services/theme.py +59 -0
- data_engine/services/workspace_provisioning.py +224 -0
- data_engine/services/workspaces.py +74 -0
- data_engine/ui/__init__.py +3 -0
- data_engine/ui/cli/__init__.py +19 -0
- data_engine/ui/cli/app.py +161 -0
- data_engine/ui/cli/commands_doctor.py +178 -0
- data_engine/ui/cli/commands_run.py +80 -0
- data_engine/ui/cli/commands_start.py +100 -0
- data_engine/ui/cli/commands_workspace.py +97 -0
- data_engine/ui/cli/dependencies.py +44 -0
- data_engine/ui/cli/parser.py +56 -0
- data_engine/ui/gui/__init__.py +25 -0
- data_engine/ui/gui/app.py +116 -0
- data_engine/ui/gui/bootstrap.py +487 -0
- data_engine/ui/gui/bootstrapper.py +140 -0
- data_engine/ui/gui/cache_models.py +23 -0
- data_engine/ui/gui/control_support.py +185 -0
- data_engine/ui/gui/controllers/__init__.py +6 -0
- data_engine/ui/gui/controllers/flows.py +439 -0
- data_engine/ui/gui/controllers/runtime.py +245 -0
- data_engine/ui/gui/dialogs/__init__.py +12 -0
- data_engine/ui/gui/dialogs/messages.py +88 -0
- data_engine/ui/gui/dialogs/previews.py +222 -0
- data_engine/ui/gui/helpers/__init__.py +62 -0
- data_engine/ui/gui/helpers/inspection.py +81 -0
- data_engine/ui/gui/helpers/lifecycle.py +112 -0
- data_engine/ui/gui/helpers/scroll.py +28 -0
- data_engine/ui/gui/helpers/theming.py +87 -0
- data_engine/ui/gui/icons/dark_light.svg +12 -0
- data_engine/ui/gui/icons/documentation.svg +1 -0
- data_engine/ui/gui/icons/failed.svg +3 -0
- data_engine/ui/gui/icons/group.svg +4 -0
- data_engine/ui/gui/icons/home.svg +2 -0
- data_engine/ui/gui/icons/manual.svg +2 -0
- data_engine/ui/gui/icons/poll.svg +2 -0
- data_engine/ui/gui/icons/schedule.svg +4 -0
- data_engine/ui/gui/icons/settings.svg +2 -0
- data_engine/ui/gui/icons/started.svg +3 -0
- data_engine/ui/gui/icons/success.svg +3 -0
- data_engine/ui/gui/icons/view-log.svg +3 -0
- data_engine/ui/gui/icons.py +50 -0
- data_engine/ui/gui/launcher.py +48 -0
- data_engine/ui/gui/presenters/__init__.py +72 -0
- data_engine/ui/gui/presenters/docs.py +140 -0
- data_engine/ui/gui/presenters/logs.py +58 -0
- data_engine/ui/gui/presenters/runtime_projection.py +29 -0
- data_engine/ui/gui/presenters/sidebar.py +88 -0
- data_engine/ui/gui/presenters/steps.py +148 -0
- data_engine/ui/gui/presenters/workspace.py +39 -0
- data_engine/ui/gui/presenters/workspace_binding.py +75 -0
- data_engine/ui/gui/presenters/workspace_settings.py +182 -0
- data_engine/ui/gui/preview_models.py +37 -0
- data_engine/ui/gui/render_support.py +241 -0
- data_engine/ui/gui/rendering/__init__.py +12 -0
- data_engine/ui/gui/rendering/artifacts.py +95 -0
- data_engine/ui/gui/rendering/icons.py +50 -0
- data_engine/ui/gui/runtime.py +47 -0
- data_engine/ui/gui/state_support.py +193 -0
- data_engine/ui/gui/support.py +214 -0
- data_engine/ui/gui/surface.py +209 -0
- data_engine/ui/gui/theme.py +720 -0
- data_engine/ui/gui/widgets/__init__.py +34 -0
- data_engine/ui/gui/widgets/config.py +41 -0
- data_engine/ui/gui/widgets/logs.py +62 -0
- data_engine/ui/gui/widgets/panels.py +507 -0
- data_engine/ui/gui/widgets/sidebar.py +130 -0
- data_engine/ui/gui/widgets/steps.py +84 -0
- data_engine/ui/tui/__init__.py +5 -0
- data_engine/ui/tui/app.py +222 -0
- data_engine/ui/tui/bootstrap.py +475 -0
- data_engine/ui/tui/bootstrapper.py +117 -0
- data_engine/ui/tui/controllers/__init__.py +6 -0
- data_engine/ui/tui/controllers/flows.py +349 -0
- data_engine/ui/tui/controllers/runtime.py +167 -0
- data_engine/ui/tui/runtime.py +34 -0
- data_engine/ui/tui/state_support.py +141 -0
- data_engine/ui/tui/support.py +63 -0
- data_engine/ui/tui/theme.py +204 -0
- data_engine/ui/tui/widgets.py +123 -0
- data_engine/views/__init__.py +109 -0
- data_engine/views/actions.py +80 -0
- data_engine/views/artifacts.py +58 -0
- data_engine/views/flow_display.py +69 -0
- data_engine/views/logs.py +54 -0
- data_engine/views/models.py +96 -0
- data_engine/views/presentation.py +133 -0
- data_engine/views/runs.py +62 -0
- data_engine/views/state.py +39 -0
- data_engine/views/status.py +13 -0
- data_engine/views/text.py +109 -0
- py_data_engine-0.1.0.dist-info/METADATA +330 -0
- py_data_engine-0.1.0.dist-info/RECORD +200 -0
- py_data_engine-0.1.0.dist-info/WHEEL +5 -0
- py_data_engine-0.1.0.dist-info/entry_points.txt +2 -0
- py_data_engine-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
"""Flow loading, selection, and action-state controllers for the desktop GUI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from PySide6.QtCore import QTimer
|
|
8
|
+
|
|
9
|
+
from data_engine.application import FlowCatalogApplication, WorkspaceSessionApplication
|
|
10
|
+
from data_engine.services import LogService
|
|
11
|
+
from data_engine.platform.identity import APP_DISPLAY_NAME
|
|
12
|
+
from data_engine.views import GuiActionState, QtFlowCard, surface_control_status_text
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from data_engine.ui.gui.app import DataEngineWindow
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class _GuiWorkspaceCatalogController:
|
|
19
|
+
"""Own GUI workspace binding and catalog refresh orchestration."""
|
|
20
|
+
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
*,
|
|
24
|
+
workspace_session_application: WorkspaceSessionApplication,
|
|
25
|
+
flow_catalog_application: FlowCatalogApplication,
|
|
26
|
+
) -> None:
|
|
27
|
+
self.workspace_session_application = workspace_session_application
|
|
28
|
+
self.flow_catalog_application = flow_catalog_application
|
|
29
|
+
|
|
30
|
+
def load_flows(self, window: "DataEngineWindow", presentation: "_GuiFlowPresentationController") -> None:
|
|
31
|
+
missing_message = (
|
|
32
|
+
"Workspace collection root is not configured."
|
|
33
|
+
if not window.workspace_paths.workspace_configured
|
|
34
|
+
else "No flow modules discovered."
|
|
35
|
+
)
|
|
36
|
+
result = self.flow_catalog_application.load_workspace_catalog(
|
|
37
|
+
workspace_paths=window.workspace_paths,
|
|
38
|
+
current_state=window.flow_catalog_state,
|
|
39
|
+
missing_message=missing_message,
|
|
40
|
+
)
|
|
41
|
+
if not result.loaded and result.error_text is None:
|
|
42
|
+
window.flow_catalog_state = result.catalog_state.with_empty_message(
|
|
43
|
+
window._empty_flow_message_for_error(missing_message)
|
|
44
|
+
).with_selected_flow_name(None)
|
|
45
|
+
self.populate_flow_tree(window)
|
|
46
|
+
presentation.refresh_selection(window, None)
|
|
47
|
+
window._refresh_log_view(force_scroll_to_bottom=True)
|
|
48
|
+
presentation.refresh_action_buttons(window)
|
|
49
|
+
presentation.refresh_summary(window)
|
|
50
|
+
window._refresh_workspace_visibility_panel()
|
|
51
|
+
return
|
|
52
|
+
if result.error_text is not None:
|
|
53
|
+
message = result.error_text
|
|
54
|
+
window.flow_catalog_state = result.catalog_state.with_empty_message(
|
|
55
|
+
window._empty_flow_message_for_error(message)
|
|
56
|
+
).with_selected_flow_name(None)
|
|
57
|
+
self.populate_flow_tree(window)
|
|
58
|
+
window._append_log_line(f"Failed to load flows: {message}")
|
|
59
|
+
presentation.refresh_selection(window, None)
|
|
60
|
+
window._refresh_log_view(force_scroll_to_bottom=True)
|
|
61
|
+
presentation.refresh_action_buttons(window)
|
|
62
|
+
presentation.refresh_summary(window)
|
|
63
|
+
window._refresh_workspace_visibility_panel()
|
|
64
|
+
if not window._is_bootstrap_ready_error(message):
|
|
65
|
+
window._show_message_box(
|
|
66
|
+
title=APP_DISPLAY_NAME,
|
|
67
|
+
text=f"Failed to load flows.\n\n{message}",
|
|
68
|
+
tone="error",
|
|
69
|
+
)
|
|
70
|
+
return
|
|
71
|
+
|
|
72
|
+
window.flow_catalog_state = result.catalog_state
|
|
73
|
+
self.populate_flow_tree(window)
|
|
74
|
+
presentation.select_flow(window, window.selected_flow_name)
|
|
75
|
+
presentation.refresh_summary(window)
|
|
76
|
+
window._refresh_workspace_visibility_panel()
|
|
77
|
+
window._rebuild_runtime_snapshot()
|
|
78
|
+
|
|
79
|
+
def populate_flow_tree(self, window: "DataEngineWindow") -> None:
|
|
80
|
+
presentation = self.flow_catalog_application.build_presentation(
|
|
81
|
+
catalog_state=window.flow_catalog_state,
|
|
82
|
+
)
|
|
83
|
+
window.sidebar_flow_widgets = {}
|
|
84
|
+
window.sidebar_group_widgets = {}
|
|
85
|
+
window.sidebar_content.setUpdatesEnabled(False)
|
|
86
|
+
while window.sidebar_layout.count() > 1:
|
|
87
|
+
item = window.sidebar_layout.takeAt(0)
|
|
88
|
+
widget = item.widget()
|
|
89
|
+
if widget is not None:
|
|
90
|
+
widget.setParent(None)
|
|
91
|
+
widget.deleteLater()
|
|
92
|
+
for group_name, entries in presentation.grouped_cards:
|
|
93
|
+
group_widget = window._build_group_row_widget(group_name, list(entries))
|
|
94
|
+
window.sidebar_group_widgets[group_name] = group_widget
|
|
95
|
+
window.sidebar_layout.insertWidget(window.sidebar_layout.count() - 1, group_widget)
|
|
96
|
+
for index, card in enumerate(entries, start=1):
|
|
97
|
+
widget = window._build_flow_row_widget(card)
|
|
98
|
+
widget.setProperty("flowIndex", index)
|
|
99
|
+
window.sidebar_flow_widgets[card.name] = widget
|
|
100
|
+
window.sidebar_layout.insertWidget(window.sidebar_layout.count() - 1, widget)
|
|
101
|
+
window.sidebar_content.setUpdatesEnabled(True)
|
|
102
|
+
window.sidebar_content.updateGeometry()
|
|
103
|
+
window.sidebar_content.update()
|
|
104
|
+
window._refresh_sidebar_selection()
|
|
105
|
+
window._update_sidebar_scroll_cues()
|
|
106
|
+
|
|
107
|
+
def reload_workspace_options(self, window: "DataEngineWindow") -> None:
|
|
108
|
+
window.workspace_session_state = self.workspace_session_application.refresh_session(
|
|
109
|
+
workspace_paths=window.workspace_paths,
|
|
110
|
+
override_root=window.workspace_collection_root_override,
|
|
111
|
+
)
|
|
112
|
+
current_id = window.workspace_session_state.current_workspace_id
|
|
113
|
+
workspace_ids = window.workspace_session_state.discovered_workspace_ids
|
|
114
|
+
window.workspace_selector.blockSignals(True)
|
|
115
|
+
try:
|
|
116
|
+
window.workspace_selector.clear()
|
|
117
|
+
if not workspace_ids:
|
|
118
|
+
window.workspace_selector.addItem("(no workspace)", "")
|
|
119
|
+
window.workspace_selector.setCurrentIndex(0)
|
|
120
|
+
window.workspace_selector.setEnabled(False)
|
|
121
|
+
else:
|
|
122
|
+
for workspace_id in workspace_ids:
|
|
123
|
+
window.workspace_selector.addItem(workspace_id, workspace_id)
|
|
124
|
+
selected_index = window.workspace_selector.findData(current_id)
|
|
125
|
+
if selected_index < 0:
|
|
126
|
+
selected_index = 0
|
|
127
|
+
window.workspace_selector.setCurrentIndex(selected_index)
|
|
128
|
+
window.workspace_selector.setEnabled(True)
|
|
129
|
+
finally:
|
|
130
|
+
window.workspace_selector.blockSignals(False)
|
|
131
|
+
|
|
132
|
+
def workspace_selection_changed(self, window: "DataEngineWindow", index: int) -> None:
|
|
133
|
+
if index < 0:
|
|
134
|
+
return
|
|
135
|
+
workspace_id = str(window.workspace_selector.itemData(index) or "").strip()
|
|
136
|
+
if not workspace_id or workspace_id == window.workspace_paths.workspace_id:
|
|
137
|
+
return
|
|
138
|
+
self.switch_workspace(window, workspace_id)
|
|
139
|
+
|
|
140
|
+
def switch_workspace(self, window: "DataEngineWindow", workspace_id: str) -> None:
|
|
141
|
+
try:
|
|
142
|
+
window.workspace_selector.hidePopup()
|
|
143
|
+
except Exception:
|
|
144
|
+
pass
|
|
145
|
+
window._workspace_switch_generation += 1
|
|
146
|
+
switch_generation = window._workspace_switch_generation
|
|
147
|
+
|
|
148
|
+
def _finish_switch() -> None:
|
|
149
|
+
if switch_generation != window._workspace_switch_generation:
|
|
150
|
+
return
|
|
151
|
+
if window.ui_closing:
|
|
152
|
+
return
|
|
153
|
+
if workspace_id == window.workspace_paths.workspace_id:
|
|
154
|
+
return
|
|
155
|
+
window._rebind_workspace_context(workspace_id=workspace_id)
|
|
156
|
+
|
|
157
|
+
QTimer.singleShot(0, _finish_switch)
|
|
158
|
+
|
|
159
|
+
def refresh_flows_requested(self, window: "DataEngineWindow", presentation: "_GuiFlowPresentationController") -> None:
|
|
160
|
+
result = window.control_application.refresh_flows(
|
|
161
|
+
paths=window.workspace_paths,
|
|
162
|
+
runtime_session=window.runtime_session,
|
|
163
|
+
has_authored_workspace=window._has_authored_workspace(),
|
|
164
|
+
timeout=5.0,
|
|
165
|
+
)
|
|
166
|
+
if result.error_text is not None:
|
|
167
|
+
window._show_message_box_later(
|
|
168
|
+
title=APP_DISPLAY_NAME,
|
|
169
|
+
text=result.error_text,
|
|
170
|
+
tone="error",
|
|
171
|
+
)
|
|
172
|
+
return
|
|
173
|
+
if result.reload_catalog:
|
|
174
|
+
self.reload_workspace_options(window)
|
|
175
|
+
self.load_flows(window, presentation)
|
|
176
|
+
if result.sync_after:
|
|
177
|
+
window._sync_from_daemon()
|
|
178
|
+
if result.status_text is not None and window.flow_cards:
|
|
179
|
+
window._append_log_line(result.status_text)
|
|
180
|
+
if result.warning_text is not None:
|
|
181
|
+
window._append_log_line(f"Flow refresh warning: {result.warning_text}")
|
|
182
|
+
if window.flow_cards:
|
|
183
|
+
window._show_message_box_later(
|
|
184
|
+
title=APP_DISPLAY_NAME,
|
|
185
|
+
text=f"Refreshed local flow definitions, but daemon refresh failed.\n\n{result.warning_text}",
|
|
186
|
+
tone="error",
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class _GuiFlowPresentationController:
|
|
191
|
+
"""Own GUI selection, action-state, and summary presentation orchestration."""
|
|
192
|
+
|
|
193
|
+
def __init__(
|
|
194
|
+
self,
|
|
195
|
+
*,
|
|
196
|
+
flow_catalog_application: FlowCatalogApplication,
|
|
197
|
+
log_service: LogService,
|
|
198
|
+
) -> None:
|
|
199
|
+
self.flow_catalog_application = flow_catalog_application
|
|
200
|
+
self.log_service = log_service
|
|
201
|
+
|
|
202
|
+
def select_flow(self, window: "DataEngineWindow", flow_name: str | None) -> None:
|
|
203
|
+
window.flow_catalog_state = self.flow_catalog_application.select_flow(
|
|
204
|
+
catalog_state=window.flow_catalog_state,
|
|
205
|
+
flow_name=flow_name,
|
|
206
|
+
)
|
|
207
|
+
presentation = self.flow_catalog_application.build_presentation(
|
|
208
|
+
catalog_state=window.flow_catalog_state,
|
|
209
|
+
)
|
|
210
|
+
if presentation.selected_card is None:
|
|
211
|
+
self.refresh_selection(window, None)
|
|
212
|
+
self.refresh_action_buttons(window)
|
|
213
|
+
window._refresh_sidebar_selection()
|
|
214
|
+
window._refresh_log_view(force_scroll_to_bottom=True)
|
|
215
|
+
return
|
|
216
|
+
self.refresh_selection(window, presentation.selected_card)
|
|
217
|
+
self.refresh_action_buttons(window)
|
|
218
|
+
window._refresh_sidebar_selection()
|
|
219
|
+
window._refresh_log_view(force_scroll_to_bottom=True)
|
|
220
|
+
|
|
221
|
+
def refresh_selection(self, window: "DataEngineWindow", card: QtFlowCard | None) -> None:
|
|
222
|
+
presentation = window.detail_application.build_selected_flow_presentation(
|
|
223
|
+
card=card,
|
|
224
|
+
tracker=window.operation_tracker,
|
|
225
|
+
flow_states=window.flow_states,
|
|
226
|
+
run_groups=(),
|
|
227
|
+
selected_run_key=None,
|
|
228
|
+
)
|
|
229
|
+
if presentation.detail_state is None:
|
|
230
|
+
window.flow_error_label.clear()
|
|
231
|
+
window._set_operation_cards(())
|
|
232
|
+
return
|
|
233
|
+
|
|
234
|
+
window.flow_error_label.setText(presentation.detail_state.error)
|
|
235
|
+
window._set_operation_cards(tuple(row.name for row in presentation.detail_state.operation_rows))
|
|
236
|
+
assert card is not None
|
|
237
|
+
window._render_operation_durations(card.name)
|
|
238
|
+
|
|
239
|
+
def refresh_summary(self, window: "DataEngineWindow") -> None:
|
|
240
|
+
self.refresh_lease_status(window)
|
|
241
|
+
|
|
242
|
+
def refresh_action_buttons(self, window: "DataEngineWindow") -> None:
|
|
243
|
+
card = window.flow_cards.get(window.selected_flow_name or "")
|
|
244
|
+
action_context = window.action_state_application.build_action_context(
|
|
245
|
+
card=card,
|
|
246
|
+
flow_states=window.flow_states,
|
|
247
|
+
runtime_session=window.runtime_session,
|
|
248
|
+
flow_groups_by_name={flow_name: flow_card.group for flow_name, flow_card in window.flow_cards.items()},
|
|
249
|
+
active_flow_states=window._ACTIVE_FLOW_STATES,
|
|
250
|
+
has_logs=bool(
|
|
251
|
+
card is not None and self.log_service.entries_for_flow(window.runtime_binding.log_store, card.name)
|
|
252
|
+
),
|
|
253
|
+
has_automated_flows=any(flow_card.valid and flow_card.mode in {"poll", "schedule"} for flow_card in window.flow_cards.values()),
|
|
254
|
+
workspace_available=window._has_authored_workspace(),
|
|
255
|
+
)
|
|
256
|
+
action_state = GuiActionState.from_context(action_context)
|
|
257
|
+
window.flow_run_button.setText(action_state.flow_run_label)
|
|
258
|
+
window.flow_run_button.setEnabled(action_state.flow_run_enabled)
|
|
259
|
+
window.flow_config_button.setEnabled(action_state.flow_config_enabled)
|
|
260
|
+
window.engine_button.setEnabled(action_state.engine_enabled)
|
|
261
|
+
window.engine_button.setText(action_state.engine_label)
|
|
262
|
+
window.engine_button.setProperty("engineState", action_state.engine_state)
|
|
263
|
+
window.refresh_button.setEnabled(action_state.refresh_enabled)
|
|
264
|
+
window.clear_flow_log_button.setEnabled(action_state.clear_flow_log_enabled)
|
|
265
|
+
window.request_control_button.setVisible(action_state.request_control_visible)
|
|
266
|
+
window.request_control_button.setEnabled(action_state.request_control_enabled)
|
|
267
|
+
if window.workspace_selector.count() > 0:
|
|
268
|
+
window.workspace_selector.setEnabled(bool(window.workspace_session_state.discovered_workspace_ids))
|
|
269
|
+
style = window.engine_button.style()
|
|
270
|
+
style.unpolish(window.engine_button)
|
|
271
|
+
style.polish(window.engine_button)
|
|
272
|
+
window.engine_button.update()
|
|
273
|
+
|
|
274
|
+
def refresh_lease_status(self, window: "DataEngineWindow") -> None:
|
|
275
|
+
snapshot = window.daemon_state_service.sync(window.runtime_binding.daemon_manager)
|
|
276
|
+
window.workspace_control_state = window.daemon_state_service.control_state(
|
|
277
|
+
window.runtime_binding.daemon_manager,
|
|
278
|
+
snapshot,
|
|
279
|
+
daemon_startup_in_progress=window._daemon_startup_in_progress,
|
|
280
|
+
)
|
|
281
|
+
status_text = surface_control_status_text(
|
|
282
|
+
window.workspace_control_state.control_status_text,
|
|
283
|
+
empty_flow_message=window.empty_flow_message,
|
|
284
|
+
)
|
|
285
|
+
if not status_text:
|
|
286
|
+
window.lease_status_label.clear()
|
|
287
|
+
window.lease_status_label.setVisible(False)
|
|
288
|
+
return
|
|
289
|
+
window.lease_status_label.setText(status_text)
|
|
290
|
+
window.lease_status_label.setVisible(True)
|
|
291
|
+
|
|
292
|
+
def request_control(self, window: "DataEngineWindow") -> None:
|
|
293
|
+
result = window.control_application.request_control(window.runtime_binding.daemon_manager)
|
|
294
|
+
if result.error_text is not None:
|
|
295
|
+
window._append_log_line(result.error_text.replace("\n\n", ": "))
|
|
296
|
+
window._show_message_box(
|
|
297
|
+
title=APP_DISPLAY_NAME,
|
|
298
|
+
text=result.error_text,
|
|
299
|
+
tone="error",
|
|
300
|
+
)
|
|
301
|
+
return
|
|
302
|
+
if result.status_text is not None:
|
|
303
|
+
window._append_log_line(result.status_text)
|
|
304
|
+
if result.ensure_daemon_started:
|
|
305
|
+
window._ensure_daemon_started()
|
|
306
|
+
if result.sync_after:
|
|
307
|
+
window._sync_from_daemon()
|
|
308
|
+
|
|
309
|
+
def update_engine_button(self, window: "DataEngineWindow") -> None:
|
|
310
|
+
self.refresh_action_buttons(window)
|
|
311
|
+
|
|
312
|
+
def set_flow_state(self, window: "DataEngineWindow", flow_name: str, state: str) -> None:
|
|
313
|
+
self.set_flow_states(window, {flow_name: state})
|
|
314
|
+
|
|
315
|
+
def set_flow_states(self, window: "DataEngineWindow", updates: dict[str, str]) -> None:
|
|
316
|
+
if not updates:
|
|
317
|
+
return
|
|
318
|
+
next_states = dict(window.flow_states)
|
|
319
|
+
next_states.update(updates)
|
|
320
|
+
refresh_plan = window.runtime_application.plan_flow_state_refresh(
|
|
321
|
+
previous_states=window.flow_states,
|
|
322
|
+
next_states=next_states,
|
|
323
|
+
runtime_session=window.runtime_session,
|
|
324
|
+
)
|
|
325
|
+
if not refresh_plan.changed_flow_names:
|
|
326
|
+
return
|
|
327
|
+
window.flow_states = refresh_plan.flow_states
|
|
328
|
+
window._refresh_sidebar_state_views(set(refresh_plan.changed_flow_names))
|
|
329
|
+
if window.selected_flow_name is not None and window.selected_flow_name in refresh_plan.changed_flow_names:
|
|
330
|
+
self.refresh_selection(window, window.flow_cards[window.selected_flow_name])
|
|
331
|
+
self.refresh_summary(window)
|
|
332
|
+
|
|
333
|
+
def refresh_flows_requested(self, window: "DataEngineWindow") -> None:
|
|
334
|
+
result = window.control_application.refresh_flows(
|
|
335
|
+
paths=window.workspace_paths,
|
|
336
|
+
runtime_session=window.runtime_session,
|
|
337
|
+
has_authored_workspace=window._has_authored_workspace(),
|
|
338
|
+
timeout=5.0,
|
|
339
|
+
)
|
|
340
|
+
if result.error_text is not None:
|
|
341
|
+
window._show_message_box_later(
|
|
342
|
+
title=APP_DISPLAY_NAME,
|
|
343
|
+
text=result.error_text,
|
|
344
|
+
tone="error",
|
|
345
|
+
)
|
|
346
|
+
return
|
|
347
|
+
if result.reload_catalog:
|
|
348
|
+
self.reload_workspace_options(window)
|
|
349
|
+
self.load_flows(window)
|
|
350
|
+
if result.sync_after:
|
|
351
|
+
window._sync_from_daemon()
|
|
352
|
+
if result.status_text is not None and window.flow_cards:
|
|
353
|
+
window._append_log_line(result.status_text)
|
|
354
|
+
if result.warning_text is not None:
|
|
355
|
+
window._append_log_line(f"Flow refresh warning: {result.warning_text}")
|
|
356
|
+
if window.flow_cards:
|
|
357
|
+
window._show_message_box_later(
|
|
358
|
+
title=APP_DISPLAY_NAME,
|
|
359
|
+
text=f"Refreshed local flow definitions, but daemon refresh failed.\n\n{result.warning_text}",
|
|
360
|
+
tone="error",
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
def clear_logs(self, window: "DataEngineWindow") -> None:
|
|
364
|
+
if window.selected_flow_name is None:
|
|
365
|
+
return
|
|
366
|
+
self.log_service.clear_flow(window.runtime_binding.log_store, window.selected_flow_name)
|
|
367
|
+
window._refresh_log_view(force_scroll_to_bottom=True)
|
|
368
|
+
self.refresh_action_buttons(window)
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
class GuiFlowController:
|
|
372
|
+
"""Compose narrower GUI flow collaborators behind one stable controller seam."""
|
|
373
|
+
|
|
374
|
+
def __init__(
|
|
375
|
+
self,
|
|
376
|
+
*,
|
|
377
|
+
workspace_session_application: WorkspaceSessionApplication,
|
|
378
|
+
flow_catalog_application: FlowCatalogApplication,
|
|
379
|
+
log_service: LogService,
|
|
380
|
+
) -> None:
|
|
381
|
+
self.workspace = _GuiWorkspaceCatalogController(
|
|
382
|
+
workspace_session_application=workspace_session_application,
|
|
383
|
+
flow_catalog_application=flow_catalog_application,
|
|
384
|
+
)
|
|
385
|
+
self.presentation = _GuiFlowPresentationController(
|
|
386
|
+
flow_catalog_application=flow_catalog_application,
|
|
387
|
+
log_service=log_service,
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
def load_flows(self, window: "DataEngineWindow") -> None:
|
|
391
|
+
self.workspace.load_flows(window, self.presentation)
|
|
392
|
+
|
|
393
|
+
def populate_flow_tree(self, window: "DataEngineWindow") -> None:
|
|
394
|
+
self.workspace.populate_flow_tree(window)
|
|
395
|
+
|
|
396
|
+
def select_flow(self, window: "DataEngineWindow", flow_name: str | None) -> None:
|
|
397
|
+
self.presentation.select_flow(window, flow_name)
|
|
398
|
+
|
|
399
|
+
def refresh_selection(self, window: "DataEngineWindow", card: QtFlowCard | None) -> None:
|
|
400
|
+
self.presentation.refresh_selection(window, card)
|
|
401
|
+
|
|
402
|
+
def refresh_summary(self, window: "DataEngineWindow") -> None:
|
|
403
|
+
self.presentation.refresh_summary(window)
|
|
404
|
+
|
|
405
|
+
def refresh_action_buttons(self, window: "DataEngineWindow") -> None:
|
|
406
|
+
self.presentation.refresh_action_buttons(window)
|
|
407
|
+
|
|
408
|
+
def reload_workspace_options(self, window: "DataEngineWindow") -> None:
|
|
409
|
+
self.workspace.reload_workspace_options(window)
|
|
410
|
+
|
|
411
|
+
def workspace_selection_changed(self, window: "DataEngineWindow", index: int) -> None:
|
|
412
|
+
self.workspace.workspace_selection_changed(window, index)
|
|
413
|
+
|
|
414
|
+
def switch_workspace(self, window: "DataEngineWindow", workspace_id: str) -> None:
|
|
415
|
+
self.workspace.switch_workspace(window, workspace_id)
|
|
416
|
+
|
|
417
|
+
def refresh_lease_status(self, window: "DataEngineWindow") -> None:
|
|
418
|
+
self.presentation.refresh_lease_status(window)
|
|
419
|
+
|
|
420
|
+
def request_control(self, window: "DataEngineWindow") -> None:
|
|
421
|
+
self.presentation.request_control(window)
|
|
422
|
+
|
|
423
|
+
def update_engine_button(self, window: "DataEngineWindow") -> None:
|
|
424
|
+
self.presentation.update_engine_button(window)
|
|
425
|
+
|
|
426
|
+
def set_flow_state(self, window: "DataEngineWindow", flow_name: str, state: str) -> None:
|
|
427
|
+
self.presentation.set_flow_state(window, flow_name, state)
|
|
428
|
+
|
|
429
|
+
def set_flow_states(self, window: "DataEngineWindow", updates: dict[str, str]) -> None:
|
|
430
|
+
self.presentation.set_flow_states(window, updates)
|
|
431
|
+
|
|
432
|
+
def refresh_flows_requested(self, window: "DataEngineWindow") -> None:
|
|
433
|
+
self.workspace.refresh_flows_requested(window, self.presentation)
|
|
434
|
+
|
|
435
|
+
def clear_logs(self, window: "DataEngineWindow") -> None:
|
|
436
|
+
self.presentation.clear_logs(window)
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
__all__ = ["GuiFlowController"]
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
"""Runtime and daemon orchestration controllers for the desktop GUI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from data_engine.application import RuntimeApplication
|
|
8
|
+
from data_engine.services import DaemonService, LogService
|
|
9
|
+
from data_engine.domain import DaemonStatusState, RuntimeSessionState, WorkspaceControlState
|
|
10
|
+
from data_engine.platform.identity import APP_DISPLAY_NAME
|
|
11
|
+
from data_engine.ui.gui.helpers import start_worker_thread
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from data_engine.ui.gui.app import DataEngineWindow
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class GuiRuntimeController:
|
|
17
|
+
"""Own daemon/runtime orchestration for the desktop GUI."""
|
|
18
|
+
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
*,
|
|
22
|
+
runtime_application: RuntimeApplication,
|
|
23
|
+
daemon_service: DaemonService,
|
|
24
|
+
log_service: LogService,
|
|
25
|
+
) -> None:
|
|
26
|
+
self.runtime_application = runtime_application
|
|
27
|
+
self.daemon_service = daemon_service
|
|
28
|
+
self.log_service = log_service
|
|
29
|
+
|
|
30
|
+
def sync_from_daemon(self, window: "DataEngineWindow") -> None:
|
|
31
|
+
if not window._has_authored_workspace():
|
|
32
|
+
window.daemon_status = DaemonStatusState.empty()
|
|
33
|
+
window.workspace_control_state = WorkspaceControlState.empty()
|
|
34
|
+
window.runtime_session = RuntimeSessionState.empty()
|
|
35
|
+
window.flow_controller.reload_workspace_options(window)
|
|
36
|
+
window.flow_controller.load_flows(window)
|
|
37
|
+
return
|
|
38
|
+
try:
|
|
39
|
+
live = self.daemon_service.is_live(window.workspace_paths)
|
|
40
|
+
except Exception:
|
|
41
|
+
live = False
|
|
42
|
+
if not live and window._auto_daemon_enabled:
|
|
43
|
+
self.ensure_daemon_started(window)
|
|
44
|
+
sync_state = self.runtime_application.sync_state(
|
|
45
|
+
paths=window.workspace_paths,
|
|
46
|
+
daemon_manager=window.runtime_binding.daemon_manager,
|
|
47
|
+
flow_cards=window.flow_cards.values(),
|
|
48
|
+
runtime_ledger=window.runtime_binding.runtime_ledger,
|
|
49
|
+
daemon_startup_in_progress=window._daemon_startup_in_progress,
|
|
50
|
+
)
|
|
51
|
+
window.daemon_status = sync_state.daemon_status
|
|
52
|
+
window.workspace_control_state = sync_state.workspace_control_state
|
|
53
|
+
window.runtime_session = sync_state.runtime_session
|
|
54
|
+
window._apply_daemon_snapshot(sync_state.snapshot)
|
|
55
|
+
self.rebuild_runtime_snapshot(window)
|
|
56
|
+
|
|
57
|
+
def ensure_daemon_started(self, window: "DataEngineWindow") -> bool:
|
|
58
|
+
if not window._has_authored_workspace():
|
|
59
|
+
return False
|
|
60
|
+
try:
|
|
61
|
+
if self.daemon_service.is_live(window.workspace_paths):
|
|
62
|
+
return True
|
|
63
|
+
except Exception:
|
|
64
|
+
pass
|
|
65
|
+
if window._daemon_startup_in_progress or not window._auto_daemon_enabled or window.ui_closing:
|
|
66
|
+
return False
|
|
67
|
+
now = window._monotonic()
|
|
68
|
+
if now - window._last_daemon_spawn_attempt < 2.0:
|
|
69
|
+
return False
|
|
70
|
+
window._last_daemon_spawn_attempt = now
|
|
71
|
+
window._daemon_startup_in_progress = True
|
|
72
|
+
start_worker_thread(window, target=window._start_daemon_worker)
|
|
73
|
+
return False
|
|
74
|
+
|
|
75
|
+
def start_daemon_worker(self, window: "DataEngineWindow") -> None:
|
|
76
|
+
success = False
|
|
77
|
+
error_text = ""
|
|
78
|
+
spawn_result = self.runtime_application.spawn_daemon(window.workspace_paths)
|
|
79
|
+
if not spawn_result.ok:
|
|
80
|
+
error_text = spawn_result.error
|
|
81
|
+
else:
|
|
82
|
+
success = self.daemon_service.is_live(window.workspace_paths)
|
|
83
|
+
if not success and not error_text:
|
|
84
|
+
error_text = "Daemon startup did not provide any additional error details."
|
|
85
|
+
window.signals.daemon_startup_finished.emit(success, error_text)
|
|
86
|
+
|
|
87
|
+
def rebuild_runtime_snapshot(self, window: "DataEngineWindow") -> None:
|
|
88
|
+
self.log_service.reload(window.runtime_binding.log_store)
|
|
89
|
+
window._rehydrate_step_outputs_from_ledger()
|
|
90
|
+
snapshot = self.runtime_application.build_runtime_snapshot(
|
|
91
|
+
flow_cards=window.flow_cards.values(),
|
|
92
|
+
log_entries=self.log_service.all_entries(window.runtime_binding.log_store),
|
|
93
|
+
runtime_session=window.runtime_session,
|
|
94
|
+
now=window._monotonic(),
|
|
95
|
+
)
|
|
96
|
+
refresh_plan = self.runtime_application.plan_flow_state_refresh(
|
|
97
|
+
previous_states=window.flow_states,
|
|
98
|
+
next_states=snapshot.flow_states,
|
|
99
|
+
runtime_session=window.runtime_session,
|
|
100
|
+
)
|
|
101
|
+
window.operation_tracker = snapshot.operation_tracker
|
|
102
|
+
window.flow_states = refresh_plan.flow_states
|
|
103
|
+
window._refresh_sidebar_state_views(set(refresh_plan.changed_flow_names))
|
|
104
|
+
if window.selected_flow_name is not None and window.selected_flow_name in window.flow_cards:
|
|
105
|
+
window.flow_controller.refresh_selection(window, window.flow_cards[window.selected_flow_name])
|
|
106
|
+
window.flow_controller.refresh_summary(window)
|
|
107
|
+
window._refresh_workspace_visibility_panel()
|
|
108
|
+
window._refresh_log_view()
|
|
109
|
+
window.flow_controller.refresh_action_buttons(window)
|
|
110
|
+
|
|
111
|
+
def start_runtime(self, window: "DataEngineWindow") -> None:
|
|
112
|
+
if not window._has_authored_workspace():
|
|
113
|
+
window._sync_from_daemon()
|
|
114
|
+
return
|
|
115
|
+
result = window.control_application.start_engine(
|
|
116
|
+
paths=window.workspace_paths,
|
|
117
|
+
runtime_session=window.runtime_session,
|
|
118
|
+
has_automated_flows=any(card.valid and card.mode in {"poll", "schedule"} for card in window.flow_cards.values()),
|
|
119
|
+
blocked_status_text=window.workspace_control_state.blocked_status_text,
|
|
120
|
+
timeout=2.0,
|
|
121
|
+
)
|
|
122
|
+
if result.error_text is not None:
|
|
123
|
+
window._show_message_box_later(
|
|
124
|
+
title=APP_DISPLAY_NAME,
|
|
125
|
+
text=result.error_text,
|
|
126
|
+
tone="error",
|
|
127
|
+
)
|
|
128
|
+
return
|
|
129
|
+
if result.sync_after:
|
|
130
|
+
window._sync_from_daemon()
|
|
131
|
+
|
|
132
|
+
def stop_runtime(self, window: "DataEngineWindow") -> None:
|
|
133
|
+
result = window.control_application.stop_pipeline(
|
|
134
|
+
paths=window.workspace_paths,
|
|
135
|
+
runtime_session=window.runtime_session,
|
|
136
|
+
selected_flow_group=None,
|
|
137
|
+
blocked_status_text=window.workspace_control_state.blocked_status_text,
|
|
138
|
+
timeout=2.0,
|
|
139
|
+
)
|
|
140
|
+
if result.error_text is not None:
|
|
141
|
+
window._show_message_box_later(
|
|
142
|
+
title=APP_DISPLAY_NAME,
|
|
143
|
+
text=result.error_text,
|
|
144
|
+
tone="error",
|
|
145
|
+
)
|
|
146
|
+
return
|
|
147
|
+
if result.sync_after:
|
|
148
|
+
window._sync_from_daemon()
|
|
149
|
+
|
|
150
|
+
def toggle_runtime(self, window: "DataEngineWindow") -> None:
|
|
151
|
+
if window.runtime_session.runtime_active:
|
|
152
|
+
self.stop_runtime(window)
|
|
153
|
+
return
|
|
154
|
+
self.start_runtime(window)
|
|
155
|
+
|
|
156
|
+
def stop_pipeline(self, window: "DataEngineWindow") -> None:
|
|
157
|
+
card = window.flow_cards.get(window.selected_flow_name or "")
|
|
158
|
+
result = window.control_application.stop_pipeline(
|
|
159
|
+
paths=window.workspace_paths,
|
|
160
|
+
runtime_session=window.runtime_session,
|
|
161
|
+
selected_flow_group=card.group if card is not None else None,
|
|
162
|
+
blocked_status_text=window.workspace_control_state.blocked_status_text,
|
|
163
|
+
timeout=2.0,
|
|
164
|
+
)
|
|
165
|
+
if result.error_text is not None:
|
|
166
|
+
window._show_message_box_later(
|
|
167
|
+
title=APP_DISPLAY_NAME,
|
|
168
|
+
text=result.error_text,
|
|
169
|
+
tone="error",
|
|
170
|
+
)
|
|
171
|
+
return
|
|
172
|
+
if result.sync_after:
|
|
173
|
+
window._sync_from_daemon()
|
|
174
|
+
|
|
175
|
+
def finish_run(self, window: "DataEngineWindow", flow_name: object, results: object, error: object) -> None:
|
|
176
|
+
assert isinstance(flow_name, str)
|
|
177
|
+
card = window.flow_cards.get(flow_name)
|
|
178
|
+
group_name = card.group if card is not None else next(
|
|
179
|
+
(run.group_name for run in window.runtime_session.manual_runs if run.flow_name == flow_name),
|
|
180
|
+
None,
|
|
181
|
+
)
|
|
182
|
+
stop_event = window.manual_flow_stop_events.pop(group_name, None)
|
|
183
|
+
stop_requested = stop_event.is_set() if stop_event is not None else False
|
|
184
|
+
if stop_event is not None:
|
|
185
|
+
stop_event.clear()
|
|
186
|
+
completion = self.runtime_application.complete_manual_run(
|
|
187
|
+
runtime_session=window.runtime_session,
|
|
188
|
+
flow_name=flow_name,
|
|
189
|
+
group_name=group_name,
|
|
190
|
+
flow_mode=card.mode if card is not None else "manual",
|
|
191
|
+
results=results,
|
|
192
|
+
error=error,
|
|
193
|
+
stop_requested=stop_requested,
|
|
194
|
+
)
|
|
195
|
+
window.runtime_session = completion.runtime_session
|
|
196
|
+
window.flow_controller.set_flow_states(window, completion.state_updates)
|
|
197
|
+
for message in completion.log_messages:
|
|
198
|
+
window._append_log_line(message.text, flow_name=message.flow_name)
|
|
199
|
+
if completion.capture_results:
|
|
200
|
+
window._capture_step_outputs(flow_name, results)
|
|
201
|
+
if completion.normalize_operations:
|
|
202
|
+
window._normalize_completed_operation_rows(flow_name)
|
|
203
|
+
if completion.render_durations:
|
|
204
|
+
window._render_operation_durations(flow_name)
|
|
205
|
+
if completion.show_error_text is not None:
|
|
206
|
+
window._show_message_box_later(
|
|
207
|
+
title=APP_DISPLAY_NAME,
|
|
208
|
+
text=completion.show_error_text,
|
|
209
|
+
tone="error",
|
|
210
|
+
)
|
|
211
|
+
window.flow_controller.refresh_action_buttons(window)
|
|
212
|
+
|
|
213
|
+
def finish_runtime(self, window: "DataEngineWindow", flow_names: object, results: object, error: object) -> None:
|
|
214
|
+
active_runtime_flow_names = tuple(flow_names) if isinstance(flow_names, tuple) else window.runtime_session.active_runtime_flow_names
|
|
215
|
+
runtime_stop_requested = window.engine_runtime_stop_event.is_set()
|
|
216
|
+
flow_stop_requested = window.engine_flow_stop_event.is_set()
|
|
217
|
+
completion = self.runtime_application.complete_engine_run(
|
|
218
|
+
runtime_session=window.runtime_session,
|
|
219
|
+
flow_names=active_runtime_flow_names,
|
|
220
|
+
flow_modes_by_name={
|
|
221
|
+
flow_name: (window.flow_cards[flow_name].mode if flow_name in window.flow_cards else None)
|
|
222
|
+
for flow_name in active_runtime_flow_names
|
|
223
|
+
},
|
|
224
|
+
error=error,
|
|
225
|
+
runtime_stop_requested=runtime_stop_requested,
|
|
226
|
+
flow_stop_requested=flow_stop_requested,
|
|
227
|
+
)
|
|
228
|
+
window.runtime_session = completion.runtime_session
|
|
229
|
+
window.engine_runtime_stop_event.clear()
|
|
230
|
+
window.engine_flow_stop_event.clear()
|
|
231
|
+
window.flow_controller.set_flow_states(window, completion.state_updates)
|
|
232
|
+
for message in completion.log_messages:
|
|
233
|
+
window._append_log_line(message.text, flow_name=message.flow_name)
|
|
234
|
+
for failed_flow_name in completion.failed_flow_names:
|
|
235
|
+
window.flow_controller.set_flow_state(window, failed_flow_name, "failed")
|
|
236
|
+
window.flow_controller.refresh_action_buttons(window)
|
|
237
|
+
|
|
238
|
+
def is_group_active(self, window: "DataEngineWindow", group_name: str) -> bool:
|
|
239
|
+
return window.runtime_session.is_group_active(
|
|
240
|
+
group_name,
|
|
241
|
+
{flow_name: card.group for flow_name, card in window.flow_cards.items()},
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
__all__ = ["GuiRuntimeController"]
|