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.
- {simulo-0.13.2/src/simulo.egg-info → simulo-0.14.0}/PKG-INFO +2 -2
- {simulo-0.13.2 → simulo-0.14.0}/pyproject.toml +2 -2
- {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/bundle.py +24 -0
- {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/manifest.py +8 -2
- simulo-0.14.0/src/simulo/_client/runtime.py +185 -0
- {simulo-0.13.2 → simulo-0.14.0/src/simulo.egg-info}/PKG-INFO +2 -2
- {simulo-0.13.2 → simulo-0.14.0}/src/simulo.egg-info/requires.txt +1 -1
- simulo-0.13.2/src/simulo/_client/runtime.py +0 -90
- {simulo-0.13.2 → simulo-0.14.0}/MANIFEST.in +0 -0
- {simulo-0.13.2 → simulo-0.14.0}/PYPI.md +0 -0
- {simulo-0.13.2 → simulo-0.14.0}/setup.cfg +0 -0
- {simulo-0.13.2 → simulo-0.14.0}/src/simulo/__init__.py +0 -0
- {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/__init__.py +0 -0
- {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/_entrypoint.py +0 -0
- {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/_mounts.py +0 -0
- {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/_runner.py +0 -0
- {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/app.py +0 -0
- {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/asset.py +0 -0
- {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/asset_api.py +0 -0
- {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/asset_package.py +0 -0
- {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/asset_pins.py +0 -0
- {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/builtin_aliases.py +0 -0
- {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/cancel_api.py +0 -0
- {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/cli.py +0 -0
- {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/config.py +0 -0
- {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/credentials.py +0 -0
- {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/discovery.py +0 -0
- {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/http.py +0 -0
- {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/jobs_api.py +0 -0
- {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/learning.py +0 -0
- {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/login.py +0 -0
- {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/mode.py +0 -0
- {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/packaging.py +0 -0
- {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/registry.py +0 -0
- {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/runtime_display.py +0 -0
- {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/seed_ref.py +0 -0
- {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/stub.py +0 -0
- {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/submit_api.py +0 -0
- {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/templates/__init__.py +0 -0
- {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/templates/inference/app.py.tmpl +0 -0
- {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/templates/inference/simuloignore.tmpl +0 -0
- {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/templates/scenario/app.py.tmpl +0 -0
- {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/templates/scenario/simuloignore.tmpl +0 -0
- {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/templates/training/app.py.tmpl +0 -0
- {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/templates/training/simuloignore.tmpl +0 -0
- {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/view_session_api.py +0 -0
- {simulo-0.13.2 → simulo-0.14.0}/src/simulo/_client/volume.py +0 -0
- {simulo-0.13.2 → simulo-0.14.0}/src/simulo/callbacks.py +0 -0
- {simulo-0.13.2 → simulo-0.14.0}/src/simulo/py.typed +0 -0
- {simulo-0.13.2 → simulo-0.14.0}/src/simulo.egg-info/SOURCES.txt +0 -0
- {simulo-0.13.2 → simulo-0.14.0}/src/simulo.egg-info/dependency_links.txt +0 -0
- {simulo-0.13.2 → simulo-0.14.0}/src/simulo.egg-info/entry_points.txt +0 -0
- {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.
|
|
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.
|
|
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.
|
|
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.
|
|
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,
|
|
6
|
-
|
|
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.
|
|
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.
|
|
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,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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|