simulo 0.13.2__tar.gz → 0.14.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 (53) hide show
  1. {simulo-0.13.2/src/simulo.egg-info → simulo-0.14.0}/PKG-INFO +2 -2
  2. {simulo-0.13.2 → simulo-0.14.0}/pyproject.toml +2 -2
  3. {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/bundle.py +24 -0
  4. {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/manifest.py +8 -2
  5. simulo-0.14.0/src/simulo/_client/runtime.py +185 -0
  6. {simulo-0.13.2 → simulo-0.14.0/src/simulo.egg-info}/PKG-INFO +2 -2
  7. {simulo-0.13.2 → simulo-0.14.0}/src/simulo.egg-info/requires.txt +1 -1
  8. simulo-0.13.2/src/simulo/_client/runtime.py +0 -90
  9. {simulo-0.13.2 → simulo-0.14.0}/MANIFEST.in +0 -0
  10. {simulo-0.13.2 → simulo-0.14.0}/PYPI.md +0 -0
  11. {simulo-0.13.2 → simulo-0.14.0}/setup.cfg +0 -0
  12. {simulo-0.13.2 → simulo-0.14.0}/src/simulo/__init__.py +0 -0
  13. {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/__init__.py +0 -0
  14. {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/_entrypoint.py +0 -0
  15. {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/_mounts.py +0 -0
  16. {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/_runner.py +0 -0
  17. {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/app.py +0 -0
  18. {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/asset.py +0 -0
  19. {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/asset_api.py +0 -0
  20. {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/asset_package.py +0 -0
  21. {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/asset_pins.py +0 -0
  22. {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/builtin_aliases.py +0 -0
  23. {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/cancel_api.py +0 -0
  24. {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/cli.py +0 -0
  25. {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/config.py +0 -0
  26. {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/credentials.py +0 -0
  27. {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/discovery.py +0 -0
  28. {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/http.py +0 -0
  29. {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/jobs_api.py +0 -0
  30. {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/learning.py +0 -0
  31. {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/login.py +0 -0
  32. {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/mode.py +0 -0
  33. {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/packaging.py +0 -0
  34. {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/registry.py +0 -0
  35. {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/runtime_display.py +0 -0
  36. {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/seed_ref.py +0 -0
  37. {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/stub.py +0 -0
  38. {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/submit_api.py +0 -0
  39. {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/templates/__init__.py +0 -0
  40. {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/templates/inference/app.py.tmpl +0 -0
  41. {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/templates/inference/simuloignore.tmpl +0 -0
  42. {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/templates/scenario/app.py.tmpl +0 -0
  43. {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/templates/scenario/simuloignore.tmpl +0 -0
  44. {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/templates/training/app.py.tmpl +0 -0
  45. {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/templates/training/simuloignore.tmpl +0 -0
  46. {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/view_session_api.py +0 -0
  47. {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/volume.py +0 -0
  48. {simulo-0.13.2 → simulo-0.14.0}/src/simulo/callbacks.py +0 -0
  49. {simulo-0.13.2 → simulo-0.14.0}/src/simulo/py.typed +0 -0
  50. {simulo-0.13.2 → simulo-0.14.0}/src/simulo.egg-info/SOURCES.txt +0 -0
  51. {simulo-0.13.2 → simulo-0.14.0}/src/simulo.egg-info/dependency_links.txt +0 -0
  52. {simulo-0.13.2 → simulo-0.14.0}/src/simulo.egg-info/entry_points.txt +0 -0
  53. {simulo-0.13.2 → simulo-0.14.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.13.2
3
+ Version: 0.14.0
4
4
  Summary: Simulo SDK and CLI — define robotics simulation and training apps in Python and run them on the Simulo cloud.
5
5
  Author-email: Simulo Team <team@simulo.ai>
6
6
  License: BSD-3-Clause
@@ -15,7 +15,7 @@ Classifier: Programming Language :: Python :: 3.12
15
15
  Classifier: Typing :: Typed
16
16
  Requires-Python: >=3.11
17
17
  Description-Content-Type: text/markdown
18
- Requires-Dist: simulo-interfaces<0.6,>=0.5
18
+ Requires-Dist: simulo-interfaces<0.7,>=0.6
19
19
  Requires-Dist: mcap<2,>=1.3
20
20
  Requires-Dist: defusedxml>=0.7.1
21
21
  Requires-Dist: usd-core<27,>=25.5
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "simulo"
7
- version = "0.13.2"
7
+ version = "0.14.0"
8
8
  description = "Simulo SDK and CLI — define robotics simulation and training apps in Python and run them on the Simulo cloud."
9
9
  readme = "PYPI.md"
10
10
  requires-python = ">=3.11"
@@ -26,7 +26,7 @@ classifiers = [
26
26
  # mcap: used by `simulo recordings` to read back a downloaded recording and
27
27
  # verify it (message count) — MIT-licensed, deps only lz4+zstandard.
28
28
  dependencies = [
29
- "simulo-interfaces>=0.5,<0.6",
29
+ "simulo-interfaces>=0.6,<0.7",
30
30
  "mcap>=1.3,<2",
31
31
  # Hardened XML parsing for URDF ingestion (USD Asset Catalogs, PR-12 fix
32
32
  # loop 2, security NIT): stdlib xml.etree.ElementTree relies on
@@ -23,6 +23,7 @@ from simulo._client.asset import Asset
23
23
  from simulo._client.manifest import Manifest
24
24
  from simulo._client.packaging import canonical_args, package, read_bundle_package_id
25
25
  from simulo._client.volume import Volume
26
+ from simulo.interfaces.platform import is_platform_pinned
26
27
 
27
28
  if TYPE_CHECKING: # typing-only; avoids any import cycle with app.py at runtime
28
29
  from simulo._client.app import App, JobSpec
@@ -122,6 +123,16 @@ def build_manifest(
122
123
  setup (``runner._setup_mounts``, also keyed off ``resources``) see it — an
123
124
  unmounted §7.1 handle must never resolve to a silent zero-pin submit (freeze
124
125
  doc "PR-9 amendments").
126
+
127
+ **Submit-time denylist gate:** every package name declared via the
128
+ runtime's ``pip_install()`` is checked against
129
+ ``simulo.interfaces.platform.is_platform_pinned`` — a layer naming a
130
+ package the Simulo base runtime already provides (torch, numpy, the CUDA
131
+ wheels, the ``simulo`` distributions) fails the whole submit here with a
132
+ ``SystemExit`` listing the offenders, before any package is written or
133
+ uploaded. Fast-fail by design: a shadowing layer version once broke the
134
+ runtime's compiled camera-sensor bindings in ways undiagnosable from the
135
+ job's own traceback (#285), so this raises — it never skips-and-warns.
125
136
  """
126
137
  rel = app_file.resolve().relative_to(project_root)
127
138
  module = ".".join(rel.with_suffix("").parts)
@@ -140,13 +151,26 @@ def build_manifest(
140
151
  resources: list[Any] = list(resource_by_uri.values())
141
152
 
142
153
  runtime = app.runtime
154
+ pip_dependencies = [dict(layer) for layer in getattr(runtime, "pip_dependencies", [])]
155
+ pinned = list(
156
+ dict.fromkeys( # de-duplicate across layers, preserving declaration order
157
+ spec for layer in pip_dependencies for spec in layer.get("packages", []) if is_platform_pinned(str(spec))
158
+ )
159
+ )
160
+ if pinned:
161
+ raise SystemExit(
162
+ "Cannot pip_install these — the Simulo base runtime already provides them: "
163
+ f"{', '.join(pinned)}. Remove them; the base image includes them."
164
+ )
143
165
  return Manifest(
144
166
  source_digest="", # stamped by package()
145
167
  runtime_id=str(getattr(runtime, "runtime_id", "")),
146
168
  entrypoint={"file": rel.as_posix(), "module": module, "local_entrypoint": local_entrypoint},
169
+ runtime_env=dict(getattr(runtime, "runtime_env", {})),
147
170
  jobs={name: _job_entry(spec) for name, spec in app.jobs.items()},
148
171
  resources=resources,
149
172
  volumes=volumes,
173
+ pip_dependencies=pip_dependencies,
150
174
  args=dict(args or {}),
151
175
  )
152
176
 
@@ -2,8 +2,12 @@
2
2
 
3
3
  A frozen, JSON-serialisable description of a packaged app: the content digest of
4
4
  its source, the submission's package identity, the entrypoint, the picked Simulo
5
- runtime, declared jobs, and mounts. The backend runner reads only named fields.
6
- Kept deliberately stdlib-only and decoupled from the runner.
5
+ runtime, the guarded runtime customisation (``runtime_env`` — the user's
6
+ ``env()`` overrides; ``pip_dependencies`` — the declared public-PyPI layers),
7
+ declared jobs, and mounts. The backend runner reads only named fields, so the
8
+ additive ``runtime_env`` / ``pip_dependencies`` fields are ignored by a runner
9
+ that predates them (no break). Kept deliberately stdlib-only and decoupled from
10
+ the runner.
7
11
 
8
12
  Two distinct identities are recorded:
9
13
 
@@ -32,9 +36,11 @@ class Manifest:
32
36
  runtime_id: str
33
37
  package_id: str = ""
34
38
  entrypoint: dict[str, Any] = field(default_factory=dict)
39
+ runtime_env: dict[str, str] = field(default_factory=dict)
35
40
  jobs: dict[str, Any] = field(default_factory=dict)
36
41
  resources: list[Any] = field(default_factory=list)
37
42
  volumes: dict[str, Any] = field(default_factory=dict)
43
+ pip_dependencies: list[dict[str, Any]] = field(default_factory=list)
38
44
  args: dict[str, Any] = field(default_factory=dict)
39
45
  manifest_version: int = 1
40
46
 
@@ -0,0 +1,185 @@
1
+ """``Runtime`` — a Simulo-curated runtime (implements ``RuntimeProtocol``).
2
+
3
+ Simulo owns the runtime: every job executes in a prebuilt, Simulo-managed
4
+ environment that ships everything a robotics training job needs — the
5
+ simulation engine, GPU stack, and the scientific-Python toolchain. You never
6
+ bring your own image or registry; you *pick* a Simulo runtime (today there is
7
+ one, :data:`DEFAULT_RUNTIME`, and it is the default — the common path writes
8
+ nothing) and may layer a guarded set of extras on top: :meth:`Runtime.env`
9
+ merges environment variables into the job's execution environment, and
10
+ :meth:`Runtime.pip_install` declares additional **public-PyPI** packages the
11
+ Simulo image builder installs on top of the base runtime. There is no
12
+ private-registry, URL, VCS, or local-path install surface — a package the
13
+ base runtime already provides cannot be overridden (see
14
+ ``simulo.interfaces.platform.is_platform_pinned``).
15
+
16
+ Locally this class is a lean metadata recorder: ``from_registry`` names the
17
+ Simulo runtime, ``env`` accumulates environment overrides, ``pip_install``
18
+ records ordered public-PyPI dependency layers, ``torch_jit`` is a no-op
19
+ marker at submit (and real ``torch.jit.script`` on the worker), and
20
+ ``imports`` is the remote-only-import boundary (Rule #3) — a real
21
+ ``nullcontext`` in execution mode, a stub-installing context manager in
22
+ discovery mode. Nothing is ever installed on your machine at submit.
23
+ """
24
+
25
+ import copy
26
+ import importlib
27
+ from contextlib import AbstractContextManager, nullcontext
28
+ from typing import Any, Callable, Mapping, TypeVar, cast
29
+
30
+ from simulo._client.discovery import stub_import_context
31
+ from simulo._client.mode import DISCOVERY, EXECUTION, current_mode
32
+ from simulo.interfaces.platform import canonical_pypi_name
33
+
34
+ F = TypeVar("F", bound=Callable[..., object])
35
+
36
+
37
+ def _reject_non_pypi_spec(spec: str) -> None:
38
+ """Raise ``SystemExit`` if *spec* is not a plain public-PyPI requirement.
39
+
40
+ Delegates the actual grammar check to
41
+ ``simulo.interfaces.platform.canonical_pypi_name`` — the SAME positive
42
+ allowlist ``is_platform_pinned`` fails closed on — rather than
43
+ duplicating a rule in this package too (review round 2, critic MINOR: a
44
+ duplicated rule is how one copy gets hardened and the other doesn't).
45
+ Here the reject happens at CALL time with a message naming the actual
46
+ problem (not a misleading "platform-pinned" one at submit time).
47
+ """
48
+ if canonical_pypi_name(spec) is None:
49
+ raise SystemExit(f"pip_install accepts PyPI package specs only — not URLs, paths, or VCS refs: {spec}")
50
+
51
+
52
+ class Runtime:
53
+ """A Simulo-curated runtime a job executes in. Torch-free metadata recorder.
54
+
55
+ You normally never construct one: every :class:`simulo.App` defaults to
56
+ :data:`DEFAULT_RUNTIME`. Pick a Simulo runtime by name and layer your own
57
+ environment variables and public-PyPI packages on top::
58
+
59
+ runtime = (
60
+ simulo.Runtime.from_registry("simulo/gpu-rl:2026.06")
61
+ .pip_install("wandb==0.17.0")
62
+ .env({"WANDB_PROJECT": "cartpole"})
63
+ )
64
+ app = simulo.App("cartpole", runtime=runtime)
65
+
66
+ Nothing is installed on your machine — the runtime is Simulo-built and
67
+ lives in the cloud; submit only records which runtime the app picked plus
68
+ the declared env/dependency layers, and the cloud materialises them at
69
+ execution time.
70
+ """
71
+
72
+ def __init__(self, runtime_id: str) -> None:
73
+ self._runtime_id = runtime_id
74
+ self._env: dict[str, str] = {}
75
+ self._remote_imports: list[str] = []
76
+ self._torch_jit_marks: list[str] = []
77
+ self._pip_layers: list[dict[str, Any]] = []
78
+
79
+ @classmethod
80
+ def from_registry(cls, image: str) -> "Runtime":
81
+ """Pick a Simulo runtime by name (e.g. ``"simulo/gpu-rl:2026.06"``)."""
82
+ return cls(runtime_id=image)
83
+
84
+ @property
85
+ def runtime_id(self) -> str:
86
+ """The Simulo runtime name passed to :meth:`from_registry`."""
87
+ return self._runtime_id
88
+
89
+ @property
90
+ def runtime_env(self) -> Mapping[str, str]:
91
+ """The accumulated :meth:`env` overrides (a copy)."""
92
+ return dict(self._env)
93
+
94
+ @property
95
+ def pip_dependencies(self) -> list[dict[str, Any]]:
96
+ """Ordered public-PyPI dependency layers (defensive deep copy).
97
+
98
+ Each layer is ``{"packages": [...], "pre": bool}`` — one entry per
99
+ :meth:`pip_install` call, in call order. The Simulo image builder
100
+ installs these on top of the base runtime; submit only records them.
101
+ """
102
+ return copy.deepcopy(self._pip_layers)
103
+
104
+ def env(self, variables: Mapping[str, str]) -> "Runtime":
105
+ """Merge environment variables into the job's execution environment.
106
+
107
+ Later calls win on key conflicts. Returns ``self`` for chaining.
108
+ Guarded: runner/worker-owned keys
109
+ (``simulo.interfaces.platform.RESERVED_RUNTIME_ENV_KEYS`` /
110
+ ``RESERVED_RUNTIME_ENV_PREFIXES``) are never honoured at execution —
111
+ enforcement lives at the runner/worker, which also sanitises the pip
112
+ environment so no variable declared here can redirect the image
113
+ builder's package installs.
114
+ """
115
+ self._env.update(variables)
116
+ return self
117
+
118
+ def pip_install(self, *packages: str, pre: bool = False) -> "Runtime":
119
+ """Declare public-PyPI packages to add on top of the Simulo runtime.
120
+
121
+ Each *package* is a plain PyPI requirement — ``name``, optionally
122
+ with extras and a version specifier (``"wandb==0.17.0"``,
123
+ ``"transformers[torch]>=4.40"``). URLs, VCS refs
124
+ (``git+...``), local paths, and PEP 508 direct references
125
+ (``name @ url``) are rejected: the Simulo image builder installs from
126
+ public PyPI only. ``pre=True`` allows pre-release versions for this
127
+ layer.
128
+
129
+ Each call appends an **ordered layer**; the builder installs the
130
+ layers in order on top of the base runtime. Submit only *records* the
131
+ layers — nothing is installed locally. A package the base runtime
132
+ already provides (torch, numpy, the CUDA wheels, the ``simulo``
133
+ distributions — ``simulo.interfaces.platform.PLATFORM_PINNED_PACKAGES``)
134
+ cannot be re-declared here; submit fails fast if one is.
135
+
136
+ Called with no *packages* (``pip_install()``), this is a no-op — no
137
+ layer is recorded — rather than appending a vacuous empty layer for
138
+ every downstream consumer (the manifest, the worker) to special-case.
139
+
140
+ Returns ``self`` for chaining.
141
+ """
142
+ for spec in packages:
143
+ _reject_non_pypi_spec(spec)
144
+ if packages:
145
+ self._pip_layers.append({"packages": list(packages), "pre": pre})
146
+ return self
147
+
148
+ def torch_jit(self, fn: F) -> F:
149
+ """Record the function's qualname; JIT-compile only on the worker.
150
+
151
+ In discovery (submit) this is a no-op marker — it records the qualname and
152
+ returns ``fn`` unchanged, so no ``torch`` import happens locally. In
153
+ execution mode (the backend runner, which has the heavy runtime) it
154
+ replaces ``@torch.jit.script``: ``torch`` is imported lazily inside the
155
+ method so module load stays lean.
156
+ """
157
+ self._torch_jit_marks.append(fn.__qualname__)
158
+ if current_mode() == EXECUTION:
159
+ # Lazy import via importlib (only the worker has torch). importlib keeps
160
+ # module load lean AND avoids a static ``import torch`` that a torch-free
161
+ # type-check would flag.
162
+ script = getattr(importlib.import_module("torch.jit"), "script")
163
+ return cast(F, script(fn))
164
+ return fn
165
+
166
+ def imports(self) -> AbstractContextManager[None]:
167
+ """Remote-only-import boundary.
168
+
169
+ In execution mode this is a plain ``nullcontext`` (the imports really
170
+ happen). In discovery mode it installs a stub import finder that records
171
+ the deferred top-level imports without resolving them — how heavy
172
+ libraries (``torch`` and friends) stay off your machine at submit.
173
+ """
174
+ if current_mode() == DISCOVERY:
175
+ return stub_import_context(self._remote_imports)
176
+ return nullcontext()
177
+
178
+
179
+ #: The Simulo runtime every :class:`simulo.App` executes in unless it picks a
180
+ #: different one. Simulo-owned and prebuilt — it ships the full simulation +
181
+ #: GPU training stack, so the common path declares no runtime at all. Shared
182
+ #: across every App that omits ``runtime=``: an app that wants its own
183
+ #: ``env()`` / ``pip_install()`` layers should construct its own Runtime via
184
+ #: :meth:`Runtime.from_registry` rather than mutating this shared default.
185
+ DEFAULT_RUNTIME = Runtime.from_registry("simulo/gpu-rl:2026.06")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: simulo
3
- Version: 0.13.2
3
+ Version: 0.14.0
4
4
  Summary: Simulo SDK and CLI — define robotics simulation and training apps in Python and run them on the Simulo cloud.
5
5
  Author-email: Simulo Team <team@simulo.ai>
6
6
  License: BSD-3-Clause
@@ -15,7 +15,7 @@ Classifier: Programming Language :: Python :: 3.12
15
15
  Classifier: Typing :: Typed
16
16
  Requires-Python: >=3.11
17
17
  Description-Content-Type: text/markdown
18
- Requires-Dist: simulo-interfaces<0.6,>=0.5
18
+ Requires-Dist: simulo-interfaces<0.7,>=0.6
19
19
  Requires-Dist: mcap<2,>=1.3
20
20
  Requires-Dist: defusedxml>=0.7.1
21
21
  Requires-Dist: usd-core<27,>=25.5
@@ -1,4 +1,4 @@
1
- simulo-interfaces<0.6,>=0.5
1
+ simulo-interfaces<0.7,>=0.6
2
2
  mcap<2,>=1.3
3
3
  defusedxml>=0.7.1
4
4
  usd-core<27,>=25.5
@@ -1,90 +0,0 @@
1
- """``Runtime`` — a Simulo-curated runtime (implements ``RuntimeProtocol``).
2
-
3
- Simulo owns the runtime: every job executes in a prebuilt, Simulo-managed
4
- environment that ships everything a robotics training job needs — the
5
- simulation engine, GPU stack, and the scientific-Python toolchain. You never
6
- bring your own image, dependencies, or registry; you *pick* a Simulo runtime
7
- (today there is one, :data:`DEFAULT_RUNTIME`, and it is the default — the
8
- common path writes nothing).
9
-
10
- Locally this class is a lean metadata recorder: ``from_registry`` names the
11
- Simulo runtime, ``torch_jit`` is a no-op marker at submit (and real
12
- ``torch.jit.script`` on the worker), and ``imports`` is the remote-only-import
13
- boundary (Rule #3) — a real ``nullcontext`` in execution mode, a
14
- stub-installing context manager in discovery mode.
15
- """
16
-
17
- import importlib
18
- from contextlib import AbstractContextManager, nullcontext
19
- from typing import Callable, TypeVar, cast
20
-
21
- from simulo._client.discovery import stub_import_context
22
- from simulo._client.mode import DISCOVERY, EXECUTION, current_mode
23
-
24
- F = TypeVar("F", bound=Callable[..., object])
25
-
26
-
27
- class Runtime:
28
- """A Simulo-curated runtime a job executes in. Torch-free metadata recorder.
29
-
30
- You normally never construct one: every :class:`simulo.App` defaults to
31
- :data:`DEFAULT_RUNTIME`. As the curated lineup grows, pick a different
32
- Simulo runtime by name::
33
-
34
- runtime = simulo.Runtime.from_registry("simulo/gpu-rl:2026.06")
35
- app = simulo.App("cartpole", runtime=runtime)
36
-
37
- Nothing is installed on your machine — the runtime is Simulo-built and
38
- lives in the cloud; submit only records which one the app picked.
39
- """
40
-
41
- def __init__(self, runtime_id: str) -> None:
42
- self._runtime_id = runtime_id
43
- self._remote_imports: list[str] = []
44
- self._torch_jit_marks: list[str] = []
45
-
46
- @classmethod
47
- def from_registry(cls, image: str) -> "Runtime":
48
- """Pick a Simulo runtime by name (e.g. ``"simulo/gpu-rl:2026.06"``)."""
49
- return cls(runtime_id=image)
50
-
51
- @property
52
- def runtime_id(self) -> str:
53
- """The Simulo runtime name passed to :meth:`from_registry`."""
54
- return self._runtime_id
55
-
56
- def torch_jit(self, fn: F) -> F:
57
- """Record the function's qualname; JIT-compile only on the worker.
58
-
59
- In discovery (submit) this is a no-op marker — it records the qualname and
60
- returns ``fn`` unchanged, so no ``torch`` import happens locally. In
61
- execution mode (the backend runner, which has the heavy runtime) it
62
- replaces ``@torch.jit.script``: ``torch`` is imported lazily inside the
63
- method so module load stays lean.
64
- """
65
- self._torch_jit_marks.append(fn.__qualname__)
66
- if current_mode() == EXECUTION:
67
- # Lazy import via importlib (only the worker has torch). importlib keeps
68
- # module load lean AND avoids a static ``import torch`` that a torch-free
69
- # type-check would flag.
70
- script = getattr(importlib.import_module("torch.jit"), "script")
71
- return cast(F, script(fn))
72
- return fn
73
-
74
- def imports(self) -> AbstractContextManager[None]:
75
- """Remote-only-import boundary.
76
-
77
- In execution mode this is a plain ``nullcontext`` (the imports really
78
- happen). In discovery mode it installs a stub import finder that records
79
- the deferred top-level imports without resolving them — how heavy
80
- libraries (``torch`` and friends) stay off your machine at submit.
81
- """
82
- if current_mode() == DISCOVERY:
83
- return stub_import_context(self._remote_imports)
84
- return nullcontext()
85
-
86
-
87
- #: The Simulo runtime every :class:`simulo.App` executes in unless it picks a
88
- #: different one. Simulo-owned and prebuilt — it ships the full simulation +
89
- #: GPU training stack, so the common path declares no runtime at all.
90
- DEFAULT_RUNTIME = Runtime.from_registry("simulo/gpu-rl:2026.06")
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes