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,259 @@
1
+ """Shared theme tokens and host-theme detection across Data Engine surfaces."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ import os
7
+ import platform
8
+ import subprocess
9
+
10
+
11
+ @dataclass(frozen=True)
12
+ class ThemePalette:
13
+ """One complete application palette."""
14
+
15
+ name: str
16
+ window_bg: str
17
+ app_bg: str
18
+ panel_bg: str
19
+ panel_border: str
20
+ text: str
21
+ muted_text: str
22
+ section_text: str
23
+ accent_text: str
24
+ warning_text: str
25
+ error_text: str
26
+ button_bg: str
27
+ button_hover: str
28
+ button_checked_bg: str
29
+ button_checked_border: str
30
+ button_disabled_bg: str
31
+ button_disabled_border: str
32
+ button_disabled_text: str
33
+ input_bg: str
34
+ input_border: str
35
+ hover_bg: str
36
+ hover_border: str
37
+ selection_bg: str
38
+ selection_text: str
39
+ selection_border: str
40
+ tab_bg: str
41
+ tab_hover_bg: str
42
+ tab_selected_bg: str
43
+ progress_bg: str
44
+ progress_chunk: str
45
+ summary_bg: str
46
+ summary_border: str
47
+ request_control_bg: str
48
+ request_control_border: str
49
+ request_control_hover: str
50
+ engine_start_bg: str
51
+ engine_start_border: str
52
+ engine_start_hover: str
53
+ engine_stop_bg: str
54
+ engine_stop_border: str
55
+ engine_stop_hover: str
56
+
57
+
58
+ GITHUB_DARK = ThemePalette(
59
+ name="dark",
60
+ window_bg="#0d1117",
61
+ app_bg="#0d1117",
62
+ panel_bg="#161b22",
63
+ panel_border="#30363d",
64
+ text="#c9d1d9",
65
+ muted_text="#8b949e",
66
+ section_text="#7d8590",
67
+ accent_text="#2ea043",
68
+ warning_text="#d29922",
69
+ error_text="#f85149",
70
+ button_bg="#21262d",
71
+ button_hover="#30363d",
72
+ button_checked_bg="#1f6feb",
73
+ button_checked_border="#388bfd",
74
+ button_disabled_bg="#161b22",
75
+ button_disabled_border="#30363d",
76
+ button_disabled_text="#6e7681",
77
+ input_bg="#0d1117",
78
+ input_border="#30363d",
79
+ hover_bg="#1b2230",
80
+ hover_border="#3b4556",
81
+ selection_bg="#1f6feb",
82
+ selection_text="#f0f6fc",
83
+ selection_border="#388bfd",
84
+ tab_bg="#161b22",
85
+ tab_hover_bg="#1b2230",
86
+ tab_selected_bg="#21262d",
87
+ progress_bg="#0d1117",
88
+ progress_chunk="#2ea043",
89
+ summary_bg="#21262d",
90
+ summary_border="#30363d",
91
+ request_control_bg="#F04A00",
92
+ request_control_border="#c23c00",
93
+ request_control_hover="#d84300",
94
+ engine_start_bg="#1f883d",
95
+ engine_start_border="#1a7f37",
96
+ engine_start_hover="#1a7f37",
97
+ engine_stop_bg="#cf222e",
98
+ engine_stop_border="#a40e26",
99
+ engine_stop_hover="#a40e26",
100
+ )
101
+
102
+ GITHUB_LIGHT = ThemePalette(
103
+ name="light",
104
+ window_bg="#ffffff",
105
+ app_bg="#f6f8fa",
106
+ panel_bg="#ffffff",
107
+ panel_border="#d0d7de",
108
+ text="#1f2328",
109
+ muted_text="#656d76",
110
+ section_text="#57606a",
111
+ accent_text="#1a7f37",
112
+ warning_text="#9a6700",
113
+ error_text="#cf222e",
114
+ button_bg="#f6f8fa",
115
+ button_hover="#eef2f6",
116
+ button_checked_bg="#ddf4ff",
117
+ button_checked_border="#54aeff",
118
+ button_disabled_bg="#f6f8fa",
119
+ button_disabled_border="#d8dee4",
120
+ button_disabled_text="#8c959f",
121
+ input_bg="#ffffff",
122
+ input_border="#d0d7de",
123
+ hover_bg="#f6f8fa",
124
+ hover_border="#c7d2dd",
125
+ selection_bg="#0969da",
126
+ selection_text="#ffffff",
127
+ selection_border="#54aeff",
128
+ tab_bg="#f6f8fa",
129
+ tab_hover_bg="#eef2f6",
130
+ tab_selected_bg="#ffffff",
131
+ progress_bg="#eef2f6",
132
+ progress_chunk="#1a7f37",
133
+ summary_bg="#f6f8fa",
134
+ summary_border="#d0d7de",
135
+ request_control_bg="#F04A00",
136
+ request_control_border="#c23c00",
137
+ request_control_hover="#d84300",
138
+ engine_start_bg="#1f883d",
139
+ engine_start_border="#1a7f37",
140
+ engine_start_hover="#1a7f37",
141
+ engine_stop_bg="#cf222e",
142
+ engine_stop_border="#a40e26",
143
+ engine_stop_hover="#a40e26",
144
+ )
145
+
146
+ THEMES = {
147
+ "dark": GITHUB_DARK,
148
+ "light": GITHUB_LIGHT,
149
+ }
150
+
151
+ DEFAULT_THEME = "system"
152
+
153
+
154
+ def toggle_theme_name(theme_name: str) -> str:
155
+ """Return the opposite theme name."""
156
+ return "light" if theme_name == "dark" else "dark"
157
+
158
+
159
+ def theme_button_text(theme_name: str) -> str:
160
+ """Return the user-facing label for the theme toggle button."""
161
+ return "Switch to Light" if theme_name == "dark" else "Switch to Dark"
162
+
163
+
164
+ def _qt_theme_name() -> str | None:
165
+ try:
166
+ from PySide6.QtCore import Qt # type: ignore
167
+ from PySide6.QtGui import QGuiApplication # type: ignore
168
+ except Exception:
169
+ return None
170
+ app = QGuiApplication.instance()
171
+ if app is None:
172
+ return None
173
+ try:
174
+ scheme = app.styleHints().colorScheme()
175
+ except AttributeError:
176
+ return None
177
+ return "dark" if scheme == Qt.ColorScheme.Dark else "light"
178
+
179
+
180
+ def _macos_theme_name() -> str | None:
181
+ try:
182
+ result = subprocess.run(
183
+ ["defaults", "read", "-g", "AppleInterfaceStyle"],
184
+ check=False,
185
+ capture_output=True,
186
+ text=True,
187
+ timeout=0.5,
188
+ )
189
+ except Exception:
190
+ return None
191
+ return "dark" if "dark" in (result.stdout or "").strip().lower() else "light"
192
+
193
+
194
+ def _windows_theme_name() -> str | None:
195
+ try:
196
+ import winreg # type: ignore
197
+ except Exception:
198
+ return None
199
+ try:
200
+ with winreg.OpenKey(
201
+ winreg.HKEY_CURRENT_USER,
202
+ r"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize",
203
+ ) as key:
204
+ value, _ = winreg.QueryValueEx(key, "AppsUseLightTheme")
205
+ except Exception:
206
+ return None
207
+ return "light" if int(value) else "dark"
208
+
209
+
210
+ def _linux_theme_name() -> str | None:
211
+ gtk_theme = os.environ.get("GTK_THEME", "").lower()
212
+ if ":dark" in gtk_theme or gtk_theme.endswith("-dark"):
213
+ return "dark"
214
+ if gtk_theme:
215
+ return "light"
216
+ colorfgbg = os.environ.get("COLORFGBG", "")
217
+ if colorfgbg:
218
+ try:
219
+ bg = int(colorfgbg.split(";")[-1])
220
+ except ValueError:
221
+ return None
222
+ return "dark" if bg <= 6 else "light"
223
+ return None
224
+
225
+
226
+ def system_theme_name() -> str:
227
+ """Return the host light/dark theme using shared cross-surface detection."""
228
+ override = os.environ.get("DATA_ENGINE_THEME", "").strip().lower()
229
+ if override in THEMES:
230
+ return override
231
+ qt_theme = _qt_theme_name()
232
+ if qt_theme is not None:
233
+ return qt_theme
234
+ system = platform.system()
235
+ if system == "Darwin":
236
+ return _macos_theme_name() or "dark"
237
+ if system == "Windows":
238
+ return _windows_theme_name() or "dark"
239
+ return _linux_theme_name() or "dark"
240
+
241
+
242
+ def resolve_theme_name(theme_name: str = DEFAULT_THEME) -> str:
243
+ """Resolve a requested theme name, honoring system-following default behavior."""
244
+ if theme_name == DEFAULT_THEME:
245
+ return system_theme_name()
246
+ return theme_name if theme_name in THEMES else system_theme_name()
247
+
248
+
249
+ __all__ = [
250
+ "DEFAULT_THEME",
251
+ "GITHUB_DARK",
252
+ "GITHUB_LIGHT",
253
+ "THEMES",
254
+ "ThemePalette",
255
+ "resolve_theme_name",
256
+ "system_theme_name",
257
+ "theme_button_text",
258
+ "toggle_theme_name",
259
+ ]
@@ -0,0 +1,190 @@
1
+ """Shared workspace path models and pure helpers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ import hashlib
7
+ from pathlib import Path
8
+ import socket
9
+ import sys
10
+ import unicodedata
11
+
12
+ from data_engine.platform.identity import APP_INTERNAL_ID, env_var
13
+
14
+
15
+ def _resolve_app_root_path() -> Path:
16
+ """Resolve the application root in both dev and frozen executable contexts."""
17
+ if getattr(sys, "frozen", False):
18
+ return Path(sys.executable).resolve().parent
19
+ return Path(__file__).resolve().parents[3]
20
+
21
+
22
+ APP_ROOT_PATH: Path = _resolve_app_root_path()
23
+ DATA_ENGINE_APP_ROOT_ENV_VAR: str = env_var("app_root")
24
+ DATA_ENGINE_WORKSPACE_ROOT_ENV_VAR: str = env_var("workspace_root")
25
+ DATA_ENGINE_WORKSPACE_ID_ENV_VAR: str = env_var("workspace_id")
26
+ DATA_ENGINE_WORKSPACE_COLLECTION_ROOT_ENV_VAR: str = env_var("workspace_collection_root")
27
+ DATA_ENGINE_RUNTIME_ROOT_ENV_VAR: str = env_var("runtime_root")
28
+ DATA_ENGINE_RUNTIME_DB_PATH_ENV_VAR: str = env_var("runtime_db_path")
29
+ WORKSPACE_CONFIG_DIR_NAME: str = "config"
30
+ WORKSPACE_FLOW_MODULES_DIR_NAME: str = "flow_modules"
31
+ WORKSPACE_FLOW_HELPERS_DIR_NAME: str = "flow_helpers"
32
+ WORKSPACE_DATABASES_DIR_NAME: str = "databases"
33
+ WORKSPACE_STATE_DIR_NAME: str = ".workspace_state"
34
+ WORKSPACE_AVAILABLE_MARKERS_DIR_NAME: str = "available"
35
+ WORKSPACE_LEASED_MARKERS_DIR_NAME: str = "leased"
36
+ WORKSPACE_STALE_MARKERS_DIR_NAME: str = "stale"
37
+ WORKSPACE_LEASE_METADATA_DIR_NAME: str = "leases"
38
+ WORKSPACE_CONTROL_REQUESTS_DIR_NAME: str = "control_requests"
39
+ WORKSPACE_SHARED_STATE_DIR_NAME: str = "state"
40
+ WORKSPACE_SHARED_RUNS_DIR_NAME: str = "runs"
41
+ WORKSPACE_SHARED_STEP_RUNS_DIR_NAME: str = "step_runs"
42
+ WORKSPACE_SHARED_LOGS_DIR_NAME: str = "logs"
43
+ WORKSPACE_SHARED_FILE_STATE_DIR_NAME: str = "file_state"
44
+
45
+
46
+ def normalized_path_text(value: Path | str) -> str:
47
+ """Return a stable forward-slash path string for display and comparisons."""
48
+ return unicodedata.normalize("NFC", str(value).replace("\\", "/"))
49
+
50
+
51
+ def path_display(value: Path | str | None, *, empty: str = "(not set)") -> str:
52
+ """Render a path value consistently for UI/display use."""
53
+ if value is None:
54
+ return empty
55
+ return normalized_path_text(value)
56
+
57
+
58
+ def toml_path_text(value: Path | str) -> str:
59
+ """Render a path as TOML-safe text without Windows backslash escapes."""
60
+ return normalized_path_text(value)
61
+
62
+
63
+ class InvalidWorkspaceIdError(ValueError):
64
+ """Raised when a workspace id contains unsafe path components."""
65
+
66
+
67
+ def validate_workspace_id(workspace_id: str) -> str:
68
+ """Return a workspace id that is safe to use in path components."""
69
+ candidate = str(workspace_id)
70
+ if not candidate.strip():
71
+ raise InvalidWorkspaceIdError("Workspace id cannot be empty.")
72
+ if candidate in {".", ".."}:
73
+ raise InvalidWorkspaceIdError(f"Workspace id {candidate!r} is not allowed.")
74
+ if "\x00" in candidate:
75
+ raise InvalidWorkspaceIdError("Workspace id cannot contain NUL bytes.")
76
+ if "/" in candidate or "\\" in candidate:
77
+ raise InvalidWorkspaceIdError(f"Workspace id {candidate!r} must not contain path separators.")
78
+ return candidate
79
+
80
+
81
+ def local_workspace_namespace(workspace_root: Path | str, workspace_id: str) -> str:
82
+ """Return the machine-local namespace for one workspace root."""
83
+ workspace_id = validate_workspace_id(workspace_id)
84
+ digest = hashlib.sha1(normalized_path_text(Path(workspace_root).expanduser().resolve()).encode("utf-8")).hexdigest()[:12]
85
+ return f"{workspace_id}_{digest}"
86
+
87
+
88
+ @dataclass(frozen=True)
89
+ class WorkspaceSettings:
90
+ """Machine-local workspace discovery settings."""
91
+
92
+ app_root: Path
93
+ settings_path: Path
94
+ state_root: Path
95
+ runtime_root: Path
96
+ workspace_collection_root: Path | None
97
+ default_selected: str | None
98
+
99
+
100
+ @dataclass(frozen=True)
101
+ class DiscoveredWorkspace:
102
+ """One discovered authored workspace."""
103
+
104
+ workspace_id: str
105
+ workspace_root: Path
106
+
107
+
108
+ @dataclass(frozen=True)
109
+ class WorkspacePaths:
110
+ """Resolved authored, shared, and local-artifact paths for one workspace."""
111
+
112
+ app_root: Path
113
+ workspace_collection_root: Path
114
+ workspace_id: str
115
+ workspace_root: Path
116
+ config_dir: Path
117
+ flow_modules_dir: Path
118
+ databases_dir: Path
119
+ workspace_state_dir: Path
120
+ available_markers_dir: Path
121
+ leased_markers_dir: Path
122
+ stale_markers_dir: Path
123
+ lease_metadata_dir: Path
124
+ lease_metadata_path: Path
125
+ control_requests_dir: Path
126
+ control_request_path: Path
127
+ shared_state_dir: Path
128
+ shared_runs_path: Path
129
+ shared_step_runs_path: Path
130
+ shared_logs_path: Path
131
+ shared_file_state_path: Path
132
+ artifacts_dir: Path
133
+ workspace_cache_dir: Path
134
+ compiled_flow_modules_dir: Path
135
+ runtime_state_dir: Path
136
+ runtime_db_path: Path
137
+ daemon_log_path: Path
138
+ documentation_dir: Path
139
+ daemon_endpoint_kind: str
140
+ daemon_endpoint_path: str
141
+ sphinx_source_dir: Path
142
+ workspace_configured: bool = True
143
+
144
+
145
+ def authored_workspace_is_available(paths: WorkspacePaths) -> bool:
146
+ """Return whether one authored workspace root is still present and usable."""
147
+ return paths.workspace_configured and paths.workspace_root.is_dir() and paths.flow_modules_dir.is_dir()
148
+
149
+
150
+ def machine_id_text() -> str:
151
+ """Return a stable local machine identifier for lease metadata."""
152
+ return socket.gethostname()
153
+
154
+
155
+ __all__ = [
156
+ "APP_INTERNAL_ID",
157
+ "APP_ROOT_PATH",
158
+ "DATA_ENGINE_APP_ROOT_ENV_VAR",
159
+ "DATA_ENGINE_RUNTIME_DB_PATH_ENV_VAR",
160
+ "DATA_ENGINE_RUNTIME_ROOT_ENV_VAR",
161
+ "DATA_ENGINE_WORKSPACE_COLLECTION_ROOT_ENV_VAR",
162
+ "DATA_ENGINE_WORKSPACE_ID_ENV_VAR",
163
+ "DATA_ENGINE_WORKSPACE_ROOT_ENV_VAR",
164
+ "DiscoveredWorkspace",
165
+ "InvalidWorkspaceIdError",
166
+ "WORKSPACE_AVAILABLE_MARKERS_DIR_NAME",
167
+ "WORKSPACE_CONFIG_DIR_NAME",
168
+ "WORKSPACE_CONTROL_REQUESTS_DIR_NAME",
169
+ "WORKSPACE_DATABASES_DIR_NAME",
170
+ "WORKSPACE_FLOW_HELPERS_DIR_NAME",
171
+ "WORKSPACE_FLOW_MODULES_DIR_NAME",
172
+ "WORKSPACE_LEASED_MARKERS_DIR_NAME",
173
+ "WORKSPACE_LEASE_METADATA_DIR_NAME",
174
+ "WORKSPACE_SHARED_FILE_STATE_DIR_NAME",
175
+ "WORKSPACE_SHARED_LOGS_DIR_NAME",
176
+ "WORKSPACE_SHARED_RUNS_DIR_NAME",
177
+ "WORKSPACE_SHARED_STATE_DIR_NAME",
178
+ "WORKSPACE_SHARED_STEP_RUNS_DIR_NAME",
179
+ "WORKSPACE_STALE_MARKERS_DIR_NAME",
180
+ "WORKSPACE_STATE_DIR_NAME",
181
+ "WorkspacePaths",
182
+ "WorkspaceSettings",
183
+ "authored_workspace_is_available",
184
+ "local_workspace_namespace",
185
+ "machine_id_text",
186
+ "normalized_path_text",
187
+ "path_display",
188
+ "toml_path_text",
189
+ "validate_workspace_id",
190
+ ]