simulo 0.20.1__tar.gz → 0.22.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.
- {simulo-0.20.1 → simulo-0.22.0}/MANIFEST.in +1 -1
- {simulo-0.20.1/src/simulo.egg-info → simulo-0.22.0}/PKG-INFO +2 -2
- {simulo-0.20.1 → simulo-0.22.0}/pyproject.toml +2 -2
- {simulo-0.20.1 → simulo-0.22.0}/src/simulo/__init__.py +29 -14
- {simulo-0.20.1 → simulo-0.22.0}/src/simulo/_client/_runner.py +24 -9
- {simulo-0.20.1 → simulo-0.22.0}/src/simulo/_client/app.py +31 -8
- {simulo-0.20.1 → simulo-0.22.0}/src/simulo/_client/cancel_api.py +2 -2
- {simulo-0.20.1 → simulo-0.22.0}/src/simulo/_client/cli.py +1082 -395
- {simulo-0.20.1 → simulo-0.22.0}/src/simulo/_client/credentials.py +4 -2
- {simulo-0.20.1 → simulo-0.22.0}/src/simulo/_client/export_api.py +24 -17
- simulo-0.22.0/src/simulo/_client/identity_api.py +117 -0
- {simulo-0.20.1 → simulo-0.22.0}/src/simulo/_client/jobs_api.py +17 -12
- {simulo-0.20.1 → simulo-0.22.0}/src/simulo/_client/learning.py +11 -11
- {simulo-0.20.1 → simulo-0.22.0}/src/simulo/_client/login.py +18 -3
- simulo-0.20.1/src/simulo/_client/artifacts.py → simulo-0.22.0/src/simulo/_client/outputs.py +24 -22
- {simulo-0.20.1 → simulo-0.22.0}/src/simulo/_client/seed_ref.py +3 -3
- {simulo-0.20.1 → simulo-0.22.0}/src/simulo/_client/submit_api.py +210 -132
- {simulo-0.20.1 → simulo-0.22.0}/src/simulo/_client/view_session_api.py +6 -6
- {simulo-0.20.1 → simulo-0.22.0}/src/simulo/callbacks.py +3 -3
- {simulo-0.20.1 → simulo-0.22.0/src/simulo.egg-info}/PKG-INFO +2 -2
- {simulo-0.20.1 → simulo-0.22.0}/src/simulo.egg-info/SOURCES.txt +2 -1
- {simulo-0.20.1 → simulo-0.22.0}/src/simulo.egg-info/requires.txt +1 -1
- {simulo-0.20.1 → simulo-0.22.0}/PYPI.md +0 -0
- {simulo-0.20.1 → simulo-0.22.0}/setup.cfg +0 -0
- {simulo-0.20.1 → simulo-0.22.0}/src/simulo/_client/__init__.py +0 -0
- {simulo-0.20.1 → simulo-0.22.0}/src/simulo/_client/_entrypoint.py +0 -0
- {simulo-0.20.1 → simulo-0.22.0}/src/simulo/_client/_mounts.py +0 -0
- {simulo-0.20.1 → simulo-0.22.0}/src/simulo/_client/asset.py +0 -0
- {simulo-0.20.1 → simulo-0.22.0}/src/simulo/_client/asset_api.py +0 -0
- {simulo-0.20.1 → simulo-0.22.0}/src/simulo/_client/asset_package.py +0 -0
- {simulo-0.20.1 → simulo-0.22.0}/src/simulo/_client/asset_pins.py +0 -0
- {simulo-0.20.1 → simulo-0.22.0}/src/simulo/_client/builtin_aliases.py +0 -0
- {simulo-0.20.1 → simulo-0.22.0}/src/simulo/_client/bundle.py +0 -0
- {simulo-0.20.1 → simulo-0.22.0}/src/simulo/_client/config.py +0 -0
- {simulo-0.20.1 → simulo-0.22.0}/src/simulo/_client/discovery.py +0 -0
- {simulo-0.20.1 → simulo-0.22.0}/src/simulo/_client/export_bundle.py +0 -0
- {simulo-0.20.1 → simulo-0.22.0}/src/simulo/_client/facades.py +0 -0
- {simulo-0.20.1 → simulo-0.22.0}/src/simulo/_client/http.py +0 -0
- {simulo-0.20.1 → simulo-0.22.0}/src/simulo/_client/mode.py +0 -0
- {simulo-0.20.1 → simulo-0.22.0}/src/simulo/_client/packaging.py +0 -0
- {simulo-0.20.1 → simulo-0.22.0}/src/simulo/_client/preflight_api.py +0 -0
- {simulo-0.20.1 → simulo-0.22.0}/src/simulo/_client/preflight_render.py +0 -0
- {simulo-0.20.1 → simulo-0.22.0}/src/simulo/_client/registry.py +0 -0
- {simulo-0.20.1 → simulo-0.22.0}/src/simulo/_client/runtime.py +0 -0
- {simulo-0.20.1 → simulo-0.22.0}/src/simulo/_client/runtime_display.py +0 -0
- {simulo-0.20.1 → simulo-0.22.0}/src/simulo/_client/stub.py +0 -0
- {simulo-0.20.1 → simulo-0.22.0}/src/simulo/_client/templates/__init__.py +0 -0
- {simulo-0.20.1 → simulo-0.22.0}/src/simulo/_client/templates/inference/app.py.tmpl +0 -0
- {simulo-0.20.1 → simulo-0.22.0}/src/simulo/_client/templates/inference/simuloignore.tmpl +0 -0
- {simulo-0.20.1 → simulo-0.22.0}/src/simulo/_client/templates/scenario/app.py.tmpl +0 -0
- {simulo-0.20.1 → simulo-0.22.0}/src/simulo/_client/templates/scenario/simuloignore.tmpl +0 -0
- {simulo-0.20.1 → simulo-0.22.0}/src/simulo/_client/templates/training/app.py.tmpl +0 -0
- {simulo-0.20.1 → simulo-0.22.0}/src/simulo/_client/templates/training/simuloignore.tmpl +0 -0
- {simulo-0.20.1 → simulo-0.22.0}/src/simulo/_client/volume.py +0 -0
- {simulo-0.20.1 → simulo-0.22.0}/src/simulo/py.typed +0 -0
- {simulo-0.20.1 → simulo-0.22.0}/src/simulo.egg-info/dependency_links.txt +0 -0
- {simulo-0.20.1 → simulo-0.22.0}/src/simulo.egg-info/entry_points.txt +0 -0
- {simulo-0.20.1 → simulo-0.22.0}/src/simulo.egg-info/top_level.txt +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Public-metadata discretion boundary (see release.yml Guard 5): the sdist is
|
|
1
|
+
# Public-metadata discretion boundary (see release-pypi.yml Guard 5): the sdist is
|
|
2
2
|
# PUBLIC on PyPI's "Download files" page just like the wheel, but setuptools'
|
|
3
3
|
# sdist command auto-includes README.md and any top-level tests/ directory
|
|
4
4
|
# regardless of the `readme = "PYPI.md"` pyproject setting. Exclude the
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: simulo
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.22.0
|
|
4
4
|
Summary: Simulo SDK and CLI — define robotics simulation and training apps in Python and run them on the Simulo cloud.
|
|
5
5
|
Author-email: Simulo Team <team@simulo.ai>
|
|
6
6
|
License: BSD-3-Clause
|
|
@@ -15,7 +15,7 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
15
15
|
Classifier: Typing :: Typed
|
|
16
16
|
Requires-Python: >=3.11
|
|
17
17
|
Description-Content-Type: text/markdown
|
|
18
|
-
Requires-Dist: simulo-interfaces<0.
|
|
18
|
+
Requires-Dist: simulo-interfaces<0.17,>=0.16.0
|
|
19
19
|
Requires-Dist: mcap<2,>=1.3
|
|
20
20
|
Requires-Dist: defusedxml>=0.7.1
|
|
21
21
|
Requires-Dist: usd-core<27,>=25.5
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "simulo"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.22.0"
|
|
8
8
|
description = "Simulo SDK and CLI — define robotics simulation and training apps in Python and run them on the Simulo cloud."
|
|
9
9
|
readme = "PYPI.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -26,7 +26,7 @@ classifiers = [
|
|
|
26
26
|
# mcap: used by `simulo recordings` to read back a downloaded recording and
|
|
27
27
|
# verify it (message count) — MIT-licensed, deps only lz4+zstandard.
|
|
28
28
|
dependencies = [
|
|
29
|
-
"simulo-interfaces>=0.
|
|
29
|
+
"simulo-interfaces>=0.16.0,<0.17",
|
|
30
30
|
"mcap>=1.3,<2",
|
|
31
31
|
# Hardened XML parsing for URDF ingestion (USD Asset Catalogs, PR-12 fix
|
|
32
32
|
# loop 2, security NIT): stdlib xml.etree.ElementTree relies on
|
|
@@ -28,10 +28,10 @@ missed that way before the rule replaced it.
|
|
|
28
28
|
|
|
29
29
|
Three kinds of name live on ``simulo``:
|
|
30
30
|
|
|
31
|
-
* **Platform** names — ``App / Runtime / Volume / Asset /
|
|
32
|
-
|
|
33
|
-
They are always torch-free (``
|
|
34
|
-
contract dataclass, ``
|
|
31
|
+
* **Platform** names — ``App / Runtime / Volume / Asset / Output /
|
|
32
|
+
OutputKind / callbacks`` — are **real, eager** attributes bound below.
|
|
33
|
+
They are always torch-free (``Output`` is the ``simulo-interfaces``
|
|
34
|
+
contract dataclass, ``OutputKind`` its wire-stable ``StrEnum``, both
|
|
35
35
|
re-exported).
|
|
36
36
|
* **Contract vocabulary** — the shared error hierarchy (``SimuloError`` and its
|
|
37
37
|
subclasses), the ``NewType`` identifier aliases (``JobId``, ``ProjectId``, …),
|
|
@@ -43,7 +43,7 @@ Three kinds of name live on ``simulo``:
|
|
|
43
43
|
so this adds no import and no dependency. Errors and IDs are likewise
|
|
44
44
|
stdlib-only (``NewType`` is the underlying ``str`` at runtime).
|
|
45
45
|
* **Lazy, mode-aware** names — the learning names ``Task / Scene / Robot /
|
|
46
|
-
LearningEnv / RLTrainer / …`` plus the execution-time ``
|
|
46
|
+
LearningEnv / RLTrainer / …`` plus the execution-time ``save_output`` and
|
|
47
47
|
``run`` (the scenario execution loop) — are
|
|
48
48
|
resolved through the PEP 562 ``__getattr__`` below, which delegates to the
|
|
49
49
|
thin client's internal learning-name resolver. At submit (discovery mode) they resolve to
|
|
@@ -86,9 +86,13 @@ from simulo.interfaces import (
|
|
|
86
86
|
ImportResolutionError,
|
|
87
87
|
JobFailedError,
|
|
88
88
|
JobId,
|
|
89
|
+
JobPublicId,
|
|
90
|
+
ModelPublicId,
|
|
89
91
|
OrganizationId,
|
|
92
|
+
OutputPublicId,
|
|
90
93
|
PackageId,
|
|
91
94
|
ProjectId,
|
|
95
|
+
RecordingPublicId,
|
|
92
96
|
ResourceId,
|
|
93
97
|
ResourceNotFoundError,
|
|
94
98
|
ResourceUri,
|
|
@@ -96,6 +100,10 @@ from simulo.interfaces import (
|
|
|
96
100
|
SimuloError,
|
|
97
101
|
TagId,
|
|
98
102
|
TrainedModelId,
|
|
103
|
+
parse_job_public_id,
|
|
104
|
+
parse_model_public_id,
|
|
105
|
+
parse_output_public_id,
|
|
106
|
+
parse_recording_public_id,
|
|
99
107
|
)
|
|
100
108
|
|
|
101
109
|
# The authoring value types that the lazy resolver does NOT already carry.
|
|
@@ -144,7 +152,7 @@ from simulo.interfaces.authoring import (
|
|
|
144
152
|
USDWorld,
|
|
145
153
|
VisualMarkerBase,
|
|
146
154
|
)
|
|
147
|
-
from simulo.interfaces.platform import
|
|
155
|
+
from simulo.interfaces.platform import Output, OutputKind
|
|
148
156
|
|
|
149
157
|
# Public surface = eager platform exports (real attributes; ``__getattr__`` never
|
|
150
158
|
# fires for these) + the lazily-resolved learning names. Kept a static literal so
|
|
@@ -152,8 +160,8 @@ from simulo.interfaces.platform import Artifact, ArtifactKind
|
|
|
152
160
|
__all__ = [
|
|
153
161
|
# platform (eager, real)
|
|
154
162
|
"App",
|
|
155
|
-
"
|
|
156
|
-
"
|
|
163
|
+
"Output",
|
|
164
|
+
"OutputKind",
|
|
157
165
|
"Runtime",
|
|
158
166
|
"Volume",
|
|
159
167
|
"Asset",
|
|
@@ -170,6 +178,14 @@ __all__ = [
|
|
|
170
178
|
"ProjectId",
|
|
171
179
|
"PackageId",
|
|
172
180
|
"JobId",
|
|
181
|
+
"JobPublicId",
|
|
182
|
+
"ModelPublicId",
|
|
183
|
+
"RecordingPublicId",
|
|
184
|
+
"OutputPublicId",
|
|
185
|
+
"parse_job_public_id",
|
|
186
|
+
"parse_model_public_id",
|
|
187
|
+
"parse_recording_public_id",
|
|
188
|
+
"parse_output_public_id",
|
|
173
189
|
"ResourceId",
|
|
174
190
|
"CheckpointId",
|
|
175
191
|
"TrainedModelId",
|
|
@@ -206,7 +222,7 @@ __all__ = [
|
|
|
206
222
|
"SphereMarker",
|
|
207
223
|
"ArrowMarker",
|
|
208
224
|
"PointMarker",
|
|
209
|
-
# lazy, mode-aware (the learning names +
|
|
225
|
+
# lazy, mode-aware (the learning names + save_output)
|
|
210
226
|
"ActuatorGainsConfig",
|
|
211
227
|
"Camera",
|
|
212
228
|
"CameraSpawnConfig",
|
|
@@ -252,7 +268,7 @@ __all__ = [
|
|
|
252
268
|
"Visual",
|
|
253
269
|
"World",
|
|
254
270
|
"run",
|
|
255
|
-
"
|
|
271
|
+
"save_output",
|
|
256
272
|
]
|
|
257
273
|
|
|
258
274
|
|
|
@@ -381,11 +397,9 @@ if TYPE_CHECKING:
|
|
|
381
397
|
TerminationCondition = TerminationConditionProtocol
|
|
382
398
|
# Annotation-position mirror name (see the rule above).
|
|
383
399
|
Scene = SceneProtocol
|
|
384
|
-
# ``
|
|
385
|
-
# torch-free thin-client function), so ``simulo.
|
|
400
|
+
# ``save_output`` aliases its real execution-mode implementation (a
|
|
401
|
+
# torch-free thin-client function), so ``simulo.save_output("summary",
|
|
386
402
|
# path)`` type-checks with the precise signature instead of ``Any``.
|
|
387
|
-
from simulo._client.artifacts import save_artifact as save_artifact
|
|
388
|
-
|
|
389
403
|
# ``run`` (the scenario execution loop) aliases its typing FACADE — a
|
|
390
404
|
# plain FUNCTION facade, not a class one (there is no ``RunProtocol`` to
|
|
391
405
|
# alias against; ``run`` is not a contract method). Without this, ``run``
|
|
@@ -400,6 +414,7 @@ if TYPE_CHECKING:
|
|
|
400
414
|
# where drift against the real function is caught (a live, unmocked
|
|
401
415
|
# signature comparison in ``simulo-backend``'s test suite, not mypy).
|
|
402
416
|
from simulo._client.facades import run as run
|
|
417
|
+
from simulo._client.outputs import save_output as save_output
|
|
403
418
|
|
|
404
419
|
# RELOCATED authoring value types (Plan C — Waves C1, C2, then C4's
|
|
405
420
|
# ``RecordConfig``): the runtime object IS the real, dependency-free
|
|
@@ -25,9 +25,9 @@ import time
|
|
|
25
25
|
from pathlib import Path
|
|
26
26
|
from typing import Any, NoReturn, Optional
|
|
27
27
|
|
|
28
|
-
from simulo.interfaces.ids import JobId
|
|
28
|
+
from simulo.interfaces.ids import JobId, JobPublicId
|
|
29
29
|
from simulo.interfaces.platform.enums import JobStatus
|
|
30
|
-
from simulo.interfaces.platform.runs import TERMINAL_JOB_STATUSES
|
|
30
|
+
from simulo.interfaces.platform.runs import JOB_SCOPE_MINE, TERMINAL_JOB_STATUSES
|
|
31
31
|
|
|
32
32
|
#: Poll cadence for cloud ``.get()``. Module-level so tests can shrink it.
|
|
33
33
|
_POLL_INTERVAL_S = 1.0
|
|
@@ -62,8 +62,8 @@ class JobHandle:
|
|
|
62
62
|
"""Handle to a submitted job — local-disk package, or a cloud job id.
|
|
63
63
|
|
|
64
64
|
Returned by :meth:`JobFunction.spawn`. In cloud mode it carries the
|
|
65
|
-
server-assigned ``job_id
|
|
66
|
-
to poll it; ``execute_hint()`` still names the local package path
|
|
65
|
+
server-assigned machine ``job_id``, human-facing ``public_id``, and enough
|
|
66
|
+
context (``base_url`` / ``token``) to poll it; ``execute_hint()`` still names the local package path
|
|
67
67
|
(preserved for debugging) but ``get()`` talks to the platform instead of
|
|
68
68
|
raising.
|
|
69
69
|
"""
|
|
@@ -74,11 +74,13 @@ class JobHandle:
|
|
|
74
74
|
package_path: Path,
|
|
75
75
|
job_name: str,
|
|
76
76
|
*,
|
|
77
|
+
public_id: Optional[JobPublicId] = None,
|
|
77
78
|
cloud: bool = False,
|
|
78
79
|
base_url: Optional[str] = None,
|
|
79
80
|
token: Optional[str] = None,
|
|
80
81
|
) -> None:
|
|
81
82
|
self._job_id = job_id
|
|
83
|
+
self._public_id = public_id
|
|
82
84
|
self._package_path = package_path
|
|
83
85
|
self._job_name = job_name
|
|
84
86
|
self._cloud = cloud
|
|
@@ -90,6 +92,11 @@ class JobHandle:
|
|
|
90
92
|
"""Opaque id for this submission — server-assigned in cloud mode."""
|
|
91
93
|
return self._job_id
|
|
92
94
|
|
|
95
|
+
@property
|
|
96
|
+
def public_id(self) -> Optional[JobPublicId]:
|
|
97
|
+
"""Human-facing Job ID in cloud mode; ``None`` for local package-only submits."""
|
|
98
|
+
return self._public_id
|
|
99
|
+
|
|
93
100
|
@property
|
|
94
101
|
def package_path(self) -> Path:
|
|
95
102
|
"""Directory the package was written to (``<out>/<bundle_dir_name(package_id)>/``)."""
|
|
@@ -121,7 +128,9 @@ class JobHandle:
|
|
|
121
128
|
|
|
122
129
|
def logs_hint(self) -> str:
|
|
123
130
|
"""The command that streams this job's logs (cloud mode)."""
|
|
124
|
-
|
|
131
|
+
if self._public_id is None:
|
|
132
|
+
return "simulo jobs"
|
|
133
|
+
return f"simulo logs {self._public_id} --follow"
|
|
125
134
|
|
|
126
135
|
def get(self, timeout: Optional[float] = None) -> Any:
|
|
127
136
|
"""Return the job's result.
|
|
@@ -143,23 +152,29 @@ class JobHandle:
|
|
|
143
152
|
client = JobsApiClient(self._base_url, token=self._token)
|
|
144
153
|
deadline = None if timeout is None else time.monotonic() + timeout
|
|
145
154
|
while True:
|
|
146
|
-
record = client.get_job(str(self._job_id))
|
|
155
|
+
record = client.get_job(str(self._job_id), scope=JOB_SCOPE_MINE)
|
|
147
156
|
status = str(record.get("status"))
|
|
148
157
|
if status in _TERMINAL_STATUS_STRINGS:
|
|
149
158
|
if status == _COMPLETED_STATUS:
|
|
150
|
-
return client.get_result(str(self._job_id))
|
|
159
|
+
return client.get_result(str(self._job_id), scope=JOB_SCOPE_MINE)
|
|
151
160
|
exit_code = record.get("exit_code")
|
|
152
161
|
raise JobFailedError(
|
|
153
|
-
f"
|
|
162
|
+
f"{self._display_name()} {status} (exit_code={exit_code}).\n"
|
|
154
163
|
f" View its logs with:\n {self.logs_hint()}"
|
|
155
164
|
)
|
|
156
165
|
if deadline is not None and time.monotonic() >= deadline:
|
|
157
166
|
raise JobTimeoutError(
|
|
158
|
-
f"
|
|
167
|
+
f"{self._display_name()} did not reach a terminal status within "
|
|
159
168
|
f"{timeout}s (still {status!r}). Check it with:\n {self.logs_hint()}"
|
|
160
169
|
)
|
|
161
170
|
time.sleep(_POLL_INTERVAL_S)
|
|
162
171
|
|
|
172
|
+
def _display_name(self) -> str:
|
|
173
|
+
"""A user-facing label that never falls back to the machine UUID."""
|
|
174
|
+
if self._public_id is None:
|
|
175
|
+
return f"Job {self._job_name!r}"
|
|
176
|
+
return f"Job {self._job_name!r} ({self._public_id})"
|
|
177
|
+
|
|
163
178
|
def _raise_local_disk_unavailable(self) -> NoReturn:
|
|
164
179
|
raise JobResultUnavailable(
|
|
165
180
|
f"No result is available locally for job {self._job_name!r}: submitting writes the "
|
|
@@ -38,7 +38,7 @@ from typing import TYPE_CHECKING, Any, Callable, Iterator, Mapping, Optional, Se
|
|
|
38
38
|
from simulo._client import config, credentials, registry
|
|
39
39
|
from simulo._client._runner import JobFailedError, JobHandle, JobResultUnavailable
|
|
40
40
|
from simulo._client.runtime import DEFAULT_RUNTIME
|
|
41
|
-
from simulo.interfaces.ids import JobId
|
|
41
|
+
from simulo.interfaces.ids import JobId, parse_job_public_id
|
|
42
42
|
from simulo.interfaces.platform import JobCallbackProtocol
|
|
43
43
|
from simulo.interfaces.platform.app import Mount
|
|
44
44
|
from simulo.interfaces.platform.enums import ResumePolicy
|
|
@@ -379,9 +379,8 @@ def _spawn_cloud(
|
|
|
379
379
|
the identical rule: the seed reference is a SUBMIT-time job property,
|
|
380
380
|
never a package property — it rides the job-create call only, verbatim
|
|
381
381
|
(the control plane resolves prefixes and ``:best``/``:latest``). When the
|
|
382
|
-
server honors it, the
|
|
383
|
-
|
|
384
|
-
the job the user meant); when an older server ignores it, ``create_job``
|
|
382
|
+
server honors it, the resolved provenance is announced on stderr without
|
|
383
|
+
exposing its machine UUID; when an older server ignores it, ``create_job``
|
|
385
384
|
raises rather than let a fresh run masquerade as a seeded one.
|
|
386
385
|
|
|
387
386
|
``skip_preflight`` (``simulo run --skip-preflight``) bypasses the
|
|
@@ -420,7 +419,7 @@ def _spawn_cloud(
|
|
|
420
419
|
from simulo._client.asset_pins import DEFAULT_ASSET_RUNTIME, collect_asset_refs, resolve_and_pin_assets
|
|
421
420
|
from simulo._client.packaging import MANIFEST_FILENAME, ensure_tar
|
|
422
421
|
from simulo._client.preflight_api import PreflightApiClient
|
|
423
|
-
from simulo._client.submit_api import SubmitApiClient
|
|
422
|
+
from simulo._client.submit_api import SubmitApiClient, SubmitApiError
|
|
424
423
|
from simulo.interfaces.platform.manifest import Manifest
|
|
425
424
|
|
|
426
425
|
manifest_text = (package_path / MANIFEST_FILENAME).read_text(encoding="utf-8")
|
|
@@ -500,17 +499,41 @@ def _spawn_cloud(
|
|
|
500
499
|
app_name=app_name,
|
|
501
500
|
)
|
|
502
501
|
|
|
502
|
+
raw_public_id = record.get("public_id")
|
|
503
|
+
try:
|
|
504
|
+
if not isinstance(raw_public_id, str):
|
|
505
|
+
raise TypeError("public_id is not a string")
|
|
506
|
+
public_id = parse_job_public_id(raw_public_id)
|
|
507
|
+
except (TypeError, ValueError):
|
|
508
|
+
# A current public cloud response must carry the human-facing Job ID.
|
|
509
|
+
# The machine UUID is intentionally not used as display fallback: the
|
|
510
|
+
# job may already have been created, so give a neutral recovery path.
|
|
511
|
+
raise SubmitApiError(
|
|
512
|
+
"The platform created the job but did not return a usable Job ID. "
|
|
513
|
+
"Run `simulo jobs` to find the submission before retrying."
|
|
514
|
+
) from None
|
|
515
|
+
|
|
503
516
|
if seed_from is not None:
|
|
504
517
|
# create_job has already guaranteed the seed dict is present (the
|
|
505
518
|
# loud-degradation guard) — this line is the human confirmation that
|
|
506
|
-
# the ref
|
|
519
|
+
# the ref resolved to the intended source job. Seed provenance still
|
|
520
|
+
# carries machine UUIDs only, so keep this confirmation neutral rather
|
|
521
|
+
# than leaking a UUID while that response contract catches up.
|
|
507
522
|
seed = record.get("seed") or {}
|
|
508
523
|
print(
|
|
509
|
-
f"Continuing from job
|
|
524
|
+
f"Continuing from selected job ({seed.get('name')}, {seed.get('kind')} checkpoint)",
|
|
510
525
|
file=sys.stderr,
|
|
511
526
|
)
|
|
512
527
|
|
|
513
|
-
return JobHandle(
|
|
528
|
+
return JobHandle(
|
|
529
|
+
JobId(str(record["job_id"])),
|
|
530
|
+
package_path,
|
|
531
|
+
job_name,
|
|
532
|
+
public_id=public_id,
|
|
533
|
+
cloud=True,
|
|
534
|
+
base_url=base_url,
|
|
535
|
+
token=token,
|
|
536
|
+
)
|
|
514
537
|
|
|
515
538
|
|
|
516
539
|
class JobFunction:
|
|
@@ -97,8 +97,8 @@ class CancelApiClient:
|
|
|
97
97
|
unavailable_hint=_UNAVAILABLE_HINT,
|
|
98
98
|
)
|
|
99
99
|
if not isinstance(payload, dict):
|
|
100
|
-
raise CancelApiError(
|
|
100
|
+
raise CancelApiError("Malformed cancel response: expected a JSON object.")
|
|
101
101
|
missing = [field for field in _REQUIRED_FIELDS if field not in payload]
|
|
102
102
|
if missing:
|
|
103
|
-
raise CancelApiError(f"Malformed cancel response
|
|
103
|
+
raise CancelApiError(f"Malformed cancel response: missing field(s) {missing}.")
|
|
104
104
|
return payload
|