simulo 0.6.0rc1__tar.gz → 0.7.1__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.7.1/MANIFEST.in +9 -0
- simulo-0.7.1/PKG-INFO +87 -0
- simulo-0.7.1/PYPI.md +56 -0
- simulo-0.7.1/pyproject.toml +98 -0
- simulo-0.7.1/src/simulo/__init__.py +154 -0
- simulo-0.7.1/src/simulo/_client/__init__.py +6 -0
- simulo-0.7.1/src/simulo/_client/_entrypoint.py +104 -0
- simulo-0.7.1/src/simulo/_client/_mounts.py +25 -0
- simulo-0.7.1/src/simulo/_client/_runner.py +171 -0
- simulo-0.7.1/src/simulo/_client/app.py +440 -0
- simulo-0.7.1/src/simulo/_client/asset.py +119 -0
- simulo-0.7.1/src/simulo/_client/bundle.py +157 -0
- simulo-0.7.1/src/simulo/_client/cli.py +1076 -0
- simulo-0.7.1/src/simulo/_client/config.py +136 -0
- simulo-0.7.1/src/simulo/_client/credentials.py +175 -0
- simulo-0.7.1/src/simulo/_client/discovery.py +214 -0
- simulo-0.7.1/src/simulo/_client/http.py +191 -0
- simulo-0.7.1/src/simulo/_client/jobs_api.py +160 -0
- simulo-0.7.1/src/simulo/_client/learning.py +189 -0
- simulo-0.7.1/src/simulo/_client/login.py +282 -0
- simulo-0.7.1/src/simulo/_client/manifest.py +56 -0
- simulo-0.7.1/src/simulo/_client/mode.py +29 -0
- simulo-0.7.1/src/simulo/_client/packaging.py +283 -0
- simulo-0.7.1/src/simulo/_client/registry.py +85 -0
- simulo-0.7.1/src/simulo/_client/runtime.py +189 -0
- simulo-0.7.1/src/simulo/_client/stub.py +41 -0
- simulo-0.7.1/src/simulo/_client/submit_api.py +291 -0
- simulo-0.7.1/src/simulo/_client/volume.py +52 -0
- simulo-0.7.1/src/simulo/callbacks.py +249 -0
- simulo-0.7.1/src/simulo.egg-info/PKG-INFO +87 -0
- simulo-0.7.1/src/simulo.egg-info/SOURCES.txt +35 -0
- simulo-0.7.1/src/simulo.egg-info/entry_points.txt +2 -0
- simulo-0.7.1/src/simulo.egg-info/requires.txt +15 -0
- simulo-0.7.1/src/simulo.egg-info/top_level.txt +1 -0
- simulo-0.6.0rc1/LICENSE +0 -29
- simulo-0.6.0rc1/PKG-INFO +0 -80
- simulo-0.6.0rc1/README.md +0 -38
- simulo-0.6.0rc1/cli/src/simulo_cli/__init__.py +0 -1
- simulo-0.6.0rc1/cli/src/simulo_cli/__main__.py +0 -6
- simulo-0.6.0rc1/cli/src/simulo_cli/api/__init__.py +0 -1
- simulo-0.6.0rc1/cli/src/simulo_cli/api/client.py +0 -98
- simulo-0.6.0rc1/cli/src/simulo_cli/auth/__init__.py +0 -1
- simulo-0.6.0rc1/cli/src/simulo_cli/auth/credentials.py +0 -57
- simulo-0.6.0rc1/cli/src/simulo_cli/cli.py +0 -33
- simulo-0.6.0rc1/cli/src/simulo_cli/commands/__init__.py +0 -1
- simulo-0.6.0rc1/cli/src/simulo_cli/commands/auth.py +0 -272
- simulo-0.6.0rc1/cli/src/simulo_cli/commands/org.py +0 -29
- simulo-0.6.0rc1/cli/src/simulo_cli/config.py +0 -138
- simulo-0.6.0rc1/pyproject.toml +0 -181
- simulo-0.6.0rc1/sdk/src/simulo/__init__.py +0 -162
- simulo-0.6.0rc1/sdk/src/simulo/backend/__init__.py +0 -1
- simulo-0.6.0rc1/sdk/src/simulo/backend/isaaclab/__init__.py +0 -5
- simulo-0.6.0rc1/sdk/src/simulo/backend/isaaclab/asset_registry.py +0 -365
- simulo-0.6.0rc1/sdk/src/simulo/backend/isaaclab/backend.py +0 -227
- simulo-0.6.0rc1/sdk/src/simulo/backend/isaaclab/scene_builder.py +0 -2237
- simulo-0.6.0rc1/sdk/src/simulo/backend/isaaclab/stream_adapter.py +0 -182
- simulo-0.6.0rc1/sdk/src/simulo/core/__init__.py +0 -88
- simulo-0.6.0rc1/sdk/src/simulo/core/actuator.py +0 -314
- simulo-0.6.0rc1/sdk/src/simulo/core/asset.py +0 -63
- simulo-0.6.0rc1/sdk/src/simulo/core/controller.py +0 -1012
- simulo-0.6.0rc1/sdk/src/simulo/core/entity.py +0 -180
- simulo-0.6.0rc1/sdk/src/simulo/core/env.py +0 -553
- simulo-0.6.0rc1/sdk/src/simulo/core/light.py +0 -84
- simulo-0.6.0rc1/sdk/src/simulo/core/materials.py +0 -61
- simulo-0.6.0rc1/sdk/src/simulo/core/math_utils.py +0 -74
- simulo-0.6.0rc1/sdk/src/simulo/core/observations.py +0 -481
- simulo-0.6.0rc1/sdk/src/simulo/core/physics.py +0 -154
- simulo-0.6.0rc1/sdk/src/simulo/core/player.py +0 -604
- simulo-0.6.0rc1/sdk/src/simulo/core/policy.py +0 -245
- simulo-0.6.0rc1/sdk/src/simulo/core/pose.py +0 -99
- simulo-0.6.0rc1/sdk/src/simulo/core/primitives.py +0 -132
- simulo-0.6.0rc1/sdk/src/simulo/core/rewards.py +0 -798
- simulo-0.6.0rc1/sdk/src/simulo/core/robot.py +0 -1041
- simulo-0.6.0rc1/sdk/src/simulo/core/scene.py +0 -462
- simulo-0.6.0rc1/sdk/src/simulo/core/sensor.py +0 -923
- simulo-0.6.0rc1/sdk/src/simulo/core/task.py +0 -215
- simulo-0.6.0rc1/sdk/src/simulo/core/terminations.py +0 -326
- simulo-0.6.0rc1/sdk/src/simulo/core/terrain.py +0 -380
- simulo-0.6.0rc1/sdk/src/simulo/core/trainer.py +0 -437
- simulo-0.6.0rc1/sdk/src/simulo/core/visual.py +0 -302
- simulo-0.6.0rc1/sdk/src/simulo/core/world.py +0 -138
- simulo-0.6.0rc1/sdk/src/simulo/py.typed +0 -0
- simulo-0.6.0rc1/sdk/src/simulo/scenario.py +0 -196
- simulo-0.6.0rc1/sdk/src/simulo/streaming/__init__.py +0 -31
- simulo-0.6.0rc1/sdk/src/simulo/streaming/adapter.py +0 -209
- simulo-0.6.0rc1/sdk/src/simulo/streaming/config.py +0 -90
- simulo-0.6.0rc1/sdk/src/simulo/streaming/types.py +0 -103
- simulo-0.6.0rc1/simulo.egg-info/PKG-INFO +0 -80
- simulo-0.6.0rc1/simulo.egg-info/SOURCES.txt +0 -59
- simulo-0.6.0rc1/simulo.egg-info/entry_points.txt +0 -2
- simulo-0.6.0rc1/simulo.egg-info/requires.txt +0 -22
- simulo-0.6.0rc1/simulo.egg-info/top_level.txt +0 -2
- {simulo-0.6.0rc1 → simulo-0.7.1}/setup.cfg +0 -0
- {simulo-0.6.0rc1/cli/src/simulo_cli → simulo-0.7.1/src/simulo}/py.typed +0 -0
- {simulo-0.6.0rc1 → simulo-0.7.1/src}/simulo.egg-info/dependency_links.txt +0 -0
simulo-0.7.1/MANIFEST.in
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Public-metadata discretion boundary (see release.yml Guard 5): the sdist is
|
|
2
|
+
# PUBLIC on PyPI's "Download files" page just like the wheel, but setuptools'
|
|
3
|
+
# sdist command auto-includes README.md and any top-level tests/ directory
|
|
4
|
+
# regardless of the `readme = "PYPI.md"` pyproject setting. Exclude the
|
|
5
|
+
# internal docs and test suite explicitly so only PYPI.md ships as prose.
|
|
6
|
+
exclude README.md
|
|
7
|
+
exclude CLAUDE.md
|
|
8
|
+
exclude CHANGELOG.md
|
|
9
|
+
prune tests
|
simulo-0.7.1/PKG-INFO
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: simulo
|
|
3
|
+
Version: 0.7.1
|
|
4
|
+
Summary: Simulo SDK and CLI — define robotics simulation and training apps in Python and run them on the Simulo cloud.
|
|
5
|
+
Author-email: Simulo Team <team@simulo.ai>
|
|
6
|
+
License: BSD-3-Clause
|
|
7
|
+
Project-URL: Homepage, https://simulo.ai
|
|
8
|
+
Keywords: robotics,simulation,reinforcement-learning,simulo
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Typing :: Typed
|
|
16
|
+
Requires-Python: >=3.11
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
Requires-Dist: simulo-interfaces<0.2,>=0.1
|
|
19
|
+
Provides-Extra: dev
|
|
20
|
+
Requires-Dist: black>=24.0; extra == "dev"
|
|
21
|
+
Requires-Dist: isort>=5.13; extra == "dev"
|
|
22
|
+
Requires-Dist: ruff>=0.5; extra == "dev"
|
|
23
|
+
Requires-Dist: mypy>=1.10; extra == "dev"
|
|
24
|
+
Requires-Dist: pytest>=8.2; extra == "dev"
|
|
25
|
+
Requires-Dist: build>=1.2; extra == "dev"
|
|
26
|
+
Requires-Dist: twine>=5.1; extra == "dev"
|
|
27
|
+
Provides-Extra: release
|
|
28
|
+
Requires-Dist: commitizen>=3.27; extra == "release"
|
|
29
|
+
Requires-Dist: build>=1.2; extra == "release"
|
|
30
|
+
Requires-Dist: twine>=5.1; extra == "release"
|
|
31
|
+
|
|
32
|
+
# simulo
|
|
33
|
+
|
|
34
|
+
The Simulo SDK and CLI — define robotics simulation and training apps in Python,
|
|
35
|
+
submit them to the Simulo cloud, monitor jobs, and retrieve trained models.
|
|
36
|
+
|
|
37
|
+
Simulo is a cloud-first robotics simulation and training platform. Write your app
|
|
38
|
+
against a small, stable Python API; Simulo handles running it.
|
|
39
|
+
|
|
40
|
+
## Install
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install simulo
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Requires Python 3.11+.
|
|
47
|
+
|
|
48
|
+
## Quickstart
|
|
49
|
+
|
|
50
|
+
Log in once to submit to the Simulo cloud:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
simulo login
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Write an app:
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
# app.py
|
|
60
|
+
import simulo
|
|
61
|
+
|
|
62
|
+
runtime = simulo.Runtime.from_registry("simulo/python-cpu:3.11")
|
|
63
|
+
app = simulo.App("hello", runtime=runtime)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@app.job(timeout=5 * 60)
|
|
67
|
+
def hello(name: str = "world", repeat: int = 3) -> dict:
|
|
68
|
+
for i in range(repeat):
|
|
69
|
+
print(f"[hello] {i + 1}/{repeat}: hello, {name}!")
|
|
70
|
+
return {"greeting": f"hello, {name}", "repeat": repeat}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@app.local_entrypoint
|
|
74
|
+
def main(name: str = "world", repeat: int = 3) -> None:
|
|
75
|
+
handle = hello.spawn(name=name, repeat=repeat)
|
|
76
|
+
print(f"Submitted job: {handle.job_name}")
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Run it:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
simulo run app.py --name robot --repeat 5
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Without logging in, `simulo run` packages the app locally without submitting it.
|
|
86
|
+
|
|
87
|
+
Learn more at [simulo.ai](https://simulo.ai).
|
simulo-0.7.1/PYPI.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# simulo
|
|
2
|
+
|
|
3
|
+
The Simulo SDK and CLI — define robotics simulation and training apps in Python,
|
|
4
|
+
submit them to the Simulo cloud, monitor jobs, and retrieve trained models.
|
|
5
|
+
|
|
6
|
+
Simulo is a cloud-first robotics simulation and training platform. Write your app
|
|
7
|
+
against a small, stable Python API; Simulo handles running it.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install simulo
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Requires Python 3.11+.
|
|
16
|
+
|
|
17
|
+
## Quickstart
|
|
18
|
+
|
|
19
|
+
Log in once to submit to the Simulo cloud:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
simulo login
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Write an app:
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
# app.py
|
|
29
|
+
import simulo
|
|
30
|
+
|
|
31
|
+
runtime = simulo.Runtime.from_registry("simulo/python-cpu:3.11")
|
|
32
|
+
app = simulo.App("hello", runtime=runtime)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@app.job(timeout=5 * 60)
|
|
36
|
+
def hello(name: str = "world", repeat: int = 3) -> dict:
|
|
37
|
+
for i in range(repeat):
|
|
38
|
+
print(f"[hello] {i + 1}/{repeat}: hello, {name}!")
|
|
39
|
+
return {"greeting": f"hello, {name}", "repeat": repeat}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@app.local_entrypoint
|
|
43
|
+
def main(name: str = "world", repeat: int = 3) -> None:
|
|
44
|
+
handle = hello.spawn(name=name, repeat=repeat)
|
|
45
|
+
print(f"Submitted job: {handle.job_name}")
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Run it:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
simulo run app.py --name robot --repeat 5
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Without logging in, `simulo run` packages the app locally without submitting it.
|
|
55
|
+
|
|
56
|
+
Learn more at [simulo.ai](https://simulo.ai).
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "simulo"
|
|
7
|
+
version = "0.7.1"
|
|
8
|
+
description = "Simulo SDK and CLI — define robotics simulation and training apps in Python and run them on the Simulo cloud."
|
|
9
|
+
readme = "PYPI.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = { text = "BSD-3-Clause" }
|
|
12
|
+
authors = [{ name = "Simulo Team", email = "team@simulo.ai" }]
|
|
13
|
+
keywords = ["robotics", "simulation", "reinforcement-learning", "simulo"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: BSD License",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Programming Language :: Python :: 3.11",
|
|
20
|
+
"Programming Language :: Python :: 3.12",
|
|
21
|
+
"Typing :: Typed",
|
|
22
|
+
]
|
|
23
|
+
# Torch-free by construction: depends only on the contract layer. Heavy
|
|
24
|
+
# imports (e.g. torch) belong inside the user's `runtime.imports()` guard,
|
|
25
|
+
# resolved only at job-execution time — never in this distribution.
|
|
26
|
+
dependencies = ["simulo-interfaces>=0.1,<0.2"]
|
|
27
|
+
|
|
28
|
+
[project.optional-dependencies]
|
|
29
|
+
dev = ["black>=24.0", "isort>=5.13", "ruff>=0.5", "mypy>=1.10", "pytest>=8.2", "build>=1.2", "twine>=5.1"]
|
|
30
|
+
# `dev` carries build/twine but deliberately not commitizen; `release` below
|
|
31
|
+
# adds commitizen for cutting version bumps without a second build/twine copy.
|
|
32
|
+
release = ["commitizen>=3.27", "build>=1.2", "twine>=5.1"]
|
|
33
|
+
|
|
34
|
+
[project.scripts]
|
|
35
|
+
simulo = "simulo._client.cli:main"
|
|
36
|
+
|
|
37
|
+
[project.urls]
|
|
38
|
+
Homepage = "https://simulo.ai"
|
|
39
|
+
|
|
40
|
+
# Namespace packaging: this is the only distribution that ships
|
|
41
|
+
# `src/simulo/__init__.py`, so sibling `simulo.*` distributions can share the
|
|
42
|
+
# top-level `simulo` name — do not add another top-level __init__.py to a
|
|
43
|
+
# sibling distribution. `namespaces = true` keeps setuptools from choking on
|
|
44
|
+
# the shared top-level name.
|
|
45
|
+
[tool.setuptools.packages.find]
|
|
46
|
+
where = ["src"]
|
|
47
|
+
namespaces = true
|
|
48
|
+
|
|
49
|
+
[tool.setuptools.package-data]
|
|
50
|
+
"simulo" = ["py.typed"]
|
|
51
|
+
|
|
52
|
+
[tool.mypy]
|
|
53
|
+
python_version = "3.11"
|
|
54
|
+
mypy_path = ["src"]
|
|
55
|
+
namespace_packages = true
|
|
56
|
+
explicit_package_bases = true
|
|
57
|
+
strict = true
|
|
58
|
+
|
|
59
|
+
[tool.pytest.ini_options]
|
|
60
|
+
testpaths = ["tests"]
|
|
61
|
+
addopts = "--import-mode=importlib"
|
|
62
|
+
markers = [
|
|
63
|
+
"isaac: requires NVIDIA Isaac Sim / IsaacLab (skipped on dev machines and in PR CI)",
|
|
64
|
+
"gpu: requires a CUDA GPU (skipped on dev machines and in PR CI)",
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
[tool.isort]
|
|
68
|
+
profile = "black"
|
|
69
|
+
line_length = 120
|
|
70
|
+
known_first_party = ["simulo"]
|
|
71
|
+
|
|
72
|
+
[tool.ruff]
|
|
73
|
+
line-length = 120
|
|
74
|
+
src = ["src"]
|
|
75
|
+
|
|
76
|
+
[tool.black]
|
|
77
|
+
line-length = 120
|
|
78
|
+
target-version = ["py311"]
|
|
79
|
+
|
|
80
|
+
# --- Commitizen ------------------------------------------------------------
|
|
81
|
+
# `cz bump` creates the version-bump commit + tag locally; CI publishes to
|
|
82
|
+
# PyPI on tag push. version_provider = "pep621" reads/writes [project].version
|
|
83
|
+
# directly; there is no in-tree __version__ mirror to keep in sync
|
|
84
|
+
# (version_files = []).
|
|
85
|
+
#
|
|
86
|
+
# The FIRST release (0.7.0) was a hand-written bump commit, not `cz bump` —
|
|
87
|
+
# 0.1.0 -> 0.7.0 is not a conventional-commit increment. 0.7.0 (not 0.2.0) is
|
|
88
|
+
# deliberate: it intentionally exceeds a version already published under this
|
|
89
|
+
# same PyPI project name, so `pip install simulo` resolves to this package.
|
|
90
|
+
[tool.commitizen]
|
|
91
|
+
name = "cz_conventional_commits"
|
|
92
|
+
version_provider = "pep621"
|
|
93
|
+
version_files = []
|
|
94
|
+
tag_format = "simulo-v$version"
|
|
95
|
+
update_changelog_on_bump = true
|
|
96
|
+
changelog_incremental = true
|
|
97
|
+
major_version_zero = true
|
|
98
|
+
bump_message = "chore(release): bump simulo to v$new_version"
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""Simulo thin client — the user-facing, torch-free SDK surface.
|
|
2
|
+
|
|
3
|
+
This is the ONE Simulo distribution that ships ``simulo/__init__.py``. The
|
|
4
|
+
``extend_path`` call below unions every ``simulo/`` directory on ``sys.path`` into
|
|
5
|
+
this package's ``__path__`` so the PEP 420 namespace portions shipped by sibling
|
|
6
|
+
distributions (``simulo.core`` / ``simulo.backend`` / ``simulo.recording`` from
|
|
7
|
+
``simulo-backend``; ``simulo.interfaces`` from ``simulo-interfaces``) resolve
|
|
8
|
+
alongside the clean top-level ``simulo.App`` re-exported here.
|
|
9
|
+
|
|
10
|
+
It MUST run before the re-exports below, because importing ``simulo._client.app``
|
|
11
|
+
pulls in ``simulo.interfaces`` (a sibling distribution).
|
|
12
|
+
|
|
13
|
+
Two kinds of name live on ``simulo``:
|
|
14
|
+
|
|
15
|
+
* **Platform** names — ``App / Runtime / Volume / Asset / callbacks`` — are **real,
|
|
16
|
+
eager** attributes bound below. They are always torch-free.
|
|
17
|
+
* **Learning** names — ``Task / Scene / Robot / LearningEnv / RLTrainer / …`` — are
|
|
18
|
+
resolved **lazily and mode-aware** through the PEP 562 ``__getattr__`` below,
|
|
19
|
+
which delegates to :mod:`simulo._client.learning`. At submit (discovery mode)
|
|
20
|
+
they resolve to lean stand-ins so the user app imports without the heavy runtime;
|
|
21
|
+
only the backend runner (execution mode) resolves them to ``simulo.core``. Because
|
|
22
|
+
the platform names are real attributes, ``__getattr__`` fires *only* for the
|
|
23
|
+
learning names.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
import pkgutil as _pkgutil
|
|
27
|
+
from typing import TYPE_CHECKING
|
|
28
|
+
|
|
29
|
+
__path__ = _pkgutil.extend_path(__path__, __name__)
|
|
30
|
+
|
|
31
|
+
from simulo import callbacks
|
|
32
|
+
from simulo._client.app import App
|
|
33
|
+
from simulo._client.asset import Asset
|
|
34
|
+
from simulo._client.learning import LEARNING_NAMES as _LEARNING_NAMES
|
|
35
|
+
from simulo._client.learning import resolve as _resolve_learning
|
|
36
|
+
from simulo._client.runtime import Runtime
|
|
37
|
+
from simulo._client.volume import Volume
|
|
38
|
+
|
|
39
|
+
# Public surface = eager platform exports (real attributes; ``__getattr__`` never
|
|
40
|
+
# fires for these) + the lazily-resolved learning names. Kept a static literal so
|
|
41
|
+
# tooling recognises the re-exports; a test guards it against ``LEARNING_NAMES`` drift.
|
|
42
|
+
__all__ = [
|
|
43
|
+
# platform (eager, real)
|
|
44
|
+
"App",
|
|
45
|
+
"Runtime",
|
|
46
|
+
"Volume",
|
|
47
|
+
"Asset",
|
|
48
|
+
"callbacks",
|
|
49
|
+
# learning (lazy, mode-aware)
|
|
50
|
+
"Entity",
|
|
51
|
+
"LearningEnv",
|
|
52
|
+
"Light",
|
|
53
|
+
"Material",
|
|
54
|
+
"ObservationComponent",
|
|
55
|
+
"Physics",
|
|
56
|
+
"Player",
|
|
57
|
+
"Policy",
|
|
58
|
+
"Pose",
|
|
59
|
+
"RLPlayer",
|
|
60
|
+
"RLTrainer",
|
|
61
|
+
"RecordConfig",
|
|
62
|
+
"RewardComponent",
|
|
63
|
+
"Robot",
|
|
64
|
+
"Scenario",
|
|
65
|
+
"Scene",
|
|
66
|
+
"Task",
|
|
67
|
+
"Terrain",
|
|
68
|
+
"TerminationCondition",
|
|
69
|
+
"Trainer",
|
|
70
|
+
"Visual",
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def __getattr__(name: str) -> object:
|
|
75
|
+
"""PEP 562 hook: lazily resolve the mode-aware learning names.
|
|
76
|
+
|
|
77
|
+
Fires only for attributes missing from the module dict — i.e. never for the
|
|
78
|
+
real platform exports above. Delegates the learning names to
|
|
79
|
+
:func:`simulo._client.learning.resolve` (discovery stand-in vs. real
|
|
80
|
+
``simulo.core`` class, by mode); everything else is a genuine ``AttributeError``.
|
|
81
|
+
"""
|
|
82
|
+
if name in _LEARNING_NAMES:
|
|
83
|
+
return _resolve_learning(name)
|
|
84
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def __dir__() -> list[str]:
|
|
88
|
+
"""Expose only the public surface to ``dir(simulo)``.
|
|
89
|
+
|
|
90
|
+
That is ``__all__`` (eager platform exports + learning names) unioned with
|
|
91
|
+
:data:`LEARNING_NAMES`. Private module globals (``_pkgutil``,
|
|
92
|
+
``_resolve_learning``, ``_LEARNING_NAMES``, the ``TYPE_CHECKING`` import, …)
|
|
93
|
+
are deliberately filtered out so tab-completion shows the user-facing API only.
|
|
94
|
+
"""
|
|
95
|
+
return sorted(set(__all__) | set(_LEARNING_NAMES))
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
if TYPE_CHECKING:
|
|
99
|
+
# Static aliases so a type checker sees the learning names with precise types
|
|
100
|
+
# (PEP 562 ``__getattr__`` would otherwise type them as ``Any``). The rule that
|
|
101
|
+
# this block MUST follow — and that the mypy positive test in
|
|
102
|
+
# ``tests/test_learning_typing.py`` locks — is:
|
|
103
|
+
#
|
|
104
|
+
# * **Subclassable** names (used as ``class X(simulo.Task)``) alias their
|
|
105
|
+
# ``interfaces.runtime`` Protocol, so the subclass is checked against the
|
|
106
|
+
# contract (e.g. ``TaskProtocol``).
|
|
107
|
+
# * **Instantiated / value-config** names (used as ``simulo.LearningEnv(task=...)``,
|
|
108
|
+
# ``simulo.RLTrainer(env=...)``, ``simulo.Scene(...)``) alias ``Any``. Their
|
|
109
|
+
# concrete type is the heavy ``simulo.core`` class, available only on the
|
|
110
|
+
# worker — aliasing them to a Protocol would make mypy reject instantiation
|
|
111
|
+
# ("Cannot instantiate protocol class").
|
|
112
|
+
from typing import Any
|
|
113
|
+
|
|
114
|
+
from simulo.interfaces.runtime import (
|
|
115
|
+
ObservationComponentProtocol,
|
|
116
|
+
PlayerProtocol,
|
|
117
|
+
PolicyProtocol,
|
|
118
|
+
RewardComponentProtocol,
|
|
119
|
+
ScenarioProtocol,
|
|
120
|
+
TaskProtocol,
|
|
121
|
+
TerminationConditionProtocol,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
# Subclassable names alias their Protocol so ``class X(simulo.Task)`` is checked
|
|
125
|
+
# against ``TaskProtocol``.
|
|
126
|
+
Task = TaskProtocol
|
|
127
|
+
Scenario = ScenarioProtocol
|
|
128
|
+
Policy = PolicyProtocol
|
|
129
|
+
Player = PlayerProtocol
|
|
130
|
+
ObservationComponent = ObservationComponentProtocol
|
|
131
|
+
RewardComponent = RewardComponentProtocol
|
|
132
|
+
TerminationCondition = TerminationConditionProtocol
|
|
133
|
+
# Instantiated / value-config names: concrete type is the heavy ``simulo.core``
|
|
134
|
+
# (or ``simulo.recording``, for ``RecordConfig``) class, available only on the
|
|
135
|
+
# worker; alias ``Any`` so mypy accepts ``simulo.Name(...)``.
|
|
136
|
+
# ``LearningEnv`` is here — ``simulo.LearningEnv(task=...)`` is instantiated, so
|
|
137
|
+
# it must NOT alias ``LearningEnvProtocol`` (mypy would reject the call).
|
|
138
|
+
# ``RLPlayer``/``RecordConfig`` are instantiated too (``simulo.RLPlayer(env=...)``,
|
|
139
|
+
# ``simulo.RecordConfig(output_path=...)``), so they alias ``Any`` — only the
|
|
140
|
+
# subclassable ``Player`` base aliases ``PlayerProtocol``.
|
|
141
|
+
LearningEnv = Any
|
|
142
|
+
Scene = Any
|
|
143
|
+
Robot = Any
|
|
144
|
+
RLTrainer = Any
|
|
145
|
+
Trainer = Any
|
|
146
|
+
RLPlayer = Any
|
|
147
|
+
RecordConfig = Any
|
|
148
|
+
Pose = Any
|
|
149
|
+
Terrain = Any
|
|
150
|
+
Light = Any
|
|
151
|
+
Entity = Any
|
|
152
|
+
Material = Any
|
|
153
|
+
Physics = Any
|
|
154
|
+
Visual = Any
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"""Internal implementation package for the Simulo thin client.
|
|
2
|
+
|
|
3
|
+
Nothing here is part of the public API. Users import from the top-level
|
|
4
|
+
``simulo`` namespace (``simulo.App`` / ``simulo.Runtime`` / ``simulo.callbacks``);
|
|
5
|
+
these modules are the private machinery behind those re-exports.
|
|
6
|
+
"""
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""Run a ``@app.local_entrypoint`` from command-line arguments.
|
|
2
|
+
|
|
3
|
+
The local entrypoint is invoked by ``simulo run app.py`` — the canonical (and
|
|
4
|
+
only) submit command — which maps ``--flag value`` arguments onto the
|
|
5
|
+
entrypoint's parameters and ends at exactly one submit. (A direct ``python
|
|
6
|
+
app.py`` never reaches this module: :meth:`App.local_entrypoint` prints the
|
|
7
|
+
exact equivalent ``simulo run`` command to stderr and exits 2.)
|
|
8
|
+
|
|
9
|
+
An argument parser is built from the entrypoint's signature: each parameter
|
|
10
|
+
``foo_bar`` becomes ``--foo-bar`` (dash for underscore), coerced from the
|
|
11
|
+
parameter's default type, then its annotation. A parameter without a default is
|
|
12
|
+
required. This keeps authoring frictionless — ``def main(num_envs: int = 4096)``
|
|
13
|
+
just works as ``--num-envs 4096`` — with no argparse boilerplate in the app file.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import argparse
|
|
19
|
+
import inspect
|
|
20
|
+
from typing import Any, Callable, Mapping, Sequence
|
|
21
|
+
|
|
22
|
+
_STR_TO_TYPE: Mapping[str, type] = {"int": int, "float": float, "bool": bool, "str": str}
|
|
23
|
+
|
|
24
|
+
_SKIP_KINDS = (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _param_type(param: inspect.Parameter) -> type:
|
|
28
|
+
"""Best-effort scalar type for a parameter: default's type, then annotation.
|
|
29
|
+
|
|
30
|
+
A concrete (non-``None``) default is the most reliable signal — ``= 4096``
|
|
31
|
+
means ``int``. Otherwise fall back to the annotation (a real type, or a PEP
|
|
32
|
+
563 string like ``"int"``). Defaults to ``str`` when nothing is conclusive.
|
|
33
|
+
"""
|
|
34
|
+
default = param.default
|
|
35
|
+
if default is not inspect.Parameter.empty and default is not None:
|
|
36
|
+
return type(default)
|
|
37
|
+
ann = param.annotation
|
|
38
|
+
if isinstance(ann, type):
|
|
39
|
+
return ann
|
|
40
|
+
if isinstance(ann, str):
|
|
41
|
+
return _STR_TO_TYPE.get(ann, str)
|
|
42
|
+
return str
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _str_to_bool(raw: str) -> bool:
|
|
46
|
+
"""Parse a CLI ``--flag value`` boolean (``true``/``1``/``yes`` → ``True``)."""
|
|
47
|
+
lowered = raw.strip().lower()
|
|
48
|
+
if lowered in ("true", "1", "yes", "y", "on"):
|
|
49
|
+
return True
|
|
50
|
+
if lowered in ("false", "0", "no", "n", "off"):
|
|
51
|
+
return False
|
|
52
|
+
raise argparse.ArgumentTypeError(f"expected a boolean (true/false), got {raw!r}")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def build_parser(fn: Callable[..., object]) -> argparse.ArgumentParser:
|
|
56
|
+
"""Build an ``argparse`` parser from a local-entrypoint's signature."""
|
|
57
|
+
sig = inspect.signature(fn)
|
|
58
|
+
doc = (inspect.getdoc(fn) or "").strip().splitlines()
|
|
59
|
+
parser = argparse.ArgumentParser(
|
|
60
|
+
prog=getattr(fn, "__name__", "entrypoint"),
|
|
61
|
+
description=doc[0] if doc else None,
|
|
62
|
+
)
|
|
63
|
+
for name, param in sig.parameters.items():
|
|
64
|
+
if param.kind in _SKIP_KINDS:
|
|
65
|
+
continue
|
|
66
|
+
flag = "--" + name.replace("_", "-")
|
|
67
|
+
required = param.default is inspect.Parameter.empty
|
|
68
|
+
typ = _param_type(param)
|
|
69
|
+
if typ is bool:
|
|
70
|
+
parser.add_argument(
|
|
71
|
+
flag,
|
|
72
|
+
dest=name,
|
|
73
|
+
type=_str_to_bool,
|
|
74
|
+
required=required,
|
|
75
|
+
default=None if required else param.default,
|
|
76
|
+
metavar="BOOL",
|
|
77
|
+
)
|
|
78
|
+
else:
|
|
79
|
+
parser.add_argument(
|
|
80
|
+
flag,
|
|
81
|
+
dest=name,
|
|
82
|
+
type=typ,
|
|
83
|
+
required=required,
|
|
84
|
+
default=None if required else param.default,
|
|
85
|
+
)
|
|
86
|
+
return parser
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def parse_args(fn: Callable[..., object], argv: Sequence[str]) -> dict[str, Any]:
|
|
90
|
+
"""Parse ``argv`` against ``fn``'s signature into a keyword-argument dict."""
|
|
91
|
+
parser = build_parser(fn)
|
|
92
|
+
namespace = parser.parse_args(list(argv))
|
|
93
|
+
sig = inspect.signature(fn)
|
|
94
|
+
kwargs: dict[str, Any] = {}
|
|
95
|
+
for name, param in sig.parameters.items():
|
|
96
|
+
if param.kind in _SKIP_KINDS:
|
|
97
|
+
continue
|
|
98
|
+
kwargs[name] = getattr(namespace, name)
|
|
99
|
+
return kwargs
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def run_entrypoint(fn: Callable[..., object], argv: Sequence[str]) -> object:
|
|
103
|
+
"""Parse ``argv`` for ``fn`` and invoke it (the single submit happens inside)."""
|
|
104
|
+
return fn(**parse_args(fn, argv))
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Shared naming convention for mount environment variables.
|
|
2
|
+
|
|
3
|
+
The backend runner (follow-up PR) exports one env var per mount before invoking
|
|
4
|
+
a job body; ``Volume.path`` / ``Asset.path`` read them back. Centralising the
|
|
5
|
+
key derivation here keeps the producer and consumer in lock-step.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import re
|
|
9
|
+
|
|
10
|
+
_NON_ALNUM = re.compile(r"[^A-Za-z0-9]+")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _sanitize(segment: str) -> str:
|
|
14
|
+
"""Upper-case, collapse runs of non-alphanumerics to ``_``, strip edges."""
|
|
15
|
+
return _NON_ALNUM.sub("_", segment).strip("_").upper()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def volume_env_key(name: str) -> str:
|
|
19
|
+
"""Env var the runner sets to a volume's local mount path."""
|
|
20
|
+
return f"SIMULO_VOLUME_{_sanitize(name)}"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def asset_env_key(uri: str) -> str:
|
|
24
|
+
"""Env var the runner sets to an asset's local (read-only) mount path."""
|
|
25
|
+
return f"SIMULO_ASSET_{_sanitize(uri)}"
|