simulo 0.28.2__tar.gz → 0.29.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.
Files changed (66) hide show
  1. {simulo-0.28.2/src/simulo.egg-info → simulo-0.29.0}/PKG-INFO +14 -13
  2. {simulo-0.28.2 → simulo-0.29.0}/PYPI.md +12 -11
  3. {simulo-0.28.2 → simulo-0.29.0}/pyproject.toml +3 -3
  4. {simulo-0.28.2 → simulo-0.29.0}/src/simulo/__init__.py +20 -35
  5. simulo-0.28.2/src/simulo/_client/_entrypoint.py → simulo-0.29.0/src/simulo/_client/_arguments.py +17 -51
  6. {simulo-0.28.2 → simulo-0.29.0}/src/simulo/_client/_runner.py +34 -1
  7. {simulo-0.28.2 → simulo-0.29.0}/src/simulo/_client/_secure_downloads.py +1 -1
  8. {simulo-0.28.2 → simulo-0.29.0}/src/simulo/_client/app.py +360 -281
  9. {simulo-0.28.2 → simulo-0.29.0}/src/simulo/_client/asset.py +121 -6
  10. {simulo-0.28.2 → simulo-0.29.0}/src/simulo/_client/asset_api.py +2 -2
  11. {simulo-0.28.2 → simulo-0.29.0}/src/simulo/_client/asset_package.py +6 -0
  12. {simulo-0.28.2 → simulo-0.29.0}/src/simulo/_client/asset_pins.py +1 -1
  13. {simulo-0.28.2 → simulo-0.29.0}/src/simulo/_client/bundle.py +12 -23
  14. {simulo-0.28.2 → simulo-0.29.0}/src/simulo/_client/cli.py +1011 -1657
  15. {simulo-0.28.2 → simulo-0.29.0}/src/simulo/_client/credentials.py +25 -4
  16. {simulo-0.28.2 → simulo-0.29.0}/src/simulo/_client/discovery.py +114 -12
  17. simulo-0.29.0/src/simulo/_client/export_api.py +108 -0
  18. {simulo-0.28.2 → simulo-0.29.0}/src/simulo/_client/facades.py +4 -56
  19. {simulo-0.28.2 → simulo-0.29.0}/src/simulo/_client/jobs_api.py +38 -8
  20. {simulo-0.28.2 → simulo-0.29.0}/src/simulo/_client/learning.py +11 -41
  21. {simulo-0.28.2 → simulo-0.29.0}/src/simulo/_client/outputs.py +0 -2
  22. simulo-0.29.0/src/simulo/_client/policies_api.py +173 -0
  23. {simulo-0.28.2 → simulo-0.29.0}/src/simulo/_client/preflight_api.py +8 -4
  24. {simulo-0.28.2 → simulo-0.29.0}/src/simulo/_client/preflight_render.py +9 -20
  25. {simulo-0.28.2 → simulo-0.29.0}/src/simulo/_client/submit_api.py +73 -318
  26. {simulo-0.28.2 → simulo-0.29.0}/src/simulo/_client/templates/__init__.py +3 -3
  27. simulo-0.29.0/src/simulo/_client/templates/training/simuloignore.tmpl +14 -0
  28. simulo-0.29.0/src/simulo/_client/templates/training/task.py.tmpl +108 -0
  29. simulo-0.29.0/src/simulo/_client/templates/training/train.py.tmpl +23 -0
  30. {simulo-0.28.2 → simulo-0.29.0}/src/simulo/_client/volume.py +10 -5
  31. {simulo-0.28.2 → simulo-0.29.0}/src/simulo/callbacks.py +2 -29
  32. {simulo-0.28.2 → simulo-0.29.0/src/simulo.egg-info}/PKG-INFO +14 -13
  33. {simulo-0.28.2 → simulo-0.29.0}/src/simulo.egg-info/SOURCES.txt +5 -8
  34. {simulo-0.28.2 → simulo-0.29.0}/src/simulo.egg-info/requires.txt +1 -1
  35. simulo-0.28.2/src/simulo/_client/export_api.py +0 -212
  36. simulo-0.28.2/src/simulo/_client/seed_ref.py +0 -76
  37. simulo-0.28.2/src/simulo/_client/templates/inference/app.py.tmpl +0 -316
  38. simulo-0.28.2/src/simulo/_client/templates/inference/simuloignore.tmpl +0 -30
  39. simulo-0.28.2/src/simulo/_client/templates/scenario/app.py.tmpl +0 -93
  40. simulo-0.28.2/src/simulo/_client/templates/scenario/simuloignore.tmpl +0 -27
  41. simulo-0.28.2/src/simulo/_client/templates/training/app.py.tmpl +0 -235
  42. simulo-0.28.2/src/simulo/_client/templates/training/simuloignore.tmpl +0 -29
  43. {simulo-0.28.2 → simulo-0.29.0}/MANIFEST.in +0 -0
  44. {simulo-0.28.2 → simulo-0.29.0}/setup.cfg +0 -0
  45. {simulo-0.28.2 → simulo-0.29.0}/src/simulo/_client/__init__.py +0 -0
  46. {simulo-0.28.2 → simulo-0.29.0}/src/simulo/_client/_mounts.py +0 -0
  47. {simulo-0.28.2 → simulo-0.29.0}/src/simulo/_client/builtin_aliases.py +0 -0
  48. {simulo-0.28.2 → simulo-0.29.0}/src/simulo/_client/cancel_api.py +0 -0
  49. {simulo-0.28.2 → simulo-0.29.0}/src/simulo/_client/config.py +0 -0
  50. {simulo-0.28.2 → simulo-0.29.0}/src/simulo/_client/export_bundle.py +0 -0
  51. {simulo-0.28.2 → simulo-0.29.0}/src/simulo/_client/http.py +0 -0
  52. {simulo-0.28.2 → simulo-0.29.0}/src/simulo/_client/identity_api.py +0 -0
  53. {simulo-0.28.2 → simulo-0.29.0}/src/simulo/_client/install_samples.py +0 -0
  54. {simulo-0.28.2 → simulo-0.29.0}/src/simulo/_client/login.py +0 -0
  55. {simulo-0.28.2 → simulo-0.29.0}/src/simulo/_client/mode.py +0 -0
  56. {simulo-0.28.2 → simulo-0.29.0}/src/simulo/_client/packaging.py +0 -0
  57. {simulo-0.28.2 → simulo-0.29.0}/src/simulo/_client/registry.py +0 -0
  58. {simulo-0.28.2 → simulo-0.29.0}/src/simulo/_client/runtime.py +0 -0
  59. {simulo-0.28.2 → simulo-0.29.0}/src/simulo/_client/runtime_display.py +0 -0
  60. {simulo-0.28.2 → simulo-0.29.0}/src/simulo/_client/stub.py +0 -0
  61. {simulo-0.28.2 → simulo-0.29.0}/src/simulo/_client/view_fragment.py +0 -0
  62. {simulo-0.28.2 → simulo-0.29.0}/src/simulo/_client/view_session_api.py +0 -0
  63. {simulo-0.28.2 → simulo-0.29.0}/src/simulo/py.typed +0 -0
  64. {simulo-0.28.2 → simulo-0.29.0}/src/simulo.egg-info/dependency_links.txt +0 -0
  65. {simulo-0.28.2 → simulo-0.29.0}/src/simulo.egg-info/entry_points.txt +0 -0
  66. {simulo-0.28.2 → simulo-0.29.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.28.2
3
+ Version: 0.29.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
@@ -31,7 +31,7 @@ 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.21,>=0.20.2
34
+ Requires-Dist: simulo-interfaces<0.22,>=0.21.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
@@ -62,12 +62,11 @@ Simulo is a cloud-native AI platform for robotics simulation and training. Its
62
62
  lightweight `simulo` Python SDK and CLI let you define robot-learning applications
63
63
  locally, submit them to the Simulo cloud, and inspect the results. The platform
64
64
  handles managed GPU execution, reproducible packaging, versioned assets,
65
- checkpoints, trained models, recordings, and live 3D viewing.
65
+ policies, checkpoints, recordings, ONNX bundles, and live 3D viewing.
66
66
 
67
67
  The package installed on your computer is torch-free and requires no local GPU or
68
- simulator. The managed runtime executes reinforcement-learning training, policy
69
- evaluation and inference, and scripted robotics simulations on managed GPUs in
70
- the cloud.
68
+ simulator. The managed runtime executes reinforcement-learning training on
69
+ managed GPUs in the cloud.
71
70
 
72
71
  ## What you can do
73
72
 
@@ -75,7 +74,7 @@ the cloud.
75
74
  - Build vectorized reinforcement-learning environments and train robot policies with PPO.
76
75
  - Start with validated catalog robots or publish your own OpenUSD (USD) and URDF assets.
77
76
  - Continue training from the best or latest checkpoint produced by an earlier job.
78
- - Evaluate policies, run inference, and export trained policies as verified ONNX bundles.
77
+ - Export an explicit policy checkpoint as a verified ONNX bundle.
79
78
  - Stream logs, retrieve results and outputs, capture MCAP rollout recordings, and watch a live 3D view.
80
79
 
81
80
  ## Install
@@ -93,12 +92,13 @@ Simulo requires Python 3.11 or newer. One installation provides both the
93
92
  simulo create mybot
94
93
  simulo login
95
94
  cd mybot
96
- simulo run app.py --frozen --strict-assets
95
+ simulo run train.py --frozen --strict-assets
97
96
  ```
98
97
 
99
98
  `simulo create` writes a complete starter application to your machine without
100
- requiring a login or network connection. The default application trains a PPO
101
- policy on a validated cartpole asset. `simulo run` packages the source, resolves
99
+ requiring a login or network connection. It creates `task.py`, `train.py`, and
100
+ `.simuloignore`; the default application trains a PPO policy on a validated cartpole
101
+ asset. `simulo run` packages the source, resolves
102
102
  and pins its assets, submits the job, and follows its cloud logs.
103
103
 
104
104
  ## Explore robotics samples
@@ -114,8 +114,9 @@ See the repository README for setup and usage.
114
114
  simulo jobs # list submitted jobs and status
115
115
  simulo logs --follow # stream the latest job's logs
116
116
  simulo result # read the job's result
117
- simulo models # list or download trained models
118
- simulo export # export the best policy as verified ONNX
117
+ simulo policy list # list training policies and checkpoints
118
+ simulo policy get policy_x:best # download a verified checkpoint
119
+ simulo export policy_x:best # export a verified ONNX bundle
119
120
  simulo recordings # download MCAP rollout recordings
120
121
  simulo outputs # retrieve other saved outputs
121
122
  simulo view # view a running --viewstream job in 3D
@@ -135,7 +136,7 @@ simulo asset publish ./my-robot --kind robot
135
136
 
136
137
  Simulo catalogs keep robot, world, and prop assets versioned and traceable. You
137
138
  can use validated global assets immediately or publish your own USD or URDF
138
- package for reuse across training, evaluation, and inference jobs.
139
+ package for reuse across training jobs.
139
140
 
140
141
  ## Learn more
141
142
 
@@ -6,12 +6,11 @@ Simulo is a cloud-native AI platform for robotics simulation and training. Its
6
6
  lightweight `simulo` Python SDK and CLI let you define robot-learning applications
7
7
  locally, submit them to the Simulo cloud, and inspect the results. The platform
8
8
  handles managed GPU execution, reproducible packaging, versioned assets,
9
- checkpoints, trained models, recordings, and live 3D viewing.
9
+ policies, checkpoints, recordings, ONNX bundles, and live 3D viewing.
10
10
 
11
11
  The package installed on your computer is torch-free and requires no local GPU or
12
- simulator. The managed runtime executes reinforcement-learning training, policy
13
- evaluation and inference, and scripted robotics simulations on managed GPUs in
14
- the cloud.
12
+ simulator. The managed runtime executes reinforcement-learning training on
13
+ managed GPUs in the cloud.
15
14
 
16
15
  ## What you can do
17
16
 
@@ -19,7 +18,7 @@ the cloud.
19
18
  - Build vectorized reinforcement-learning environments and train robot policies with PPO.
20
19
  - Start with validated catalog robots or publish your own OpenUSD (USD) and URDF assets.
21
20
  - Continue training from the best or latest checkpoint produced by an earlier job.
22
- - Evaluate policies, run inference, and export trained policies as verified ONNX bundles.
21
+ - Export an explicit policy checkpoint as a verified ONNX bundle.
23
22
  - Stream logs, retrieve results and outputs, capture MCAP rollout recordings, and watch a live 3D view.
24
23
 
25
24
  ## Install
@@ -37,12 +36,13 @@ Simulo requires Python 3.11 or newer. One installation provides both the
37
36
  simulo create mybot
38
37
  simulo login
39
38
  cd mybot
40
- simulo run app.py --frozen --strict-assets
39
+ simulo run train.py --frozen --strict-assets
41
40
  ```
42
41
 
43
42
  `simulo create` writes a complete starter application to your machine without
44
- requiring a login or network connection. The default application trains a PPO
45
- policy on a validated cartpole asset. `simulo run` packages the source, resolves
43
+ requiring a login or network connection. It creates `task.py`, `train.py`, and
44
+ `.simuloignore`; the default application trains a PPO policy on a validated cartpole
45
+ asset. `simulo run` packages the source, resolves
46
46
  and pins its assets, submits the job, and follows its cloud logs.
47
47
 
48
48
  ## Explore robotics samples
@@ -58,8 +58,9 @@ See the repository README for setup and usage.
58
58
  simulo jobs # list submitted jobs and status
59
59
  simulo logs --follow # stream the latest job's logs
60
60
  simulo result # read the job's result
61
- simulo models # list or download trained models
62
- simulo export # export the best policy as verified ONNX
61
+ simulo policy list # list training policies and checkpoints
62
+ simulo policy get policy_x:best # download a verified checkpoint
63
+ simulo export policy_x:best # export a verified ONNX bundle
63
64
  simulo recordings # download MCAP rollout recordings
64
65
  simulo outputs # retrieve other saved outputs
65
66
  simulo view # view a running --viewstream job in 3D
@@ -79,7 +80,7 @@ simulo asset publish ./my-robot --kind robot
79
80
 
80
81
  Simulo catalogs keep robot, world, and prop assets versioned and traceable. You
81
82
  can use validated global assets immediately or publish your own USD or URDF
82
- package for reuse across training, evaluation, and inference jobs.
83
+ package for reuse across training jobs.
83
84
 
84
85
  ## Learn more
85
86
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "simulo"
7
- version = "0.28.2"
7
+ version = "0.29.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"
@@ -59,7 +59,7 @@ classifiers = [
59
59
  # mcap: used by `simulo recordings` to read back a downloaded recording and
60
60
  # verify it (message count) — MIT-licensed, deps only lz4+zstandard.
61
61
  dependencies = [
62
- "simulo-interfaces>=0.20.2,<0.21",
62
+ "simulo-interfaces>=0.21.0,<0.22",
63
63
  "mcap>=1.3,<2",
64
64
  # Hardened XML parsing for URDF ingestion (USD Asset Catalogs, PR-12 fix
65
65
  # loop 2, security NIT): stdlib xml.etree.ElementTree relies on
@@ -157,7 +157,7 @@ namespaces = true
157
157
  # each is read at runtime via `importlib.resources.files(...)`, never `__file__`
158
158
  # path math, so this glob actually landing in the built wheel is load-bearing —
159
159
  # see tests/test_create_wheel_packaging.py.
160
- "simulo._client.templates" = ["training/*.tmpl", "inference/*.tmpl", "scenario/*.tmpl"]
160
+ "simulo._client.templates" = ["training/*.tmpl"]
161
161
 
162
162
  [tool.mypy]
163
163
  python_version = "3.11"
@@ -44,8 +44,8 @@ Three kinds of name live on ``simulo``:
44
44
  stdlib-only (``NewType`` is the underlying ``str`` at runtime).
45
45
  * **Lazy, mode-aware** names — the learning names ``Task / Scene / Robot /
46
46
  LearningEnv / RLTrainer / …`` plus the execution-time ``save_output`` and
47
- ``run`` (the scenario execution loop) — are
48
- resolved through the PEP 562 ``__getattr__`` below, which delegates to the
47
+ authoring value types described below — are resolved through the PEP 562
48
+ ``__getattr__`` below, which delegates to the
49
49
  thin client's internal learning-name resolver. At submit (discovery mode) they resolve to
50
50
  lean stand-ins so the user app imports without the heavy runtime; only the
51
51
  backend runner (execution mode) resolves them to the real implementations.
@@ -81,16 +81,18 @@ from simulo._client.volume import Volume
81
81
  # never a second import.
82
82
  from simulo.interfaces import (
83
83
  CheckpointId,
84
+ CheckpointRefIsJobError,
84
85
  ContractViolationError,
85
86
  Digest,
86
87
  ImportResolutionError,
87
88
  JobFailedError,
88
89
  JobId,
89
90
  JobPublicId,
90
- ModelPublicId,
91
+ JobType,
91
92
  OrganizationId,
92
93
  OutputPublicId,
93
94
  PackageId,
95
+ PolicyPublicId,
94
96
  ProjectId,
95
97
  RecordingPublicId,
96
98
  ResourceId,
@@ -99,11 +101,12 @@ from simulo.interfaces import (
99
101
  ResumeIncompatibleError,
100
102
  SimuloError,
101
103
  TagId,
102
- TrainedModelId,
104
+ checkpoint_ref_is_job_hint,
105
+ parse_checkpoint_ref,
103
106
  parse_job_public_id,
104
- parse_model_public_id,
105
107
  parse_output_public_id,
106
108
  parse_recording_public_id,
109
+ policy_id_for_job,
107
110
  )
108
111
 
109
112
  # The authoring value types that the lazy resolver does NOT already carry.
@@ -179,17 +182,20 @@ __all__ = [
179
182
  "ProjectId",
180
183
  "PackageId",
181
184
  "JobId",
185
+ "JobType",
182
186
  "JobPublicId",
183
- "ModelPublicId",
187
+ "PolicyPublicId",
184
188
  "RecordingPublicId",
185
189
  "OutputPublicId",
186
190
  "parse_job_public_id",
187
- "parse_model_public_id",
191
+ "parse_checkpoint_ref",
192
+ "policy_id_for_job",
193
+ "checkpoint_ref_is_job_hint",
194
+ "CheckpointRefIsJobError",
188
195
  "parse_recording_public_id",
189
196
  "parse_output_public_id",
190
197
  "ResourceId",
191
198
  "CheckpointId",
192
- "TrainedModelId",
193
199
  "TagId",
194
200
  "ResourceUri",
195
201
  "Digest",
@@ -256,7 +262,6 @@ __all__ = [
256
262
  "RewardComponent",
257
263
  "RewardSet",
258
264
  "Robot",
259
- "Scenario",
260
265
  "Scene",
261
266
  "SensorOffset",
262
267
  "Simulation",
@@ -268,7 +273,6 @@ __all__ = [
268
273
  "Trainer",
269
274
  "Visual",
270
275
  "World",
271
- "run",
272
276
  "save_output",
273
277
  ]
274
278
 
@@ -283,6 +287,12 @@ def __getattr__(name: str) -> object:
283
287
  """
284
288
  if name in _LEARNING_NAMES:
285
289
  return _resolve_learning(name)
290
+ removed_hints = {
291
+ "Scenario": "simulo.Scenario was removed. Use a typed @app.job instead.",
292
+ "run": "simulo.run was removed. Use `simulo run train.py` instead.",
293
+ }
294
+ if name in removed_hints:
295
+ raise AttributeError(removed_hints[name])
286
296
  raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
287
297
 
288
298
 
@@ -318,15 +328,6 @@ if TYPE_CHECKING:
318
328
  # NOT the declared type; see the facades module docstring for the
319
329
  # known gap); facade fidelity to the contract AND to the concrete
320
330
  # worker class is pinned by the backend suite's conformance engine.
321
- # * **Called mirror** name (``simulo.run(scenario_class, ...)`` — the one
322
- # non-learning, non-instantiated member of this kind): same problem as
323
- # an instantiated mirror one level down — with no override, ``run``
324
- # types as ``object`` (``__getattr__``'s declared return), and
325
- # ``object`` is not *callable* at all. It aliases a plain FUNCTION
326
- # facade (there is no Protocol for a bare function to alias against),
327
- # never registered in the backend's class-shaped conformance engine —
328
- # see the facades module docstring for where its drift guard lives
329
- # instead.
330
331
  # * **Annotation-position mirror** names alias their contract Protocol:
331
332
  # ``Scene`` is ``SceneProtocol`` — measured across every shipped user
332
333
  # surface it appears only as ``def build(self, scene: simulo.Scene)``,
@@ -380,7 +381,6 @@ if TYPE_CHECKING:
380
381
  PlayerProtocol,
381
382
  PolicyProtocol,
382
383
  RewardComponentProtocol,
383
- ScenarioProtocol,
384
384
  TaskProtocol,
385
385
  TerminationConditionProtocol,
386
386
  TrainerProtocol,
@@ -389,7 +389,6 @@ if TYPE_CHECKING:
389
389
  # Subclassable names alias their Protocol so ``class X(simulo.Task)`` is checked
390
390
  # against ``TaskProtocol``.
391
391
  Task = TaskProtocol
392
- Scenario = ScenarioProtocol
393
392
  Policy = PolicyProtocol
394
393
  Player = PlayerProtocol
395
394
  Trainer = TrainerProtocol
@@ -401,20 +400,6 @@ if TYPE_CHECKING:
401
400
  # ``save_output`` aliases its real execution-mode implementation (a
402
401
  # torch-free thin-client function), so ``simulo.save_output("summary",
403
402
  # path)`` type-checks with the precise signature instead of ``Any``.
404
- # ``run`` (the scenario execution loop) aliases its typing FACADE — a
405
- # plain FUNCTION facade, not a class one (there is no ``RunProtocol`` to
406
- # alias against; ``run`` is not a contract method). Without this, ``run``
407
- # falls back to ``__getattr__``'s ``-> object`` return type, and
408
- # ``object`` is not callable: ``simulo.run(MyScenario, ...)`` in a freshly
409
- # scaffolded app is a hard mypy error no ``--ignore-missing-imports`` can
410
- # suppress. The facade's real target (``simulo.scenario.run``) lives in
411
- # the ``simulo-backend`` distribution — outside this package's own
412
- # ``MYPYPATH`` — but the facade itself is thin-client-resident code
413
- # (``simulo/_client/facades.py``), exactly like ``Camera``'s facade for
414
- # the same reason; see that module's docstring for the full rationale and
415
- # where drift against the real function is caught (a live, unmocked
416
- # signature comparison in ``simulo-backend``'s test suite, not mypy).
417
- from simulo._client.facades import run as run
418
403
  from simulo._client.outputs import save_output as save_output
419
404
 
420
405
  # RELOCATED authoring value types (Plan C — Waves C1, C2, then C4's
@@ -1,21 +1,8 @@
1
- """Map ``simulo run`` command-line arguments onto a function's signature.
2
-
3
- Two submit paths share this module's introspection, pointed at different
4
- targets:
5
-
6
- * **The entrypoint path** — ``simulo run app.py`` maps ``--flag value``
7
- arguments onto the registered ``@app.entrypoint``'s parameters and
8
- invokes it; the entrypoint's own ``spawn()`` calls end at exactly one
9
- submit (:func:`run_entrypoint` / :func:`parse_args`). (A direct ``python
10
- app.py`` never reaches this module: :meth:`App.entrypoint` prints the
11
- exact equivalent ``simulo run`` command to stderr and exits 2.)
12
- * **The no-entrypoint path** — with no ``@app.entrypoint`` declared, the
13
- CLI maps the same ``--flag value`` arguments directly onto the chosen
14
- ``@app.job``'s own signature (:func:`parse_explicit_args`) and spawns it.
15
- Only EXPLICITLY-passed flags are returned there: a defaulted-but-unpassed
16
- parameter must stay out of the submitted ``args`` so an upgraded client
17
- submits the byte-identical package (same ``canonical_args`` → same
18
- ``package_id``) for the no-flags case it always produced ``args={}`` for.
1
+ """Map ``simulo run`` arguments onto a sole job's signature.
2
+
3
+ Only explicitly passed flags are returned: a defaulted-but-unpassed parameter
4
+ must stay out of submitted ``args`` so the no-flags package identity remains
5
+ stable.
19
6
 
20
7
  An argument parser is built from the target's signature: each parameter
21
8
  ``foo_bar`` becomes ``--foo-bar`` (dash for underscore), coerced from the
@@ -135,10 +122,8 @@ def _strip_optional_annotation(ann: Any) -> Any:
135
122
  def conclusive_param_type(param: inspect.Parameter) -> Optional[type]:
136
123
  """The scalar type a ``--flag`` value is conclusively coerced to, or ``None``.
137
124
 
138
- Unlike :func:`_param_type` — whose ``str`` FALLBACK is fine for an
139
- entrypoint (the entrypoint body runs client-side and can fix things up) —
140
- the no-entrypoint job path submits the coerced value straight into the
141
- job's ``args``, so a guess is a silent wrong submission: ``tags: list = []``
125
+ The sole-job path submits the coerced value straight into the job's
126
+ ``args``, so a guess is a silent wrong submission: ``tags: list = []``
142
127
  would coerce ``--tags release`` through ``list("release")`` into
143
128
  ``["r", "e", "l", ...]``, and a ``Path``/``set``/enum value would crash
144
129
  ``json.dumps`` at manifest time. ``None`` here means "reject at submit".
@@ -146,8 +131,7 @@ def conclusive_param_type(param: inspect.Parameter) -> Optional[type]:
146
131
  Conclusive: a non-``None`` scalar default (its type), or an ``int`` /
147
132
  ``float`` / ``bool`` / ``str`` annotation — real type, PEP 563 string, or
148
133
  either wrapped in ``Optional[...]`` / ``| None``. An unannotated parameter
149
- stays ``str`` (the documented text fallback, unchanged from the entrypoint
150
- convention).
134
+ stays ``str`` (the documented text fallback).
151
135
  """
152
136
  default = param.default
153
137
  if default is not inspect.Parameter.empty and default is not None:
@@ -204,11 +188,11 @@ def build_parser(
204
188
  ) -> argparse.ArgumentParser:
205
189
  """Build an ``argparse`` parser from a function's signature.
206
190
 
207
- With ``sentinel_defaults=False`` (the entrypoint path — behavior unchanged),
208
- a defaulted parameter's parser default is the signature's own default, so
191
+ With ``sentinel_defaults=False``, a defaulted parameter's parser default is
192
+ the signature's own default, so
209
193
  ``parse_args`` yields EVERY parameter, and values coerce via
210
194
  :func:`_param_type` (``str`` fallback included). With
211
- ``sentinel_defaults=True`` (the no-entrypoint job path), defaulted
195
+ ``sentinel_defaults=True`` (the sole-job path), defaulted
212
196
  parameters default to the private ``_UNSET`` sentinel instead — so
213
197
  :func:`parse_explicit_args` can tell an explicitly-passed flag from an
214
198
  untouched default — and values coerce via :func:`conclusive_param_type`
@@ -217,7 +201,7 @@ def build_parser(
217
201
  function's own name, as before.
218
202
 
219
203
  ``exclude`` drops the named parameters from the parser entirely — the
220
- no-entrypoint path passes its DEFERRED unmappable-but-defaulted parameters
204
+ sole-job path passes its DEFERRED unmappable-but-defaulted parameters
221
205
  here (see :func:`unmappable_params`), so a flag targeting one — even via
222
206
  an argparse abbreviation the caller's exact-token scan cannot see — fails
223
207
  at submit as an unrecognized argument rather than being mis-parsed.
@@ -225,7 +209,7 @@ def build_parser(
225
209
  sig = inspect.signature(fn)
226
210
  doc = (inspect.getdoc(fn) or "").strip().splitlines()
227
211
  parser = argparse.ArgumentParser(
228
- prog=prog if prog is not None else getattr(fn, "__name__", "entrypoint"),
212
+ prog=prog if prog is not None else getattr(fn, "__name__", "job"),
229
213
  description=doc[0] if doc else None,
230
214
  )
231
215
  for name, param in sig.parameters.items():
@@ -264,19 +248,6 @@ def build_parser(
264
248
  return parser
265
249
 
266
250
 
267
- def parse_args(fn: Callable[..., object], argv: Sequence[str]) -> dict[str, Any]:
268
- """Parse ``argv`` against ``fn``'s signature into a keyword-argument dict."""
269
- parser = build_parser(fn)
270
- namespace = parser.parse_args(list(argv))
271
- sig = inspect.signature(fn)
272
- kwargs: dict[str, Any] = {}
273
- for name, param in sig.parameters.items():
274
- if param.kind in _SKIP_KINDS:
275
- continue
276
- kwargs[name] = getattr(namespace, name)
277
- return kwargs
278
-
279
-
280
251
  def parse_explicit_args(
281
252
  fn: Callable[..., object],
282
253
  argv: Sequence[str],
@@ -286,13 +257,13 @@ def parse_explicit_args(
286
257
  ) -> dict[str, Any]:
287
258
  """Parse ``argv`` against ``fn``'s signature; return ONLY explicitly-passed kwargs.
288
259
 
289
- The no-entrypoint submit path maps CLI flags onto a ``@app.job``'s own
290
- signature with this: a parameter the user did not pass stays OUT of the
260
+ The sole-job submit path maps CLI flags onto a ``@app.job`` signature: a
261
+ parameter the user did not pass stays OUT of the
291
262
  returned dict (the job body applies its own default on the worker), so the
292
263
  no-flags case still submits ``args={}`` — exactly what pre-mapping clients
293
264
  always submitted — and ``package_id`` is unchanged across the upgrade.
294
- A parameter without a default is required, exactly as on the entrypoint
295
- path. ``exclude`` names parameters left out of the parser and the result
265
+ A parameter without a default is required. ``exclude`` names parameters
266
+ left out of the parser and the result
296
267
  (the deferred unmappable-but-defaulted ones — see :func:`build_parser`).
297
268
  """
298
269
  parser = build_parser(fn, sentinel_defaults=True, prog=prog, exclude=exclude)
@@ -306,8 +277,3 @@ def parse_explicit_args(
306
277
  if value is not _UNSET:
307
278
  kwargs[name] = value
308
279
  return kwargs
309
-
310
-
311
- def run_entrypoint(fn: Callable[..., object], argv: Sequence[str]) -> object:
312
- """Parse ``argv`` for ``fn`` and invoke it (the single submit happens inside)."""
313
- return fn(**parse_args(fn, argv))
@@ -26,7 +26,7 @@ from pathlib import Path
26
26
  from typing import Any, NoReturn, Optional
27
27
 
28
28
  from simulo.interfaces.ids import JobId, JobPublicId
29
- from simulo.interfaces.platform.enums import JobStatus
29
+ from simulo.interfaces.platform.enums import JobStatus, JobType
30
30
  from simulo.interfaces.platform.runs import JOB_SCOPE_MINE
31
31
 
32
32
  #: Poll cadence for cloud ``.get()``. Module-level so tests can shrink it.
@@ -90,6 +90,10 @@ class JobHandle:
90
90
  cloud: bool = False,
91
91
  base_url: Optional[str] = None,
92
92
  token: Optional[str] = None,
93
+ job_type: Optional[JobType] = None,
94
+ input_checkpoint: Optional[str] = None,
95
+ deferred_stdout: str = "",
96
+ deferred_stderr: str = "",
93
97
  ) -> None:
94
98
  self._job_id = job_id
95
99
  self._public_id = public_id
@@ -98,6 +102,10 @@ class JobHandle:
98
102
  self._cloud = cloud
99
103
  self._base_url = base_url
100
104
  self._token = token
105
+ self._job_type = job_type
106
+ self._input_checkpoint = input_checkpoint
107
+ self._deferred_stdout = deferred_stdout
108
+ self._deferred_stderr = deferred_stderr
101
109
 
102
110
  @property
103
111
  def job_id(self) -> JobId:
@@ -134,6 +142,31 @@ class JobHandle:
134
142
  """The bearer token to observe this job with (cloud mode only)."""
135
143
  return self._token
136
144
 
145
+ @property
146
+ def job_type(self) -> Optional[JobType]:
147
+ """Declared type for a cloud submission; absent on legacy local handles."""
148
+ return self._job_type
149
+
150
+ @property
151
+ def input_checkpoint(self) -> Optional[str]:
152
+ """Policy checkpoint requested with ``simulo run --from``."""
153
+ return self._input_checkpoint
154
+
155
+ @property
156
+ def deferred_stdout(self) -> str:
157
+ """Submit-time output held until the cloud job header is printed."""
158
+ return self._deferred_stdout
159
+
160
+ @property
161
+ def deferred_stderr(self) -> str:
162
+ """Submit-time stderr held until the cloud job header is printed."""
163
+ return self._deferred_stderr
164
+
165
+ def _defer_submit_output(self, *, stdout: str = "", stderr: str = "") -> None:
166
+ """Append client-owned submit output for rendering after the job header."""
167
+ self._deferred_stdout += stdout
168
+ self._deferred_stderr += stderr
169
+
137
170
  def execute_hint(self) -> str:
138
171
  """The command that executes this submitted package locally (local-disk mode)."""
139
172
  return f"simulo-backend run-package {self._package_path} --job {self._job_name}"
@@ -4,7 +4,7 @@ THE RACE THIS CLOSES. The bulk download path validates its assembled
4
4
  filename and the selected destination directory as PATH STRINGS
5
5
  (``_contained_bulk_download_destination`` in ``cli.py``: resolve, then
6
6
  ``relative_to``) before any bytes are fetched. The actual write — inside
7
- this client's ``download_model``/``download_output``/``download_recording``
7
+ this client's checkpoint/output/recording download paths
8
8
  — re-derives the SAME destination from its path string again, after a
9
9
  network fetch: an ``exist_ok=True`` ``mkdir`` (which silently ACCEPTS a
10
10
  symlink planted at a previously-missing path, so long as it resolves to a