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,23 @@
|
|
|
1
|
+
"""Local daemon host surface for workspace runtime control."""
|
|
2
|
+
|
|
3
|
+
from data_engine.hosts.daemon.app import DaemonClientError
|
|
4
|
+
from data_engine.hosts.daemon.app import DataEngineDaemonService
|
|
5
|
+
from data_engine.hosts.daemon.app import WorkspaceLeaseError
|
|
6
|
+
from data_engine.hosts.daemon.app import daemon_request
|
|
7
|
+
from data_engine.hosts.daemon.app import is_daemon_live
|
|
8
|
+
from data_engine.hosts.daemon.app import serve_workspace_daemon
|
|
9
|
+
from data_engine.hosts.daemon.app import spawn_daemon_process
|
|
10
|
+
from data_engine.hosts.daemon.manager import WorkspaceDaemonManager
|
|
11
|
+
from data_engine.hosts.daemon.manager import WorkspaceDaemonSnapshot
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"DaemonClientError",
|
|
15
|
+
"DataEngineDaemonService",
|
|
16
|
+
"WorkspaceDaemonManager",
|
|
17
|
+
"WorkspaceDaemonSnapshot",
|
|
18
|
+
"WorkspaceLeaseError",
|
|
19
|
+
"daemon_request",
|
|
20
|
+
"is_daemon_live",
|
|
21
|
+
"serve_workspace_daemon",
|
|
22
|
+
"spawn_daemon_process",
|
|
23
|
+
]
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"""Daemon host object."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from datetime import UTC, datetime, timedelta
|
|
6
|
+
import threading
|
|
7
|
+
from typing import TYPE_CHECKING, Any
|
|
8
|
+
|
|
9
|
+
from data_engine.domain import DaemonLifecyclePolicy
|
|
10
|
+
from data_engine.domain.time import parse_utc_text, utcnow_text
|
|
11
|
+
from data_engine.hosts.daemon.client import (
|
|
12
|
+
DaemonClientError,
|
|
13
|
+
force_shutdown_daemon_process,
|
|
14
|
+
WorkspaceLeaseError,
|
|
15
|
+
_remove_stale_unix_endpoint,
|
|
16
|
+
daemon_request,
|
|
17
|
+
is_daemon_live,
|
|
18
|
+
spawn_daemon_process,
|
|
19
|
+
)
|
|
20
|
+
from data_engine.hosts.daemon.composition import (
|
|
21
|
+
DaemonHostDependencies,
|
|
22
|
+
DaemonHostFacade,
|
|
23
|
+
DaemonHostIdentity,
|
|
24
|
+
DaemonHostState,
|
|
25
|
+
)
|
|
26
|
+
from data_engine.hosts.daemon.bootstrap import initialize_service
|
|
27
|
+
from data_engine.hosts.daemon.entrypoints import (
|
|
28
|
+
default_workspace_service_factory,
|
|
29
|
+
main as run_daemon_module,
|
|
30
|
+
serve_workspace_daemon as serve_daemon_entrypoint,
|
|
31
|
+
)
|
|
32
|
+
from data_engine.hosts.daemon.commands import (
|
|
33
|
+
DaemonCommandHandler,
|
|
34
|
+
)
|
|
35
|
+
from data_engine.hosts.daemon.constants import (
|
|
36
|
+
APP_VERSION,
|
|
37
|
+
CHECKPOINT_INTERVAL_SECONDS,
|
|
38
|
+
CONTROL_REQUEST_POLL_INTERVAL_SECONDS,
|
|
39
|
+
STALE_AFTER_SECONDS,
|
|
40
|
+
)
|
|
41
|
+
from data_engine.hosts.daemon.lifecycle import (
|
|
42
|
+
checkpoint_loop,
|
|
43
|
+
relinquish_workspace_after_checkpoint_failures,
|
|
44
|
+
relinquish_workspace_for_control_request,
|
|
45
|
+
relinquish_workspace_for_missing_root,
|
|
46
|
+
shutdown,
|
|
47
|
+
shutdown_if_unowned_and_idle,
|
|
48
|
+
)
|
|
49
|
+
from data_engine.hosts.daemon.server import serve_forever as serve_daemon_forever
|
|
50
|
+
from data_engine.platform.workspace_models import (
|
|
51
|
+
WorkspacePaths,
|
|
52
|
+
authored_workspace_is_available,
|
|
53
|
+
)
|
|
54
|
+
from data_engine.views.models import QtFlowCard, load_qt_flow_cards
|
|
55
|
+
|
|
56
|
+
if TYPE_CHECKING:
|
|
57
|
+
from multiprocessing.connection import Listener
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
DAEMON_LOG_RETENTION_DAYS = 30
|
|
61
|
+
|
|
62
|
+
class DataEngineDaemonService:
|
|
63
|
+
"""Own one workspace daemon instance and its runtime state."""
|
|
64
|
+
|
|
65
|
+
def __init__(
|
|
66
|
+
self,
|
|
67
|
+
paths: WorkspacePaths,
|
|
68
|
+
*,
|
|
69
|
+
dependencies: DaemonHostDependencies | None = None,
|
|
70
|
+
identity: DaemonHostIdentity | None = None,
|
|
71
|
+
lifecycle_policy: DaemonLifecyclePolicy = DaemonLifecyclePolicy.PERSISTENT,
|
|
72
|
+
) -> None:
|
|
73
|
+
self.paths = paths
|
|
74
|
+
dependencies = dependencies or DaemonHostDependencies.build_default(paths)
|
|
75
|
+
daemon_identity = identity or DaemonHostIdentity.current_process()
|
|
76
|
+
self.lifecycle_policy = DaemonLifecyclePolicy.coerce(lifecycle_policy)
|
|
77
|
+
self.started_at_utc = utcnow_text()
|
|
78
|
+
self.state = DaemonHostState.build(started_at_utc=self.started_at_utc)
|
|
79
|
+
self.host = DaemonHostFacade(self.state)
|
|
80
|
+
|
|
81
|
+
self.runtime_ledger = dependencies.runtime_ledger
|
|
82
|
+
self.flow_catalog_service = dependencies.flow_catalog_service
|
|
83
|
+
self.flow_execution_service = dependencies.flow_execution_service
|
|
84
|
+
self.runtime_execution_service = dependencies.runtime_execution_service
|
|
85
|
+
self.shared_state_adapter = dependencies.shared_state_adapter
|
|
86
|
+
self.machine_id = daemon_identity.machine_id
|
|
87
|
+
self.daemon_id = daemon_identity.daemon_id
|
|
88
|
+
self.pid = daemon_identity.pid
|
|
89
|
+
self._state_lock = threading.RLock()
|
|
90
|
+
self.command_handler = DaemonCommandHandler(self)
|
|
91
|
+
|
|
92
|
+
def _workspace_root_is_available(self) -> bool:
|
|
93
|
+
"""Return whether the authored workspace still exists at the configured root."""
|
|
94
|
+
return authored_workspace_is_available(self.paths)
|
|
95
|
+
|
|
96
|
+
def _retained_daemon_log_lines(self, lines: list[str], *, now: datetime | None = None) -> list[str]:
|
|
97
|
+
"""Return daemon-log lines that still fall within the retention window."""
|
|
98
|
+
cutoff = (now or datetime.now(UTC)) - timedelta(days=DAEMON_LOG_RETENTION_DAYS)
|
|
99
|
+
retained: list[str] = []
|
|
100
|
+
for line in lines:
|
|
101
|
+
timestamp_text = line.split(" ", 1)[0].strip()
|
|
102
|
+
try:
|
|
103
|
+
parsed = parse_utc_text(timestamp_text)
|
|
104
|
+
except Exception:
|
|
105
|
+
parsed = None
|
|
106
|
+
if parsed is None or parsed >= cutoff:
|
|
107
|
+
retained.append(line)
|
|
108
|
+
return retained
|
|
109
|
+
|
|
110
|
+
def _debug_log(self, message: str) -> None:
|
|
111
|
+
"""Append one daemon diagnostic line and keep only the last retention window."""
|
|
112
|
+
try:
|
|
113
|
+
self.paths.runtime_state_dir.mkdir(parents=True, exist_ok=True)
|
|
114
|
+
existing_lines: list[str] = []
|
|
115
|
+
if self.paths.daemon_log_path.exists():
|
|
116
|
+
existing_lines = self.paths.daemon_log_path.read_text(encoding="utf-8").splitlines(keepends=True)
|
|
117
|
+
retained_lines = self._retained_daemon_log_lines(existing_lines)
|
|
118
|
+
retained_lines.append(f"{utcnow_text()} pid={self.pid} workspace={self.paths.workspace_id} {message}\n")
|
|
119
|
+
self.paths.daemon_log_path.write_text("".join(retained_lines), encoding="utf-8")
|
|
120
|
+
except Exception:
|
|
121
|
+
pass
|
|
122
|
+
|
|
123
|
+
def initialize(self) -> None:
|
|
124
|
+
initialize_service(self)
|
|
125
|
+
|
|
126
|
+
def serve_forever(self) -> None:
|
|
127
|
+
serve_daemon_forever(self)
|
|
128
|
+
|
|
129
|
+
def _handle_command(self, payload: Any) -> dict[str, Any]:
|
|
130
|
+
return self.command_handler.handle(payload)
|
|
131
|
+
|
|
132
|
+
def _load_flow_cards(self, *, force: bool = False) -> tuple[QtFlowCard, ...]:
|
|
133
|
+
del force
|
|
134
|
+
return load_qt_flow_cards(self.flow_catalog_service, workspace_root=self.paths.workspace_root)
|
|
135
|
+
|
|
136
|
+
def _checkpoint_loop(self) -> None:
|
|
137
|
+
checkpoint_loop(self)
|
|
138
|
+
|
|
139
|
+
def _checkpoint_once(self, *, status: str) -> None:
|
|
140
|
+
self.command_handler.checkpoint_once(status=status)
|
|
141
|
+
|
|
142
|
+
def _refresh_observer_snapshot(self) -> None:
|
|
143
|
+
self.command_handler.refresh_observer_snapshot()
|
|
144
|
+
|
|
145
|
+
def _update_daemon_state(self, *, status: str) -> None:
|
|
146
|
+
self.command_handler.update_daemon_state(status=status)
|
|
147
|
+
|
|
148
|
+
def _relinquish_workspace_after_checkpoint_failures(self) -> None:
|
|
149
|
+
relinquish_workspace_after_checkpoint_failures(self)
|
|
150
|
+
|
|
151
|
+
def _relinquish_workspace_for_control_request(self, requester_machine_id: str) -> None:
|
|
152
|
+
relinquish_workspace_for_control_request(self, requester_machine_id)
|
|
153
|
+
|
|
154
|
+
def _relinquish_workspace_for_missing_root(self) -> None:
|
|
155
|
+
relinquish_workspace_for_missing_root(self)
|
|
156
|
+
|
|
157
|
+
def _shutdown_if_unowned_and_idle(self, *, reason: str) -> None:
|
|
158
|
+
shutdown_if_unowned_and_idle(self, reason=reason)
|
|
159
|
+
|
|
160
|
+
def _wake_listener(self) -> None:
|
|
161
|
+
try:
|
|
162
|
+
daemon_request(self.paths, {"command": "daemon_ping"}, timeout=0.5)
|
|
163
|
+
except Exception:
|
|
164
|
+
pass
|
|
165
|
+
|
|
166
|
+
def _shutdown(self) -> None:
|
|
167
|
+
shutdown(self)
|
|
168
|
+
def main(
|
|
169
|
+
argv: list[str] | None = None,
|
|
170
|
+
*,
|
|
171
|
+
workspace_service=None,
|
|
172
|
+
workspace_service_factory=None,
|
|
173
|
+
resolve_paths_func=None,
|
|
174
|
+
) -> int:
|
|
175
|
+
"""Module entrypoint for launching one workspace daemon process."""
|
|
176
|
+
return run_daemon_module(
|
|
177
|
+
DataEngineDaemonService,
|
|
178
|
+
argv,
|
|
179
|
+
workspace_service=workspace_service,
|
|
180
|
+
workspace_service_factory=workspace_service_factory,
|
|
181
|
+
resolve_paths_func=resolve_paths_func,
|
|
182
|
+
serve_workspace_daemon_func=lambda service_type, **kwargs: serve_workspace_daemon(**kwargs),
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def serve_workspace_daemon(
|
|
187
|
+
*,
|
|
188
|
+
workspace_root=None,
|
|
189
|
+
workspace_id=None,
|
|
190
|
+
lifecycle_policy: DaemonLifecyclePolicy = DaemonLifecyclePolicy.PERSISTENT,
|
|
191
|
+
workspace_service=None,
|
|
192
|
+
resolve_paths_func=None,
|
|
193
|
+
) -> int:
|
|
194
|
+
return serve_daemon_entrypoint(
|
|
195
|
+
DataEngineDaemonService,
|
|
196
|
+
workspace_root=workspace_root,
|
|
197
|
+
workspace_id=workspace_id,
|
|
198
|
+
lifecycle_policy=lifecycle_policy,
|
|
199
|
+
workspace_service=workspace_service,
|
|
200
|
+
resolve_paths_func=resolve_paths_func,
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
__all__ = [
|
|
205
|
+
"CHECKPOINT_INTERVAL_SECONDS",
|
|
206
|
+
"DaemonClientError",
|
|
207
|
+
"DataEngineDaemonService",
|
|
208
|
+
"STALE_AFTER_SECONDS",
|
|
209
|
+
"WorkspaceLeaseError",
|
|
210
|
+
"force_shutdown_daemon_process",
|
|
211
|
+
"_remove_stale_unix_endpoint",
|
|
212
|
+
"daemon_request",
|
|
213
|
+
"default_workspace_service_factory",
|
|
214
|
+
"is_daemon_live",
|
|
215
|
+
"serve_workspace_daemon",
|
|
216
|
+
"spawn_daemon_process",
|
|
217
|
+
]
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
if __name__ == "__main__":
|
|
221
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Bootstrap and claim-state initialization for the daemon host."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from data_engine.hosts.daemon.client import _recover_broken_local_lease, _should_force_recover_local_lease
|
|
8
|
+
from data_engine.hosts.daemon.constants import APP_VERSION, STALE_AFTER_SECONDS
|
|
9
|
+
from data_engine.hosts.daemon.ownership import release_workspace_claim
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from data_engine.hosts.daemon.app import DataEngineDaemonService
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def initialize_service(service: "DataEngineDaemonService") -> None:
|
|
16
|
+
"""Claim the workspace when possible and hydrate local state."""
|
|
17
|
+
service._debug_log("initialize starting")
|
|
18
|
+
shared_state = service.shared_state_adapter
|
|
19
|
+
shared_state.initialize_workspace(service.paths)
|
|
20
|
+
claimed = shared_state.claim_workspace(service.paths)
|
|
21
|
+
if not claimed:
|
|
22
|
+
if _should_force_recover_local_lease(service.paths):
|
|
23
|
+
service._debug_log("attempting local stale recovery before claim")
|
|
24
|
+
_recover_broken_local_lease(service.paths)
|
|
25
|
+
claimed = shared_state.claim_workspace(service.paths)
|
|
26
|
+
if not shared_state.recover_stale_workspace(
|
|
27
|
+
service.paths,
|
|
28
|
+
machine_id=service.machine_id,
|
|
29
|
+
stale_after_seconds=STALE_AFTER_SECONDS,
|
|
30
|
+
):
|
|
31
|
+
metadata = shared_state.read_lease_metadata(service.paths)
|
|
32
|
+
owner = str(metadata.get("machine_id")) if metadata is not None and metadata.get("machine_id") is not None else "another machine"
|
|
33
|
+
if owner == service.machine_id:
|
|
34
|
+
service._debug_log("initialize refused: already leased locally")
|
|
35
|
+
from data_engine.hosts.daemon.client import WorkspaceLeaseError
|
|
36
|
+
|
|
37
|
+
raise WorkspaceLeaseError(f"Workspace {service.paths.workspace_id!r} is already leased locally.")
|
|
38
|
+
release_workspace_claim(service, leased_by_machine_id=owner, status="leased")
|
|
39
|
+
shared_state.hydrate_local_runtime(service.paths, service.runtime_ledger)
|
|
40
|
+
service._update_daemon_state(status="leased")
|
|
41
|
+
service._debug_log(f"initialize observer mode owner={owner}")
|
|
42
|
+
return
|
|
43
|
+
claimed = True
|
|
44
|
+
with service._state_lock:
|
|
45
|
+
if claimed:
|
|
46
|
+
service.state.claim_workspace()
|
|
47
|
+
else:
|
|
48
|
+
service.state.release_workspace()
|
|
49
|
+
shared_state.write_lease_metadata(
|
|
50
|
+
service.paths,
|
|
51
|
+
workspace_id=service.paths.workspace_id,
|
|
52
|
+
machine_id=service.machine_id,
|
|
53
|
+
daemon_id=service.daemon_id,
|
|
54
|
+
pid=service.pid,
|
|
55
|
+
status="starting",
|
|
56
|
+
started_at_utc=service.started_at_utc,
|
|
57
|
+
last_checkpoint_at_utc=service.state.last_checkpoint_at_utc,
|
|
58
|
+
app_version=APP_VERSION,
|
|
59
|
+
)
|
|
60
|
+
shared_state.hydrate_local_runtime(service.paths, service.runtime_ledger)
|
|
61
|
+
service._update_daemon_state(status="starting")
|
|
62
|
+
service._checkpoint_once(status="idle")
|
|
63
|
+
with service._state_lock:
|
|
64
|
+
service.state.status = "idle"
|
|
65
|
+
service._update_daemon_state(status="idle")
|
|
66
|
+
service._debug_log("initialize complete workspace claimed")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
__all__ = ["initialize_service"]
|