feat(isaac): [isaac] extras + entry-point + PEP 562 lazy import (1/5 of #31 split)#43
Conversation
strands-labs#31 split) PR-1 of the 5-PR split of strands-labs#31 (tracker: strands-labs#42). Lands the dependency-free bottom of the graph: the [isaac] optional-dependencies extra, the `strands_robots.backends` entry points (`isaac` + `isaac_sim` alias), and the PEP 562 lazy stub at `strands_robots_sim/isaac/__init__.py`. This is the slice that demonstrates entry-point discovery without any simulation code -- the lazy stub references `strands_robots_sim.isaac.simulation:IsaacSimulation` and `strands_robots_sim.isaac.config:IsaacConfig` which land in PR-4 and PR-2 respectively. The entry point in pyproject.toml is intentionally "dangling" until PR-4 merges; PR-1's tests verify *declaration* (parsing pyproject.toml) and the lazy-import contract (`import strands_robots_sim.isaac` adds zero `omni.*` modules to `sys.modules`), not loadability of the resolved class. Changes: - pyproject.toml: `[isaac]` extra (usd-core>=24.5,<26.0, warp-lang>=1.13.0, pytest>=7.0), `[project.entry-points."strands_robots.backends"]` for `isaac` + `isaac_sim` alias, `[tool.pytest.ini_options]` markers (`gpu` for the test_gpu_integ.py marker that lands in PR-4), `[isaac]` extra rolled into `all`, hatch test script switched from import-smoke to `pytest strands_robots_sim/isaac/tests/ -v`. - strands_robots_sim/isaac/__init__.py: PEP 562 `__getattr__` lazy resolution for `IsaacSimulation` + `IsaacConfig`. `__all__` advertises the planned public surface. - strands_robots_sim/isaac/tests/__init__.py: empty package marker. - strands_robots_sim/isaac/tests/test_entrypoint.py: 10 tests scoped to what PR-1 actually delivers -- entry-point declaration in pyproject.toml, `[isaac]` extras presence + pytest inclusion, importlib.metadata discovery (skipped if package not pip-installed), no-omni-on-import contract, `__all__` advertisement, AttributeError-not-ImportError on unknown attrs, and presence of the PEP 562 hook itself. Tests covering `SimEngine` subclassing, abstract-method completeness, `is_available()` return shape, and no-GPU constructor instantiation are intentionally deferred to PR-4 because they require importing the `simulation` module that hasn't landed yet -- including them here would re-create the cross-PR dependency the split is trying to avoid. Local validation: - `hatch run lint` -> clean (black/isort/flake8 on all touched files) - `hatch run test` -> 9 passed, 1 skipped (importlib.metadata skip is expected; hatch's hash-locked virtualenv doesn't see the source-tree editable install. Re-running under `pip install -e .` -> 10 passed.) - `pip install -e .` + `python -c" "import strands_robots_sim.isaac" -> OK - entry-point lookup in editable env -> both `isaac` and `isaac_sim` resolve to `strands_robots_sim.isaac.simulation:IsaacSimulation`. Refs: strands-labs#31, strands-labs#42 (umbrella tracker for the 5-PR split)
cagataycali
left a comment
There was a problem hiding this comment.
Local review on Thor — duplicate of #44 but actually has the better artifact
Reviewed PR43 vs PR44 side-by-side. They have the same lazy __init__.py contract but differ on test surface and hatch script:
| Aspect | PR43 (this) | PR44 |
|---|---|---|
| Branch | feat/isaac-extras-and-lazy-import |
pr1/isaac-foundation |
| Tests | 10 tests in test_entrypoint.py, all pass |
0 tests |
Hatch test script |
pytest strands_robots_sim/isaac/tests/ -v |
placeholder import-smoke |
pyproject [isaac] extra |
includes pytest>=7.0 |
doesn't |
| Lazy contract verified | ✅ (zero omni modules loaded) | ✅ |
| Stack lineage | orphan (PR45/46/47/48 don't branch from this) | parent of PR45/46/47/48 |
Verdict
PR43 is the better artifact, but PR44 is the structurally correct one because PR45-48 all branched from PR44's commit 3a0a854, not from PR43's e717e71.
Recommendation
- Close this PR (#43) as superseded by #44.
- Port the 10 tests + hatch test script +
pytest>=7.0extra from this PR into #44 as a new commit onpr1/isaac-foundation. The 10 tests are good (they already pass cleanly on Thor) and PR44's lack-of-tests is the main gap I'd block #44 on otherwise. - The 10 tests carved cleanly so they only verify what PR-1 actually delivers (entry-point declaration + lazy-import contract) — no cross-PR coupling. Safe to add.
If you'd rather close PR44 and keep this one, the cost is rebasing PR45/46/47/48 onto PR43's branch — also fine, just 4 rebases.
|
Closing as duplicate of #44 — both cover the same "1/5 of #31 split" slice, but #44 is the canonical chain head (followed by #45 / #46 / #47 / #48 per Substantive shape difference:
The deferral of No work is lost: #44 keeps the same |
#31 split) (#44) * feat(isaac): [isaac] extras + entry-point + PEP 562 lazy import (1/5 of #31 split) Part 1 / 5 of the split of #31 — tracked by #42. Adds the package skeleton for the Isaac Sim backend with zero `omni` overhead on `import strands_robots_sim`: - `pyproject.toml`: - new `[isaac]` extra (lightweight Isaac-companion deps that ARE pip-installable: `usd-core>=24.5,<26.0`, `warp-lang>=1.13.0`). Comment explains that Isaac Sim itself is NOT installable from PyPI — must come via Omniverse Launcher / Isaac Lab / nvcr.io. - new `[all]` aggregate extra (currently delegates to `[isaac]`; will gain `[newton]` etc. as later backends land). - new `[project.entry-points."strands_robots.backends"]` declaring both `isaac` and `isaac_sim` aliases pointing at `strands_robots_sim.isaac.simulation:IsaacSimulation` (the class itself lands in PR-4 / `feat(isaac): IsaacSimulation backend`). - new `pytest>=7.0` in `dev` extras and `[tool.hatch.envs.default]` deps so future PRs in this split chain can run pytest under `hatch run test`. - new `[tool.pytest.ini_options]` with a `gpu` marker so PR-4's GPU-only integ tests can gate on `STRANDS_GPU_TEST=1`. - `strands_robots_sim/isaac/__init__.py`: PEP 562 lazy stub — declares `__all__ = ["IsaacSimulation", "IsaacConfig"]` and routes attribute access through `__getattr__` to deferred imports of `.simulation` and `.config`. The bare `import strands_robots_sim.isaac` does NOT import any `omni` modules; that overhead only fires when a caller actually accesses `IsaacSimulation` or `IsaacConfig`. - `strands_robots_sim/isaac/tests/__init__.py`: empty package marker so future test files in this directory are collectable. Test files themselves land in subsequent slices alongside the code they cover. CI signal: lint clean (black / isort / flake8); the existing `hatch run test` import-smoke passes (it doesn't touch `isaac`, so no missing-submodule errors). Pytest doesn't run yet from `hatch run test` — the test command stays as the import-smoke placeholder in this slice and flips to `pytest strands_robots_sim/isaac/tests/` in PR-3 once the first real test files land. Why this lands first: PR-2 / PR-3 / PR-4 all import from `strands_robots_sim.isaac`, so the package stub + extras + entry points have to exist before any of those files can be code-reviewed against current `main`. Original work by @cagataycali in #31 (`413ff15..befb1e3`); this slice cherry-picks just the foundation files. Review-thread anchors on the original commits stay intact for whichever child PR consumes the relevant code. * review(isaac): port 10 entry-point + lazy-import tests from #43 (PR #44 R1) Closes @cagataycali's R1 review on #44 -- the lazy-import contract ships with concrete CI signal now, not just a manual probe in the PR description. Three coordinated changes per the review: 1. **Port `tests/test_entrypoint.py` (140 LOC, 10 tests)** from the closed orphan PR #43 (`feat/isaac-extras-and-lazy-import`). These tests verify *only* PR-1's surface and have **zero dependency on `simulation.py`** (which lands in PR-4 of the #31 split): - `TestEntryPointDeclaration` (6 tests): - `test_pyproject_exists` - `test_isaac_entry_point_declared_in_pyproject` - `test_isaac_sim_alias_entry_point_declared_in_pyproject` - `test_isaac_extra_declared_in_pyproject` - `test_isaac_extra_includes_pytest` - `test_entry_points_visible_via_importlib_metadata_when_installed` (skips with actionable hint when not pip-installed) - `TestLazyImportSurface` (4 tests): - `test_import_isaac_does_not_load_omni` -- pins the `omni`-not- loaded contract via `sys.modules` set diff before/after import. - `test_isaac_subpackage_exposes_lazy_attrs_in___all__` - `test_unknown_attr_raises_attributeerror` -- pins the PEP 562 `__getattr__` raises `AttributeError` (not `ImportError`) on unknown attrs. - `test_dunder_getattr_is_present` 2. **Add `pytest>=7.0` to the `[isaac]` extra** in `pyproject.toml`. Means `pip install '.[isaac]'` covers the test deps without a separate dev extra on CI hosts. Also pinned by `test_isaac_extra_includes_pytest`. 3. **Flip `hatch run test`** from the import-smoke placeholder (`python -c "import strands_robots_sim; ..."`) to `pytest strands_robots_sim/isaac/tests/ -v`. The 10 tests collect and pass standalone; PR-4's simulation-module tests join the collection automatically when that PR rebases. Verification: - `pytest strands_robots_sim/isaac/tests/ -v` -> **10 passed in 0.14s** - `black --check` / `isort --check-only` / `flake8` (max-line=120) on `strands_robots_sim/` + `examples/` -> clean - AST parse on touched files -> ok - The 10 tests have no import path through `simulation.py`; they validate only PR-1's actual surface (pyproject parsing, entry- point declaration, PEP 562 `__getattr__` contract). The original `test_entrypoint.py` on `cagataycali/feat/isaac-sim` imports from `strands_robots_sim.isaac.simulation`, which is why PR-1 of the split couldn't ship that file unmodified. The version ported here was rewritten on the #43 branch specifically for the PR-1 surface; cleanly self-contained against this slice.
…#45) * feat(isaac): [isaac] extras + entry-point + PEP 562 lazy import (1/5 of #31 split) Part 1 / 5 of the split of #31 — tracked by #42. Adds the package skeleton for the Isaac Sim backend with zero `omni` overhead on `import strands_robots_sim`: - `pyproject.toml`: - new `[isaac]` extra (lightweight Isaac-companion deps that ARE pip-installable: `usd-core>=24.5,<26.0`, `warp-lang>=1.13.0`). Comment explains that Isaac Sim itself is NOT installable from PyPI — must come via Omniverse Launcher / Isaac Lab / nvcr.io. - new `[all]` aggregate extra (currently delegates to `[isaac]`; will gain `[newton]` etc. as later backends land). - new `[project.entry-points."strands_robots.backends"]` declaring both `isaac` and `isaac_sim` aliases pointing at `strands_robots_sim.isaac.simulation:IsaacSimulation` (the class itself lands in PR-4 / `feat(isaac): IsaacSimulation backend`). - new `pytest>=7.0` in `dev` extras and `[tool.hatch.envs.default]` deps so future PRs in this split chain can run pytest under `hatch run test`. - new `[tool.pytest.ini_options]` with a `gpu` marker so PR-4's GPU-only integ tests can gate on `STRANDS_GPU_TEST=1`. - `strands_robots_sim/isaac/__init__.py`: PEP 562 lazy stub — declares `__all__ = ["IsaacSimulation", "IsaacConfig"]` and routes attribute access through `__getattr__` to deferred imports of `.simulation` and `.config`. The bare `import strands_robots_sim.isaac` does NOT import any `omni` modules; that overhead only fires when a caller actually accesses `IsaacSimulation` or `IsaacConfig`. - `strands_robots_sim/isaac/tests/__init__.py`: empty package marker so future test files in this directory are collectable. Test files themselves land in subsequent slices alongside the code they cover. CI signal: lint clean (black / isort / flake8); the existing `hatch run test` import-smoke passes (it doesn't touch `isaac`, so no missing-submodule errors). Pytest doesn't run yet from `hatch run test` — the test command stays as the import-smoke placeholder in this slice and flips to `pytest strands_robots_sim/isaac/tests/` in PR-3 once the first real test files land. Why this lands first: PR-2 / PR-3 / PR-4 all import from `strands_robots_sim.isaac`, so the package stub + extras + entry points have to exist before any of those files can be code-reviewed against current `main`. Original work by @cagataycali in #31 (`413ff15..befb1e3`); this slice cherry-picks just the foundation files. Review-thread anchors on the original commits stay intact for whichever child PR consumes the relevant code. * review(isaac): port 10 entry-point + lazy-import tests from #43 (PR #44 R1) Closes @cagataycali's R1 review on #44 -- the lazy-import contract ships with concrete CI signal now, not just a manual probe in the PR description. Three coordinated changes per the review: 1. **Port `tests/test_entrypoint.py` (140 LOC, 10 tests)** from the closed orphan PR #43 (`feat/isaac-extras-and-lazy-import`). These tests verify *only* PR-1's surface and have **zero dependency on `simulation.py`** (which lands in PR-4 of the #31 split): - `TestEntryPointDeclaration` (6 tests): - `test_pyproject_exists` - `test_isaac_entry_point_declared_in_pyproject` - `test_isaac_sim_alias_entry_point_declared_in_pyproject` - `test_isaac_extra_declared_in_pyproject` - `test_isaac_extra_includes_pytest` - `test_entry_points_visible_via_importlib_metadata_when_installed` (skips with actionable hint when not pip-installed) - `TestLazyImportSurface` (4 tests): - `test_import_isaac_does_not_load_omni` -- pins the `omni`-not- loaded contract via `sys.modules` set diff before/after import. - `test_isaac_subpackage_exposes_lazy_attrs_in___all__` - `test_unknown_attr_raises_attributeerror` -- pins the PEP 562 `__getattr__` raises `AttributeError` (not `ImportError`) on unknown attrs. - `test_dunder_getattr_is_present` 2. **Add `pytest>=7.0` to the `[isaac]` extra** in `pyproject.toml`. Means `pip install '.[isaac]'` covers the test deps without a separate dev extra on CI hosts. Also pinned by `test_isaac_extra_includes_pytest`. 3. **Flip `hatch run test`** from the import-smoke placeholder (`python -c "import strands_robots_sim; ..."`) to `pytest strands_robots_sim/isaac/tests/ -v`. The 10 tests collect and pass standalone; PR-4's simulation-module tests join the collection automatically when that PR rebases. Verification: - `pytest strands_robots_sim/isaac/tests/ -v` -> **10 passed in 0.14s** - `black --check` / `isort --check-only` / `flake8` (max-line=120) on `strands_robots_sim/` + `examples/` -> clean - AST parse on touched files -> ok - The 10 tests have no import path through `simulation.py`; they validate only PR-1's actual surface (pyproject parsing, entry- point declaration, PEP 562 `__getattr__` contract). The original `test_entrypoint.py` on `cagataycali/feat/isaac-sim` imports from `strands_robots_sim.isaac.simulation`, which is why PR-1 of the split couldn't ship that file unmodified. The version ported here was rewritten on the #43 branch specifically for the PR-1 surface; cleanly self-contained against this slice. * feat(isaac): IsaacConfig dataclass with validation (2/5 of #31 split) Part 2 / 5 of the split of #31 — tracked by #42. Branched off PR-1 (`pr1/isaac-foundation` -> #44); rebases cleanly to `main` once PR-1 merges. Adds the configuration dataclass that `IsaacSimulation` (PR-4) consumes: - `strands_robots_sim/isaac/config.py` (116 LOC): `IsaacConfig` dataclass with: - `num_envs`, `device` (default `"cuda:0"`), `headless` (default `True` -- safe for cloud/CI runners), `render_mode`, `physics_dt`, `gravity`, `ground_plane`, `camera_width`, `camera_height`, `rtx_pathtracing`, `nucleus_url`. - environment-variable overrides via `STRANDS_ISAAC_*` (`HEADLESS`, `RTX_PATHTRACING`, `NUCLEUS_URL`). - dataclass-level validation in `__post_init__` so e.g. `IsaacConfig(num_envs=0)` raises `ValueError` at construction. - `from_kwargs` classmethod that PR-4's `IsaacSimulation.__init__` uses to validate caller-supplied kwargs and surface unknown keys as `TypeError` (the R1 "reject unknown kwargs" pin). No `omni` / Isaac Sim imports. Pure-Python dataclass; can be imported on any host without a GPU. Tests for this module ship in PR-4 alongside `test_unit.py::TestIsaacConfig` (cleaner than carving a test slice for ~120 LOC of dataclass). CI signal: lint clean (black / isort / flake8); the lazy `__init__.py` from PR-1 now resolves `IsaacConfig` to a real class when accessed -- the `from strands_robots_sim.isaac import IsaacConfig` import path becomes live in this slice. Original work by @cagataycali in #31 (`413ff15`); this slice cherry-picks just `config.py`. R1's `from_kwargs` validation pin (commit `32ef307`) is already baked into this file. * fix(isaac): address review comment on PR #45 cagataycali's comment at strands_robots_sim/isaac/config.py:116 flagged that from_kwargs was promised by the PR description but missing from the slice. Added the classmethod so PR-4 (#47) can DRY its unknown-kwarg validation against IsaacConfig.from_kwargs rather than inlining dataclasses.fields() reflection at simulation.py:174-180.
…split) (#46) * feat(isaac): [isaac] extras + entry-point + PEP 562 lazy import (1/5 of #31 split) Part 1 / 5 of the split of #31 — tracked by #42. Adds the package skeleton for the Isaac Sim backend with zero `omni` overhead on `import strands_robots_sim`: - `pyproject.toml`: - new `[isaac]` extra (lightweight Isaac-companion deps that ARE pip-installable: `usd-core>=24.5,<26.0`, `warp-lang>=1.13.0`). Comment explains that Isaac Sim itself is NOT installable from PyPI — must come via Omniverse Launcher / Isaac Lab / nvcr.io. - new `[all]` aggregate extra (currently delegates to `[isaac]`; will gain `[newton]` etc. as later backends land). - new `[project.entry-points."strands_robots.backends"]` declaring both `isaac` and `isaac_sim` aliases pointing at `strands_robots_sim.isaac.simulation:IsaacSimulation` (the class itself lands in PR-4 / `feat(isaac): IsaacSimulation backend`). - new `pytest>=7.0` in `dev` extras and `[tool.hatch.envs.default]` deps so future PRs in this split chain can run pytest under `hatch run test`. - new `[tool.pytest.ini_options]` with a `gpu` marker so PR-4's GPU-only integ tests can gate on `STRANDS_GPU_TEST=1`. - `strands_robots_sim/isaac/__init__.py`: PEP 562 lazy stub — declares `__all__ = ["IsaacSimulation", "IsaacConfig"]` and routes attribute access through `__getattr__` to deferred imports of `.simulation` and `.config`. The bare `import strands_robots_sim.isaac` does NOT import any `omni` modules; that overhead only fires when a caller actually accesses `IsaacSimulation` or `IsaacConfig`. - `strands_robots_sim/isaac/tests/__init__.py`: empty package marker so future test files in this directory are collectable. Test files themselves land in subsequent slices alongside the code they cover. CI signal: lint clean (black / isort / flake8); the existing `hatch run test` import-smoke passes (it doesn't touch `isaac`, so no missing-submodule errors). Pytest doesn't run yet from `hatch run test` — the test command stays as the import-smoke placeholder in this slice and flips to `pytest strands_robots_sim/isaac/tests/` in PR-3 once the first real test files land. Why this lands first: PR-2 / PR-3 / PR-4 all import from `strands_robots_sim.isaac`, so the package stub + extras + entry points have to exist before any of those files can be code-reviewed against current `main`. Original work by @cagataycali in #31 (`413ff15..befb1e3`); this slice cherry-picks just the foundation files. Review-thread anchors on the original commits stay intact for whichever child PR consumes the relevant code. * review(isaac): port 10 entry-point + lazy-import tests from #43 (PR #44 R1) Closes @cagataycali's R1 review on #44 -- the lazy-import contract ships with concrete CI signal now, not just a manual probe in the PR description. Three coordinated changes per the review: 1. **Port `tests/test_entrypoint.py` (140 LOC, 10 tests)** from the closed orphan PR #43 (`feat/isaac-extras-and-lazy-import`). These tests verify *only* PR-1's surface and have **zero dependency on `simulation.py`** (which lands in PR-4 of the #31 split): - `TestEntryPointDeclaration` (6 tests): - `test_pyproject_exists` - `test_isaac_entry_point_declared_in_pyproject` - `test_isaac_sim_alias_entry_point_declared_in_pyproject` - `test_isaac_extra_declared_in_pyproject` - `test_isaac_extra_includes_pytest` - `test_entry_points_visible_via_importlib_metadata_when_installed` (skips with actionable hint when not pip-installed) - `TestLazyImportSurface` (4 tests): - `test_import_isaac_does_not_load_omni` -- pins the `omni`-not- loaded contract via `sys.modules` set diff before/after import. - `test_isaac_subpackage_exposes_lazy_attrs_in___all__` - `test_unknown_attr_raises_attributeerror` -- pins the PEP 562 `__getattr__` raises `AttributeError` (not `ImportError`) on unknown attrs. - `test_dunder_getattr_is_present` 2. **Add `pytest>=7.0` to the `[isaac]` extra** in `pyproject.toml`. Means `pip install '.[isaac]'` covers the test deps without a separate dev extra on CI hosts. Also pinned by `test_isaac_extra_includes_pytest`. 3. **Flip `hatch run test`** from the import-smoke placeholder (`python -c "import strands_robots_sim; ..."`) to `pytest strands_robots_sim/isaac/tests/ -v`. The 10 tests collect and pass standalone; PR-4's simulation-module tests join the collection automatically when that PR rebases. Verification: - `pytest strands_robots_sim/isaac/tests/ -v` -> **10 passed in 0.14s** - `black --check` / `isort --check-only` / `flake8` (max-line=120) on `strands_robots_sim/` + `examples/` -> clean - AST parse on touched files -> ok - The 10 tests have no import path through `simulation.py`; they validate only PR-1's actual surface (pyproject parsing, entry- point declaration, PEP 562 `__getattr__` contract). The original `test_entrypoint.py` on `cagataycali/feat/isaac-sim` imports from `strands_robots_sim.isaac.simulation`, which is why PR-1 of the split couldn't ship that file unmodified. The version ported here was rewritten on the #43 branch specifically for the PR-1 surface; cleanly self-contained against this slice. * feat(isaac): procedural USD builders (SO-100, Panda, G1) (3/5 of #31 split) Part 3 / 5 of the split of #31 — tracked by #42. Branched off PR-1 (`pr1/isaac-foundation` -> #44); rebases cleanly onto `main` once PR-1 merges. Adds the procedural USD robot builders that `IsaacSimulation` (PR-4) consumes via `add_robot(data_config=<name>, procedural=True)` without requiring binary asset downloads from Nucleus: - `strands_robots_sim/isaac/procedural.py` (258 LOC): - `ProceduralRobot` named-tuple shape: `(prim_root, num_joints, joint_names, body_names, parent_body_indices)`. - `get_procedural_robot(name: str) -> ProceduralRobot` registry. - Three builders behind the registry: - `_build_so100`: 6-DOF SO-100 arm (HuggingFace's open-source teaching arm). - `_build_franka_panda`: 9-DOF Panda + 2-DOF gripper. - `_build_unitree_g1`: 21-DOF G1 humanoid (1 torso + 6 left leg + 6 right leg + 4 left arm + 4 right arm). - All builders are pure-Python; no `omni` / Isaac Sim imports. - `strands_robots_sim/isaac/tests/test_procedural_g1_dof.py` (~70 LOC, 3 tests): Carved out of cagataycali's original `test_phase1_doc_honesty.py` -- the `TestG1DOFCount` class only. The companion `TestIsaacDocsPhase1Banner` class (which reads `docs/backends/isaac.md`) lands in PR-5 alongside the docs file itself. Pin tests: - `test_g1_actual_joint_count_is_21`: truth-source pin against `procedural.py`'s actual joint set. - `test_g1_module_docstring_advertises_21_not_29`: pin against R2's stale 29-DOF claim in the module docstring (commit `85e180f`). - `test_g1_builder_docstring_advertises_21_not_29`: same pin on the `_build_unitree_g1` function docstring. CI signal: lint clean (black / isort / flake8); the 3 G1-DOF pin tests pass standalone (`pytest strands_robots_sim/isaac/tests/ test_procedural_g1_dof.py -v` -> 3 passed). Note: this slice does NOT pin the kinematic-tree topology defect on the G1 joint graph (duplicate `(parent_body, child_body)` edges -- e.g. `l_hip_roll` and `l_hip_pitch` both map bodies 3 -> 4). That defect is documented in `procedural.py` line 165 as deferred to Phase 2; it requires intermediate massless link bodies that this Phase 1 stub does not instantiate. The dormant- on-Phase-1 framing is the same one R2 captured. Why this lands in parallel with PR-2 (config): both are pure modules with no cross-dependencies; either can land first. PR-4 (simulation) needs both. Original work by @cagataycali in #31 (`413ff15..85e180f`); this slice cherry-picks `procedural.py` plus the carved-out G1 test file. R2's DOF-count fix (commit `85e180f`) is already baked into this file's docstrings. * fix(isaac): add Phase-1 fail-fast guard for duplicate kinematic edges cagataycali's comment at strands_robots_sim/isaac/procedural.py:213 flagged that the documented duplicate-edge defect on G1 is silently returned by _build_unitree_g1 and would only surface as a cryptic USD/MuJoCo articulation error two layers down on Phase-2 wireup. Adds _validate_kinematic_tree(), wired into the end of all three procedural builders, opt-in via STRANDS_ISAAC_VALIDATE_KINEMATICS=1 (Phase-2 dev path). Default is no-op so Phase-1 callers (which never instantiate the articulation) are unaffected; with the env var set, G1 raises ValueError naming the duplicate edges + offending joints, while SO-100 and Panda still build cleanly. Pinned by tests/test_procedural_kinematic_guard.py: default-off behaviour, accepted env values (1/true/yes, case-insensitive), G1 raises with diagnostic context, SO-100 and Panda pass guard when enabled.
Summary
PR-1 of 5 in the #31 split tracked by #42. Lands the dependency-free bottom of the 5-PR graph: the
[isaac]optional-dependencies extra, thestrands_robots.backendsentry points (isaac+isaac_simalias), and the PEP 562 lazy stub atstrands_robots_sim/isaac/__init__.py.This is the slice that demonstrates entry-point discovery without any simulation code. The lazy stub references
strands_robots_sim.isaac.simulation:IsaacSimulationandstrands_robots_sim.isaac.config:IsaacConfig, which land in PR-4 and PR-2 respectively. The entry points inpyproject.tomlare intentionally "dangling" until PR-4 merges; PR-1's tests verify declaration (parsingpyproject.toml) and the lazy-import contract (import strands_robots_sim.isaacadds zeroomni.*modules tosys.modules), not loadability of the resolved class.Lands first in the dependency graph below; PR-2 + PR-3 unblock once this merges, then PR-4 needs both, and PR-5 is parallel-mergeable with PR-4.
What changed
pyproject.toml[isaac]extras (usd-core>=24.5,<26.0,warp-lang>=1.13.0,pytest>=7.0); +[project.entry-points."strands_robots.backends"]forisaacandisaac_simalias; +[tool.pytest.ini_options]markers (gpumarker reserved for PR-4'stest_gpu_integ.py); +allextra rolling up[isaac]; hatchtestscript switched from import-smoke topytest strands_robots_sim/isaac/tests/ -v.strands_robots_sim/isaac/__init__.py__getattr__lazy resolution forIsaacSimulation+IsaacConfig.__all__advertises the planned public surface.strands_robots_sim/isaac/tests/__init__.pystrands_robots_sim/isaac/tests/test_entrypoint.pyDiff size: +233 / −7, 4 files. Within the issue's "~250 LOC" budget for PR-1.
Test surface (10 tests)
Two test classes, scoped to what PR-1 can prove standalone:
TestEntryPointDeclaration(6 tests)test_pyproject_exists— locatepyproject.tomltest_isaac_entry_point_declared_in_pyproject—isaac = "strands_robots_sim.isaac.simulation:IsaacSimulation"presenttest_isaac_sim_alias_entry_point_declared_in_pyproject—isaac_simalias presenttest_isaac_extra_declared_in_pyproject—[isaac]extras block existstest_isaac_extra_includes_pytest—[isaac]ships pytest sopip install '.[isaac]'covers test depstest_entry_points_visible_via_importlib_metadata_when_installed— runtime entry-point discovery (skips gracefully if package isn't pip-installed in the env)TestLazyImportSurface(4 tests)test_import_isaac_does_not_load_omni—import strands_robots_sim.isaacadds zeroomni.*modules tosys.modulestest_isaac_subpackage_exposes_lazy_attrs_in___all__—__all__advertises both classestest_unknown_attr_raises_attributeerror— unknown attribute access raisesAttributeError, notImportError(PEP 562 hook hygiene)test_dunder_getattr_is_present— module-level__getattr__exists and is callableTests covering
SimEnginesubclassing, abstract-method completeness,is_available()return shape, and no-GPU constructor instantiation are intentionally deferred to PR-4 because they require importing thesimulationmodule that hasn't landed yet. Including them here would re-create the cross-PR dependency the split is trying to avoid.Acceptance criteria from #42 (PR-1 row)
pyproject.toml:[isaac]extraspyproject.toml: entry-points (isaac,isaac_simalias)isaac/__init__.py: PEP 562 lazy stubisaac/tests/__init__.pyisaac/tests/test_entrypoint.pyLocal validation
Out-of-scope follow-ups (deferred to other PRs in the split)
IsaacConfigdataclass + R1's "reject unknown kwargs" pinIsaacSimulationbackend with R1'somni.isaac.kit-specificis_available()probe + dropped unsafe__del__+ narrowexceptclauses + R4'sget_observationdiagnostic logsdocs/backends/isaac.mdreference with R2's Phase-1-status disclosure bannerThe 9 review threads on #31 stay open until the corresponding child PR closes them. R1/R2/R3/R4 commit chronicle on #31 remains the forensic source of truth; child PRs preserve those commits as cherry-picks where the review-thread anchors apply (mirrors
strands-labs/robots#195 → #220-#228/robots#219precedent).Project board
This PR's issue #42 is the umbrella tracker; please move to "In review" on the Strands Labs - Robots board.
Refs: #31, #42