simulo 0.29.0__tar.gz → 0.31.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.29.0/src/simulo.egg-info → simulo-0.31.0}/PKG-INFO +9 -6
- {simulo-0.29.0 → simulo-0.31.0}/PYPI.md +7 -4
- {simulo-0.29.0 → simulo-0.31.0}/pyproject.toml +2 -2
- {simulo-0.29.0 → simulo-0.31.0}/src/simulo/__init__.py +10 -3
- {simulo-0.29.0 → simulo-0.31.0}/src/simulo/_client/app.py +91 -13
- {simulo-0.29.0 → simulo-0.31.0}/src/simulo/_client/cli.py +346 -68
- {simulo-0.29.0 → simulo-0.31.0}/src/simulo/_client/export_api.py +1 -1
- {simulo-0.29.0 → simulo-0.31.0}/src/simulo/_client/facades.py +31 -4
- {simulo-0.29.0 → simulo-0.31.0}/src/simulo/_client/learning.py +17 -6
- simulo-0.29.0/src/simulo/_client/export_bundle.py → simulo-0.31.0/src/simulo/_client/onnx_bundle.py +84 -82
- {simulo-0.29.0 → simulo-0.31.0}/src/simulo/_client/preflight_render.py +4 -0
- simulo-0.31.0/src/simulo/_client/preview_report.py +322 -0
- {simulo-0.29.0 → simulo-0.31.0}/src/simulo/_client/submit_api.py +1 -1
- {simulo-0.29.0 → simulo-0.31.0}/src/simulo/_client/templates/__init__.py +3 -2
- simulo-0.31.0/src/simulo/_client/templates/training/play.py.tmpl +26 -0
- simulo-0.31.0/src/simulo/_client/templates/training/preview.py.tmpl +10 -0
- {simulo-0.29.0 → simulo-0.31.0}/src/simulo/_client/templates/training/task.py.tmpl +16 -0
- {simulo-0.29.0 → simulo-0.31.0/src/simulo.egg-info}/PKG-INFO +9 -6
- {simulo-0.29.0 → simulo-0.31.0}/src/simulo.egg-info/SOURCES.txt +4 -1
- {simulo-0.29.0 → simulo-0.31.0}/src/simulo.egg-info/requires.txt +1 -1
- {simulo-0.29.0 → simulo-0.31.0}/MANIFEST.in +0 -0
- {simulo-0.29.0 → simulo-0.31.0}/setup.cfg +0 -0
- {simulo-0.29.0 → simulo-0.31.0}/src/simulo/_client/__init__.py +0 -0
- {simulo-0.29.0 → simulo-0.31.0}/src/simulo/_client/_arguments.py +0 -0
- {simulo-0.29.0 → simulo-0.31.0}/src/simulo/_client/_mounts.py +0 -0
- {simulo-0.29.0 → simulo-0.31.0}/src/simulo/_client/_runner.py +0 -0
- {simulo-0.29.0 → simulo-0.31.0}/src/simulo/_client/_secure_downloads.py +0 -0
- {simulo-0.29.0 → simulo-0.31.0}/src/simulo/_client/asset.py +0 -0
- {simulo-0.29.0 → simulo-0.31.0}/src/simulo/_client/asset_api.py +0 -0
- {simulo-0.29.0 → simulo-0.31.0}/src/simulo/_client/asset_package.py +0 -0
- {simulo-0.29.0 → simulo-0.31.0}/src/simulo/_client/asset_pins.py +0 -0
- {simulo-0.29.0 → simulo-0.31.0}/src/simulo/_client/builtin_aliases.py +0 -0
- {simulo-0.29.0 → simulo-0.31.0}/src/simulo/_client/bundle.py +0 -0
- {simulo-0.29.0 → simulo-0.31.0}/src/simulo/_client/cancel_api.py +0 -0
- {simulo-0.29.0 → simulo-0.31.0}/src/simulo/_client/config.py +0 -0
- {simulo-0.29.0 → simulo-0.31.0}/src/simulo/_client/credentials.py +0 -0
- {simulo-0.29.0 → simulo-0.31.0}/src/simulo/_client/discovery.py +0 -0
- {simulo-0.29.0 → simulo-0.31.0}/src/simulo/_client/http.py +0 -0
- {simulo-0.29.0 → simulo-0.31.0}/src/simulo/_client/identity_api.py +0 -0
- {simulo-0.29.0 → simulo-0.31.0}/src/simulo/_client/install_samples.py +0 -0
- {simulo-0.29.0 → simulo-0.31.0}/src/simulo/_client/jobs_api.py +0 -0
- {simulo-0.29.0 → simulo-0.31.0}/src/simulo/_client/login.py +0 -0
- {simulo-0.29.0 → simulo-0.31.0}/src/simulo/_client/mode.py +0 -0
- {simulo-0.29.0 → simulo-0.31.0}/src/simulo/_client/outputs.py +0 -0
- {simulo-0.29.0 → simulo-0.31.0}/src/simulo/_client/packaging.py +0 -0
- {simulo-0.29.0 → simulo-0.31.0}/src/simulo/_client/policies_api.py +0 -0
- {simulo-0.29.0 → simulo-0.31.0}/src/simulo/_client/preflight_api.py +0 -0
- {simulo-0.29.0 → simulo-0.31.0}/src/simulo/_client/registry.py +0 -0
- {simulo-0.29.0 → simulo-0.31.0}/src/simulo/_client/runtime.py +0 -0
- {simulo-0.29.0 → simulo-0.31.0}/src/simulo/_client/runtime_display.py +0 -0
- {simulo-0.29.0 → simulo-0.31.0}/src/simulo/_client/stub.py +0 -0
- {simulo-0.29.0 → simulo-0.31.0}/src/simulo/_client/templates/training/simuloignore.tmpl +0 -0
- {simulo-0.29.0 → simulo-0.31.0}/src/simulo/_client/templates/training/train.py.tmpl +0 -0
- {simulo-0.29.0 → simulo-0.31.0}/src/simulo/_client/view_fragment.py +0 -0
- {simulo-0.29.0 → simulo-0.31.0}/src/simulo/_client/view_session_api.py +0 -0
- {simulo-0.29.0 → simulo-0.31.0}/src/simulo/_client/volume.py +0 -0
- {simulo-0.29.0 → simulo-0.31.0}/src/simulo/callbacks.py +0 -0
- {simulo-0.29.0 → simulo-0.31.0}/src/simulo/py.typed +0 -0
- {simulo-0.29.0 → simulo-0.31.0}/src/simulo.egg-info/dependency_links.txt +0 -0
- {simulo-0.29.0 → simulo-0.31.0}/src/simulo.egg-info/entry_points.txt +0 -0
- {simulo-0.29.0 → simulo-0.31.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.31.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.
|
|
34
|
+
Requires-Dist: simulo-interfaces<0.23,>=0.22.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
|
|
@@ -92,14 +92,17 @@ Simulo requires Python 3.11 or newer. One installation provides both the
|
|
|
92
92
|
simulo create mybot
|
|
93
93
|
simulo login
|
|
94
94
|
cd mybot
|
|
95
|
+
simulo run preview.py --frozen --strict-assets
|
|
95
96
|
simulo run train.py --frozen --strict-assets
|
|
97
|
+
simulo run play.py --policy policy_<job>:best --frozen --strict-assets
|
|
96
98
|
```
|
|
97
99
|
|
|
98
100
|
`simulo create` writes a complete starter application to your machine without
|
|
99
|
-
requiring a login or network connection. It creates `task.py`, `
|
|
100
|
-
|
|
101
|
-
asset
|
|
102
|
-
|
|
101
|
+
requiring a login or network connection. It creates `task.py`, `preview.py`,
|
|
102
|
+
`train.py`, `play.py`, and `.simuloignore`. Run `preview.py` first as a separate
|
|
103
|
+
job to check the validated cartpole asset in its task, run `train.py` to train a
|
|
104
|
+
PPO policy, then pass that policy to `play.py`. `simulo run` packages the source,
|
|
105
|
+
resolves and pins its assets, submits the job, and follows its cloud logs.
|
|
103
106
|
|
|
104
107
|
## Explore robotics samples
|
|
105
108
|
|
|
@@ -36,14 +36,17 @@ Simulo requires Python 3.11 or newer. One installation provides both the
|
|
|
36
36
|
simulo create mybot
|
|
37
37
|
simulo login
|
|
38
38
|
cd mybot
|
|
39
|
+
simulo run preview.py --frozen --strict-assets
|
|
39
40
|
simulo run train.py --frozen --strict-assets
|
|
41
|
+
simulo run play.py --policy policy_<job>:best --frozen --strict-assets
|
|
40
42
|
```
|
|
41
43
|
|
|
42
44
|
`simulo create` writes a complete starter application to your machine without
|
|
43
|
-
requiring a login or network connection. It creates `task.py`, `
|
|
44
|
-
|
|
45
|
-
asset
|
|
46
|
-
|
|
45
|
+
requiring a login or network connection. It creates `task.py`, `preview.py`,
|
|
46
|
+
`train.py`, `play.py`, and `.simuloignore`. Run `preview.py` first as a separate
|
|
47
|
+
job to check the validated cartpole asset in its task, run `train.py` to train a
|
|
48
|
+
PPO policy, then pass that policy to `play.py`. `simulo run` packages the source,
|
|
49
|
+
resolves and pins its assets, submits the job, and follows its cloud logs.
|
|
47
50
|
|
|
48
51
|
## Explore robotics samples
|
|
49
52
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "simulo"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.31.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.
|
|
62
|
+
"simulo-interfaces>=0.22.0,<0.23",
|
|
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
|
|
@@ -92,6 +92,7 @@ from simulo.interfaces import (
|
|
|
92
92
|
OrganizationId,
|
|
93
93
|
OutputPublicId,
|
|
94
94
|
PackageId,
|
|
95
|
+
PolicyNetworkProtocol,
|
|
95
96
|
PolicyPublicId,
|
|
96
97
|
ProjectId,
|
|
97
98
|
RecordingPublicId,
|
|
@@ -141,6 +142,7 @@ from simulo.interfaces.authoring import (
|
|
|
141
142
|
GroundPlane,
|
|
142
143
|
LightConfig,
|
|
143
144
|
PointMarker,
|
|
145
|
+
PolicyCheckpoint,
|
|
144
146
|
PrimitiveShape,
|
|
145
147
|
RigidPhysics,
|
|
146
148
|
RoughTerrain,
|
|
@@ -185,6 +187,7 @@ __all__ = [
|
|
|
185
187
|
"JobType",
|
|
186
188
|
"JobPublicId",
|
|
187
189
|
"PolicyPublicId",
|
|
190
|
+
"PolicyNetworkProtocol",
|
|
188
191
|
"RecordingPublicId",
|
|
189
192
|
"OutputPublicId",
|
|
190
193
|
"parse_job_public_id",
|
|
@@ -251,7 +254,8 @@ __all__ = [
|
|
|
251
254
|
"ParallelGripperActuator",
|
|
252
255
|
"Physics",
|
|
253
256
|
"Player",
|
|
254
|
-
"
|
|
257
|
+
"PolicyCheckpoint",
|
|
258
|
+
"PolicyNetwork",
|
|
255
259
|
"Pose",
|
|
256
260
|
"Prop",
|
|
257
261
|
"RLPlayer",
|
|
@@ -273,6 +277,7 @@ __all__ = [
|
|
|
273
277
|
"Trainer",
|
|
274
278
|
"Visual",
|
|
275
279
|
"World",
|
|
280
|
+
"preview",
|
|
276
281
|
"save_output",
|
|
277
282
|
]
|
|
278
283
|
|
|
@@ -288,6 +293,7 @@ def __getattr__(name: str) -> object:
|
|
|
288
293
|
if name in _LEARNING_NAMES:
|
|
289
294
|
return _resolve_learning(name)
|
|
290
295
|
removed_hints = {
|
|
296
|
+
"Policy": "simulo.Policy was removed. Load a checkpoint with checkpoint.load(), which returns a simulo.PolicyNetwork.",
|
|
291
297
|
"Scenario": "simulo.Scenario was removed. Use a typed @app.job instead.",
|
|
292
298
|
"run": "simulo.run was removed. Use `simulo run train.py` instead.",
|
|
293
299
|
}
|
|
@@ -375,11 +381,12 @@ if TYPE_CHECKING:
|
|
|
375
381
|
from simulo._client.facades import RLPlayer as RLPlayer
|
|
376
382
|
from simulo._client.facades import RLTrainer as RLTrainer
|
|
377
383
|
from simulo._client.facades import Robot as Robot
|
|
384
|
+
from simulo._client.facades import preview as preview
|
|
378
385
|
from simulo.interfaces.authoring import SceneProtocol
|
|
379
386
|
from simulo.interfaces.runtime import (
|
|
380
387
|
ObservationComponentProtocol,
|
|
381
388
|
PlayerProtocol,
|
|
382
|
-
|
|
389
|
+
PolicyNetworkProtocol,
|
|
383
390
|
RewardComponentProtocol,
|
|
384
391
|
TaskProtocol,
|
|
385
392
|
TerminationConditionProtocol,
|
|
@@ -389,7 +396,7 @@ if TYPE_CHECKING:
|
|
|
389
396
|
# Subclassable names alias their Protocol so ``class X(simulo.Task)`` is checked
|
|
390
397
|
# against ``TaskProtocol``.
|
|
391
398
|
Task = TaskProtocol
|
|
392
|
-
|
|
399
|
+
PolicyNetwork = PolicyNetworkProtocol
|
|
393
400
|
Player = PlayerProtocol
|
|
394
401
|
Trainer = TrainerProtocol
|
|
395
402
|
ObservationComponent = ObservationComponentProtocol
|
|
@@ -23,9 +23,11 @@ import atexit
|
|
|
23
23
|
import contextlib
|
|
24
24
|
import functools
|
|
25
25
|
import hashlib
|
|
26
|
+
import inspect
|
|
26
27
|
import io
|
|
27
28
|
import json
|
|
28
29
|
import os
|
|
30
|
+
import re
|
|
29
31
|
import shlex
|
|
30
32
|
import sys
|
|
31
33
|
import types
|
|
@@ -157,6 +159,7 @@ def viewstream_requested(enabled: bool) -> Iterator[None]:
|
|
|
157
159
|
# ---------------------------------------------------------------------------
|
|
158
160
|
|
|
159
161
|
_INPUT_CHECKPOINT_REF: Optional[str] = None
|
|
162
|
+
_INPUT_CHECKPOINT_FLAG = "--from"
|
|
160
163
|
|
|
161
164
|
|
|
162
165
|
class InputCheckpointRequiresCloudError(RuntimeError):
|
|
@@ -171,15 +174,18 @@ class InputCheckpointRequiresCloudError(RuntimeError):
|
|
|
171
174
|
|
|
172
175
|
|
|
173
176
|
@contextlib.contextmanager
|
|
174
|
-
def input_checkpoint_requested(checkpoint_ref: Optional[str]) -> Iterator[None]:
|
|
175
|
-
"""Scope
|
|
176
|
-
global _INPUT_CHECKPOINT_REF
|
|
177
|
+
def input_checkpoint_requested(checkpoint_ref: Optional[str], *, flag: str = "--from") -> Iterator[None]:
|
|
178
|
+
"""Scope one checkpoint flag's reference for one ``simulo run`` submit call."""
|
|
179
|
+
global _INPUT_CHECKPOINT_REF, _INPUT_CHECKPOINT_FLAG
|
|
177
180
|
previous = _INPUT_CHECKPOINT_REF
|
|
181
|
+
previous_flag = _INPUT_CHECKPOINT_FLAG
|
|
178
182
|
_INPUT_CHECKPOINT_REF = checkpoint_ref
|
|
183
|
+
_INPUT_CHECKPOINT_FLAG = flag
|
|
179
184
|
try:
|
|
180
185
|
yield
|
|
181
186
|
finally:
|
|
182
187
|
_INPUT_CHECKPOINT_REF = previous
|
|
188
|
+
_INPUT_CHECKPOINT_FLAG = previous_flag
|
|
183
189
|
|
|
184
190
|
|
|
185
191
|
# ---------------------------------------------------------------------------
|
|
@@ -283,15 +289,16 @@ class PreflightBlockedError(RuntimeError):
|
|
|
283
289
|
|
|
284
290
|
|
|
285
291
|
def _require_no_input_checkpoint_for_local_submit(reason: str) -> None:
|
|
286
|
-
"""Refuse a local-disk submit that carries an ambient
|
|
292
|
+
"""Refuse a local-disk submit that carries an ambient checkpoint reference."""
|
|
287
293
|
if _INPUT_CHECKPOINT_REF is None:
|
|
288
294
|
return
|
|
295
|
+
retry = "" if _INPUT_CHECKPOINT_FLAG == "--policy" else f", or drop {_INPUT_CHECKPOINT_FLAG}"
|
|
289
296
|
raise InputCheckpointRequiresCloudError(
|
|
290
|
-
f"
|
|
291
|
-
f"{reason}. A local-disk submit only writes the package and cannot
|
|
292
|
-
"checkpoint, so the run would
|
|
297
|
+
f"{_INPUT_CHECKPOINT_FLAG} {_INPUT_CHECKPOINT_REF!r} needs a cloud control plane to resolve the checkpoint, but "
|
|
298
|
+
f"{reason}. A local-disk submit only writes the package and cannot use a policy "
|
|
299
|
+
"checkpoint, so the run would not honor the selected policy. Log in first "
|
|
293
300
|
"(`simulo login`) or point SIMULO_API_URL/SIMULO_ENV at a control plane (and unset "
|
|
294
|
-
"SIMULO_SUBMIT=local)
|
|
301
|
+
f"SIMULO_SUBMIT=local){retry}."
|
|
295
302
|
)
|
|
296
303
|
|
|
297
304
|
|
|
@@ -304,6 +311,7 @@ class JobSpec:
|
|
|
304
311
|
qualname: str
|
|
305
312
|
fn: Callable[..., object]
|
|
306
313
|
job_type: JobType
|
|
314
|
+
policy_param: Optional[str] = None
|
|
307
315
|
resources: dict[str, Any] = field(default_factory=dict)
|
|
308
316
|
timeout: Optional[int] = None
|
|
309
317
|
retries: int = 0
|
|
@@ -311,6 +319,26 @@ class JobSpec:
|
|
|
311
319
|
mounts: dict[str, Mount] = field(default_factory=dict)
|
|
312
320
|
|
|
313
321
|
|
|
322
|
+
_POLICY_CHECKPOINT_ANNOTATION = re.compile(r"\bPolicyCheckpoint\b")
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def _is_policy_checkpoint_annotation(annotation: object) -> bool:
|
|
326
|
+
"""Whether an annotation names the runner-injected checkpoint type."""
|
|
327
|
+
if annotation is inspect.Parameter.empty:
|
|
328
|
+
return False
|
|
329
|
+
text = annotation if isinstance(annotation, str) else repr(annotation)
|
|
330
|
+
return bool(_POLICY_CHECKPOINT_ANNOTATION.search(text))
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def _policy_checkpoint_declaration_error(func: Callable[..., object], job_type: JobType) -> JobDeclarationError:
|
|
334
|
+
label = "Play" if job_type is JobType.PLAY else "Eval"
|
|
335
|
+
return JobDeclarationError(
|
|
336
|
+
f"{label} job {func.__name__!r} in {Path(func.__code__.co_filename).name} must take one "
|
|
337
|
+
"simulo.PolicyCheckpoint parameter, for example `def play(policy: simulo.PolicyCheckpoint, "
|
|
338
|
+
"episodes: int = 3)`."
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
|
|
314
342
|
def _app_source_location(module_name: str) -> tuple[Path, Path]:
|
|
315
343
|
"""Resolve the on-disk app file (and its directory) for a job's module.
|
|
316
344
|
|
|
@@ -361,7 +389,7 @@ def _skip_preflight_hint(blocking: "Sequence[PreflightFinding]") -> str:
|
|
|
361
389
|
docstring rule ("a blocking finding can never become an unbypassable
|
|
362
390
|
wall") is unchanged.
|
|
363
391
|
|
|
364
|
-
``runtime_not_available`` is
|
|
392
|
+
``runtime_not_available`` is one of two exceptions, and only the ADVICE
|
|
365
393
|
changes, not the flag's behavior. The generic hint's two stated reasons
|
|
366
394
|
to bypass are "preflight is wrong about your app" and "the platform is
|
|
367
395
|
unreachable" — this finding is neither: it fires only when the
|
|
@@ -388,8 +416,17 @@ def _skip_preflight_hint(blocking: "Sequence[PreflightFinding]") -> str:
|
|
|
388
416
|
the whole raise even when an ordinary, safely-bypassable finding is also
|
|
389
417
|
blocking alongside it — bypassing would still re-enable that finding's
|
|
390
418
|
harm too.
|
|
419
|
+
|
|
420
|
+
``intake_paused`` comes first, ahead of ``runtime_not_available``:
|
|
421
|
+
while Simulo staff have paused job intake, ``--skip-preflight`` only
|
|
422
|
+
uploads the package before ``create_job`` refuses with the same
|
|
423
|
+
``intake_paused``, so the hint says to try again later and never
|
|
424
|
+
names the flag.
|
|
391
425
|
"""
|
|
392
|
-
from simulo._client.preflight_render import RUNTIME_NOT_AVAILABLE_CODE
|
|
426
|
+
from simulo._client.preflight_render import INTAKE_PAUSED_CODE, RUNTIME_NOT_AVAILABLE_CODE
|
|
427
|
+
|
|
428
|
+
if any(finding.code == INTAKE_PAUSED_CODE for finding in blocking):
|
|
429
|
+
return "Simulo has paused new jobs, so skipping preflight won't help until intake resumes; try again later."
|
|
393
430
|
|
|
394
431
|
if any(finding.code == RUNTIME_NOT_AVAILABLE_CODE for finding in blocking):
|
|
395
432
|
return (
|
|
@@ -828,6 +865,20 @@ class JobFunction:
|
|
|
828
865
|
return self.spawn(**kwargs).job_id
|
|
829
866
|
|
|
830
867
|
|
|
868
|
+
def _policy_checkpoint_parameters(fn: Callable[..., object]) -> list[str]:
|
|
869
|
+
"""Names of ``fn``'s parameters annotated with ``simulo.PolicyCheckpoint``.
|
|
870
|
+
|
|
871
|
+
Matched on the annotation's text, not its identity: the annotation may be a
|
|
872
|
+
string (``from __future__ import annotations``) that discovery never
|
|
873
|
+
evaluates, and an ``Optional[...]`` or ``| None`` spelling still names it.
|
|
874
|
+
"""
|
|
875
|
+
names = []
|
|
876
|
+
for parameter in inspect.signature(fn).parameters.values():
|
|
877
|
+
if _is_policy_checkpoint_annotation(parameter.annotation):
|
|
878
|
+
names.append(parameter.name)
|
|
879
|
+
return names
|
|
880
|
+
|
|
881
|
+
|
|
831
882
|
def _cli_redirect_message() -> str:
|
|
832
883
|
"""The ONE stderr message for a direct ``python app.py`` run — both guard paths.
|
|
833
884
|
|
|
@@ -1327,12 +1378,34 @@ class App:
|
|
|
1327
1378
|
if system is not None:
|
|
1328
1379
|
resources["system"] = system.value
|
|
1329
1380
|
resources.update(extra)
|
|
1381
|
+
policy_params = [
|
|
1382
|
+
name
|
|
1383
|
+
for name, parameter in inspect.signature(func).parameters.items()
|
|
1384
|
+
if _is_policy_checkpoint_annotation(parameter.annotation)
|
|
1385
|
+
]
|
|
1386
|
+
policy_param: Optional[str] = None
|
|
1387
|
+
if resolved_job_type in (JobType.PLAY, JobType.EVAL):
|
|
1388
|
+
if len(policy_params) != 1:
|
|
1389
|
+
raise _policy_checkpoint_declaration_error(func, resolved_job_type)
|
|
1390
|
+
policy_param = policy_params[0]
|
|
1391
|
+
policy_parameter = inspect.signature(func).parameters[policy_param]
|
|
1392
|
+
if policy_parameter.default is not inspect.Parameter.empty or policy_parameter.kind not in (
|
|
1393
|
+
inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
|
1394
|
+
inspect.Parameter.KEYWORD_ONLY,
|
|
1395
|
+
):
|
|
1396
|
+
raise _policy_checkpoint_declaration_error(func, resolved_job_type)
|
|
1397
|
+
elif policy_params and resolved_job_type is not JobType.PREVIEW:
|
|
1398
|
+
raise JobDeclarationError(
|
|
1399
|
+
f"{resolved_job_type.value.title()} job {func.__name__!r} in {location} cannot take a "
|
|
1400
|
+
"simulo.PolicyCheckpoint parameter. Only play and eval jobs receive a policy checkpoint."
|
|
1401
|
+
)
|
|
1330
1402
|
spec = JobSpec(
|
|
1331
1403
|
name=func.__name__,
|
|
1332
1404
|
module=func.__module__,
|
|
1333
1405
|
qualname=func.__qualname__,
|
|
1334
1406
|
fn=func,
|
|
1335
1407
|
job_type=resolved_job_type,
|
|
1408
|
+
policy_param=policy_param,
|
|
1336
1409
|
resources=resources,
|
|
1337
1410
|
timeout=timeout,
|
|
1338
1411
|
retries=retries,
|
|
@@ -1393,11 +1466,16 @@ class App:
|
|
|
1393
1466
|
)
|
|
1394
1467
|
|
|
1395
1468
|
if spec.job_type is JobType.PREVIEW:
|
|
1396
|
-
|
|
1469
|
+
policy_parameters = _policy_checkpoint_parameters(spec.fn)
|
|
1470
|
+
if policy_parameters:
|
|
1471
|
+
names = ", ".join(policy_parameters)
|
|
1472
|
+
raise JobDeclarationError(
|
|
1473
|
+
f"Preview job {job_name!r} in {where} has a simulo.PolicyCheckpoint parameter ({names}), "
|
|
1474
|
+
"but a preview job takes no policy. Remove it; a policy goes to a play or eval file "
|
|
1475
|
+
"with --policy."
|
|
1476
|
+
)
|
|
1397
1477
|
if spec.job_type is JobType.EVAL:
|
|
1398
1478
|
raise JobDeclarationError("evaluation isn't available yet. Use a train job for now.")
|
|
1399
|
-
if spec.job_type is JobType.PLAY:
|
|
1400
|
-
raise JobDeclarationError("play isn't available yet. It arrives with --policy.")
|
|
1401
1479
|
if spec.job_type is JobType.ASSET_VALIDATION:
|
|
1402
1480
|
raise JobDeclarationError("Asset validation runs when you publish an asset: simulo asset publish.")
|
|
1403
1481
|
if spec.job_type is JobType.EXPORT:
|