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,148 @@
|
|
|
1
|
+
"""Step-list presentation helpers for the desktop UI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from PySide6.QtCore import QTimer
|
|
8
|
+
from PySide6.QtWidgets import QFrame
|
|
9
|
+
|
|
10
|
+
from data_engine.domain import RuntimeStepEvent
|
|
11
|
+
from data_engine.views.models import default_flow_state
|
|
12
|
+
from data_engine.views.presentation import format_seconds
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from data_engine.ui.gui.app import DataEngineWindow
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def reset_operation_state(window: "DataEngineWindow", flow_name: str) -> None:
|
|
19
|
+
window.operation_tracker = window.operation_tracker.reset_flow(flow_name, window.flow_cards[flow_name].operation_items)
|
|
20
|
+
if window.selected_flow_name == flow_name:
|
|
21
|
+
render_operation_durations(window, flow_name)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def apply_runtime_event(window: "DataEngineWindow", event: RuntimeStepEvent) -> None:
|
|
25
|
+
if event.flow_name not in window.flow_cards:
|
|
26
|
+
return
|
|
27
|
+
flow_name = event.flow_name
|
|
28
|
+
card = window.flow_cards[flow_name]
|
|
29
|
+
if event.step_name is None:
|
|
30
|
+
if event.status == "failed":
|
|
31
|
+
window._set_flow_state(flow_name, "failed")
|
|
32
|
+
elif event.status in {"started", "success", "stopped"}:
|
|
33
|
+
if window.runtime_session.runtime_active and flow_name in window.runtime_session.active_runtime_flow_names:
|
|
34
|
+
window._set_flow_state(
|
|
35
|
+
flow_name,
|
|
36
|
+
"polling" if card.mode == "poll" else "scheduled" if card.mode == "schedule" else default_flow_state(card.mode),
|
|
37
|
+
)
|
|
38
|
+
elif window.flow_states.get(flow_name) == "failed":
|
|
39
|
+
window._set_flow_state(flow_name, default_flow_state(card.mode))
|
|
40
|
+
return
|
|
41
|
+
window.operation_tracker, flash_index = window.operation_tracker.apply_event(
|
|
42
|
+
flow_name,
|
|
43
|
+
card.operation_items,
|
|
44
|
+
event,
|
|
45
|
+
now=window._monotonic(),
|
|
46
|
+
)
|
|
47
|
+
if flash_index is not None and window.selected_flow_name == flow_name:
|
|
48
|
+
flash_operation_row(window, flash_index)
|
|
49
|
+
if window.selected_flow_name == flow_name:
|
|
50
|
+
render_operation_durations(window, flow_name)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def render_operation_durations(window: "DataEngineWindow", flow_name: str) -> None:
|
|
54
|
+
card = window.flow_cards.get(flow_name)
|
|
55
|
+
state = window.operation_tracker.state_for(flow_name)
|
|
56
|
+
if card is None or state is None:
|
|
57
|
+
for row_widgets in window.operation_row_widgets:
|
|
58
|
+
row_widgets.duration_label.setText("")
|
|
59
|
+
return
|
|
60
|
+
for index, operation_name in enumerate(card.operation_items):
|
|
61
|
+
if index >= len(window.operation_row_widgets):
|
|
62
|
+
break
|
|
63
|
+
row_widgets = window.operation_row_widgets[index]
|
|
64
|
+
row_card = row_widgets.row_card
|
|
65
|
+
duration_label = row_widgets.duration_label
|
|
66
|
+
row_state = window.operation_tracker.row_state(flow_name, operation_name)
|
|
67
|
+
apply_operation_row_state(row_card, row_state.status if row_state is not None else "idle")
|
|
68
|
+
duration_label.setText(duration_text(window, flow_name, operation_name))
|
|
69
|
+
window._refresh_operation_buttons(flow_name)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def duration_text(window: "DataEngineWindow", flow_name: str, operation_name: str) -> str:
|
|
73
|
+
return window.operation_tracker.duration_text(flow_name, operation_name, now=window._monotonic(), formatter=format_seconds)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def refresh_live_operation_durations(window: "DataEngineWindow") -> None:
|
|
77
|
+
if window.selected_flow_name is None:
|
|
78
|
+
return
|
|
79
|
+
card = window.flow_cards.get(window.selected_flow_name)
|
|
80
|
+
state = window.operation_tracker.state_for(window.selected_flow_name)
|
|
81
|
+
if card is None or state is None:
|
|
82
|
+
return
|
|
83
|
+
for index, operation_name in enumerate(card.operation_items):
|
|
84
|
+
if index >= len(window.operation_row_widgets):
|
|
85
|
+
break
|
|
86
|
+
row_state = window.operation_tracker.row_state(window.selected_flow_name, operation_name)
|
|
87
|
+
if row_state is None or row_state.status != "running":
|
|
88
|
+
continue
|
|
89
|
+
row_widgets = window.operation_row_widgets[index]
|
|
90
|
+
row_widgets.duration_label.setText(duration_text(window, window.selected_flow_name, operation_name))
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def apply_operation_row_state(row_card: QFrame, status: str) -> None:
|
|
94
|
+
if row_card.property("stepState") == status:
|
|
95
|
+
return
|
|
96
|
+
row_card.setProperty("stepState", status)
|
|
97
|
+
style = row_card.style()
|
|
98
|
+
style.unpolish(row_card)
|
|
99
|
+
style.polish(row_card)
|
|
100
|
+
row_card.update()
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def flash_operation_row(window: "DataEngineWindow", index: int) -> None:
|
|
104
|
+
if index >= len(window.operation_row_widgets):
|
|
105
|
+
return
|
|
106
|
+
row_card = window.operation_row_widgets[index].row_card
|
|
107
|
+
row_card.setProperty("flashState", "complete")
|
|
108
|
+
style = row_card.style()
|
|
109
|
+
style.unpolish(row_card)
|
|
110
|
+
style.polish(row_card)
|
|
111
|
+
row_card.update()
|
|
112
|
+
|
|
113
|
+
timer = QTimer(window)
|
|
114
|
+
timer.setSingleShot(True)
|
|
115
|
+
|
|
116
|
+
def clear_flash() -> None:
|
|
117
|
+
try:
|
|
118
|
+
row_card.setProperty("flashState", "")
|
|
119
|
+
style = row_card.style()
|
|
120
|
+
style.unpolish(row_card)
|
|
121
|
+
style.polish(row_card)
|
|
122
|
+
row_card.update()
|
|
123
|
+
except RuntimeError:
|
|
124
|
+
pass
|
|
125
|
+
if timer in window.operation_flash_timers:
|
|
126
|
+
window.operation_flash_timers.remove(timer)
|
|
127
|
+
|
|
128
|
+
timer.timeout.connect(clear_flash)
|
|
129
|
+
window.operation_flash_timers.append(timer)
|
|
130
|
+
timer.start(140)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def normalize_completed_operation_rows(window: "DataEngineWindow", flow_name: str) -> None:
|
|
134
|
+
window.operation_tracker = window.operation_tracker.normalize_completed(flow_name)
|
|
135
|
+
if window.selected_flow_name == flow_name:
|
|
136
|
+
render_operation_durations(window, flow_name)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
__all__ = [
|
|
140
|
+
"apply_runtime_event",
|
|
141
|
+
"duration_text",
|
|
142
|
+
"flash_operation_row",
|
|
143
|
+
"format_seconds",
|
|
144
|
+
"normalize_completed_operation_rows",
|
|
145
|
+
"refresh_live_operation_durations",
|
|
146
|
+
"render_operation_durations",
|
|
147
|
+
"reset_operation_state",
|
|
148
|
+
]
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Compatibility re-exports for the split workspace presenter helpers."""
|
|
2
|
+
|
|
3
|
+
from data_engine.ui.gui.presenters.docs import (
|
|
4
|
+
create_docs_browser,
|
|
5
|
+
docs_build_dir,
|
|
6
|
+
finish_docs_build,
|
|
7
|
+
initialize_docs_view,
|
|
8
|
+
load_docs_page,
|
|
9
|
+
run_docs_build_worker,
|
|
10
|
+
start_docs_build,
|
|
11
|
+
)
|
|
12
|
+
from data_engine.ui.gui.presenters.runtime_projection import (
|
|
13
|
+
apply_daemon_snapshot,
|
|
14
|
+
finish_daemon_startup,
|
|
15
|
+
)
|
|
16
|
+
from data_engine.ui.gui.presenters.workspace_binding import rebind_workspace_context
|
|
17
|
+
from data_engine.ui.gui.presenters.workspace_settings import (
|
|
18
|
+
browse_workspace_collection_root_override,
|
|
19
|
+
refresh_workspace_root_controls,
|
|
20
|
+
reset_workspace_collection_root_override,
|
|
21
|
+
save_workspace_collection_root_override,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"apply_daemon_snapshot",
|
|
26
|
+
"browse_workspace_collection_root_override",
|
|
27
|
+
"create_docs_browser",
|
|
28
|
+
"docs_build_dir",
|
|
29
|
+
"finish_daemon_startup",
|
|
30
|
+
"finish_docs_build",
|
|
31
|
+
"initialize_docs_view",
|
|
32
|
+
"load_docs_page",
|
|
33
|
+
"rebind_workspace_context",
|
|
34
|
+
"refresh_workspace_root_controls",
|
|
35
|
+
"reset_workspace_collection_root_override",
|
|
36
|
+
"run_docs_build_worker",
|
|
37
|
+
"save_workspace_collection_root_override",
|
|
38
|
+
"start_docs_build",
|
|
39
|
+
]
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""Workspace rebinding helpers for the desktop UI."""
|
|
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 StepOutputIndex, WorkspaceControlState
|
|
9
|
+
from data_engine.ui.gui.helpers import register_client_session
|
|
10
|
+
from data_engine.ui.gui.presenters.workspace_settings import refresh_workspace_root_controls
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from data_engine.ui.gui.app import DataEngineWindow
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _close_workspace_scoped_dialogs(window: "DataEngineWindow") -> None:
|
|
17
|
+
for attr_name in ("output_preview_dialog", "config_preview_dialog", "run_log_preview_dialog"):
|
|
18
|
+
dialog = getattr(window, attr_name, None)
|
|
19
|
+
if dialog is None:
|
|
20
|
+
continue
|
|
21
|
+
try:
|
|
22
|
+
dialog.close()
|
|
23
|
+
except Exception:
|
|
24
|
+
pass
|
|
25
|
+
setattr(window, attr_name, None)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def rebind_workspace_context(
|
|
29
|
+
window: "DataEngineWindow",
|
|
30
|
+
*,
|
|
31
|
+
workspace_id: str | None = None,
|
|
32
|
+
override_root: Path | None | object = ...,
|
|
33
|
+
) -> None:
|
|
34
|
+
from data_engine.ui.gui.presenters.docs import initialize_docs_view
|
|
35
|
+
|
|
36
|
+
window._message_box_generation += 1
|
|
37
|
+
window._pending_message_box = None
|
|
38
|
+
window._message_box_scheduled = False
|
|
39
|
+
_close_workspace_scoped_dialogs(window)
|
|
40
|
+
|
|
41
|
+
try:
|
|
42
|
+
window.runtime_binding_service.remove_client_session(window.runtime_binding, window.client_session_id)
|
|
43
|
+
except Exception:
|
|
44
|
+
pass
|
|
45
|
+
window.runtime_binding_service.close_binding(window.runtime_binding)
|
|
46
|
+
if override_root is ...:
|
|
47
|
+
override_root = window.workspace_collection_root_override
|
|
48
|
+
window.workspace_paths = window._resolve_workspace_paths(
|
|
49
|
+
workspace_id=workspace_id,
|
|
50
|
+
workspace_collection_root=override_root,
|
|
51
|
+
)
|
|
52
|
+
binding = window.workspace_session_application.bind_workspace(
|
|
53
|
+
workspace_paths=window.workspace_paths,
|
|
54
|
+
override_root=override_root,
|
|
55
|
+
)
|
|
56
|
+
window._operator_session_state = binding.operator_session
|
|
57
|
+
window.runtime_binding = window.runtime_binding_service.open_binding(window.workspace_paths)
|
|
58
|
+
register_client_session(window)
|
|
59
|
+
window.daemon_status = window.daemon_status.empty()
|
|
60
|
+
window.workspace_control_state = WorkspaceControlState.empty()
|
|
61
|
+
window._daemon_startup_in_progress = False
|
|
62
|
+
window._last_daemon_spawn_attempt = 0.0
|
|
63
|
+
window.manual_flow_stop_events = {}
|
|
64
|
+
window.step_output_index = StepOutputIndex.empty()
|
|
65
|
+
window._reload_workspace_options()
|
|
66
|
+
refresh_workspace_root_controls(window)
|
|
67
|
+
window._load_flows()
|
|
68
|
+
window._refresh_log_view(force_scroll_to_bottom=True)
|
|
69
|
+
window._refresh_action_buttons()
|
|
70
|
+
initialize_docs_view(window)
|
|
71
|
+
if window._auto_daemon_enabled:
|
|
72
|
+
window._sync_from_daemon()
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
__all__ = ["rebind_workspace_context"]
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"""Workspace-folder settings presentation helpers for the desktop UI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from datetime import UTC, datetime, timedelta
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
import sys
|
|
8
|
+
from typing import TYPE_CHECKING
|
|
9
|
+
|
|
10
|
+
from PySide6.QtWidgets import QFileDialog
|
|
11
|
+
|
|
12
|
+
from data_engine.domain.time import parse_utc_text
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from data_engine.ui.gui.app import DataEngineWindow
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def refresh_workspace_root_controls(window: "DataEngineWindow") -> None:
|
|
19
|
+
window.workspace_root_input.setText(window.workspace_session_state.root.input_text)
|
|
20
|
+
window.workspace_root_status_label.setText(window.workspace_session_state.root.status_text)
|
|
21
|
+
refresh_workspace_provisioning_controls(window)
|
|
22
|
+
refresh_workspace_visibility_panel(window)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def save_workspace_collection_root_override(window: "DataEngineWindow") -> None:
|
|
26
|
+
from data_engine.ui.gui.presenters.workspace_binding import rebind_workspace_context
|
|
27
|
+
|
|
28
|
+
raw_value = window.workspace_root_input.text().strip()
|
|
29
|
+
if not raw_value:
|
|
30
|
+
reset_workspace_collection_root_override(window)
|
|
31
|
+
return
|
|
32
|
+
target_root = Path(raw_value).expanduser().resolve()
|
|
33
|
+
window.settings_service.set_workspace_collection_root(target_root)
|
|
34
|
+
rebind_workspace_context(window, override_root=target_root)
|
|
35
|
+
window.workspace_root_status_label.setText(f"Workspace folder: {target_root}")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def reset_workspace_collection_root_override(window: "DataEngineWindow") -> None:
|
|
39
|
+
from data_engine.ui.gui.presenters.workspace_binding import rebind_workspace_context
|
|
40
|
+
|
|
41
|
+
window.settings_service.set_workspace_collection_root(None)
|
|
42
|
+
rebind_workspace_context(window, override_root=None)
|
|
43
|
+
window.workspace_root_status_label.setText(window.workspace_session_state.root.status_text)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def browse_workspace_collection_root_override(window: "DataEngineWindow") -> None:
|
|
47
|
+
current_text = window.workspace_root_input.text().strip()
|
|
48
|
+
if current_text:
|
|
49
|
+
start_dir = str(Path(current_text).expanduser())
|
|
50
|
+
elif window.workspace_paths.workspace_configured:
|
|
51
|
+
start_dir = str(window.workspace_paths.workspace_collection_root)
|
|
52
|
+
else:
|
|
53
|
+
start_dir = str(Path.home())
|
|
54
|
+
selected = QFileDialog.getExistingDirectory(window, "Select Workspace Folder", start_dir)
|
|
55
|
+
if selected:
|
|
56
|
+
window.workspace_root_input.setText(str(Path(selected).expanduser().resolve()))
|
|
57
|
+
save_workspace_collection_root_override(window)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def provision_selected_workspace(window: "DataEngineWindow") -> None:
|
|
61
|
+
if not window.workspace_paths.workspace_configured:
|
|
62
|
+
window._show_message_box(
|
|
63
|
+
title="Workspace Folder Required",
|
|
64
|
+
text="Choose a workspace folder before provisioning a workspace.",
|
|
65
|
+
tone="error",
|
|
66
|
+
)
|
|
67
|
+
return
|
|
68
|
+
try:
|
|
69
|
+
result = window.workspace_provisioning_service.provision_workspace(
|
|
70
|
+
window.workspace_paths,
|
|
71
|
+
interpreter_path=Path(sys.executable).expanduser(),
|
|
72
|
+
)
|
|
73
|
+
except Exception as exc:
|
|
74
|
+
window.workspace_provision_status_label.setText(f"Provisioning failed: {exc}")
|
|
75
|
+
window._show_message_box(
|
|
76
|
+
title="Provisioning Failed",
|
|
77
|
+
text=str(exc),
|
|
78
|
+
tone="error",
|
|
79
|
+
)
|
|
80
|
+
return
|
|
81
|
+
created_names = ", ".join(path.name for path in result.created_paths) if result.created_paths else "nothing new"
|
|
82
|
+
window._rebind_workspace_context(workspace_id=window.workspace_paths.workspace_id)
|
|
83
|
+
window.workspace_provision_status_label.setText(
|
|
84
|
+
f"Provisioned {result.workspace_root.name}: created {created_names}."
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def force_shutdown_daemon(window: "DataEngineWindow") -> None:
|
|
89
|
+
result = window.runtime_application.force_shutdown_daemon(window.workspace_paths, timeout=0.5)
|
|
90
|
+
if not result.ok:
|
|
91
|
+
window.force_shutdown_daemon_status_label.setText(f"Force stop failed: {result.error}")
|
|
92
|
+
window._show_message_box(
|
|
93
|
+
title="Force Stop Failed",
|
|
94
|
+
text=result.error,
|
|
95
|
+
tone="error",
|
|
96
|
+
)
|
|
97
|
+
return
|
|
98
|
+
window.force_shutdown_daemon_status_label.setText(
|
|
99
|
+
"Local daemon force-stopped. Any active engine or manual runs were terminated."
|
|
100
|
+
)
|
|
101
|
+
window._sync_from_daemon()
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def refresh_workspace_provisioning_controls(window: "DataEngineWindow") -> None:
|
|
105
|
+
if not window.workspace_paths.workspace_configured:
|
|
106
|
+
window.workspace_target_label.setText("Selected workspace: choose a workspace folder first.")
|
|
107
|
+
window.provision_workspace_button.setEnabled(False)
|
|
108
|
+
if not window.workspace_provision_status_label.text().strip():
|
|
109
|
+
window.workspace_provision_status_label.setText(
|
|
110
|
+
"Provisioning creates a workspace folder, flow_modules, and VS Code settings without overwriting existing files."
|
|
111
|
+
)
|
|
112
|
+
return
|
|
113
|
+
workspace_root = window.workspace_paths.workspace_root
|
|
114
|
+
workspace_ready = window.workspace_paths.flow_modules_dir.is_dir()
|
|
115
|
+
window.workspace_target_label.setText(f"Selected workspace: {workspace_root}")
|
|
116
|
+
window.provision_workspace_button.setEnabled(True)
|
|
117
|
+
if workspace_ready:
|
|
118
|
+
window.workspace_provision_status_label.setText(
|
|
119
|
+
"Workspace already has flow modules. Provisioning will only add missing folders or VS Code settings."
|
|
120
|
+
)
|
|
121
|
+
else:
|
|
122
|
+
window.workspace_provision_status_label.setText(
|
|
123
|
+
"Provision the selected workspace to create flow_modules and local VS Code settings."
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def refresh_workspace_visibility_panel(window: "DataEngineWindow") -> None:
|
|
128
|
+
interpreter_path = Path(sys.executable).expanduser()
|
|
129
|
+
interpreter_mode = "virtual environment" if sys.prefix != getattr(sys, "base_prefix", sys.prefix) else "system/global"
|
|
130
|
+
window.force_shutdown_daemon_button.setEnabled(window.workspace_paths.workspace_configured)
|
|
131
|
+
if not window.force_shutdown_daemon_status_label.text().strip():
|
|
132
|
+
window.force_shutdown_daemon_status_label.setText(
|
|
133
|
+
"Use only when normal stop does not return control."
|
|
134
|
+
)
|
|
135
|
+
window.visibility_interpreter_value.setText(str(interpreter_path))
|
|
136
|
+
window.visibility_interpreter_mode_value.setText(interpreter_mode.title())
|
|
137
|
+
window.workspace_counts_footer_label.setText(_workspace_counts_footer_text(window))
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _workspace_module_count(flow_modules_dir: Path) -> int:
|
|
141
|
+
if not flow_modules_dir.is_dir():
|
|
142
|
+
return 0
|
|
143
|
+
return sum(
|
|
144
|
+
1
|
|
145
|
+
for path in flow_modules_dir.iterdir()
|
|
146
|
+
if path.is_file() and path.suffix in {".py", ".ipynb"} and path.name != "__init__.py"
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _workspace_counts_footer_text(window: "DataEngineWindow") -> str:
|
|
151
|
+
cards = tuple(window.flow_cards.values())
|
|
152
|
+
module_count = _workspace_module_count(window.workspace_paths.flow_modules_dir)
|
|
153
|
+
group_count = len({card.group for card in cards})
|
|
154
|
+
flow_count = len(cards)
|
|
155
|
+
recent_runs_count = _recent_workspace_run_count(window, days=30)
|
|
156
|
+
return f"{module_count} modules - {group_count} groups - {flow_count} flows - {recent_runs_count} runs last 30 days"
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _recent_workspace_run_count(window: "DataEngineWindow", *, days: int) -> int:
|
|
160
|
+
cutoff = datetime.now(UTC) - timedelta(days=days)
|
|
161
|
+
count = 0
|
|
162
|
+
try:
|
|
163
|
+
runs = window.runtime_binding.runtime_ledger.list_runs()
|
|
164
|
+
except Exception:
|
|
165
|
+
return 0
|
|
166
|
+
for run in runs:
|
|
167
|
+
started_at = parse_utc_text(run.started_at_utc)
|
|
168
|
+
if started_at is not None and started_at >= cutoff:
|
|
169
|
+
count += 1
|
|
170
|
+
return count
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
__all__ = [
|
|
174
|
+
"browse_workspace_collection_root_override",
|
|
175
|
+
"force_shutdown_daemon",
|
|
176
|
+
"provision_selected_workspace",
|
|
177
|
+
"refresh_workspace_provisioning_controls",
|
|
178
|
+
"refresh_workspace_visibility_panel",
|
|
179
|
+
"refresh_workspace_root_controls",
|
|
180
|
+
"reset_workspace_collection_root_override",
|
|
181
|
+
"save_workspace_collection_root_override",
|
|
182
|
+
]
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Explicit request/state models for GUI preview dialogs."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from data_engine.domain import ConfigPreviewState, FlowRunState, RunDetailState
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass(frozen=True)
|
|
12
|
+
class OutputPreviewRequest:
|
|
13
|
+
"""Request state for one output-preview dialog."""
|
|
14
|
+
|
|
15
|
+
operation_name: str
|
|
16
|
+
output_path: Path
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(frozen=True)
|
|
20
|
+
class RunLogPreviewRequest:
|
|
21
|
+
"""Request state for one run-log preview dialog."""
|
|
22
|
+
|
|
23
|
+
run_group: FlowRunState
|
|
24
|
+
detail: RunDetailState
|
|
25
|
+
|
|
26
|
+
@classmethod
|
|
27
|
+
def from_run(cls, run_group: FlowRunState) -> "RunLogPreviewRequest":
|
|
28
|
+
"""Build one run-log preview request from a grouped run."""
|
|
29
|
+
return cls(run_group=run_group, detail=RunDetailState.from_run(run_group))
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass(frozen=True)
|
|
33
|
+
class ConfigPreviewRequest:
|
|
34
|
+
"""Request state for one config-preview dialog."""
|
|
35
|
+
|
|
36
|
+
preview: ConfigPreviewState
|
|
37
|
+
|