splime 0.1.2__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 (74) hide show
  1. spl/__init__.py +14 -0
  2. spl/client.py +1364 -0
  3. spl/core/__init__.py +23 -0
  4. spl/core/common.py +350 -0
  5. spl/core/entities/__init__.py +0 -0
  6. spl/core/entities/adapter.py +210 -0
  7. spl/core/entities/artifact.py +141 -0
  8. spl/core/entities/control.py +45 -0
  9. spl/core/entities/distribution.py +65 -0
  10. spl/core/entities/function.py +254 -0
  11. spl/core/entities/local_function.py +286 -0
  12. spl/core/entities/misc.py +14 -0
  13. spl/core/entities/module.py +88 -0
  14. spl/core/entities/node.py +286 -0
  15. spl/core/entities/node_function.py +79 -0
  16. spl/core/entities/node_remote.py +295 -0
  17. spl/core/entities/pipeline.py +436 -0
  18. spl/core/entities/scalar.py +55 -0
  19. spl/core/ir/__init__.py +0 -0
  20. spl/core/ir/common.py +34 -0
  21. spl/core/ir/parse.py +79 -0
  22. spl/core/ir/unparse.py +29 -0
  23. spl/core/ir/utils.py +163 -0
  24. spl/daemon/__init__.py +23 -0
  25. spl/daemon/__main__.py +11 -0
  26. spl/daemon/cli.py +582 -0
  27. spl/daemon/client.py +43 -0
  28. spl/daemon/docker_environment.py +329 -0
  29. spl/daemon/docker_pool.py +516 -0
  30. spl/daemon/environment.py +228 -0
  31. spl/daemon/environment_base.py +479 -0
  32. spl/daemon/heartbeat_service.py +119 -0
  33. spl/daemon/metadata.py +427 -0
  34. spl/daemon/remote_client.py +457 -0
  35. spl/daemon/repositories/__init__.py +17 -0
  36. spl/daemon/repositories/env.py +323 -0
  37. spl/daemon/repositories/library.py +181 -0
  38. spl/daemon/repositories/object.py +997 -0
  39. spl/daemon/repositories/run.py +279 -0
  40. spl/daemon/repositories/server_connection.py +657 -0
  41. spl/daemon/repositories/sync_event.py +129 -0
  42. spl/daemon/routes/__init__.py +1 -0
  43. spl/daemon/routes/_helpers.py +147 -0
  44. spl/daemon/routes/artifacts.py +77 -0
  45. spl/daemon/routes/diagnostics.py +114 -0
  46. spl/daemon/routes/envs.py +82 -0
  47. spl/daemon/routes/libraries.py +129 -0
  48. spl/daemon/routes/objects.py +174 -0
  49. spl/daemon/routes/remote.py +56 -0
  50. spl/daemon/routes/runs.py +96 -0
  51. spl/daemon/routes/server_connections.py +86 -0
  52. spl/daemon/runtime_backend.py +368 -0
  53. spl/daemon/runtime_config.py +133 -0
  54. spl/daemon/runtime_dependencies.py +459 -0
  55. spl/daemon/secret_store.py +187 -0
  56. spl/daemon/server.py +2224 -0
  57. spl/daemon/server_connection.py +267 -0
  58. spl/daemon/services/__init__.py +1 -0
  59. spl/daemon/services/sync.py +76 -0
  60. spl/daemon/signature.py +376 -0
  61. spl/daemon/storage_base.py +542 -0
  62. spl/daemon/store.py +436 -0
  63. spl/daemon/worker.py +526 -0
  64. spl/daemon_client.py +945 -0
  65. spl/pipeline_widget.py +1452 -0
  66. spl/py.typed +0 -0
  67. spl/server_client.py +787 -0
  68. splime-0.1.2.dist-info/METADATA +189 -0
  69. splime-0.1.2.dist-info/RECORD +74 -0
  70. splime-0.1.2.dist-info/WHEEL +5 -0
  71. splime-0.1.2.dist-info/entry_points.txt +2 -0
  72. splime-0.1.2.dist-info/licenses/LICENSE +201 -0
  73. splime-0.1.2.dist-info/licenses/NOTICE +8 -0
  74. splime-0.1.2.dist-info/top_level.txt +1 -0
