splime 0.2.5__tar.gz → 0.4.0__tar.gz
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.
- {splime-0.2.5/src/splime.egg-info → splime-0.4.0}/PKG-INFO +3 -1
- {splime-0.2.5 → splime-0.4.0}/pyproject.toml +8 -3
- {splime-0.2.5 → splime-0.4.0}/src/spl/_client.py +181 -140
- {splime-0.2.5 → splime-0.4.0}/src/spl/_http.py +9 -2
- {splime-0.2.5 → splime-0.4.0}/src/spl/_views.py +94 -65
- {splime-0.2.5 → splime-0.4.0}/src/spl/core/__init__.py +1 -6
- splime-0.4.0/src/spl/core/_common.py +1263 -0
- splime-0.4.0/src/spl/core/adapter_compat.py +459 -0
- splime-0.4.0/src/spl/core/entities/adapter.py +426 -0
- splime-0.4.0/src/spl/core/entities/artifact.py +136 -0
- splime-0.4.0/src/spl/core/entities/control.py +44 -0
- splime-0.4.0/src/spl/core/entities/distribution.py +56 -0
- splime-0.4.0/src/spl/core/entities/function.py +263 -0
- {splime-0.2.5 → splime-0.4.0}/src/spl/core/entities/local_function.py +27 -33
- {splime-0.2.5 → splime-0.4.0}/src/spl/core/entities/misc.py +3 -3
- splime-0.4.0/src/spl/core/entities/module.py +82 -0
- splime-0.4.0/src/spl/core/entities/node.py +289 -0
- splime-0.4.0/src/spl/core/entities/node_function.py +71 -0
- splime-0.4.0/src/spl/core/entities/node_remote.py +298 -0
- splime-0.4.0/src/spl/core/entities/pipeline.py +526 -0
- splime-0.4.0/src/spl/core/entities/scalar.py +48 -0
- splime-0.4.0/src/spl/core/fingerprint.py +201 -0
- splime-0.4.0/src/spl/core/ir/common.py +42 -0
- {splime-0.2.5 → splime-0.4.0}/src/spl/core/ir/parse.py +18 -18
- splime-0.4.0/src/spl/core/ir/unparse.py +38 -0
- splime-0.4.0/src/spl/core/ir/utils.py +159 -0
- splime-0.4.0/src/spl/core/manifest.py +1003 -0
- splime-0.4.0/src/spl/core/node_runtime.py +524 -0
- splime-0.4.0/src/spl/core/resume.py +324 -0
- {splime-0.2.5 → splime-0.4.0}/src/spl/daemon/canonical.py +39 -22
- {splime-0.2.5 → splime-0.4.0}/src/spl/daemon/cli.py +93 -19
- {splime-0.2.5 → splime-0.4.0}/src/spl/daemon/docker_environment.py +6 -21
- {splime-0.2.5 → splime-0.4.0}/src/spl/daemon/docker_pool.py +5 -20
- {splime-0.2.5 → splime-0.4.0}/src/spl/daemon/doctor.py +138 -12
- splime-0.4.0/src/spl/daemon/environment.py +429 -0
- {splime-0.2.5 → splime-0.4.0}/src/spl/daemon/environment_base.py +6 -19
- {splime-0.2.5 → splime-0.4.0}/src/spl/daemon/heartbeat_service.py +1 -5
- splime-0.4.0/src/spl/daemon/interpreter_visibility.py +69 -0
- {splime-0.2.5 → splime-0.4.0}/src/spl/daemon/metadata.py +12 -37
- {splime-0.2.5 → splime-0.4.0}/src/spl/daemon/remote_client.py +129 -117
- {splime-0.2.5 → splime-0.4.0}/src/spl/daemon/repositories/env.py +27 -20
- {splime-0.2.5 → splime-0.4.0}/src/spl/daemon/repositories/library.py +3 -12
- {splime-0.2.5 → splime-0.4.0}/src/spl/daemon/repositories/object.py +142 -176
- splime-0.4.0/src/spl/daemon/repositories/run.py +580 -0
- {splime-0.2.5 → splime-0.4.0}/src/spl/daemon/repositories/server_connection.py +11 -25
- {splime-0.2.5 → splime-0.4.0}/src/spl/daemon/routes/_helpers.py +38 -20
- {splime-0.2.5 → splime-0.4.0}/src/spl/daemon/routes/artifacts.py +5 -13
- {splime-0.2.5 → splime-0.4.0}/src/spl/daemon/routes/diagnostics.py +57 -11
- {splime-0.2.5 → splime-0.4.0}/src/spl/daemon/routes/envs.py +3 -5
- {splime-0.2.5 → splime-0.4.0}/src/spl/daemon/routes/libraries.py +2 -2
- {splime-0.2.5 → splime-0.4.0}/src/spl/daemon/routes/objects.py +12 -21
- {splime-0.2.5 → splime-0.4.0}/src/spl/daemon/routes/remote.py +2 -2
- splime-0.4.0/src/spl/daemon/routes/runs.py +174 -0
- splime-0.4.0/src/spl/daemon/routes/server_connections.py +155 -0
- {splime-0.2.5 → splime-0.4.0}/src/spl/daemon/run_progress.py +41 -0
- {splime-0.2.5 → splime-0.4.0}/src/spl/daemon/runtime_backend.py +85 -12
- {splime-0.2.5 → splime-0.4.0}/src/spl/daemon/runtime_config.py +3 -12
- {splime-0.2.5 → splime-0.4.0}/src/spl/daemon/runtime_dependencies.py +17 -0
- {splime-0.2.5 → splime-0.4.0}/src/spl/daemon/secret_store.py +3 -6
- {splime-0.2.5 → splime-0.4.0}/src/spl/daemon/server.py +348 -176
- {splime-0.2.5 → splime-0.4.0}/src/spl/daemon/server_connection.py +52 -7
- {splime-0.2.5 → splime-0.4.0}/src/spl/daemon/services/sync.py +3 -14
- {splime-0.2.5 → splime-0.4.0}/src/spl/daemon/signature.py +15 -38
- splime-0.4.0/src/spl/daemon/spl_free_generator.py +241 -0
- splime-0.4.0/src/spl/daemon/spl_free_runner.py +252 -0
- {splime-0.2.5 → splime-0.4.0}/src/spl/daemon/storage_base.py +87 -105
- {splime-0.2.5 → splime-0.4.0}/src/spl/daemon/store.py +38 -3
- {splime-0.2.5 → splime-0.4.0}/src/spl/daemon/worker.py +326 -111
- splime-0.4.0/src/spl/daemon/worker_runtime_marker.py +3 -0
- {splime-0.2.5 → splime-0.4.0}/src/spl/daemon_client.py +340 -121
- {splime-0.2.5 → splime-0.4.0}/src/spl/pipeline_widget.py +45 -38
- {splime-0.2.5 → splime-0.4.0}/src/spl/server_client.py +17 -33
- {splime-0.2.5 → splime-0.4.0/src/splime.egg-info}/PKG-INFO +3 -1
- {splime-0.2.5 → splime-0.4.0}/src/splime.egg-info/SOURCES.txt +9 -0
- {splime-0.2.5 → splime-0.4.0}/src/splime.egg-info/requires.txt +2 -0
- splime-0.2.5/src/spl/core/_common.py +0 -375
- splime-0.2.5/src/spl/core/entities/adapter.py +0 -210
- splime-0.2.5/src/spl/core/entities/artifact.py +0 -140
- splime-0.2.5/src/spl/core/entities/control.py +0 -45
- splime-0.2.5/src/spl/core/entities/distribution.py +0 -65
- splime-0.2.5/src/spl/core/entities/function.py +0 -257
- splime-0.2.5/src/spl/core/entities/module.py +0 -88
- splime-0.2.5/src/spl/core/entities/node.py +0 -286
- splime-0.2.5/src/spl/core/entities/node_function.py +0 -79
- splime-0.2.5/src/spl/core/entities/node_remote.py +0 -349
- splime-0.2.5/src/spl/core/entities/pipeline.py +0 -436
- splime-0.2.5/src/spl/core/entities/scalar.py +0 -55
- splime-0.2.5/src/spl/core/ir/common.py +0 -34
- splime-0.2.5/src/spl/core/ir/unparse.py +0 -29
- splime-0.2.5/src/spl/core/ir/utils.py +0 -163
- splime-0.2.5/src/spl/daemon/environment.py +0 -269
- splime-0.2.5/src/spl/daemon/repositories/run.py +0 -271
- splime-0.2.5/src/spl/daemon/routes/runs.py +0 -101
- splime-0.2.5/src/spl/daemon/routes/server_connections.py +0 -86
- {splime-0.2.5 → splime-0.4.0}/LICENSE +0 -0
- {splime-0.2.5 → splime-0.4.0}/NOTICE +0 -0
- {splime-0.2.5 → splime-0.4.0}/README.md +0 -0
- {splime-0.2.5 → splime-0.4.0}/setup.cfg +0 -0
- {splime-0.2.5 → splime-0.4.0}/src/spl/__init__.py +0 -0
- {splime-0.2.5 → splime-0.4.0}/src/spl/_deprecate.py +0 -0
- {splime-0.2.5 → splime-0.4.0}/src/spl/client.py +0 -0
- {splime-0.2.5 → splime-0.4.0}/src/spl/core/common.py +0 -0
- {splime-0.2.5 → splime-0.4.0}/src/spl/core/entities/__init__.py +0 -0
- {splime-0.2.5 → splime-0.4.0}/src/spl/core/ir/__init__.py +0 -0
- {splime-0.2.5 → splime-0.4.0}/src/spl/daemon/__init__.py +0 -0
- {splime-0.2.5 → splime-0.4.0}/src/spl/daemon/__main__.py +0 -0
- {splime-0.2.5 → splime-0.4.0}/src/spl/daemon/client.py +0 -0
- {splime-0.2.5 → splime-0.4.0}/src/spl/daemon/repositories/__init__.py +0 -0
- {splime-0.2.5 → splime-0.4.0}/src/spl/daemon/repositories/sync_event.py +0 -0
- {splime-0.2.5 → splime-0.4.0}/src/spl/daemon/routes/__init__.py +0 -0
- {splime-0.2.5 → splime-0.4.0}/src/spl/daemon/services/__init__.py +0 -0
- {splime-0.2.5 → splime-0.4.0}/src/spl/py.typed +0 -0
- {splime-0.2.5 → splime-0.4.0}/src/splime.egg-info/dependency_links.txt +0 -0
- {splime-0.2.5 → splime-0.4.0}/src/splime.egg-info/entry_points.txt +0 -0
- {splime-0.2.5 → splime-0.4.0}/src/splime.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: splime
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Reuse Python functions across projects without rewriting or redeploying them.
|
|
5
5
|
Author-email: Yastrebov Kirill <yastrebovks@gmail.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -42,6 +42,8 @@ Requires-Dist: pyexpect; extra == "test"
|
|
|
42
42
|
Requires-Dist: mypy==2.1.0; extra == "test"
|
|
43
43
|
Requires-Dist: pytest==9.1.1; extra == "test"
|
|
44
44
|
Requires-Dist: pytest-asyncio; extra == "test"
|
|
45
|
+
Requires-Dist: ruff==0.15.20; extra == "test"
|
|
46
|
+
Requires-Dist: types-PyYAML==6.0.12.20260518; extra == "test"
|
|
45
47
|
Provides-Extra: docs
|
|
46
48
|
Requires-Dist: sphinx==9.1.0; extra == "docs"
|
|
47
49
|
Requires-Dist: sphinx-rtd-theme==3.1.0; extra == "docs"
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "splime"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.4.0"
|
|
8
8
|
|
|
9
9
|
description = "Reuse Python functions across projects without rewriting or redeploying them."
|
|
10
10
|
readme = "README.md"
|
|
@@ -55,7 +55,9 @@ test = [
|
|
|
55
55
|
"pyexpect",
|
|
56
56
|
"mypy==2.1.0",
|
|
57
57
|
"pytest==9.1.1",
|
|
58
|
-
"pytest-asyncio"
|
|
58
|
+
"pytest-asyncio",
|
|
59
|
+
"ruff==0.15.20",
|
|
60
|
+
"types-PyYAML==6.0.12.20260518"]
|
|
59
61
|
|
|
60
62
|
docs = [
|
|
61
63
|
"sphinx==9.1.0",
|
|
@@ -105,7 +107,7 @@ strict = true
|
|
|
105
107
|
line-length = 120
|
|
106
108
|
|
|
107
109
|
[tool.ruff.lint]
|
|
108
|
-
select = []
|
|
110
|
+
select = ["E4", "E7", "E9", "F"]
|
|
109
111
|
ignore = []
|
|
110
112
|
|
|
111
113
|
# Scoped, justified allowances. These silence rules only where the pattern is
|
|
@@ -125,6 +127,9 @@ ignore = []
|
|
|
125
127
|
"S603", "S607", "S310", "S104", "S108", "S608",
|
|
126
128
|
"S105", "B904", "S110", "S112", "N818",
|
|
127
129
|
]
|
|
130
|
+
# The legacy worker must repair sys.path before importing packages that can be
|
|
131
|
+
# shadowed by the daemon checkout injected through PYTHONPATH.
|
|
132
|
+
"src/spl/daemon/worker.py" = ["E402"]
|
|
128
133
|
# The CLI and daemon bootstrap print to the console on purpose.
|
|
129
134
|
"src/spl/daemon/cli.py" = ["T201"]
|
|
130
135
|
"src/spl/daemon/server.py" = ["T201"]
|
|
@@ -18,6 +18,7 @@ dependencies imported yet.
|
|
|
18
18
|
|
|
19
19
|
from __future__ import annotations
|
|
20
20
|
|
|
21
|
+
import builtins
|
|
21
22
|
from dataclasses import dataclass, field, replace
|
|
22
23
|
from html import escape
|
|
23
24
|
from pathlib import Path
|
|
@@ -38,6 +39,7 @@ from spl._views import (
|
|
|
38
39
|
SignatureView,
|
|
39
40
|
wrap_action,
|
|
40
41
|
)
|
|
42
|
+
from spl.core import manifest as m_manifest
|
|
41
43
|
from spl.core.entities.node import DEFAULT_PORT
|
|
42
44
|
from spl.daemon_client import (
|
|
43
45
|
DEFAULT_DAEMON_HOST,
|
|
@@ -70,10 +72,8 @@ def _preview(value: Any, *, limit: int = 80) -> str:
|
|
|
70
72
|
|
|
71
73
|
|
|
72
74
|
def _is_missing_server_connection(exc: Exception) -> bool:
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
and _NO_SERVER_CONNECTION_MESSAGE in str(exc)
|
|
76
|
-
)
|
|
75
|
+
message = str(exc)
|
|
76
|
+
return isinstance(exc, ClientError) and _NO_SERVER_CONNECTION_MESSAGE in message
|
|
77
77
|
|
|
78
78
|
|
|
79
79
|
def _progress_callback(progress: ProgressOption) -> RunStateCallback | None:
|
|
@@ -141,10 +141,7 @@ class PublishedObject:
|
|
|
141
141
|
"entrypoint": self.entrypoint,
|
|
142
142
|
}
|
|
143
143
|
body = "".join(
|
|
144
|
-
"<tr>"
|
|
145
|
-
f"<th style='text-align:left'>{escape(key)}</th>"
|
|
146
|
-
f"<td><code>{escape(value)}</code></td>"
|
|
147
|
-
"</tr>"
|
|
144
|
+
f"<tr><th style='text-align:left'>{escape(key)}</th><td><code>{escape(value)}</code></td></tr>"
|
|
148
145
|
for key, value in rows.items()
|
|
149
146
|
)
|
|
150
147
|
return f"<table><tbody>{body}</tbody></table>"
|
|
@@ -170,11 +167,7 @@ def _catalog_rows(
|
|
|
170
167
|
rows: list[dict[str, str]] = []
|
|
171
168
|
for record in records:
|
|
172
169
|
library = record.get("library")
|
|
173
|
-
library_name = (
|
|
174
|
-
library.get("display_name") or library.get("slug")
|
|
175
|
-
if isinstance(library, dict)
|
|
176
|
-
else library
|
|
177
|
-
)
|
|
170
|
+
library_name = library.get("display_name") or library.get("slug") if isinstance(library, dict) else library
|
|
178
171
|
version_value = record.get("version")
|
|
179
172
|
if version_value is None:
|
|
180
173
|
current = record.get("current_version")
|
|
@@ -197,28 +190,16 @@ def _catalog_rows(
|
|
|
197
190
|
def _rows_to_text(rows: list[dict[str, str]], title: str) -> str:
|
|
198
191
|
if not rows:
|
|
199
192
|
return f"{title}: (empty)"
|
|
200
|
-
widths = {
|
|
201
|
-
header: max(len(header), *(len(row[header]) for row in rows))
|
|
202
|
-
for header in _CATALOG_HEADERS
|
|
203
|
-
}
|
|
193
|
+
widths = {header: max(len(header), *(len(row[header]) for row in rows)) for header in _CATALOG_HEADERS}
|
|
204
194
|
head = " ".join(header.ljust(widths[header]) for header in _CATALOG_HEADERS)
|
|
205
|
-
body = "\n".join(
|
|
206
|
-
" ".join(row[header].ljust(widths[header]) for header in _CATALOG_HEADERS)
|
|
207
|
-
for row in rows
|
|
208
|
-
)
|
|
195
|
+
body = "\n".join(" ".join(row[header].ljust(widths[header]) for header in _CATALOG_HEADERS) for row in rows)
|
|
209
196
|
return f"{title} ({len(rows)}):\n{head}\n{body}"
|
|
210
197
|
|
|
211
198
|
|
|
212
199
|
def _rows_to_html(rows: list[dict[str, str]], title: str) -> str:
|
|
213
|
-
head = "".join(
|
|
214
|
-
f"<th style='text-align:left'>{escape(header)}</th>"
|
|
215
|
-
for header in _CATALOG_HEADERS
|
|
216
|
-
)
|
|
200
|
+
head = "".join(f"<th style='text-align:left'>{escape(header)}</th>" for header in _CATALOG_HEADERS)
|
|
217
201
|
body = "".join(
|
|
218
|
-
"<tr>"
|
|
219
|
-
+ "".join(f"<td>{escape(row[header])}</td>" for header in _CATALOG_HEADERS)
|
|
220
|
-
+ "</tr>"
|
|
221
|
-
for row in rows
|
|
202
|
+
"<tr>" + "".join(f"<td>{escape(row[header])}</td>" for header in _CATALOG_HEADERS) + "</tr>" for row in rows
|
|
222
203
|
)
|
|
223
204
|
return (
|
|
224
205
|
f"<div><b>{escape(title)}</b> ({len(rows)})"
|
|
@@ -258,14 +239,10 @@ class ObjectCatalog(dict[str, Any]):
|
|
|
258
239
|
"""Local+server catalog that prints per-scope tables; ``.raw`` is a plain dict."""
|
|
259
240
|
|
|
260
241
|
def __repr__(self) -> str:
|
|
261
|
-
return "\n\n".join(
|
|
262
|
-
_rows_to_text(_catalog_rows(value), str(key)) for key, value in self.items()
|
|
263
|
-
)
|
|
242
|
+
return "\n\n".join(_rows_to_text(_catalog_rows(value), str(key)) for key, value in self.items())
|
|
264
243
|
|
|
265
244
|
def _repr_html_(self) -> str:
|
|
266
|
-
return "".join(
|
|
267
|
-
_rows_to_html(_catalog_rows(value), str(key)) for key, value in self.items()
|
|
268
|
-
)
|
|
245
|
+
return "".join(_rows_to_html(_catalog_rows(value), str(key)) for key, value in self.items())
|
|
269
246
|
|
|
270
247
|
@property
|
|
271
248
|
def raw(self) -> dict[str, Any]:
|
|
@@ -343,10 +320,7 @@ class RemoteResult:
|
|
|
343
320
|
"artifacts": str(len(self.artifacts)),
|
|
344
321
|
}
|
|
345
322
|
body = "".join(
|
|
346
|
-
"<tr>"
|
|
347
|
-
f"<th style='text-align:left'>{escape(key)}</th>"
|
|
348
|
-
f"<td><code>{escape(value)}</code></td>"
|
|
349
|
-
"</tr>"
|
|
323
|
+
f"<tr><th style='text-align:left'>{escape(key)}</th><td><code>{escape(value)}</code></td></tr>"
|
|
350
324
|
for key, value in rows.items()
|
|
351
325
|
)
|
|
352
326
|
return f"<table><tbody>{body}</tbody></table>"
|
|
@@ -375,13 +349,13 @@ class RemoteRun:
|
|
|
375
349
|
def id(self) -> str:
|
|
376
350
|
"""Return the daemon run id."""
|
|
377
351
|
|
|
378
|
-
return self.state["id"]
|
|
352
|
+
return cast(str, self.state["id"])
|
|
379
353
|
|
|
380
354
|
@property
|
|
381
355
|
def status(self) -> str:
|
|
382
356
|
"""Return the last known daemon status."""
|
|
383
357
|
|
|
384
|
-
return self.state["status"]
|
|
358
|
+
return cast(str, self.state["status"])
|
|
385
359
|
|
|
386
360
|
@property
|
|
387
361
|
def mode(self) -> str:
|
|
@@ -490,17 +464,10 @@ class RemoteRun:
|
|
|
490
464
|
)
|
|
491
465
|
if final_state["status"] != "succeeded":
|
|
492
466
|
error = final_state.get("error") or "run returned no error message"
|
|
493
|
-
raise RuntimeError(
|
|
494
|
-
f"{self.mode} run {self.id!r} ended as "
|
|
495
|
-
f"{final_state.get('status')!r}: {error}"
|
|
496
|
-
)
|
|
467
|
+
raise RuntimeError(f"{self.mode} run {self.id!r} ended as {final_state.get('status')!r}: {error}")
|
|
497
468
|
|
|
498
469
|
payload = self.result()
|
|
499
|
-
downloaded = (
|
|
500
|
-
self.download_artifacts(artifacts_dir)
|
|
501
|
-
if artifacts_dir is not None
|
|
502
|
-
else {}
|
|
503
|
-
)
|
|
470
|
+
downloaded = self.download_artifacts(artifacts_dir) if artifacts_dir is not None else {}
|
|
504
471
|
return RemoteResult(
|
|
505
472
|
run=final_state,
|
|
506
473
|
payload=payload,
|
|
@@ -593,7 +560,7 @@ class _LibraryAdmin:
|
|
|
593
560
|
grantee: str,
|
|
594
561
|
*,
|
|
595
562
|
grantee_type: str = "user",
|
|
596
|
-
scopes: list[str] | None = None,
|
|
563
|
+
scopes: builtins.list[str] | None = None,
|
|
597
564
|
) -> dict[str, Any]:
|
|
598
565
|
"""Grant a user or team access to one central-server library."""
|
|
599
566
|
|
|
@@ -779,11 +746,7 @@ class SPLClient:
|
|
|
779
746
|
)
|
|
780
747
|
conn = self.current_server_connection()
|
|
781
748
|
connection = conn.get("connection")
|
|
782
|
-
nested_url = (
|
|
783
|
-
connection.get("server_url")
|
|
784
|
-
if isinstance(connection, dict)
|
|
785
|
-
else None
|
|
786
|
-
)
|
|
749
|
+
nested_url = connection.get("server_url") if isinstance(connection, dict) else None
|
|
787
750
|
server_url = conn.get("server_url") or nested_url or DEFAULT_SERVER_URL
|
|
788
751
|
return SPLServerClient(token=self._user_token, base_url=server_url)
|
|
789
752
|
|
|
@@ -800,28 +763,42 @@ class SPLClient:
|
|
|
800
763
|
state = self._daemon.server_connection()
|
|
801
764
|
except Exception as exc:
|
|
802
765
|
if _is_missing_server_connection(exc):
|
|
803
|
-
return ConnectionStatusView(
|
|
804
|
-
{"connected": False, "offline": False, "connection": None}
|
|
805
|
-
)
|
|
766
|
+
return ConnectionStatusView({"connected": False, "offline": False, "connection": None})
|
|
806
767
|
raise
|
|
807
768
|
state.setdefault("connected", bool(state.get("server_url")))
|
|
808
769
|
return ConnectionStatusView(state)
|
|
809
770
|
|
|
810
771
|
def machines(self) -> dict[str, Any]:
|
|
811
|
-
"""Return the user's machines, or an empty listing when not connected.
|
|
772
|
+
"""Return the user's machines, or an empty listing when not connected.
|
|
812
773
|
|
|
813
|
-
|
|
774
|
+
The connection-state endpoint is probed for health only: a missing
|
|
775
|
+
connection maps to an empty listing and any other state failure
|
|
776
|
+
re-raises (0.3.0 contract). A degraded-but-present state (for
|
|
777
|
+
example ``heartbeat_failed``) must not hide the daemon's machine
|
|
778
|
+
payload, so the state content itself does not gate the listing.
|
|
779
|
+
"""
|
|
780
|
+
|
|
781
|
+
if self.server_connection is None:
|
|
782
|
+
try:
|
|
783
|
+
self._daemon.server_connection()
|
|
784
|
+
except Exception as exc:
|
|
785
|
+
if not _is_missing_server_connection(exc):
|
|
786
|
+
raise
|
|
787
|
+
return MachineListView({"current_machine_id": None, "machines": []})
|
|
788
|
+
try:
|
|
789
|
+
machines = self._daemon.server_machines()
|
|
790
|
+
except Exception as exc:
|
|
791
|
+
if not _is_missing_server_connection(exc):
|
|
792
|
+
raise
|
|
814
793
|
return MachineListView({"current_machine_id": None, "machines": []})
|
|
815
|
-
return MachineListView(
|
|
794
|
+
return MachineListView(machines)
|
|
816
795
|
|
|
817
796
|
def libraries(self, *, include_accessible: bool = True) -> list[dict[str, Any]]:
|
|
818
797
|
"""Return visible libraries, or an empty list when not connected."""
|
|
819
798
|
|
|
820
799
|
if not self._has_server_connection():
|
|
821
800
|
return LibraryListView([])
|
|
822
|
-
return LibraryListView(
|
|
823
|
-
self._daemon.server_libraries(include_accessible=include_accessible)
|
|
824
|
-
)
|
|
801
|
+
return LibraryListView(self._daemon.server_libraries(include_accessible=include_accessible))
|
|
825
802
|
|
|
826
803
|
# The flat library aliases (create_library, get_library, update_library,
|
|
827
804
|
# delete_library, grant_library, revoke_library_grant, add_reference,
|
|
@@ -1018,11 +995,7 @@ class SPLClient:
|
|
|
1018
995
|
"""
|
|
1019
996
|
|
|
1020
997
|
if scope == "auto":
|
|
1021
|
-
scope = (
|
|
1022
|
-
"server"
|
|
1023
|
-
if owner is not None or library is not None or self._has_server_connection()
|
|
1024
|
-
else "local"
|
|
1025
|
-
)
|
|
998
|
+
scope = "server" if owner is not None or library is not None or self._has_server_connection() else "local"
|
|
1026
999
|
if scope == "local":
|
|
1027
1000
|
if owner is not None or library is not None:
|
|
1028
1001
|
raise ValueError("owner/library require scope='server', scope='all', or scope='auto'")
|
|
@@ -1220,9 +1193,7 @@ class SPLClient:
|
|
|
1220
1193
|
"""Return normalized function/node/link metadata for one object."""
|
|
1221
1194
|
|
|
1222
1195
|
if self._is_node_remote(name):
|
|
1223
|
-
return DecompositionView(
|
|
1224
|
-
self._remote_decomposition_response(name, version=version)["decomposition"]
|
|
1225
|
-
)
|
|
1196
|
+
return DecompositionView(self._remote_decomposition_response(name, version=version)["decomposition"])
|
|
1226
1197
|
if owner is not None or library is not None:
|
|
1227
1198
|
response = self._remote_decomposition_response(
|
|
1228
1199
|
{
|
|
@@ -1269,11 +1240,7 @@ class SPLClient:
|
|
|
1269
1240
|
record = response.get("object") or {}
|
|
1270
1241
|
remote = response.get("remote") or {}
|
|
1271
1242
|
object_name = (
|
|
1272
|
-
title
|
|
1273
|
-
or record.get("display_name")
|
|
1274
|
-
or record.get("name")
|
|
1275
|
-
or remote.get("name")
|
|
1276
|
-
or pipeline.name
|
|
1243
|
+
title or record.get("display_name") or record.get("name") or remote.get("name") or pipeline.name
|
|
1277
1244
|
)
|
|
1278
1245
|
return PipelineGraphWidget(
|
|
1279
1246
|
decomposition,
|
|
@@ -1328,10 +1295,7 @@ class SPLClient:
|
|
|
1328
1295
|
theme=theme,
|
|
1329
1296
|
)
|
|
1330
1297
|
|
|
1331
|
-
raise TypeError(
|
|
1332
|
-
"pipeline_widget expects an object name, PublishedObject, "
|
|
1333
|
-
"spl.core Pipeline, or NodeRemote"
|
|
1334
|
-
)
|
|
1298
|
+
raise TypeError("pipeline_widget expects an object name, PublishedObject, spl.core Pipeline, or NodeRemote")
|
|
1335
1299
|
|
|
1336
1300
|
def draw_pipeline(
|
|
1337
1301
|
self,
|
|
@@ -1371,20 +1335,11 @@ class SPLClient:
|
|
|
1371
1335
|
function=function,
|
|
1372
1336
|
)
|
|
1373
1337
|
display_name = signature.get("display_name") or signature["name"]
|
|
1374
|
-
lines = [
|
|
1375
|
-
(
|
|
1376
|
-
f"{display_name} "
|
|
1377
|
-
f"v{signature['version']} ({signature['kind']})"
|
|
1378
|
-
)
|
|
1379
|
-
]
|
|
1338
|
+
lines = [(f"{display_name} v{signature['version']} ({signature['kind']})")]
|
|
1380
1339
|
if signature.get("description"):
|
|
1381
1340
|
lines.append(signature["description"])
|
|
1382
1341
|
|
|
1383
|
-
if (
|
|
1384
|
-
function is None
|
|
1385
|
-
and signature.get("kind") == "pipeline"
|
|
1386
|
-
and signature.get("internal_functions")
|
|
1387
|
-
):
|
|
1342
|
+
if function is None and signature.get("kind") == "pipeline" and signature.get("internal_functions"):
|
|
1388
1343
|
lines.append("Functions:")
|
|
1389
1344
|
for item in signature["internal_functions"]:
|
|
1390
1345
|
lines.append(f" - {item['name']}")
|
|
@@ -1393,29 +1348,16 @@ class SPLClient:
|
|
|
1393
1348
|
if signature["inputs"]:
|
|
1394
1349
|
for item in signature["inputs"]:
|
|
1395
1350
|
required = "required" if item["required"] else "optional"
|
|
1396
|
-
default =
|
|
1397
|
-
|
|
1398
|
-
if item["default"] is None
|
|
1399
|
-
else f", default={item['default']}"
|
|
1400
|
-
)
|
|
1401
|
-
lines.append(
|
|
1402
|
-
f" - {item['name']}: {item['type'] or 'Any'} "
|
|
1403
|
-
f"({required}{default})"
|
|
1404
|
-
)
|
|
1351
|
+
default = "" if item["default"] is None else f", default={item['default']}"
|
|
1352
|
+
lines.append(f" - {item['name']}: {item['type'] or 'Any'} ({required}{default})")
|
|
1405
1353
|
else:
|
|
1406
1354
|
lines.append(" - none")
|
|
1407
1355
|
|
|
1408
1356
|
lines.append("Outputs:")
|
|
1409
1357
|
if signature["outputs"]:
|
|
1410
1358
|
for item in signature["outputs"]:
|
|
1411
|
-
selector =
|
|
1412
|
-
|
|
1413
|
-
if item["selector"] is not None
|
|
1414
|
-
else "no output selector"
|
|
1415
|
-
)
|
|
1416
|
-
lines.append(
|
|
1417
|
-
f" - {item['name']}: {selector}; read {item['read']}"
|
|
1418
|
-
)
|
|
1359
|
+
selector = f'output="{item["selector"]}"' if item["selector"] is not None else "no output selector"
|
|
1360
|
+
lines.append(f" - {item['name']}: {selector}; read {item['read']}")
|
|
1419
1361
|
else:
|
|
1420
1362
|
lines.append(" - none")
|
|
1421
1363
|
|
|
@@ -1446,10 +1388,89 @@ class SPLClient:
|
|
|
1446
1388
|
"environment build",
|
|
1447
1389
|
)
|
|
1448
1390
|
|
|
1449
|
-
def runs(self) -> list[dict[str, Any]]:
|
|
1391
|
+
def runs(self, *, local: bool = False) -> list[dict[str, Any]]:
|
|
1450
1392
|
"""Return known daemon runs, newest first."""
|
|
1451
1393
|
|
|
1452
|
-
return RunListView(self._daemon.list_runs())
|
|
1394
|
+
return RunListView(m_manifest.list_local_runs() if local else self._daemon.list_runs())
|
|
1395
|
+
|
|
1396
|
+
def run_show(self, run_id: str, *, full_inline: bool = False, local: bool = False) -> dict[str, Any]:
|
|
1397
|
+
"""Return one retained run manifest.
|
|
1398
|
+
|
|
1399
|
+
Inline JSON values are summarized by default; pass ``full_inline=True``
|
|
1400
|
+
to include the full manifest values.
|
|
1401
|
+
"""
|
|
1402
|
+
|
|
1403
|
+
payload = (
|
|
1404
|
+
m_manifest.show_local_run(run_id, include_inline_values=full_inline)
|
|
1405
|
+
if local
|
|
1406
|
+
else self._daemon.show_run(run_id, full_inline=full_inline)
|
|
1407
|
+
)
|
|
1408
|
+
return RunRecordView(payload)
|
|
1409
|
+
|
|
1410
|
+
def resume(
|
|
1411
|
+
self,
|
|
1412
|
+
run_id: str,
|
|
1413
|
+
*,
|
|
1414
|
+
from_: Any,
|
|
1415
|
+
kwargs: dict[str, Any] | None = None,
|
|
1416
|
+
output: str | None = None,
|
|
1417
|
+
timeout_seconds: float | None = None,
|
|
1418
|
+
adapters: dict[str, Any] | None = None,
|
|
1419
|
+
runtimes: dict[str, str] | None = None,
|
|
1420
|
+
keep: bool | str | None = None,
|
|
1421
|
+
wait: bool = False,
|
|
1422
|
+
artifacts_dir: str | Path | None = None,
|
|
1423
|
+
progress: ProgressOption = True,
|
|
1424
|
+
) -> RemoteRun | RemoteResult:
|
|
1425
|
+
"""Resume a retained daemon pipeline run from selected recalculation nodes."""
|
|
1426
|
+
|
|
1427
|
+
state = self._daemon.resume_run(
|
|
1428
|
+
run_id,
|
|
1429
|
+
from_=from_,
|
|
1430
|
+
kwargs=kwargs,
|
|
1431
|
+
output=output,
|
|
1432
|
+
timeout_seconds=timeout_seconds,
|
|
1433
|
+
adapters=adapters,
|
|
1434
|
+
runtimes=runtimes,
|
|
1435
|
+
keep=keep,
|
|
1436
|
+
)
|
|
1437
|
+
run = RemoteRun(self, state)
|
|
1438
|
+
if not wait:
|
|
1439
|
+
return run
|
|
1440
|
+
return run.collect(
|
|
1441
|
+
artifacts_dir=artifacts_dir,
|
|
1442
|
+
timeout_seconds=timeout_seconds,
|
|
1443
|
+
progress=progress,
|
|
1444
|
+
)
|
|
1445
|
+
|
|
1446
|
+
def prune_runs(
|
|
1447
|
+
self,
|
|
1448
|
+
*,
|
|
1449
|
+
run_id: str | None = None,
|
|
1450
|
+
status: str | list[str] | None = None,
|
|
1451
|
+
older_than_seconds: float | None = None,
|
|
1452
|
+
dry_run: bool = False,
|
|
1453
|
+
local: bool = False,
|
|
1454
|
+
) -> dict[str, Any]:
|
|
1455
|
+
"""Prune inactive retained runs by id, status, age, or retention TTL."""
|
|
1456
|
+
|
|
1457
|
+
statuses = [status] if isinstance(status, str) else status
|
|
1458
|
+
result = (
|
|
1459
|
+
m_manifest.prune_local_runs(
|
|
1460
|
+
run_id=run_id,
|
|
1461
|
+
statuses=statuses,
|
|
1462
|
+
older_than_seconds=older_than_seconds,
|
|
1463
|
+
dry_run=dry_run,
|
|
1464
|
+
)
|
|
1465
|
+
if local
|
|
1466
|
+
else self._daemon.prune_runs(
|
|
1467
|
+
run_id=run_id,
|
|
1468
|
+
statuses=statuses,
|
|
1469
|
+
older_than_seconds=older_than_seconds,
|
|
1470
|
+
dry_run=dry_run,
|
|
1471
|
+
)
|
|
1472
|
+
)
|
|
1473
|
+
return wrap_action(result, "runs pruned")
|
|
1453
1474
|
|
|
1454
1475
|
def _start_run(
|
|
1455
1476
|
self,
|
|
@@ -1465,24 +1486,37 @@ class SPLClient:
|
|
|
1465
1486
|
offline_policy: OfflinePolicy | None = None,
|
|
1466
1487
|
function: str | None = None,
|
|
1467
1488
|
source: RunSource = "auto",
|
|
1489
|
+
adapters: Any | None = None,
|
|
1490
|
+
runtimes: dict[str, str] | None = None,
|
|
1491
|
+
keep: bool | str | None = None,
|
|
1468
1492
|
) -> RemoteRun:
|
|
1469
1493
|
"""Shared implementation behind ``submit``/``call`` (and legacy aliases)."""
|
|
1470
1494
|
|
|
1495
|
+
if adapters is not None:
|
|
1496
|
+
raise NotImplementedError(
|
|
1497
|
+
"run-level adapter overrides are supported only by local Deployment.run in 0.4.0; "
|
|
1498
|
+
"SPLClient daemon runs cannot serialize Python adapter callables yet"
|
|
1499
|
+
)
|
|
1500
|
+
|
|
1471
1501
|
remote = target_machine is not None or owner is not None or library is not None
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1502
|
+
run_kwargs: dict[str, Any] = {
|
|
1503
|
+
"args": args,
|
|
1504
|
+
"kwargs": kwargs,
|
|
1505
|
+
"output": output,
|
|
1506
|
+
"timeout_seconds": timeout_seconds,
|
|
1507
|
+
"target_machine": target_machine,
|
|
1508
|
+
"object_owner_id": owner,
|
|
1509
|
+
"library": library,
|
|
1510
|
+
"offline_policy": offline_policy,
|
|
1511
|
+
"function": function,
|
|
1512
|
+
"source": source,
|
|
1513
|
+
"remote": remote or None,
|
|
1514
|
+
}
|
|
1515
|
+
if runtimes is not None:
|
|
1516
|
+
run_kwargs["runtimes"] = runtimes
|
|
1517
|
+
if keep is not None:
|
|
1518
|
+
run_kwargs["keep"] = keep
|
|
1519
|
+
state = self._daemon.run(name, **run_kwargs)
|
|
1486
1520
|
return RemoteRun(self, state, server_side=remote)
|
|
1487
1521
|
|
|
1488
1522
|
# ``start()`` warned through 0.1.4/0.1.5 and was removed in 0.2.0 — use
|
|
@@ -1502,12 +1536,17 @@ class SPLClient:
|
|
|
1502
1536
|
offline_policy: OfflinePolicy | None = None,
|
|
1503
1537
|
function: str | None = None,
|
|
1504
1538
|
source: RunSource = "auto",
|
|
1539
|
+
adapters: Any | None = None,
|
|
1540
|
+
runtimes: dict[str, str] | None = None,
|
|
1541
|
+
keep: bool | str | None = None,
|
|
1505
1542
|
) -> RemoteRun:
|
|
1506
1543
|
"""Canonical async entry point: start a run, return a handle immediately.
|
|
1507
1544
|
|
|
1508
1545
|
The default path is local daemon execution. Passing ``target_machine``,
|
|
1509
1546
|
``owner``, or ``library`` intentionally selects central-server remote
|
|
1510
|
-
execution through the connected daemon.
|
|
1547
|
+
execution through the connected daemon. ``adapters`` is reserved for
|
|
1548
|
+
run-level adapter overrides and currently raises because daemon runs do
|
|
1549
|
+
not serialize Python adapter callables.
|
|
1511
1550
|
"""
|
|
1512
1551
|
|
|
1513
1552
|
return self._start_run(
|
|
@@ -1522,6 +1561,9 @@ class SPLClient:
|
|
|
1522
1561
|
offline_policy=offline_policy,
|
|
1523
1562
|
function=function,
|
|
1524
1563
|
source=source,
|
|
1564
|
+
adapters=adapters,
|
|
1565
|
+
runtimes=runtimes,
|
|
1566
|
+
keep=keep,
|
|
1525
1567
|
)
|
|
1526
1568
|
|
|
1527
1569
|
# ``queue()`` warned through 0.1.4/0.1.5 and was removed in 0.2.0 — use
|
|
@@ -1542,6 +1584,9 @@ class SPLClient:
|
|
|
1542
1584
|
offline_policy: OfflinePolicy | None = None,
|
|
1543
1585
|
function: str | None = None,
|
|
1544
1586
|
source: RunSource = "auto",
|
|
1587
|
+
adapters: Any | None = None,
|
|
1588
|
+
runtimes: dict[str, str] | None = None,
|
|
1589
|
+
keep: bool | str | None = None,
|
|
1545
1590
|
progress: ProgressOption = True,
|
|
1546
1591
|
) -> RemoteResult:
|
|
1547
1592
|
"""Run an object, wait for completion, and return result/artifacts.
|
|
@@ -1554,7 +1599,9 @@ class SPLClient:
|
|
|
1554
1599
|
While waiting, slow phases (a first-run environment build, a queued
|
|
1555
1600
|
server-side run) print short progress lines to stderr. Pass
|
|
1556
1601
|
``progress=False`` to wait silently, or a callable to receive every
|
|
1557
|
-
polled run state instead.
|
|
1602
|
+
polled run state instead. ``adapters`` is reserved for run-level
|
|
1603
|
+
adapter overrides and currently raises because daemon runs do not
|
|
1604
|
+
serialize Python adapter callables.
|
|
1558
1605
|
"""
|
|
1559
1606
|
|
|
1560
1607
|
run = self._start_run(
|
|
@@ -1569,6 +1616,9 @@ class SPLClient:
|
|
|
1569
1616
|
offline_policy=offline_policy,
|
|
1570
1617
|
function=function,
|
|
1571
1618
|
source=source,
|
|
1619
|
+
adapters=adapters,
|
|
1620
|
+
runtimes=runtimes,
|
|
1621
|
+
keep=keep,
|
|
1572
1622
|
)
|
|
1573
1623
|
return run.collect(
|
|
1574
1624
|
artifacts_dir=artifacts_dir,
|
|
@@ -1628,11 +1678,7 @@ class SPLClient:
|
|
|
1628
1678
|
*,
|
|
1629
1679
|
version: int | None = None,
|
|
1630
1680
|
) -> dict[str, Any]:
|
|
1631
|
-
ref = (
|
|
1632
|
-
self._remote_node_payload(remote, version=version)
|
|
1633
|
-
if self._is_node_remote(remote)
|
|
1634
|
-
else dict(remote)
|
|
1635
|
-
)
|
|
1681
|
+
ref = self._remote_node_payload(remote, version=version) if self._is_node_remote(remote) else dict(remote)
|
|
1636
1682
|
if version is not None:
|
|
1637
1683
|
ref["version"] = version
|
|
1638
1684
|
return self._daemon.resolve_remote_decomposition(ref)
|
|
@@ -1735,11 +1781,7 @@ def export_objects_to_yaml(xs: list[Any], *, frame_offset: int = 2) -> str:
|
|
|
1735
1781
|
|
|
1736
1782
|
top_level_deps = get_top_level_deps(frame_offset, xs)
|
|
1737
1783
|
|
|
1738
|
-
mapping = {
|
|
1739
|
-
root: DSPLSelfImport(name=cast(Any, root).name)
|
|
1740
|
-
for (root, _) in top_level_deps
|
|
1741
|
-
if hasattr(root, "name")
|
|
1742
|
-
}
|
|
1784
|
+
mapping = {root: DSPLSelfImport(name=cast(Any, root).name) for (root, _) in top_level_deps if hasattr(root, "name")}
|
|
1743
1785
|
|
|
1744
1786
|
normalized_deps = {
|
|
1745
1787
|
root: [mapping.get(dependency, dependency) for dependency in dependencies]
|
|
@@ -1762,8 +1804,7 @@ def prepare_export_object(obj: Any, entrypoint: str | None) -> tuple[Any, str]:
|
|
|
1762
1804
|
if entrypoint is None:
|
|
1763
1805
|
if obj.name is None:
|
|
1764
1806
|
raise ValueError(
|
|
1765
|
-
"unnamed pipeline requires entrypoint; "
|
|
1766
|
-
"use pipeline.render(name) or publish(..., entrypoint='name')"
|
|
1807
|
+
"unnamed pipeline requires entrypoint; use pipeline.render(name) or publish(..., entrypoint='name')"
|
|
1767
1808
|
)
|
|
1768
1809
|
return obj, obj.name
|
|
1769
1810
|
return replace(obj, name=entrypoint), entrypoint
|
|
@@ -7,6 +7,9 @@ from functools import lru_cache
|
|
|
7
7
|
from typing import Any
|
|
8
8
|
from urllib.request import Request, urlopen
|
|
9
9
|
|
|
10
|
+
DEFAULT_HTTP_TIMEOUT_SECONDS = 60.0
|
|
11
|
+
DEFAULT_FILE_TRANSFER_TIMEOUT_SECONDS = 300.0
|
|
12
|
+
|
|
10
13
|
|
|
11
14
|
@lru_cache(maxsize=1)
|
|
12
15
|
def verified_https_context() -> ssl.SSLContext:
|
|
@@ -17,7 +20,11 @@ def verified_https_context() -> ssl.SSLContext:
|
|
|
17
20
|
return ssl.create_default_context(cafile=certifi.where())
|
|
18
21
|
|
|
19
22
|
|
|
20
|
-
def urlopen_verified(
|
|
23
|
+
def urlopen_verified(
|
|
24
|
+
request: Request,
|
|
25
|
+
*,
|
|
26
|
+
timeout: float | None = DEFAULT_HTTP_TIMEOUT_SECONDS,
|
|
27
|
+
) -> Any:
|
|
21
28
|
"""Open a URL request with the bundled certifi CA bundle."""
|
|
22
29
|
|
|
23
|
-
return urlopen(request, context=verified_https_context())
|
|
30
|
+
return urlopen(request, context=verified_https_context(), timeout=timeout)
|