simulo 0.33.1__tar.gz → 0.35.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.33.1/src/simulo.egg-info → simulo-0.35.0}/PKG-INFO +7 -5
- {simulo-0.33.1 → simulo-0.35.0}/PYPI.md +1 -1
- {simulo-0.33.1 → simulo-0.35.0}/pyproject.toml +7 -5
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/__init__.py +12 -1
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/_runner.py +15 -1
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/app.py +105 -66
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/asset.py +2 -2
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/asset_pins.py +21 -2
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/bundle.py +9 -7
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/cli.py +10 -7
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/cli_context.py +25 -4
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/cli_job_reporting.py +62 -22
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/cli_rendering.py +10 -0
- simulo-0.35.0/src/simulo/_client/cli_runtime_limit.py +130 -0
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/cli_waiting.py +37 -25
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/commands/assets.py +6 -3
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/commands/jobs.py +79 -24
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/commands/recordings.py +81 -42
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/commands/run.py +203 -66
- simulo-0.35.0/src/simulo/_client/commands/systems.py +93 -0
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/credentials.py +17 -2
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/evaluation_report.py +45 -6
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/facades.py +2 -0
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/http.py +66 -31
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/jobs_api.py +6 -1
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/learning.py +12 -1
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/login.py +20 -0
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/packaging.py +27 -17
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/preflight_api.py +9 -0
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/preview_report.py +133 -101
- simulo-0.35.0/src/simulo/_client/recording_export.py +54 -0
- simulo-0.35.0/src/simulo/_client/repeatable_episodes.py +22 -0
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/submit_api.py +52 -22
- simulo-0.35.0/src/simulo/_client/system_requests.py +82 -0
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/templates/training/eval.py.tmpl +1 -1
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/templates/training/play.py.tmpl +1 -1
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/templates/training/preview.py.tmpl +1 -1
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/templates/training/train.py.tmpl +1 -1
- simulo-0.35.0/src/simulo/_client/training_algorithm.py +11 -0
- simulo-0.35.0/src/simulo/_client/viewstream_policy.py +92 -0
- {simulo-0.33.1 → simulo-0.35.0/src/simulo.egg-info}/PKG-INFO +7 -5
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo.egg-info/SOURCES.txt +6 -0
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo.egg-info/requires.txt +7 -2
- simulo-0.33.1/src/simulo/_client/commands/systems.py +0 -132
- {simulo-0.33.1 → simulo-0.35.0}/MANIFEST.in +0 -0
- {simulo-0.33.1 → simulo-0.35.0}/setup.cfg +0 -0
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/__init__.py +0 -0
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/_arguments.py +0 -0
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/_mounts.py +0 -0
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/_secure_downloads.py +0 -0
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/asset_api.py +0 -0
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/asset_package.py +0 -0
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/builtin_aliases.py +0 -0
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/cancel_api.py +0 -0
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/cli_arguments.py +0 -0
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/cli_downloads.py +0 -0
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/cli_errors.py +0 -0
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/cli_listing.py +0 -0
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/cli_text.py +0 -0
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/commands/__init__.py +0 -0
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/commands/auth.py +0 -0
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/commands/create.py +0 -0
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/commands/policies.py +0 -0
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/config.py +0 -0
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/discovery.py +0 -0
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/export_api.py +0 -0
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/identity_api.py +0 -0
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/install_samples.py +0 -0
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/mode.py +0 -0
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/onnx_bundle.py +0 -0
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/outputs.py +0 -0
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/policies_api.py +0 -0
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/preflight_render.py +0 -0
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/registry.py +0 -0
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/runtime.py +0 -0
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/runtime_display.py +0 -0
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/stub.py +0 -0
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/templates/__init__.py +0 -0
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/templates/training/simuloignore.tmpl +0 -0
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/templates/training/task.py.tmpl +0 -0
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/view_fragment.py +0 -0
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/view_session_api.py +0 -0
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/_client/volume.py +0 -0
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/callbacks.py +0 -0
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo/py.typed +0 -0
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo.egg-info/dependency_links.txt +0 -0
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo.egg-info/entry_points.txt +0 -0
- {simulo-0.33.1 → simulo-0.35.0}/src/simulo.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: simulo
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.35.0
|
|
4
4
|
Summary: Python SDK and CLI for cloud-native robotics simulation, reinforcement learning, and robot policy training on managed GPUs.
|
|
5
5
|
Author-email: Simulo Team <team@simulo.ai>
|
|
6
6
|
License: BSD-3-Clause
|
|
@@ -9,7 +9,7 @@ Project-URL: Documentation, https://docs.simulo.ai
|
|
|
9
9
|
Project-URL: Quickstart, https://docs.simulo.ai/getting-started/quickstart/
|
|
10
10
|
Project-URL: Examples, https://github.com/simulo-org/simulo-samples
|
|
11
11
|
Project-URL: Console, https://console.simulo.ai
|
|
12
|
-
Keywords: simulo,robotics,robotics simulation,robot training,robot learning,reinforcement learning,cloud robotics,cloud simulation,GPU simulation,policy training,physical AI,embodied AI,
|
|
12
|
+
Keywords: simulo,robotics,robotics simulation,robot training,robot learning,reinforcement learning,cloud robotics,cloud simulation,GPU simulation,policy training,physical AI,embodied AI,OpenUSD,USD,URDF,ONNX,MCAP,Python SDK
|
|
13
13
|
Classifier: Development Status :: 3 - Alpha
|
|
14
14
|
Classifier: Environment :: Console
|
|
15
15
|
Classifier: Intended Audience :: Developers
|
|
@@ -31,14 +31,16 @@ Classifier: Topic :: System :: Distributed Computing
|
|
|
31
31
|
Classifier: Typing :: Typed
|
|
32
32
|
Requires-Python: >=3.11
|
|
33
33
|
Description-Content-Type: text/markdown
|
|
34
|
-
Requires-Dist: simulo-interfaces<0.
|
|
34
|
+
Requires-Dist: simulo-interfaces<0.27,>=0.26.0
|
|
35
35
|
Requires-Dist: mcap<2,>=1.3
|
|
36
36
|
Requires-Dist: defusedxml>=0.7.1
|
|
37
37
|
Requires-Dist: usd-core<27,>=26.5
|
|
38
|
+
Provides-Extra: viz
|
|
39
|
+
Requires-Dist: simulo-viz<0.2,>=0.1.0; python_version >= "3.12" and extra == "viz"
|
|
38
40
|
Provides-Extra: assets
|
|
39
41
|
Requires-Dist: usd-core<27,>=26.5; extra == "assets"
|
|
40
42
|
Provides-Extra: dev
|
|
41
|
-
Requires-Dist: black==26.
|
|
43
|
+
Requires-Dist: black==26.10.1; extra == "dev"
|
|
42
44
|
Requires-Dist: isort==8.0.1; extra == "dev"
|
|
43
45
|
Requires-Dist: ruff==0.15.21; extra == "dev"
|
|
44
46
|
Requires-Dist: mypy==2.3.0; extra == "dev"
|
|
@@ -122,7 +124,7 @@ simulo policy get policy_x:best # download a verified checkpoint
|
|
|
122
124
|
simulo export policy_x:best # export a verified ONNX bundle
|
|
123
125
|
simulo recordings # download MCAP rollout recordings
|
|
124
126
|
simulo outputs # retrieve other saved outputs
|
|
125
|
-
simulo view # view a running
|
|
127
|
+
simulo view # view a running job in 3D (live view is on by default)
|
|
126
128
|
simulo cancel # stop a queued or running job
|
|
127
129
|
```
|
|
128
130
|
|
|
@@ -66,7 +66,7 @@ simulo policy get policy_x:best # download a verified checkpoint
|
|
|
66
66
|
simulo export policy_x:best # export a verified ONNX bundle
|
|
67
67
|
simulo recordings # download MCAP rollout recordings
|
|
68
68
|
simulo outputs # retrieve other saved outputs
|
|
69
|
-
simulo view # view a running
|
|
69
|
+
simulo view # view a running job in 3D (live view is on by default)
|
|
70
70
|
simulo cancel # stop a queued or running job
|
|
71
71
|
```
|
|
72
72
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "simulo"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.35.0"
|
|
8
8
|
description = "Python SDK and CLI for cloud-native robotics simulation, reinforcement learning, and robot policy training on managed GPUs."
|
|
9
9
|
readme = "PYPI.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -23,8 +23,6 @@ keywords = [
|
|
|
23
23
|
"policy training",
|
|
24
24
|
"physical AI",
|
|
25
25
|
"embodied AI",
|
|
26
|
-
"NVIDIA Isaac Sim",
|
|
27
|
-
"Isaac Lab",
|
|
28
26
|
"OpenUSD",
|
|
29
27
|
"USD",
|
|
30
28
|
"URDF",
|
|
@@ -59,7 +57,7 @@ classifiers = [
|
|
|
59
57
|
# mcap: used by `simulo recordings` to read back a downloaded recording and
|
|
60
58
|
# verify it (message count) — MIT-licensed, deps only lz4+zstandard.
|
|
61
59
|
dependencies = [
|
|
62
|
-
"simulo-interfaces>=0.
|
|
60
|
+
"simulo-interfaces>=0.26.0,<0.27",
|
|
63
61
|
"mcap>=1.3,<2",
|
|
64
62
|
# Hardened XML parsing for URDF ingestion (USD Asset Catalogs, PR-12 fix
|
|
65
63
|
# loop 2, security NIT): stdlib xml.etree.ElementTree relies on
|
|
@@ -70,6 +68,8 @@ dependencies = [
|
|
|
70
68
|
# featherweight promise below (that promise was about avoiding
|
|
71
69
|
# usd-core's tens of MB via an opt-in extra; usd-core itself joined the
|
|
72
70
|
# base footprint in 0.13.1 — see its own comment just below).
|
|
71
|
+
# Uncapped on purpose: the floor keeps the hardened XML parser; an upper
|
|
72
|
+
# bound is added only when a release breaks it.
|
|
73
73
|
"defusedxml>=0.7.1",
|
|
74
74
|
# usd-core: simulator-free but tens of MB — moved from the `assets`
|
|
75
75
|
# extra into the base install in 0.13.1 so `pip install simulo` covers
|
|
@@ -81,6 +81,7 @@ dependencies = [
|
|
|
81
81
|
]
|
|
82
82
|
|
|
83
83
|
[project.optional-dependencies]
|
|
84
|
+
viz = ["simulo-viz>=0.1.0,<0.2; python_version >= '3.12'"]
|
|
84
85
|
# `assets` — kept for backward compatibility with `pip install
|
|
85
86
|
# "simulo[assets]"` (the 0.13.0-era spelling): usd-core moved into the base
|
|
86
87
|
# install above in 0.13.1, so this extra is now redundant (its dep is
|
|
@@ -104,7 +105,7 @@ assets = ["usd-core>=26.5,<27"]
|
|
|
104
105
|
# `pytest-timeout` is pinned EXACT because it enforces the suite's hang bound;
|
|
105
106
|
# changing the plugin under an unchanged timeout would change that guard's behavior.
|
|
106
107
|
dev = [
|
|
107
|
-
"black==26.
|
|
108
|
+
"black==26.10.1",
|
|
108
109
|
"isort==8.0.1",
|
|
109
110
|
"ruff==0.15.21",
|
|
110
111
|
"mypy==2.3.0",
|
|
@@ -193,6 +194,7 @@ markers = [
|
|
|
193
194
|
"isaac: requires NVIDIA Isaac Sim / IsaacLab (skipped on dev machines and in PR CI)",
|
|
194
195
|
"gpu: requires a CUDA GPU (skipped on dev machines and in PR CI)",
|
|
195
196
|
"requires_execution_backend: end-to-end test that needs a live execution backend in the loop; skipped in CPU/unit runs",
|
|
197
|
+
"smoke: a short fundamental-path test; the hosted smoke job in simulo-ci.yml runs only these, and local CI runs every test",
|
|
196
198
|
]
|
|
197
199
|
|
|
198
200
|
# --- Formatters / linters --------------------------------------------------
|
|
@@ -129,6 +129,7 @@ from simulo.interfaces import (
|
|
|
129
129
|
# re-exported here like every other concrete authoring type — no exclusion
|
|
130
130
|
# needed any more.
|
|
131
131
|
from simulo.interfaces.authoring import (
|
|
132
|
+
ActionChannel,
|
|
132
133
|
ArrowMarker,
|
|
133
134
|
AssetEntity,
|
|
134
135
|
AssetSource,
|
|
@@ -143,6 +144,8 @@ from simulo.interfaces.authoring import (
|
|
|
143
144
|
DomeLight,
|
|
144
145
|
GroundPlane,
|
|
145
146
|
LightConfig,
|
|
147
|
+
LineMarker,
|
|
148
|
+
ObjectVariants,
|
|
146
149
|
PointMarker,
|
|
147
150
|
PolicyCheckpoint,
|
|
148
151
|
PolicyInput,
|
|
@@ -210,6 +213,7 @@ __all__ = [
|
|
|
210
213
|
"Digest",
|
|
211
214
|
# authoring value types (eager, real) — the rest of the vocabulary the
|
|
212
215
|
# lazy resolver below does not carry
|
|
216
|
+
"ActionChannel",
|
|
213
217
|
"Collision",
|
|
214
218
|
"RigidPhysics",
|
|
215
219
|
"DeformableMaterial",
|
|
@@ -219,6 +223,7 @@ __all__ = [
|
|
|
219
223
|
"USDAsset",
|
|
220
224
|
"URDFAsset",
|
|
221
225
|
"LightConfig",
|
|
226
|
+
"ObjectVariants",
|
|
222
227
|
"DistantLight",
|
|
223
228
|
"DomeLight",
|
|
224
229
|
"TerrainConfig",
|
|
@@ -238,6 +243,7 @@ __all__ = [
|
|
|
238
243
|
"SphereMarker",
|
|
239
244
|
"ArrowMarker",
|
|
240
245
|
"PointMarker",
|
|
246
|
+
"LineMarker",
|
|
241
247
|
# lazy, mode-aware (the learning names + save_output)
|
|
242
248
|
"ActuatorGainsConfig",
|
|
243
249
|
"ArticulationConfig",
|
|
@@ -310,8 +316,13 @@ def __getattr__(name: str) -> object:
|
|
|
310
316
|
Fires only for attributes missing from the module dict — i.e. never for the
|
|
311
317
|
real platform exports above. Delegates the learning names to the thin
|
|
312
318
|
client's internal resolver (discovery stand-in vs. real
|
|
313
|
-
``simulo.core`` class, by mode)
|
|
319
|
+
``simulo.core`` class, by mode), and read ``__version__`` from installed
|
|
320
|
+
package metadata on demand. Unknown names raise ``AttributeError``.
|
|
314
321
|
"""
|
|
322
|
+
if name == "__version__":
|
|
323
|
+
from importlib.metadata import version
|
|
324
|
+
|
|
325
|
+
return version("simulo")
|
|
315
326
|
if name in _LEARNING_NAMES:
|
|
316
327
|
return _resolve_learning(name)
|
|
317
328
|
removed_hints = {
|
|
@@ -23,7 +23,7 @@ from __future__ import annotations
|
|
|
23
23
|
|
|
24
24
|
import time
|
|
25
25
|
from pathlib import Path
|
|
26
|
-
from typing import Any, NoReturn, Optional
|
|
26
|
+
from typing import Any, Mapping, NoReturn, Optional
|
|
27
27
|
|
|
28
28
|
from simulo.interfaces.ids import JobId, JobPublicId
|
|
29
29
|
from simulo.interfaces.platform.enums import JobStatus, JobType
|
|
@@ -91,6 +91,8 @@ class JobHandle:
|
|
|
91
91
|
base_url: Optional[str] = None,
|
|
92
92
|
token: Optional[str] = None,
|
|
93
93
|
job_type: Optional[JobType] = None,
|
|
94
|
+
runtime_budget_s: Optional[int] = None,
|
|
95
|
+
job_args: Optional[Mapping[str, Any]] = None,
|
|
94
96
|
input_checkpoint: Optional[str] = None,
|
|
95
97
|
compare_checkpoint: Optional[str] = None,
|
|
96
98
|
deferred_stdout: str = "",
|
|
@@ -104,6 +106,8 @@ class JobHandle:
|
|
|
104
106
|
self._base_url = base_url
|
|
105
107
|
self._token = token
|
|
106
108
|
self._job_type = job_type
|
|
109
|
+
self._runtime_budget_s = runtime_budget_s
|
|
110
|
+
self._job_args = dict(job_args or {})
|
|
107
111
|
self._input_checkpoint = input_checkpoint
|
|
108
112
|
self._compare_checkpoint = compare_checkpoint
|
|
109
113
|
self._deferred_stdout = deferred_stdout
|
|
@@ -149,6 +153,16 @@ class JobHandle:
|
|
|
149
153
|
"""Declared type for a cloud submission; absent on legacy local handles."""
|
|
150
154
|
return self._job_type
|
|
151
155
|
|
|
156
|
+
@property
|
|
157
|
+
def runtime_budget_s(self) -> Optional[int]:
|
|
158
|
+
"""The effective server-resolved time limit, in seconds, when served."""
|
|
159
|
+
return self._runtime_budget_s
|
|
160
|
+
|
|
161
|
+
@property
|
|
162
|
+
def job_args(self) -> Mapping[str, Any]:
|
|
163
|
+
"""Training arguments for submit guidance, including declared defaults when known."""
|
|
164
|
+
return self._job_args
|
|
165
|
+
|
|
152
166
|
@property
|
|
153
167
|
def input_checkpoint(self) -> Optional[str]:
|
|
154
168
|
"""Policy checkpoint requested with ``simulo run --from``."""
|
|
@@ -48,12 +48,14 @@ from typing import (
|
|
|
48
48
|
|
|
49
49
|
from simulo._client import config, credentials, registry
|
|
50
50
|
from simulo._client._runner import JobFailedError, JobHandle, JobResultUnavailable
|
|
51
|
+
from simulo._client.repeatable_episodes import requested_repeatable_episodes
|
|
51
52
|
from simulo._client.runtime import DEFAULT_RUNTIME
|
|
53
|
+
from simulo._client.system_requests import effective_resources, validate_resources
|
|
54
|
+
from simulo._client.training_algorithm import validate_training_algorithm
|
|
52
55
|
from simulo.interfaces.ids import JobId, parse_job_public_id
|
|
53
56
|
from simulo.interfaces.platform import JobCallbackProtocol, JobType, normalize_job_tags
|
|
54
57
|
from simulo.interfaces.platform.app import Mount
|
|
55
58
|
from simulo.interfaces.platform.enums import SystemType
|
|
56
|
-
from simulo.interfaces.platform.systems import SYSTEM_SPECS
|
|
57
59
|
|
|
58
60
|
if TYPE_CHECKING: # typing-only; avoids a module-scope dependency on the HTTP client
|
|
59
61
|
from simulo._client.preflight_api import PreflightOutcome
|
|
@@ -131,19 +133,43 @@ def spawn_recorder() -> Iterator[list["JobHandle"]]:
|
|
|
131
133
|
# through arbitrary user code" reason.
|
|
132
134
|
# ---------------------------------------------------------------------------
|
|
133
135
|
|
|
134
|
-
_VIEWSTREAM_REQUESTED =
|
|
136
|
+
_VIEWSTREAM_REQUESTED: Optional[bool] = None
|
|
137
|
+
_VIEWSTREAM_NOTICE: Optional[str] = None
|
|
135
138
|
|
|
136
139
|
|
|
137
140
|
@contextlib.contextmanager
|
|
138
|
-
def viewstream_requested(enabled: bool) -> Iterator[None]:
|
|
139
|
-
"""Scope
|
|
140
|
-
global _VIEWSTREAM_REQUESTED
|
|
141
|
+
def viewstream_requested(enabled: Optional[bool], *, notice: Optional[str] = None) -> Iterator[None]:
|
|
142
|
+
"""Scope one resolved live-view request for one ``simulo run`` submit call."""
|
|
143
|
+
global _VIEWSTREAM_REQUESTED, _VIEWSTREAM_NOTICE
|
|
141
144
|
previous = _VIEWSTREAM_REQUESTED
|
|
145
|
+
previous_notice = _VIEWSTREAM_NOTICE
|
|
142
146
|
_VIEWSTREAM_REQUESTED = enabled
|
|
147
|
+
_VIEWSTREAM_NOTICE = notice
|
|
143
148
|
try:
|
|
144
149
|
yield
|
|
145
150
|
finally:
|
|
146
151
|
_VIEWSTREAM_REQUESTED = previous
|
|
152
|
+
_VIEWSTREAM_NOTICE = previous_notice
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
# ---------------------------------------------------------------------------
|
|
156
|
+
# 3D-recording ambient signal — `simulo run --record-3d COPIES` scopes an
|
|
157
|
+
# execution option without placing it in package arguments or the manifest.
|
|
158
|
+
# ---------------------------------------------------------------------------
|
|
159
|
+
|
|
160
|
+
_RECORDING_COPIES_REQUESTED: Optional[int] = None
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@contextlib.contextmanager
|
|
164
|
+
def recording_copies_requested(copies: Optional[int]) -> Iterator[None]:
|
|
165
|
+
"""Scope an optional 3D recording copy count for one submit."""
|
|
166
|
+
global _RECORDING_COPIES_REQUESTED
|
|
167
|
+
previous = _RECORDING_COPIES_REQUESTED
|
|
168
|
+
_RECORDING_COPIES_REQUESTED = copies
|
|
169
|
+
try:
|
|
170
|
+
yield
|
|
171
|
+
finally:
|
|
172
|
+
_RECORDING_COPIES_REQUESTED = previous
|
|
147
173
|
|
|
148
174
|
|
|
149
175
|
# ---------------------------------------------------------------------------
|
|
@@ -526,7 +552,9 @@ def _spawn_cloud(
|
|
|
526
552
|
package_path: Path,
|
|
527
553
|
creds: Optional[dict[str, Any]],
|
|
528
554
|
*,
|
|
529
|
-
viewstream: bool =
|
|
555
|
+
viewstream: Optional[bool] = None,
|
|
556
|
+
recording_copies: Optional[int] = None,
|
|
557
|
+
repeatable_episodes: Optional[bool] = None,
|
|
530
558
|
input_checkpoint: Optional[str] = None,
|
|
531
559
|
compare_checkpoint: Optional[str] = None,
|
|
532
560
|
require_success: Optional[float] = None,
|
|
@@ -536,6 +564,7 @@ def _spawn_cloud(
|
|
|
536
564
|
app_name: Optional[str] = None,
|
|
537
565
|
job_type: JobType,
|
|
538
566
|
tags: Sequence[str] = (),
|
|
567
|
+
job_args: Optional[Mapping[str, Any]] = None,
|
|
539
568
|
) -> JobHandle:
|
|
540
569
|
"""Tar the bundle, preflight it, upload it, create the job, and return a
|
|
541
570
|
cloud JobHandle.
|
|
@@ -599,7 +628,7 @@ def _spawn_cloud(
|
|
|
599
628
|
job-create call's top-level ``assets`` field, never ``args``.
|
|
600
629
|
"""
|
|
601
630
|
from simulo._client.asset_pins import (
|
|
602
|
-
|
|
631
|
+
asset_runtime_for_runtime_id,
|
|
603
632
|
collect_asset_refs,
|
|
604
633
|
resolve_and_pin_assets,
|
|
605
634
|
)
|
|
@@ -630,15 +659,12 @@ def _spawn_cloud(
|
|
|
630
659
|
package_id = str(package_id)
|
|
631
660
|
source_digest = str(manifest.get("source_digest", ""))
|
|
632
661
|
args = dict(manifest.get("args") or {})
|
|
633
|
-
#
|
|
634
|
-
#
|
|
635
|
-
#
|
|
636
|
-
# (the tier's wire value, e.g. "tier1"; see `decorate()`'s
|
|
637
|
-
# `resources["system"] = system.value`). Read from the already-parsed
|
|
638
|
-
# manifest rather than threaded in as a new parameter, since the value
|
|
639
|
-
# already lives there, keyed by this same `job_name`.
|
|
662
|
+
# CLI selections override this job's declared resources for this submit.
|
|
663
|
+
# They never enter args or rewrite the packaged manifest. Omitted options
|
|
664
|
+
# retain their existing wire representation for servers awaiting adoption.
|
|
640
665
|
job_entry = manifest.get("jobs", {}).get(job_name) or {}
|
|
641
|
-
|
|
666
|
+
allocation = effective_resources(job_entry.get("resources") or {})
|
|
667
|
+
system = allocation.pop("system", None)
|
|
642
668
|
manifest_version = manifest.get("manifest_version")
|
|
643
669
|
if not isinstance(manifest_version, int) or isinstance(manifest_version, bool):
|
|
644
670
|
raise RuntimeError(
|
|
@@ -689,8 +715,10 @@ def _spawn_cloud(
|
|
|
689
715
|
archive_size=len(archive_bytes),
|
|
690
716
|
job_type=job_type,
|
|
691
717
|
input_checkpoint=input_checkpoint,
|
|
692
|
-
viewstream=viewstream,
|
|
718
|
+
viewstream=viewstream is True,
|
|
693
719
|
assets=[] if not asset_refs else None,
|
|
720
|
+
**({"system": system} if system is not None else {}),
|
|
721
|
+
**allocation,
|
|
694
722
|
)
|
|
695
723
|
deferred_stdout, deferred_stderr = _handle_preflight_outcome(outcome, job_name=job_name)
|
|
696
724
|
|
|
@@ -700,13 +728,15 @@ def _spawn_cloud(
|
|
|
700
728
|
# package is uploaded — so a missing/unpinned/unvalidated ref fails in seconds.
|
|
701
729
|
# Returns the top-level `assets: [{ref, digest}]` for create_job; `[]`
|
|
702
730
|
# when the app mounts no registry assets (no resolve call is made then).
|
|
703
|
-
asset_pins =
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
731
|
+
asset_pins: list[dict[str, str]] = []
|
|
732
|
+
if asset_refs:
|
|
733
|
+
asset_pins = resolve_and_pin_assets(
|
|
734
|
+
client,
|
|
735
|
+
asset_refs,
|
|
736
|
+
runtime=asset_runtime_for_runtime_id(str(manifest["runtime_id"])),
|
|
737
|
+
frozen=frozen_assets,
|
|
738
|
+
strict=strict_assets,
|
|
739
|
+
)
|
|
710
740
|
|
|
711
741
|
client.create_package(
|
|
712
742
|
package_id=package_id,
|
|
@@ -720,7 +750,10 @@ def _spawn_cloud(
|
|
|
720
750
|
package_id=package_id,
|
|
721
751
|
job_name=job_name,
|
|
722
752
|
args=args,
|
|
753
|
+
runtime_id=str(manifest["runtime_id"]),
|
|
723
754
|
viewstream=viewstream,
|
|
755
|
+
recording_copies=recording_copies,
|
|
756
|
+
repeatable_episodes=repeatable_episodes,
|
|
724
757
|
input_checkpoint=input_checkpoint,
|
|
725
758
|
compare_checkpoint=compare_checkpoint,
|
|
726
759
|
require_success=require_success,
|
|
@@ -731,7 +764,16 @@ def _spawn_cloud(
|
|
|
731
764
|
job_type=job_type,
|
|
732
765
|
tags=tags,
|
|
733
766
|
manifest_version=manifest_version,
|
|
767
|
+
**allocation,
|
|
734
768
|
)
|
|
769
|
+
# A submitted entrypoint can call ``spawn()`` more than once. The
|
|
770
|
+
# resolved notice describes the run-level opt-in, so print it once only
|
|
771
|
+
# after the first successful job-create response. Do not consume it when
|
|
772
|
+
# create_job raises: then the run did not successfully submit anything.
|
|
773
|
+
global _VIEWSTREAM_NOTICE
|
|
774
|
+
if _VIEWSTREAM_NOTICE is not None:
|
|
775
|
+
print(_VIEWSTREAM_NOTICE, file=sys.stderr)
|
|
776
|
+
_VIEWSTREAM_NOTICE = None
|
|
735
777
|
|
|
736
778
|
raw_public_id = record.get("public_id")
|
|
737
779
|
try:
|
|
@@ -756,6 +798,8 @@ def _spawn_cloud(
|
|
|
756
798
|
base_url=base_url,
|
|
757
799
|
token=token,
|
|
758
800
|
job_type=job_type,
|
|
801
|
+
runtime_budget_s=record.get("runtime_budget_s"),
|
|
802
|
+
job_args=job_args if job_args is not None else args,
|
|
759
803
|
input_checkpoint=input_checkpoint,
|
|
760
804
|
compare_checkpoint=compare_checkpoint,
|
|
761
805
|
deferred_stdout=deferred_stdout,
|
|
@@ -813,6 +857,12 @@ class JobFunction:
|
|
|
813
857
|
# job/success contract as ``simulo run``. This happens before the
|
|
814
858
|
# intent marker, bundle construction, asset resolution, or network I/O.
|
|
815
859
|
self._app.validate_job_submission(self._spec.name)
|
|
860
|
+
if self._spec.job_type is JobType.TRAIN:
|
|
861
|
+
parameter = inspect.signature(self._spec.fn).parameters.get("algorithm")
|
|
862
|
+
if "algorithm" in kwargs:
|
|
863
|
+
validate_training_algorithm(kwargs["algorithm"])
|
|
864
|
+
elif parameter is not None and parameter.default is not inspect.Parameter.empty:
|
|
865
|
+
validate_training_algorithm(parameter.default)
|
|
816
866
|
|
|
817
867
|
# Intent-to-submit is recorded FIRST — before any packaging or network
|
|
818
868
|
# I/O — so a submit that fails partway (or times out AFTER the server
|
|
@@ -861,6 +911,8 @@ class JobFunction:
|
|
|
861
911
|
package_path,
|
|
862
912
|
creds,
|
|
863
913
|
viewstream=_VIEWSTREAM_REQUESTED,
|
|
914
|
+
recording_copies=_RECORDING_COPIES_REQUESTED,
|
|
915
|
+
repeatable_episodes=requested_repeatable_episodes(),
|
|
864
916
|
input_checkpoint=_INPUT_CHECKPOINT_REF,
|
|
865
917
|
compare_checkpoint=_COMPARE_CHECKPOINT_REF,
|
|
866
918
|
require_success=_REQUIRE_SUCCESS,
|
|
@@ -870,6 +922,12 @@ class JobFunction:
|
|
|
870
922
|
app_name=self._app.name,
|
|
871
923
|
job_type=self._spec.job_type,
|
|
872
924
|
tags=_JOB_TAGS_REQUESTED,
|
|
925
|
+
job_args={
|
|
926
|
+
name: parameter.default
|
|
927
|
+
for name, parameter in inspect.signature(self._spec.fn).parameters.items()
|
|
928
|
+
if parameter.default is not inspect.Parameter.empty
|
|
929
|
+
}
|
|
930
|
+
| kwargs,
|
|
873
931
|
)
|
|
874
932
|
|
|
875
933
|
if _SPAWN_RECORDER is None:
|
|
@@ -1256,6 +1314,8 @@ class App:
|
|
|
1256
1314
|
type: Union[JobType, str],
|
|
1257
1315
|
callbacks: Sequence[JobCallbackProtocol] = ...,
|
|
1258
1316
|
system: Optional[SystemType] = ...,
|
|
1317
|
+
gpus: Optional[int] = ...,
|
|
1318
|
+
gpus_per_node: Optional[int] = ...,
|
|
1259
1319
|
timeout: Optional[int] = ...,
|
|
1260
1320
|
retries: int = ...,
|
|
1261
1321
|
**extra: Any,
|
|
@@ -1269,6 +1329,8 @@ class App:
|
|
|
1269
1329
|
type: Union[JobType, str],
|
|
1270
1330
|
callbacks: Sequence[JobCallbackProtocol] = ...,
|
|
1271
1331
|
system: Optional[SystemType] = ...,
|
|
1332
|
+
gpus: Optional[int] = ...,
|
|
1333
|
+
gpus_per_node: Optional[int] = ...,
|
|
1272
1334
|
timeout: Optional[int] = ...,
|
|
1273
1335
|
retries: int = ...,
|
|
1274
1336
|
**extra: Any,
|
|
@@ -1281,6 +1343,8 @@ class App:
|
|
|
1281
1343
|
type: Optional[Union[JobType, str]] = None,
|
|
1282
1344
|
callbacks: Sequence[JobCallbackProtocol] = (),
|
|
1283
1345
|
system: Optional[SystemType] = None,
|
|
1346
|
+
gpus: Optional[int] = None,
|
|
1347
|
+
gpus_per_node: Optional[int] = None,
|
|
1284
1348
|
timeout: Optional[int] = None,
|
|
1285
1349
|
retries: int = 0,
|
|
1286
1350
|
**extra: Any,
|
|
@@ -1298,23 +1362,16 @@ class App:
|
|
|
1298
1362
|
pass it alongside keyword options).
|
|
1299
1363
|
callbacks: Lifecycle callbacks from :mod:`simulo.callbacks`, recorded
|
|
1300
1364
|
into the package manifest and honored at execution.
|
|
1301
|
-
system:
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
job AND scheduled against: the control plane filters a tiered
|
|
1312
|
-
worker's claim to its own tier plus untiered work, so the tier
|
|
1313
|
-
decides which GPU model runs the job. It is not a priority
|
|
1314
|
-
setting. ``simulo systems`` reports this package's catalog,
|
|
1315
|
-
not which tiers your platform has deployed. Naming a tier it
|
|
1316
|
-
has no fleet for is refused when you submit, and the error
|
|
1317
|
-
names the tiers it does have.
|
|
1365
|
+
system: Accelerator memory class as a :class:`simulo.SystemType` member.
|
|
1366
|
+
Omitted means Tier 1 (16 GB). Tier 2 is 24 GB, Tier 3 is 48 GB,
|
|
1367
|
+
and Tier 4 is 96 GB per accelerator. Hardware within a class can
|
|
1368
|
+
change. The built-in catalog enables Tier 1 and Tier 2 only;
|
|
1369
|
+
the server must also check its fleet readiness at submission.
|
|
1370
|
+
gpus: Total GPU count, a positive integer. Omitted means one GPU.
|
|
1371
|
+
Today's built-in policy permits one GPU per job.
|
|
1372
|
+
gpus_per_node: Optional positive integer layout constraint. All GPUs
|
|
1373
|
+
must run on one machine, so this must equal the total GPU count.
|
|
1374
|
+
It never multiplies ``gpus``. Counts reject booleans.
|
|
1318
1375
|
timeout: Requested runtime limit for one managed execution, in
|
|
1319
1376
|
seconds (a positive integer; validated HERE, at decoration
|
|
1320
1377
|
time — a zero, negative, or non-integer value raises
|
|
@@ -1339,7 +1396,7 @@ class App:
|
|
|
1339
1396
|
ValueError: ``timeout`` is not ``None`` and is not a positive
|
|
1340
1397
|
integer. ``True`` and ``False`` are not accepted as timeouts.
|
|
1341
1398
|
Also raised when ``system`` names a tier the platform cannot
|
|
1342
|
-
provision
|
|
1399
|
+
provision, or a GPU count/layout is unsupported by the built-in policy.
|
|
1343
1400
|
JobDeclarationError: ``produces=`` or ``consumes=`` was passed. Outputs are
|
|
1344
1401
|
not declared on the decorator. Callbacks declare their own
|
|
1345
1402
|
supported outputs. Return small JSON values from the job, or use a named
|
|
@@ -1381,28 +1438,12 @@ class App:
|
|
|
1381
1438
|
"@app.job(system=simulo.SystemType.TIER_1). Run `simulo systems` to list the "
|
|
1382
1439
|
"tiers and which are available today. Remove gpu=."
|
|
1383
1440
|
)
|
|
1384
|
-
if system is not None:
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
"@app.job(system=...) must be a simulo.SystemType member, e.g. "
|
|
1391
|
-
f"system=simulo.SystemType.TIER_1; got {system!r}. Run `simulo systems` to "
|
|
1392
|
-
"list the tiers."
|
|
1393
|
-
)
|
|
1394
|
-
spec = SYSTEM_SPECS[system]
|
|
1395
|
-
if not spec.available:
|
|
1396
|
-
available = ", ".join(
|
|
1397
|
-
f"SystemType.{tier.name} ({entry.gpu_model})"
|
|
1398
|
-
for tier, entry in SYSTEM_SPECS.items()
|
|
1399
|
-
if entry.available
|
|
1400
|
-
)
|
|
1401
|
-
raise ValueError(
|
|
1402
|
-
f"@app.job(system=SystemType.{system.name}) ({spec.gpu_model}) is not available on "
|
|
1403
|
-
f"the platform yet. Available today: {available}. Run `simulo systems` for the "
|
|
1404
|
-
"full catalog."
|
|
1405
|
-
)
|
|
1441
|
+
if system is not None and not isinstance(system, SystemType):
|
|
1442
|
+
raise JobDeclarationError(
|
|
1443
|
+
"@app.job(system=...) must be a simulo.SystemType member, e.g. "
|
|
1444
|
+
f"system=simulo.SystemType.TIER_1; got {system!r}. Run `simulo systems` to list the tiers."
|
|
1445
|
+
)
|
|
1446
|
+
requested_resources = validate_resources(system, gpus, gpus_per_node)
|
|
1406
1447
|
|
|
1407
1448
|
def decorate(func: F) -> JobFunction:
|
|
1408
1449
|
location = Path(func.__code__.co_filename).name
|
|
@@ -1420,9 +1461,7 @@ class App:
|
|
|
1420
1461
|
f"{location}: Job {func.__name__!r} has invalid type={type!r}. Valid job types: {valid}. "
|
|
1421
1462
|
'Example: @app.job(type="train").'
|
|
1422
1463
|
) from None
|
|
1423
|
-
resources
|
|
1424
|
-
if system is not None:
|
|
1425
|
-
resources["system"] = system.value
|
|
1464
|
+
resources = dict(requested_resources)
|
|
1426
1465
|
resources.update(extra)
|
|
1427
1466
|
policy_params = [
|
|
1428
1467
|
name
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""``Asset`` — a read-only, registry-resolved mount (implements ``AssetProtocol``).
|
|
2
2
|
|
|
3
3
|
A registry asset ref is ``[<publisher>/]<kind>/<name>[:v<N>]`` (e.g.
|
|
4
|
-
``robot/so-arm-100:v3``, ``simulo/world/warehouse:
|
|
4
|
+
``robot/so-arm-100:v3``, ``simulo/world/warehouse:v3``, or the unpinned
|
|
5
5
|
``robot/forklift`` — resolved to an exact version **at submit**). The grammar
|
|
6
6
|
is the ONE shared parser from ``simulo.interfaces.platform.asset_catalog`` — the
|
|
7
7
|
CLI, the control plane, the worker, and the console all parse through it, so the
|
|
@@ -148,7 +148,7 @@ class Asset:
|
|
|
148
148
|
"""A read-only, registry-resolved resource mounted into running jobs.
|
|
149
149
|
|
|
150
150
|
A registry ref is ``[<publisher>/]<kind>/<name>[:v<N>]`` (e.g.
|
|
151
|
-
``simulo/robot/cartpole:
|
|
151
|
+
``simulo/robot/cartpole:v5``); :meth:`from_registry` names one, and :attr:`path`
|
|
152
152
|
resolves to its read-only mount inside a running job. ``Asset`` is also how a
|
|
153
153
|
task names USD/URDF model sources to load into a scene — :meth:`usd` /
|
|
154
154
|
:meth:`urdf`.
|
|
@@ -24,14 +24,33 @@ from typing import Any, Dict, List, Mapping, Optional, Sequence, TextIO
|
|
|
24
24
|
|
|
25
25
|
from simulo._client.runtime_display import display_runtime
|
|
26
26
|
from simulo._client.submit_api import AssetPinError, SubmitApiClient
|
|
27
|
-
from simulo.interfaces.platform.asset_catalog import
|
|
27
|
+
from simulo.interfaces.platform.asset_catalog import (
|
|
28
|
+
DEFAULT_ASSET_RUNTIME,
|
|
29
|
+
ResolvedAssetPin,
|
|
30
|
+
parse_asset_ref,
|
|
31
|
+
)
|
|
32
|
+
from simulo.interfaces.platform.runtimes import RUNTIME_ID_BY_ASSET_RUNTIME
|
|
28
33
|
|
|
29
34
|
#: Re-exported from ``simulo.interfaces.platform.asset_catalog`` (current code and tests
|
|
30
35
|
#: "Runtime-string seam", 2026-07-17 — hoisted there so the CLIENT's resolve
|
|
31
36
|
#: request and the VALIDATOR's finalize call can never send different runtime
|
|
32
37
|
#: strings). Kept as a module attribute here too — existing call sites
|
|
33
38
|
#: (``app.py``, tests) import it from this module.
|
|
34
|
-
__all__ = ["DEFAULT_ASSET_RUNTIME", "collect_asset_refs", "resolve_and_pin_assets"]
|
|
39
|
+
__all__ = ["DEFAULT_ASSET_RUNTIME", "asset_runtime_for_runtime_id", "collect_asset_refs", "resolve_and_pin_assets"]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def asset_runtime_for_runtime_id(runtime_id: str) -> str:
|
|
43
|
+
"""Return the catalog label published for a requested job runtime.
|
|
44
|
+
|
|
45
|
+
Asset validation records catalog labels while manifests request job runtime
|
|
46
|
+
identifiers. There is no safe label for an unlisted runtime: resolving an
|
|
47
|
+
asset under Isaac would make the validation result describe a different
|
|
48
|
+
runtime from the job that will mount it.
|
|
49
|
+
"""
|
|
50
|
+
for asset_runtime, mapped_runtime_id in RUNTIME_ID_BY_ASSET_RUNTIME.items():
|
|
51
|
+
if runtime_id == mapped_runtime_id:
|
|
52
|
+
return asset_runtime
|
|
53
|
+
raise AssetPinError(f"runtime {runtime_id!r} has no published asset catalog mapping")
|
|
35
54
|
|
|
36
55
|
|
|
37
56
|
def collect_asset_refs(manifest: Mapping[str, Any]) -> List[str]:
|
|
@@ -4,9 +4,10 @@ Shared by ``simulo run`` (the CLI) and ``JobFunction.spawn`` — the *submit* pa
|
|
|
4
4
|
discover the app's metadata and write the package (manifest + source bundle) to
|
|
5
5
|
disk. The manifest builder reads only the public ``App`` surface, so it works in
|
|
6
6
|
discovery mode (where submit runs). ``ensure_bundle`` memoises the written package
|
|
7
|
-
per *submission* — ``(app, entry_file, job_name, args)`` — so
|
|
8
|
-
same file and job with the same args
|
|
9
|
-
|
|
7
|
+
per *submission* — ``(app, entry_file, job_name, args, captured assets)`` — so
|
|
8
|
+
re-submitting the same file and job with the same args and captured assets reuses
|
|
9
|
+
the package. Package identity hashes source, entry file, job, canonical args,
|
|
10
|
+
and the canonical asset list; changing any of those writes a distinct package.
|
|
10
11
|
|
|
11
12
|
Submit NEVER executes a job and NEVER invokes the backend — it only writes the
|
|
12
13
|
package. This module imports no heavy library; it stays on the torch-free side of
|
|
@@ -59,9 +60,9 @@ DEFAULT_OUT_SUBPATH = (".simulo", "packages")
|
|
|
59
60
|
#: capture — see ``asset.py``) — included so a LATER spawn() in the same process,
|
|
60
61
|
#: after user code has constructed MORE assets since an earlier spawn() with
|
|
61
62
|
#: the identical ``(app, entry_file, job_name, args)``, is never served a stale cached bundle
|
|
62
|
-
#: whose ``resources`` predates those constructions
|
|
63
|
-
#:
|
|
64
|
-
#:
|
|
63
|
+
#: whose ``resources`` predates those constructions. The package id also hashes
|
|
64
|
+
#: resources, but a cache hit does not rebuild the manifest or recompute the id,
|
|
65
|
+
#: so this key must still distinguish the captured asset set.
|
|
65
66
|
_BUNDLE_CACHE: dict[tuple[int, str, str, str, tuple[str, ...]], tuple[Path, str]] = {}
|
|
66
67
|
|
|
67
68
|
|
|
@@ -197,7 +198,8 @@ def ensure_bundle(
|
|
|
197
198
|
This is the *submit* primitive: the manifest is built from the live app object
|
|
198
199
|
(no second discovery import) with the submitted job's ``args`` recorded, and
|
|
199
200
|
the source tree is written to ``<out>/<bundle_dir_name(package_id)>/`` — the
|
|
200
|
-
per-submission directory keyed on source + entry file + ``job_name`` +
|
|
201
|
+
per-submission directory keyed on source + entry file + ``job_name`` + canonical
|
|
202
|
+
``args`` + canonical asset list (named by
|
|
201
203
|
the id's short, filesystem-safe form; see ``packaging.bundle_dir_name``).
|
|
202
204
|
Memoised per submission (``(id(app), entry_file, job_name, canonical_json(args), sorted
|
|
203
205
|
extra_assets uris)``) so re-submitting the same job with the same args AND the
|