spl/server_client.py ADDED
@@ -0,0 +1,787 @@
1
+ """Direct client for the central SPL daemon server.
2
+
3
+ This client is intentionally separate from ``SPLClient``. ``SPLClient`` talks
4
+ to the local daemon first; ``SPLServerClient`` talks to the central server
5
+ directly with one bearer token, which is useful for external execution tokens
6
+ and small service integrations.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ import time
13
+ from dataclasses import dataclass, field
14
+ from pathlib import Path
15
+ from typing import Any, Literal
16
+ from urllib.error import HTTPError, URLError
17
+ from urllib.parse import quote, urlencode
18
+ from urllib.request import Request, urlopen
19
+
20
+
21
+ DEFAULT_SERVER_URL = "https://splime.io/api"
22
+ TERMINAL_REMOTE_RUN_STATUSES = {"succeeded", "failed", "cancelled", "stale"}
23
+
24
+ OfflinePolicy = Literal["queue", "wait", "fail_fast"]
25
+ RemoteRunScope = Literal["owned", "target", "object", "all"]
26
+
27
+
28
+ def _url_part(value: str) -> str:
29
+ return quote(value, safe="")
30
+
31
+
32
+ class ServerClientError(RuntimeError):
33
+ """Raised when the central server returns an error response."""
34
+
35
+ def __init__(self, status_code: int, message: str):
36
+ self.status_code = status_code
37
+ self.message = message
38
+ super().__init__(f"{status_code}: {message}")
39
+
40
+
41
+ @dataclass(frozen=True)
42
+ class ServerCallResult:
43
+ """Completed server run plus optional downloaded artifacts."""
44
+
45
+ run: dict[str, Any]
46
+ detail: dict[str, Any]
47
+ downloaded_artifacts: dict[str, Path] = field(default_factory=dict)
48
+
49
+ @property
50
+ def value(self) -> Any:
51
+ result = self.detail.get("result")
52
+ if isinstance(result, dict) and "value" in result:
53
+ return result["value"]
54
+ if result is not None:
55
+ return result
56
+ raw = self.run.get("result")
57
+ if isinstance(raw, dict) and "value" in raw:
58
+ return raw["value"]
59
+ return raw
60
+
61
+ @property
62
+ def artifacts(self) -> list[dict[str, Any]]:
63
+ return list(self.detail.get("artifacts") or [])
64
+
65
+
66
+ class ServerRemoteRun:
67
+ """Handle for a central-server remote run."""
68
+
69
+ def __init__(self, client: "SPLServerClient", state: dict[str, Any]):
70
+ self._client = client
71
+ self.state = state
72
+
73
+ @property
74
+ def id(self) -> str:
75
+ return self.state["id"]
76
+
77
+ @property
78
+ def status(self) -> str:
79
+ return self.state["status"]
80
+
81
+ @property
82
+ def mode(self) -> str:
83
+ return "server"
84
+
85
+ def refresh(self) -> dict[str, Any]:
86
+ self.state = self._client.get_run(self.id)
87
+ return self.state
88
+
89
+ def wait(
90
+ self,
91
+ *,
92
+ poll_interval: float = 0.5,
93
+ timeout_seconds: float | None = None,
94
+ ) -> dict[str, Any]:
95
+ started = time.monotonic()
96
+ while True:
97
+ state = self.refresh()
98
+ if state["status"] in TERMINAL_REMOTE_RUN_STATUSES:
99
+ return state
100
+ if timeout_seconds is not None and time.monotonic() - started >= timeout_seconds:
101
+ raise TimeoutError(f"remote run {self.id!r} did not finish in time")
102
+ time.sleep(max(0.0, poll_interval))
103
+
104
+ def detail(self) -> dict[str, Any]:
105
+ return self._client.get_run_detail(self.id)
106
+
107
+ def events(self) -> list[dict[str, Any]]:
108
+ return self._client.list_events(self.id)
109
+
110
+ def artifact_names(self) -> list[str]:
111
+ return [item["name"] for item in self._client.list_artifacts(self.id)]
112
+
113
+ def artifact_bytes(self, name: str) -> bytes:
114
+ return self._client.artifact_bytes(self.id, name)
115
+
116
+ def download_artifact(self, name: str, target: str | Path) -> Path:
117
+ return self._client.download_artifact(self.id, name, target)
118
+
119
+ def download_artifacts(self, target_dir: str | Path) -> dict[str, Path]:
120
+ target_path = Path(target_dir)
121
+ target_path.mkdir(parents=True, exist_ok=True)
122
+ return {
123
+ name: self._client.download_artifact(self.id, name, target_path)
124
+ for name in self.artifact_names()
125
+ }
126
+
127
+ def cancel(self) -> dict[str, Any]:
128
+ self.state = self._client.cancel_run(self.id)
129
+ return self.state
130
+
131
+ def retry(self) -> "ServerRemoteRun":
132
+ return self._client.retry_run(self.id)
133
+
134
+ def collect(
135
+ self,
136
+ *,
137
+ artifacts_dir: str | Path | None = None,
138
+ poll_interval: float = 0.5,
139
+ timeout_seconds: float | None = None,
140
+ ) -> ServerCallResult:
141
+ final_state = self.wait(
142
+ poll_interval=poll_interval,
143
+ timeout_seconds=timeout_seconds,
144
+ )
145
+ if final_state["status"] != "succeeded":
146
+ error = final_state.get("error") or "remote run returned no error message"
147
+ raise RuntimeError(
148
+ f"server run {self.id!r} ended as "
149
+ f"{final_state.get('status')!r}: {error}"
150
+ )
151
+ detail = self.detail()
152
+ downloaded = (
153
+ self.download_artifacts(artifacts_dir)
154
+ if artifacts_dir is not None
155
+ else {}
156
+ )
157
+ return ServerCallResult(
158
+ run=final_state,
159
+ detail=detail,
160
+ downloaded_artifacts=downloaded,
161
+ )
162
+
163
+
164
+ class SPLServerClient:
165
+ """Small stdlib HTTP client for the central SPL daemon server."""
166
+
167
+ def __init__(
168
+ self,
169
+ token: str,
170
+ *,
171
+ base_url: str = DEFAULT_SERVER_URL,
172
+ ):
173
+ if not token:
174
+ raise ValueError("token is required")
175
+ self.token = token
176
+ self.base_url = base_url.rstrip("/")
177
+
178
+ @classmethod
179
+ def external_token(
180
+ cls,
181
+ token: str,
182
+ *,
183
+ base_url: str = DEFAULT_SERVER_URL,
184
+ ) -> "SPLExternalTokenClient":
185
+ """Return a restricted facade for ``library_execution_token`` use."""
186
+
187
+ return SPLExternalTokenClient(token, base_url=base_url)
188
+
189
+ def _headers(self) -> dict[str, str]:
190
+ return {
191
+ "Accept": "application/json",
192
+ "Authorization": f"Bearer {self.token}",
193
+ }
194
+
195
+ def _json_request(
196
+ self,
197
+ method: str,
198
+ path: str,
199
+ payload: dict[str, Any] | None = None,
200
+ ) -> Any:
201
+ body = None
202
+ headers = self._headers()
203
+ if payload is not None:
204
+ body = json.dumps(payload, ensure_ascii=False).encode("utf-8")
205
+ headers["Content-Type"] = "application/json; charset=utf-8"
206
+ request = Request(
207
+ f"{self.base_url}{path}",
208
+ data=body,
209
+ headers=headers,
210
+ method=method,
211
+ )
212
+ try:
213
+ with urlopen(request) as response: # noqa: S310 - configured server URL.
214
+ raw = response.read().decode("utf-8")
215
+ except HTTPError as exc:
216
+ raw = exc.read().decode("utf-8")
217
+ try:
218
+ message = json.loads(raw).get("error", raw)
219
+ except json.JSONDecodeError:
220
+ message = raw
221
+ raise ServerClientError(
222
+ exc.code,
223
+ f"central SPL server returned {exc.code} at {self.base_url}{path}: {message}",
224
+ ) from exc
225
+ except URLError as exc:
226
+ raise ServerClientError(
227
+ 502,
228
+ f"central SPL server is not reachable at {self.base_url}: {exc.reason}",
229
+ ) from exc
230
+ if not raw:
231
+ return None
232
+ return json.loads(raw)
233
+
234
+ def _bytes_request(self, path: str) -> bytes:
235
+ request = Request(f"{self.base_url}{path}", headers=self._headers())
236
+ try:
237
+ with urlopen(request) as response: # noqa: S310 - configured server URL.
238
+ return response.read()
239
+ except HTTPError as exc:
240
+ raw = exc.read().decode("utf-8")
241
+ try:
242
+ message = json.loads(raw).get("error", raw)
243
+ except json.JSONDecodeError:
244
+ message = raw
245
+ raise ServerClientError(
246
+ exc.code,
247
+ f"central SPL server returned {exc.code} at {self.base_url}{path}: {message}",
248
+ ) from exc
249
+ except URLError as exc:
250
+ raise ServerClientError(
251
+ 502,
252
+ f"central SPL server is not reachable at {self.base_url}: {exc.reason}",
253
+ ) from exc
254
+
255
+ def objects(
256
+ self,
257
+ *,
258
+ owner: str | None = None,
259
+ library: str | None = None,
260
+ compact: bool = False,
261
+ ) -> list[dict[str, Any]]:
262
+ path = (
263
+ f"/owners/{_url_part(owner)}/libraries/{_url_part(library or 'default')}/objects"
264
+ if owner
265
+ else "/objects"
266
+ )
267
+ query = {}
268
+ if library and owner is None:
269
+ query["library"] = library
270
+ if compact:
271
+ query["view"] = "summary"
272
+ return self._json_request("GET", self._with_query(path, query))
273
+
274
+ def get_object(
275
+ self,
276
+ name_or_id: str,
277
+ *,
278
+ owner: str | None = None,
279
+ library: str | None = None,
280
+ version: int | None = None,
281
+ include_yaml: bool = False,
282
+ ) -> dict[str, Any]:
283
+ query: dict[str, Any] = {}
284
+ if version is not None:
285
+ query["version"] = int(version)
286
+ if include_yaml:
287
+ query["include_yaml"] = "1"
288
+ if library and owner is None:
289
+ query["library"] = library
290
+ path = self._object_path(name_or_id, owner=owner, library=library)
291
+ return self._json_request("GET", self._with_query(path, query))
292
+
293
+ def signature(
294
+ self,
295
+ name_or_id: str,
296
+ *,
297
+ owner: str | None = None,
298
+ library: str | None = None,
299
+ version: int | None = None,
300
+ function: str | None = None,
301
+ ) -> dict[str, Any]:
302
+ return self._object_view(
303
+ name_or_id,
304
+ "signature",
305
+ owner=owner,
306
+ library=library,
307
+ version=version,
308
+ function=function,
309
+ )
310
+
311
+ def inputs(
312
+ self,
313
+ name_or_id: str,
314
+ *,
315
+ owner: str | None = None,
316
+ library: str | None = None,
317
+ version: int | None = None,
318
+ function: str | None = None,
319
+ ) -> list[dict[str, Any]]:
320
+ return self._object_view(
321
+ name_or_id,
322
+ "inputs",
323
+ owner=owner,
324
+ library=library,
325
+ version=version,
326
+ function=function,
327
+ )
328
+
329
+ def outputs(
330
+ self,
331
+ name_or_id: str,
332
+ *,
333
+ owner: str | None = None,
334
+ library: str | None = None,
335
+ version: int | None = None,
336
+ function: str | None = None,
337
+ ) -> list[dict[str, Any]]:
338
+ return self._object_view(
339
+ name_or_id,
340
+ "outputs",
341
+ owner=owner,
342
+ library=library,
343
+ version=version,
344
+ function=function,
345
+ )
346
+
347
+ def decomposition(
348
+ self,
349
+ name_or_id: str,
350
+ *,
351
+ owner: str | None = None,
352
+ library: str | None = None,
353
+ version: int | None = None,
354
+ ) -> dict[str, Any]:
355
+ return self._object_view(
356
+ name_or_id,
357
+ "decomposition",
358
+ owner=owner,
359
+ library=library,
360
+ version=version,
361
+ )
362
+
363
+ def versions(
364
+ self,
365
+ name_or_id: str,
366
+ *,
367
+ owner: str | None = None,
368
+ library: str | None = None,
369
+ include_yaml: bool = False,
370
+ ) -> list[dict[str, Any]]:
371
+ query: dict[str, Any] = {}
372
+ if include_yaml:
373
+ query["include_yaml"] = "1"
374
+ if library and owner is None:
375
+ query["library"] = library
376
+ path = f"{self._object_path(name_or_id, owner=owner, library=library)}/versions"
377
+ return self._json_request("GET", self._with_query(path, query))
378
+
379
+ def start(
380
+ self,
381
+ name: str,
382
+ *,
383
+ target_machine: str | None = None,
384
+ owner: str | None = None,
385
+ library: str | None = None,
386
+ args: list[Any] | None = None,
387
+ kwargs: dict[str, Any] | None = None,
388
+ output: str | None = None,
389
+ timeout_seconds: float | None = None,
390
+ version: int | None = None,
391
+ version_id: str | None = None,
392
+ function: str | None = None,
393
+ target_owner: str | None = None,
394
+ access_token: str | None = None,
395
+ correlation_id: str | None = None,
396
+ parent_run_id: str | None = None,
397
+ context: dict[str, Any] | None = None,
398
+ offline_policy: OfflinePolicy | None = None,
399
+ ) -> ServerRemoteRun:
400
+ payload: dict[str, Any] = {"object": name}
401
+ if target_machine is not None:
402
+ payload["target_machine_id"] = target_machine
403
+ if target_owner is not None:
404
+ payload["target_owner_id"] = target_owner
405
+ if owner is not None:
406
+ payload["object_owner_id"] = owner
407
+ if library is not None:
408
+ payload["library"] = library
409
+ if args is not None:
410
+ payload["args"] = args
411
+ if kwargs is not None:
412
+ payload["kwargs"] = kwargs
413
+ if output is not None:
414
+ payload["output"] = output
415
+ if timeout_seconds is not None:
416
+ payload["timeout_seconds"] = timeout_seconds
417
+ if version is not None:
418
+ payload["version"] = int(version)
419
+ if version_id is not None:
420
+ payload["version_id"] = version_id
421
+ if function is not None:
422
+ payload["function"] = function
423
+ if access_token is not None:
424
+ payload["access_token"] = access_token
425
+ if correlation_id is not None:
426
+ payload["correlation_id"] = correlation_id
427
+ if parent_run_id is not None:
428
+ payload["parent_run_id"] = parent_run_id
429
+ if context:
430
+ payload["context"] = context
431
+ if offline_policy is not None:
432
+ payload["offline_policy"] = offline_policy
433
+ return ServerRemoteRun(self, self._json_request("POST", "/remote-runs", payload))
434
+
435
+ def call(
436
+ self,
437
+ name: str,
438
+ *,
439
+ target_machine: str | None = None,
440
+ owner: str | None = None,
441
+ library: str | None = None,
442
+ args: list[Any] | None = None,
443
+ kwargs: dict[str, Any] | None = None,
444
+ output: str | None = None,
445
+ timeout_seconds: float | None = None,
446
+ wait_timeout_seconds: float | None = None,
447
+ poll_interval: float = 0.5,
448
+ artifacts_dir: str | Path | None = None,
449
+ version: int | None = None,
450
+ version_id: str | None = None,
451
+ function: str | None = None,
452
+ target_owner: str | None = None,
453
+ access_token: str | None = None,
454
+ correlation_id: str | None = None,
455
+ parent_run_id: str | None = None,
456
+ context: dict[str, Any] | None = None,
457
+ offline_policy: OfflinePolicy | None = None,
458
+ ) -> ServerCallResult:
459
+ run = self.start(
460
+ name,
461
+ target_machine=target_machine,
462
+ owner=owner,
463
+ library=library,
464
+ args=args,
465
+ kwargs=kwargs,
466
+ output=output,
467
+ timeout_seconds=timeout_seconds,
468
+ version=version,
469
+ version_id=version_id,
470
+ function=function,
471
+ target_owner=target_owner,
472
+ access_token=access_token,
473
+ correlation_id=correlation_id,
474
+ parent_run_id=parent_run_id,
475
+ context=context,
476
+ offline_policy=offline_policy,
477
+ )
478
+ return run.collect(
479
+ artifacts_dir=artifacts_dir,
480
+ poll_interval=poll_interval,
481
+ timeout_seconds=wait_timeout_seconds,
482
+ )
483
+
484
+ def runs(self, *, scope: RemoteRunScope | None = None) -> list[dict[str, Any]]:
485
+ query = {"scope": scope} if scope else {}
486
+ return self._json_request("GET", self._with_query("/remote-runs", query))
487
+
488
+ def list_runs(self, *, scope: RemoteRunScope | None = None) -> list[dict[str, Any]]:
489
+ return self.runs(scope=scope)
490
+
491
+ def get_run(self, run_id: str) -> dict[str, Any]:
492
+ return self._json_request("GET", f"/remote-runs/{_url_part(run_id)}")
493
+
494
+ def get_run_detail(self, run_id: str) -> dict[str, Any]:
495
+ return self._json_request("GET", f"/remote-runs/{_url_part(run_id)}/detail")
496
+
497
+ def list_events(self, run_id: str) -> list[dict[str, Any]]:
498
+ return self._json_request("GET", f"/remote-runs/{_url_part(run_id)}/events")
499
+
500
+ def list_artifacts(self, run_id: str) -> list[dict[str, Any]]:
501
+ return self._json_request("GET", f"/remote-runs/{_url_part(run_id)}/artifacts")
502
+
503
+ def artifact_bytes(self, run_id: str, name: str) -> bytes:
504
+ return self._bytes_request(
505
+ f"/remote-runs/{_url_part(run_id)}/artifacts/{_url_part(name)}"
506
+ )
507
+
508
+ def download_artifact(self, run_id: str, name: str, target: str | Path) -> Path:
509
+ target_path = Path(target)
510
+ if target_path.is_dir():
511
+ target_path = target_path / name
512
+ target_path.parent.mkdir(parents=True, exist_ok=True)
513
+ target_path.write_bytes(self.artifact_bytes(run_id, name))
514
+ return target_path
515
+
516
+ def cancel_run(self, run_id: str) -> dict[str, Any]:
517
+ return self._json_request("POST", f"/remote-runs/{_url_part(run_id)}/cancel")
518
+
519
+ def retry_run(self, run_id: str) -> ServerRemoteRun:
520
+ state = self._json_request("POST", f"/remote-runs/{_url_part(run_id)}/retry")
521
+ return ServerRemoteRun(self, state)
522
+
523
+ def _object_view(
524
+ self,
525
+ name_or_id: str,
526
+ suffix: str,
527
+ *,
528
+ owner: str | None,
529
+ library: str | None,
530
+ version: int | None,
531
+ function: str | None = None,
532
+ ) -> Any:
533
+ query: dict[str, Any] = {}
534
+ if version is not None:
535
+ query["version"] = int(version)
536
+ if function is not None:
537
+ query["function"] = function
538
+ if library and owner is None:
539
+ query["library"] = library
540
+ path = f"{self._object_path(name_or_id, owner=owner, library=library)}/{suffix}"
541
+ return self._json_request("GET", self._with_query(path, query))
542
+
543
+ def _object_path(
544
+ self,
545
+ name_or_id: str,
546
+ *,
547
+ owner: str | None,
548
+ library: str | None,
549
+ ) -> str:
550
+ if owner:
551
+ return (
552
+ f"/owners/{_url_part(owner)}/libraries/"
553
+ f"{_url_part(library or 'default')}/objects/{_url_part(name_or_id)}"
554
+ )
555
+ return f"/objects/{_url_part(name_or_id)}"
556
+
557
+ def _with_query(self, path: str, query: dict[str, Any]) -> str:
558
+ clean: list[tuple[str, Any]] = []
559
+ for key, value in query.items():
560
+ if value is None or value == "" or value is False:
561
+ continue
562
+ clean.append((key, "1" if value is True else value))
563
+ if not clean:
564
+ return path
565
+ return f"{path}?{urlencode(clean)}"
566
+
567
+
568
+ class SPLExternalTokenClient:
569
+ """Restricted direct client for external library execution tokens.
570
+
571
+ This facade intentionally exposes only callable-surface reads, remote-run
572
+ launch/read, events, and artifact download helpers. It does not expose
573
+ machine management, token management, grants, admin/settings, cancel, retry,
574
+ or broad object listing helpers.
575
+ """
576
+
577
+ def __init__(
578
+ self,
579
+ token: str,
580
+ *,
581
+ base_url: str = DEFAULT_SERVER_URL,
582
+ ):
583
+ self._client = SPLServerClient(token, base_url=base_url)
584
+
585
+ @property
586
+ def token(self) -> str:
587
+ return self._client.token
588
+
589
+ @property
590
+ def base_url(self) -> str:
591
+ return self._client.base_url
592
+
593
+ def signature(
594
+ self,
595
+ name_or_id: str,
596
+ *,
597
+ owner: str | None = None,
598
+ library: str | None = None,
599
+ version: int | None = None,
600
+ function: str | None = None,
601
+ ) -> dict[str, Any]:
602
+ return self._client.signature(
603
+ name_or_id,
604
+ owner=owner,
605
+ library=library,
606
+ version=version,
607
+ function=function,
608
+ )
609
+
610
+ def inputs(
611
+ self,
612
+ name_or_id: str,
613
+ *,
614
+ owner: str | None = None,
615
+ library: str | None = None,
616
+ version: int | None = None,
617
+ function: str | None = None,
618
+ ) -> list[dict[str, Any]]:
619
+ return self._client.inputs(
620
+ name_or_id,
621
+ owner=owner,
622
+ library=library,
623
+ version=version,
624
+ function=function,
625
+ )
626
+
627
+ def outputs(
628
+ self,
629
+ name_or_id: str,
630
+ *,
631
+ owner: str | None = None,
632
+ library: str | None = None,
633
+ version: int | None = None,
634
+ function: str | None = None,
635
+ ) -> list[dict[str, Any]]:
636
+ return self._client.outputs(
637
+ name_or_id,
638
+ owner=owner,
639
+ library=library,
640
+ version=version,
641
+ function=function,
642
+ )
643
+
644
+ def decomposition(
645
+ self,
646
+ name_or_id: str,
647
+ *,
648
+ owner: str | None = None,
649
+ library: str | None = None,
650
+ version: int | None = None,
651
+ ) -> dict[str, Any]:
652
+ return self._client.decomposition(
653
+ name_or_id,
654
+ owner=owner,
655
+ library=library,
656
+ version=version,
657
+ )
658
+
659
+ def get_object(
660
+ self,
661
+ name_or_id: str,
662
+ *,
663
+ owner: str | None = None,
664
+ library: str | None = None,
665
+ version: int | None = None,
666
+ ) -> dict[str, Any]:
667
+ return self._client.get_object(
668
+ name_or_id,
669
+ owner=owner,
670
+ library=library,
671
+ version=version,
672
+ include_yaml=False,
673
+ )
674
+
675
+ def versions(
676
+ self,
677
+ name_or_id: str,
678
+ *,
679
+ owner: str | None = None,
680
+ library: str | None = None,
681
+ ) -> list[dict[str, Any]]:
682
+ return self._client.versions(
683
+ name_or_id,
684
+ owner=owner,
685
+ library=library,
686
+ include_yaml=False,
687
+ )
688
+
689
+ def start(
690
+ self,
691
+ name: str,
692
+ *,
693
+ target_machine: str | None = None,
694
+ owner: str | None = None,
695
+ library: str | None = None,
696
+ args: list[Any] | None = None,
697
+ kwargs: dict[str, Any] | None = None,
698
+ output: str | None = None,
699
+ timeout_seconds: float | None = None,
700
+ version: int | None = None,
701
+ version_id: str | None = None,
702
+ function: str | None = None,
703
+ correlation_id: str | None = None,
704
+ context: dict[str, Any] | None = None,
705
+ offline_policy: OfflinePolicy | None = None,
706
+ ) -> ServerRemoteRun:
707
+ return self._client.start(
708
+ name,
709
+ target_machine=target_machine,
710
+ owner=owner,
711
+ library=library,
712
+ args=args,
713
+ kwargs=kwargs,
714
+ output=output,
715
+ timeout_seconds=timeout_seconds,
716
+ version=version,
717
+ version_id=version_id,
718
+ function=function,
719
+ correlation_id=correlation_id,
720
+ context=context,
721
+ offline_policy=offline_policy,
722
+ )
723
+
724
+ def call(
725
+ self,
726
+ name: str,
727
+ *,
728
+ target_machine: str | None = None,
729
+ owner: str | None = None,
730
+ library: str | None = None,
731
+ args: list[Any] | None = None,
732
+ kwargs: dict[str, Any] | None = None,
733
+ output: str | None = None,
734
+ timeout_seconds: float | None = None,
735
+ wait_timeout_seconds: float | None = None,
736
+ poll_interval: float = 0.5,
737
+ artifacts_dir: str | Path | None = None,
738
+ version: int | None = None,
739
+ version_id: str | None = None,
740
+ function: str | None = None,
741
+ correlation_id: str | None = None,
742
+ context: dict[str, Any] | None = None,
743
+ offline_policy: OfflinePolicy | None = None,
744
+ ) -> ServerCallResult:
745
+ run = self.start(
746
+ name,
747
+ target_machine=target_machine,
748
+ owner=owner,
749
+ library=library,
750
+ args=args,
751
+ kwargs=kwargs,
752
+ output=output,
753
+ timeout_seconds=timeout_seconds,
754
+ version=version,
755
+ version_id=version_id,
756
+ function=function,
757
+ correlation_id=correlation_id,
758
+ context=context,
759
+ offline_policy=offline_policy,
760
+ )
761
+ return run.collect(
762
+ artifacts_dir=artifacts_dir,
763
+ poll_interval=poll_interval,
764
+ timeout_seconds=wait_timeout_seconds,
765
+ )
766
+
767
+ def get_run(self, run_id: str) -> dict[str, Any]:
768
+ return self._client.get_run(run_id)
769
+
770
+ def get_run_detail(self, run_id: str) -> dict[str, Any]:
771
+ return self._client.get_run_detail(run_id)
772
+
773
+ def list_events(self, run_id: str) -> list[dict[str, Any]]:
774
+ return self._client.list_events(run_id)
775
+
776
+ def list_artifacts(self, run_id: str) -> list[dict[str, Any]]:
777
+ return self._client.list_artifacts(run_id)
778
+
779
+ def artifact_bytes(self, run_id: str, name: str) -> bytes:
780
+ return self._client.artifact_bytes(run_id, name)
781
+
782
+ def download_artifact(self, run_id: str, name: str, target: str | Path) -> Path:
783
+ return self._client.download_artifact(run_id, name, target)
784
+
785
+
786
+ ServerClient = SPLServerClient
787
+ ExternalExecutionClient = SPLExternalTokenClient