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,34 @@
1
+ """View-layer builders for the desktop UI."""
2
+
3
+ from data_engine.ui.gui.widgets.config import build_config_value, make_label_selectable
4
+ from data_engine.ui.gui.widgets.panels import (
5
+ build_action_bar,
6
+ build_center_panel,
7
+ build_docs_view,
8
+ build_nav_rail,
9
+ build_operator_view,
10
+ build_right_panel,
11
+ build_settings_view,
12
+ build_sidebar,
13
+ )
14
+ from data_engine.ui.gui.widgets.sidebar import build_flow_row_widget, build_group_row_widget
15
+ from data_engine.ui.gui.widgets.logs import build_log_run_widget
16
+ from data_engine.ui.gui.widgets.steps import format_operation_title, set_operation_cards
17
+
18
+ __all__ = [
19
+ "build_action_bar",
20
+ "build_center_panel",
21
+ "build_config_value",
22
+ "build_docs_view",
23
+ "build_flow_row_widget",
24
+ "build_log_run_widget",
25
+ "build_group_row_widget",
26
+ "build_nav_rail",
27
+ "build_operator_view",
28
+ "build_right_panel",
29
+ "build_settings_view",
30
+ "build_sidebar",
31
+ "format_operation_title",
32
+ "make_label_selectable",
33
+ "set_operation_cards",
34
+ ]
@@ -0,0 +1,41 @@
1
+ """Small config/detail widget helpers for the desktop GUI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from PySide6.QtCore import Qt
6
+ from PySide6.QtWidgets import QFrame, QHBoxLayout, QLabel, QVBoxLayout
7
+
8
+
9
+ def make_label_selectable(label: QLabel) -> QLabel:
10
+ """Enable text selection on one read-only label."""
11
+ label.setTextInteractionFlags(
12
+ Qt.TextInteractionFlag.TextSelectableByMouse | Qt.TextInteractionFlag.TextSelectableByKeyboard
13
+ )
14
+ return label
15
+
16
+
17
+ def build_config_value(layout: QVBoxLayout, label: str) -> QLabel:
18
+ """Build one two-column config row and return the mutable value label."""
19
+ row_frame = QFrame()
20
+ row_frame.setObjectName("configRow")
21
+ row_layout = QHBoxLayout(row_frame)
22
+ row_layout.setContentsMargins(0, 6, 0, 6)
23
+ row_layout.setSpacing(10)
24
+
25
+ title = QLabel(label)
26
+ title.setObjectName("fieldLabel")
27
+ value = QLabel("-")
28
+ value.setWordWrap(True)
29
+ make_label_selectable(value)
30
+ value.setObjectName("fieldValue")
31
+ value.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop)
32
+ title.setMinimumWidth(92)
33
+ title.setMaximumWidth(92)
34
+ title.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop)
35
+ row_layout.addWidget(title, 0)
36
+ row_layout.addWidget(value, 1)
37
+ layout.addWidget(row_frame)
38
+ return value
39
+
40
+
41
+ __all__ = ["build_config_value", "make_label_selectable"]
@@ -0,0 +1,62 @@
1
+ """Log-list row builders for the desktop UI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ from PySide6.QtCore import Qt
8
+ from PySide6.QtGui import QPixmap
9
+ from PySide6.QtWidgets import QFrame, QHBoxLayout, QLabel, QPushButton
10
+
11
+ from data_engine.views import RunGroupDisplay
12
+
13
+ if TYPE_CHECKING:
14
+ from data_engine.domain import FlowRunState
15
+ from data_engine.ui.gui.app import DataEngineWindow
16
+
17
+
18
+ def build_log_run_widget(window: "DataEngineWindow", run_group: "FlowRunState") -> QFrame:
19
+ display = RunGroupDisplay.from_run(run_group)
20
+ frame = QFrame()
21
+ frame.setObjectName("logRunRow")
22
+ frame.setProperty("sourceLabel", display.source_label)
23
+ layout = QHBoxLayout(frame)
24
+ layout.setContentsMargins(8, 6, 8, 6)
25
+ layout.setSpacing(10)
26
+
27
+ title_row = QHBoxLayout()
28
+ title_row.setContentsMargins(0, 0, 0, 0)
29
+ title_row.setSpacing(8)
30
+ title = QLabel(display.primary_label)
31
+ title.setObjectName("logPrimary")
32
+ title_row.addWidget(title, 0, Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter)
33
+ if display.duration_text is not None:
34
+ duration = QLabel(display.duration_text)
35
+ duration.setObjectName("logDuration")
36
+ title_row.addWidget(duration, 0, Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter)
37
+ title_row.addStretch(1)
38
+ layout.addLayout(title_row, 1)
39
+
40
+ status_icon = QLabel()
41
+ status_icon.setObjectName("logStatusIcon")
42
+ status_icon.setPixmap(
43
+ window._render_svg_icon_pixmap(
44
+ window._LOG_ICON_NAMES[display.status_visual_state],
45
+ 16,
46
+ fill_color=window._LOG_ICON_COLORS[display.status_visual_state],
47
+ )
48
+ )
49
+ status_icon.setToolTip(display.status_text)
50
+ layout.addWidget(status_icon, 0, Qt.AlignmentFlag.AlignVCenter)
51
+
52
+ view_button = QPushButton()
53
+ view_button.setObjectName("logIconButton")
54
+ view_button.setIcon(window._log_icon("view_log"))
55
+ view_button.setIconSize(QPixmap(16, 16).size())
56
+ view_button.setToolTip("View Log")
57
+ view_button.clicked.connect(lambda _checked=False, group=run_group: window._show_run_log_preview(group))
58
+ layout.addWidget(view_button, 0, Qt.AlignmentFlag.AlignVCenter)
59
+ return frame
60
+
61
+
62
+ __all__ = ["build_log_run_widget"]
@@ -0,0 +1,507 @@
1
+ """Panel and top-level view builders for the desktop UI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ from PySide6.QtCore import Qt
8
+ from PySide6.QtGui import QFont, QPixmap
9
+ from PySide6.QtWidgets import (
10
+ QButtonGroup,
11
+ QComboBox,
12
+ QFrame,
13
+ QHBoxLayout,
14
+ QLabel,
15
+ QLineEdit,
16
+ QListWidget,
17
+ QPushButton,
18
+ QScrollArea,
19
+ QSplitter,
20
+ QTabWidget,
21
+ QToolButton,
22
+ QVBoxLayout,
23
+ QWidget,
24
+ )
25
+
26
+ if TYPE_CHECKING:
27
+ from data_engine.ui.gui.app import DataEngineWindow
28
+
29
+
30
+ def build_operator_view(window: "DataEngineWindow") -> QWidget:
31
+ container = QWidget()
32
+ layout = QVBoxLayout(container)
33
+ layout.setContentsMargins(0, 0, 0, 0)
34
+ layout.setSpacing(10)
35
+
36
+ title = QLabel("Flow Control")
37
+ title.setObjectName("heroTitle")
38
+ layout.addWidget(title)
39
+ window.action_bar = build_action_bar(window)
40
+ layout.addWidget(window.action_bar)
41
+
42
+ splitter = QSplitter(Qt.Orientation.Horizontal)
43
+ splitter.setChildrenCollapsible(False)
44
+ sidebar = build_sidebar(window)
45
+ center = build_center_panel(window)
46
+ right = build_right_panel(window)
47
+ sidebar.setMinimumWidth(320)
48
+ right.setMinimumWidth(320)
49
+ splitter.addWidget(sidebar)
50
+ splitter.addWidget(center)
51
+ splitter.addWidget(right)
52
+ splitter.setStretchFactor(0, 2)
53
+ splitter.setStretchFactor(1, 4)
54
+ splitter.setStretchFactor(2, 4)
55
+ splitter.setSizes([320, 505, 505])
56
+ layout.addWidget(splitter, 1)
57
+ return container
58
+
59
+
60
+ def build_nav_rail(window: "DataEngineWindow") -> QWidget:
61
+ panel = QFrame()
62
+ panel.setObjectName("navRail")
63
+ panel.setFixedWidth(56)
64
+ layout = QVBoxLayout(panel)
65
+ layout.setContentsMargins(6, 6, 6, 6)
66
+ layout.setSpacing(8)
67
+
68
+ window.view_button_group = QButtonGroup(window)
69
+ window.view_button_group.setExclusive(True)
70
+
71
+ window.home_button = _nav_button(window, "home", "Home")
72
+ window.docs_button = _nav_button(window, "docs", "Docs")
73
+ window.settings_button = _nav_button(window, "settings", "Settings")
74
+
75
+ for index, button in enumerate((window.home_button, window.docs_button, window.settings_button)):
76
+ window.view_button_group.addButton(button, index)
77
+ layout.addWidget(button, 0, Qt.AlignmentFlag.AlignTop)
78
+
79
+ window.view_button_group.idClicked.connect(window._switch_view)
80
+ window.home_button.setChecked(True)
81
+ layout.addStretch(1)
82
+ return panel
83
+
84
+
85
+ def _nav_button(window: "DataEngineWindow", icon_name: str, tooltip: str) -> QToolButton:
86
+ button = QToolButton()
87
+ button.setObjectName("navButton")
88
+ button.setCheckable(True)
89
+ button.setAutoExclusive(True)
90
+ button.setProperty("viewIconName", icon_name)
91
+ button.setIcon(window._view_rail_icon(icon_name))
92
+ button.setIconSize(QPixmap(18, 18).size())
93
+ button.setFixedSize(40, 40)
94
+ button.setToolTip(tooltip)
95
+ return button
96
+
97
+
98
+ def build_docs_view(window: "DataEngineWindow") -> QWidget:
99
+ container = QWidget()
100
+ container_layout = QVBoxLayout(container)
101
+ container_layout.setContentsMargins(0, 0, 0, 0)
102
+ container_layout.setSpacing(10)
103
+
104
+ title = QLabel("Documentation")
105
+ title.setObjectName("heroTitle")
106
+ container_layout.addWidget(title)
107
+
108
+ panel = QFrame()
109
+ panel.setObjectName("workspacePanel")
110
+ layout = QVBoxLayout(panel)
111
+ layout.setContentsMargins(18, 18, 18, 18)
112
+ layout.setSpacing(10)
113
+
114
+ header = QHBoxLayout()
115
+ header.setContentsMargins(0, 0, 0, 0)
116
+ header.setSpacing(8)
117
+ window.docs_status_label = QLabel("")
118
+ window.docs_status_label.setObjectName("sectionMeta")
119
+ header.addWidget(window.docs_status_label, 1)
120
+
121
+ window.docs_generate_button = QPushButton("Generate Documentation")
122
+ window.docs_generate_button.clicked.connect(window._start_docs_build)
123
+ header.addWidget(window.docs_generate_button)
124
+ layout.addLayout(header)
125
+
126
+ window.docs_browser = window._create_docs_browser()
127
+ window.docs_browser.setObjectName("docsBrowser")
128
+ window.docs_browser.setStyleSheet("background: #ffffff;")
129
+ layout.addWidget(window.docs_browser, 1)
130
+ container_layout.addWidget(panel, 1)
131
+ window._initialize_docs_view()
132
+ return container
133
+
134
+
135
+ def build_settings_view(window: "DataEngineWindow") -> QWidget:
136
+ container = QWidget()
137
+ container_layout = QVBoxLayout(container)
138
+ container_layout.setContentsMargins(0, 0, 0, 0)
139
+ container_layout.setSpacing(10)
140
+
141
+ title = QLabel("Settings")
142
+ title.setObjectName("heroTitle")
143
+ container_layout.addWidget(title)
144
+
145
+ splitter = QSplitter(Qt.Orientation.Horizontal)
146
+ splitter.setChildrenCollapsible(False)
147
+ workspace_panel = _build_workspace_settings_panel(window)
148
+ bootstrap_panel = _build_bootstrap_settings_panel(window)
149
+ workspace_panel.setMinimumWidth(360)
150
+ bootstrap_panel.setMinimumWidth(360)
151
+ splitter.addWidget(workspace_panel)
152
+ splitter.addWidget(bootstrap_panel)
153
+ splitter.setStretchFactor(0, 1)
154
+ splitter.setStretchFactor(1, 1)
155
+ splitter.setSizes([520, 520])
156
+
157
+ container_layout.addWidget(splitter, 1)
158
+ window._refresh_workspace_root_controls()
159
+ return container
160
+
161
+
162
+ def _build_workspace_settings_panel(window: "DataEngineWindow") -> QWidget:
163
+ workspace_panel = QFrame()
164
+ workspace_panel.setObjectName("workspacePanel")
165
+ workspace_layout = QVBoxLayout(workspace_panel)
166
+ workspace_layout.setContentsMargins(18, 18, 18, 18)
167
+ workspace_layout.setSpacing(12)
168
+
169
+ workspace_title = QLabel("Workspace Folder")
170
+ workspace_title.setObjectName("sectionTitle")
171
+ workspace_layout.addWidget(workspace_title)
172
+
173
+ workspace_intro = QLabel(
174
+ "Choose the folder on this workstation that contains your workspaces. "
175
+ "Selecting a folder updates the app immediately."
176
+ )
177
+ workspace_intro.setWordWrap(True)
178
+ workspace_intro.setObjectName("bodyText")
179
+ workspace_layout.addWidget(workspace_intro)
180
+
181
+ window.workspace_root_status_label = QLabel("")
182
+ window.workspace_root_status_label.setWordWrap(True)
183
+ window.workspace_root_status_label.setObjectName("sectionMeta")
184
+ workspace_layout.addWidget(window.workspace_root_status_label)
185
+
186
+ window.workspace_root_input = QLineEdit()
187
+ window.workspace_root_input.setObjectName("pathInput")
188
+ window.workspace_root_input.setReadOnly(True)
189
+ window.workspace_root_input.setPlaceholderText("Choose a workspace folder")
190
+ window.workspace_root_input.setText(window.workspace_session_state.root.input_text)
191
+ workspace_layout.addWidget(window.workspace_root_input)
192
+
193
+ workspace_actions = QHBoxLayout()
194
+ workspace_actions.setContentsMargins(0, 0, 0, 0)
195
+ workspace_actions.setSpacing(8)
196
+
197
+ window.browse_workspace_root_button = QPushButton("Browse…")
198
+ window.browse_workspace_root_button.clicked.connect(window._browse_workspace_collection_root_override)
199
+ workspace_actions.addWidget(window.browse_workspace_root_button)
200
+ workspace_actions.addStretch(1)
201
+ workspace_layout.addLayout(workspace_actions)
202
+
203
+ provision_title = QLabel("Provision Selected Workspace")
204
+ provision_title.setObjectName("sectionTitle")
205
+ workspace_layout.addWidget(provision_title)
206
+
207
+ provision_intro = QLabel(
208
+ "Create the selected workspace folder shape for this workstation without overwriting existing authored files."
209
+ )
210
+ provision_intro.setWordWrap(True)
211
+ provision_intro.setObjectName("bodyText")
212
+ workspace_layout.addWidget(provision_intro)
213
+
214
+ window.workspace_target_label = QLabel("")
215
+ window.workspace_target_label.setWordWrap(True)
216
+ window.workspace_target_label.setObjectName("sectionMeta")
217
+ workspace_layout.addWidget(window.workspace_target_label)
218
+
219
+ window.provision_workspace_button = QPushButton("Provision Workspace")
220
+ window.provision_workspace_button.clicked.connect(window._provision_selected_workspace)
221
+ workspace_layout.addWidget(window.provision_workspace_button, 0, Qt.AlignmentFlag.AlignLeft)
222
+
223
+ window.workspace_provision_status_label = QLabel("")
224
+ window.workspace_provision_status_label.setWordWrap(True)
225
+ window.workspace_provision_status_label.setObjectName("sectionMeta")
226
+ workspace_layout.addWidget(window.workspace_provision_status_label)
227
+
228
+ workspace_layout.addStretch(1)
229
+ return workspace_panel
230
+
231
+
232
+ def _build_bootstrap_settings_panel(window: "DataEngineWindow") -> QWidget:
233
+ details_panel = QFrame()
234
+ details_panel.setObjectName("workspacePanel")
235
+ details_layout = QVBoxLayout(details_panel)
236
+ details_layout.setContentsMargins(18, 18, 18, 18)
237
+ details_layout.setSpacing(14)
238
+
239
+ details_title = QLabel("Workspace Visibility")
240
+ details_title.setObjectName("sectionTitle")
241
+ details_layout.addWidget(details_title)
242
+
243
+ intro = QLabel(
244
+ "Read-only environment and daemon details for the current workspace."
245
+ )
246
+ intro.setWordWrap(True)
247
+ intro.setObjectName("sectionMeta")
248
+ details_layout.addWidget(intro)
249
+
250
+ interpreter_title = QLabel("Python Interpreter")
251
+ interpreter_title.setObjectName("sectionTitle")
252
+ details_layout.addWidget(interpreter_title)
253
+
254
+ details_layout.addLayout(_build_settings_fact_row("Environment", "visibility_interpreter_mode_value", window))
255
+ details_layout.addLayout(_build_settings_fact_row("Executable", "visibility_interpreter_value", window, selectable=True))
256
+
257
+ daemon_title = QLabel("Emergency")
258
+ daemon_title.setObjectName("sectionTitle")
259
+ details_layout.addWidget(daemon_title)
260
+
261
+ daemon_intro = QLabel(
262
+ "Use only if the selected workspace daemon stops responding."
263
+ )
264
+ daemon_intro.setWordWrap(True)
265
+ daemon_intro.setObjectName("sectionMeta")
266
+ details_layout.addWidget(daemon_intro)
267
+
268
+ window.force_shutdown_daemon_button = QPushButton("Force Stop Daemon")
269
+ window.force_shutdown_daemon_button.clicked.connect(window._force_shutdown_daemon)
270
+ details_layout.addWidget(window.force_shutdown_daemon_button, 0, Qt.AlignmentFlag.AlignLeft)
271
+
272
+ window.force_shutdown_daemon_status_label = QLabel("")
273
+ window.force_shutdown_daemon_status_label.setWordWrap(True)
274
+ window.force_shutdown_daemon_status_label.setObjectName("sectionMeta")
275
+ details_layout.addWidget(window.force_shutdown_daemon_status_label)
276
+
277
+ details_layout.addStretch(1)
278
+ return details_panel
279
+
280
+
281
+ def _build_settings_fact_row(
282
+ label_text: str,
283
+ value_attr: str,
284
+ window: "DataEngineWindow",
285
+ *,
286
+ selectable: bool = False,
287
+ ) -> QVBoxLayout:
288
+ row = QVBoxLayout()
289
+ row.setContentsMargins(0, 0, 0, 0)
290
+ row.setSpacing(2)
291
+
292
+ label = QLabel(label_text)
293
+ label.setObjectName("fieldLabel")
294
+ row.addWidget(label)
295
+
296
+ value = QLabel("")
297
+ value.setWordWrap(True)
298
+ value.setObjectName("fieldValue")
299
+ if selectable:
300
+ value.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse)
301
+ setattr(window, value_attr, value)
302
+ row.addWidget(value)
303
+ return row
304
+
305
+
306
+ def build_action_bar(window: "DataEngineWindow") -> QWidget:
307
+ frame = QFrame()
308
+ frame.setObjectName("actionBar")
309
+ row = QHBoxLayout(frame)
310
+ row.setContentsMargins(0, 0, 0, 0)
311
+ row.setSpacing(10)
312
+
313
+ window.engine_button = QPushButton("Start Engine")
314
+ window.engine_button.setObjectName("engineButton")
315
+ window.engine_button.setFixedHeight(36)
316
+ window.engine_button.clicked.connect(window._toggle_runtime)
317
+
318
+ window.refresh_button = QPushButton()
319
+ window.refresh_button.setObjectName("refreshButton")
320
+ window.refresh_button.setIcon(window._action_bar_icon("refresh"))
321
+ window.refresh_button.setIconSize(QPixmap(16, 16).size())
322
+ window.refresh_button.setFixedSize(36, 36)
323
+ window.refresh_button.setToolTip("Refresh Flows")
324
+ window.refresh_button.clicked.connect(window._refresh_flows_requested)
325
+
326
+ controls_group = QFrame()
327
+ controls_group.setObjectName("actionBarGroup")
328
+ controls_row = QHBoxLayout(controls_group)
329
+ controls_row.setContentsMargins(10, 8, 10, 8)
330
+ controls_row.setSpacing(6)
331
+ controls_row.addWidget(window.engine_button)
332
+ window.request_control_button = QPushButton("Request Control")
333
+ window.request_control_button.setObjectName("requestControlButton")
334
+ window.request_control_button.setFixedHeight(36)
335
+ window.request_control_button.setVisible(True)
336
+ window.request_control_button.clicked.connect(window._request_control)
337
+ controls_row.addWidget(window.request_control_button)
338
+ window.workspace_selector = QComboBox()
339
+ window.workspace_selector.setObjectName("workspaceSelector")
340
+ window.workspace_selector.setMinimumWidth(200)
341
+ window.workspace_selector.setFixedHeight(36)
342
+ window.workspace_selector.currentIndexChanged.connect(window._workspace_selection_changed)
343
+ controls_row.addWidget(window.workspace_selector)
344
+ row.addWidget(controls_group, 0)
345
+
346
+ window.lease_status_label = QLabel("")
347
+ window.lease_status_label.setObjectName("sectionMeta")
348
+ window.lease_status_label.setVisible(False)
349
+ row.addWidget(window.lease_status_label, 0, Qt.AlignmentFlag.AlignVCenter)
350
+
351
+ row.addStretch(1)
352
+ row.addWidget(window.refresh_button, 0, Qt.AlignmentFlag.AlignVCenter)
353
+
354
+ window.theme_toggle_button = QPushButton()
355
+ window.theme_toggle_button.setObjectName("themeToggleButton")
356
+ window.theme_toggle_button.setIcon(window._action_bar_icon("theme_toggle"))
357
+ window.theme_toggle_button.setIconSize(QPixmap(16, 16).size())
358
+ window.theme_toggle_button.setFixedSize(36, 36)
359
+ window.theme_toggle_button.setToolTip("Switch Theme")
360
+ window.theme_toggle_button.clicked.connect(window._toggle_theme)
361
+ row.addWidget(window.theme_toggle_button)
362
+ return frame
363
+
364
+
365
+ def build_sidebar(window: "DataEngineWindow") -> QWidget:
366
+ panel = QFrame()
367
+ panel.setObjectName("sidebarPanel")
368
+ layout = QVBoxLayout(panel)
369
+ layout.setContentsMargins(18, 18, 18, 18)
370
+ layout.setSpacing(10)
371
+
372
+ title = QLabel("Configured Flows")
373
+ title.setObjectName("sectionTitle")
374
+ layout.addWidget(title)
375
+
376
+ window.sidebar_scroll = QScrollArea()
377
+ window.sidebar_scroll.setWidgetResizable(True)
378
+ window.sidebar_scroll.setFrameShape(QFrame.Shape.NoFrame)
379
+ window.sidebar_scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
380
+ window.sidebar_scroll.setObjectName("sidebarScroll")
381
+ window.sidebar_scroll.verticalScrollBar().valueChanged.connect(window._update_sidebar_scroll_cues)
382
+ window.sidebar_scroll.verticalScrollBar().rangeChanged.connect(window._update_sidebar_scroll_cues)
383
+ window.sidebar_content = QWidget()
384
+ window.sidebar_content.setObjectName("sidebarContent")
385
+ window.sidebar_layout = QVBoxLayout(window.sidebar_content)
386
+ window.sidebar_layout.setContentsMargins(0, 0, 0, 0)
387
+ window.sidebar_layout.setSpacing(2)
388
+ window.sidebar_layout.addStretch(1)
389
+ window.sidebar_scroll.setWidget(window.sidebar_content)
390
+ window.sidebar_top_cue = QFrame()
391
+ window.sidebar_top_cue.setObjectName("sidebarCueTop")
392
+ window.sidebar_bottom_cue = QFrame()
393
+ window.sidebar_bottom_cue.setObjectName("sidebarCueBottom")
394
+ window.sidebar_top_cue.hide()
395
+ window.sidebar_bottom_cue.hide()
396
+ layout.addWidget(window.sidebar_top_cue)
397
+ layout.addWidget(window.sidebar_scroll, 1)
398
+ layout.addWidget(window.sidebar_bottom_cue)
399
+ return panel
400
+
401
+
402
+ def build_center_panel(window: "DataEngineWindow") -> QWidget:
403
+ panel = QFrame()
404
+ panel.setObjectName("workspacePanel")
405
+ layout = QVBoxLayout(panel)
406
+ layout.setContentsMargins(18, 18, 18, 18)
407
+ layout.setSpacing(10)
408
+
409
+ header = QFrame()
410
+ header.setObjectName("heroHeader")
411
+ header_row = QHBoxLayout(header)
412
+ header_row.setContentsMargins(0, 0, 0, 0)
413
+ header_row.setSpacing(12)
414
+
415
+ title_column = QVBoxLayout()
416
+ title_column.setContentsMargins(0, 0, 0, 0)
417
+ title_column.setSpacing(2)
418
+ window.operations_title_label = QLabel("Steps")
419
+ window.operations_title_label.setObjectName("sectionTitle")
420
+ title_column.addWidget(window.operations_title_label, 0, Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignLeft)
421
+
422
+ window.flow_error_label = QLabel("")
423
+ window.flow_error_label.setWordWrap(True)
424
+ window.flow_error_label.setObjectName("errorText")
425
+ title_column.addWidget(window.flow_error_label, 0, Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignLeft)
426
+ header_row.addLayout(title_column, 1)
427
+
428
+ title_row = QHBoxLayout()
429
+ title_row.setContentsMargins(0, 0, 0, 0)
430
+ title_row.setSpacing(6)
431
+ window.flow_run_button = QPushButton("Run Once")
432
+ window.flow_run_button.setObjectName("flowRunButton")
433
+ window.flow_run_button.clicked.connect(window._run_selected_flow)
434
+ title_row.addWidget(window.flow_run_button, 0, Qt.AlignmentFlag.AlignTop)
435
+ window.flow_config_button = QPushButton("View Config")
436
+ window.flow_config_button.clicked.connect(window._show_config_preview)
437
+ title_row.addWidget(window.flow_config_button, 0, Qt.AlignmentFlag.AlignTop)
438
+ header_row.addLayout(title_row, 0)
439
+
440
+ window.operation_scroll = QScrollArea()
441
+ window.operation_scroll.setWidgetResizable(True)
442
+ window.operation_scroll.setFrameShape(QFrame.Shape.NoFrame)
443
+ window.operation_scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
444
+ window.operation_scroll.setObjectName("operationScroll")
445
+ window.operation_container = QFrame()
446
+ window.operation_container.setObjectName("operationList")
447
+ window.operation_layout = QVBoxLayout(window.operation_container)
448
+ window.operation_layout.setContentsMargins(0, 0, 0, 0)
449
+ window.operation_layout.setSpacing(6)
450
+ window.operation_layout.addStretch(1)
451
+ window.operation_scroll.setWidget(window.operation_container)
452
+ window.operation_scroll.verticalScrollBar().valueChanged.connect(window._update_operation_scroll_cues)
453
+ window.operation_scroll.verticalScrollBar().rangeChanged.connect(window._update_operation_scroll_cues)
454
+
455
+ window.operation_top_cue = QFrame()
456
+ window.operation_top_cue.setObjectName("operationCueTop")
457
+ window.operation_bottom_cue = QFrame()
458
+ window.operation_bottom_cue.setObjectName("operationCueBottom")
459
+ window.operation_top_cue.hide()
460
+ window.operation_bottom_cue.hide()
461
+
462
+ layout.addWidget(header)
463
+ layout.addWidget(window.operation_top_cue)
464
+ layout.addWidget(window.operation_scroll, 1)
465
+ layout.addWidget(window.operation_bottom_cue)
466
+ return panel
467
+
468
+
469
+ def build_right_panel(window: "DataEngineWindow") -> QWidget:
470
+ panel = QFrame()
471
+ panel.setObjectName("workspacePanel")
472
+ layout = QVBoxLayout(panel)
473
+ layout.setContentsMargins(18, 18, 18, 18)
474
+ layout.setSpacing(10)
475
+
476
+ title = QLabel("Logs")
477
+ title.setObjectName("sectionTitle")
478
+ header = QHBoxLayout()
479
+ header.setContentsMargins(0, 0, 0, 0)
480
+ header.setSpacing(8)
481
+ header.addWidget(title)
482
+ header.addStretch(1)
483
+
484
+ window.clear_flow_log_button = QPushButton("Clear Flow Log")
485
+ window.clear_flow_log_button.clicked.connect(window._clear_logs)
486
+ header.addWidget(window.clear_flow_log_button)
487
+ layout.addLayout(header)
488
+
489
+ window.log_view = QListWidget()
490
+ window.log_view.setObjectName("logList")
491
+ mono = QFont("Menlo")
492
+ mono.setStyleHint(QFont.StyleHint.Monospace)
493
+ window.log_view.setFont(mono)
494
+ layout.addWidget(window.log_view, 1)
495
+ return panel
496
+
497
+
498
+ __all__ = [
499
+ "build_action_bar",
500
+ "build_center_panel",
501
+ "build_docs_view",
502
+ "build_nav_rail",
503
+ "build_operator_view",
504
+ "build_right_panel",
505
+ "build_settings_view",
506
+ "build_sidebar",
507
+ ]