feat(isaac): IsaacConfig dataclass with validation (2/5 of #31 split)#45
Conversation
strands-labs#31 split) Part 1 / 5 of the split of strands-labs#31 — tracked by strands-labs#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 strands-labs#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.
cagataycali
left a comment
There was a problem hiding this comment.
Local review on Thor — config works, but PR description is out of sync with code
Verified: IsaacConfig() instantiates fine, __post_init__ env-var resolution works, dataclass typo rejection via IsaacConfig(num_evns=4) raises TypeError correctly.
Inaccuracy in PR body
PR description claims:
The
from_kwargsclassmethod that PR-4'sIsaacSimulation.__init__uses to validate caller-supplied kwargs is included in this slice.
Reality: there is no from_kwargs classmethod in config.py. PR-4 (#47) doesn't call IsaacConfig.from_kwargs either — it inlines the same logic with dataclasses.fields() reflection. So the claim is harmless but stale; either:
- Remove the
from_kwargsparagraph from the PR description, OR - Actually add
from_kwargshere so PR-4 can use it (preferred — DRY between PR-4 and any futureNewtonConfig-style sibling)
Field-name drift vs PR description table
PR body table promises field physics_dt with default 1.0/60.0 (= 0.01666…). Actual code: physics_dt = 1.0/120.0 (= 0.00833…) at line ~30. Also rendering_dt exists in code but not mentioned in PR table. Minor but worth fixing the table.
What's good ✅
- All 7 fields validated in
__post_init__ - Env-var overrides applied AFTER caller-supplied values (caller wins) — correct precedence
- 116 LOC matches the budget
- Lint clean
Mergeability
- ✅ Mergeable independently — no test failures because PR-2 ships zero tests (per PR body, tests live in PR-4's
test_unit.py::TestIsaacConfig) - 🟡 Recommend adding
from_kwargshere OR updating PR-4 description to drop the claim
…abs#43 (PR strands-labs#44 R1) Closes @cagataycali's R1 review on strands-labs#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 strands-labs#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 strands-labs#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 strands-labs#43 branch specifically for the PR-1 surface; cleanly self-contained against this slice.
…bs#31 split) Part 2 / 5 of the split of strands-labs#31 — tracked by strands-labs#42. Branched off PR-1 (`pr1/isaac-foundation` -> strands-labs#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 strands-labs#31 (`413ff15`); this slice cherry-picks just `config.py`. R1's `from_kwargs` validation pin (commit `32ef307`) is already baked into this file.
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 (strands-labs#47) can DRY its unknown-kwarg validation against IsaacConfig.from_kwargs rather than inlining dataclasses.fields() reflection at simulation.py:174-180.
bfe42bb to
b4ac593
Compare
|
Rebased onto SHA shift on this branch:
Same content; rebased commit objects only. Parallel-session reconciliation: the Local verification post-rebase: The diff of this PR against |
Summary
Part 2 / 5 of the split of #31 — tracked by
#42.Adds the configuration dataclass that
IsaacSimulation(PR-4) consumes. Branched off PR-1 (#44) — needs that to merge first, then rebases cleanly ontomain(singlegit rebase mainafter PR-1 lands).What's in this slice
strands_robots_sim/isaac/config.pyIsaacConfigdataclass +from_kwargsclassmethod + env-var overridesWhat
IsaacConfigcoversnum_envs1device"cuda:0"headlessTruerender_mode"headless""headless","rgb","rgb_pathtracing"physics_dt1.0/60.0gravity(0.0, 0.0, -9.81)ground_planeTruecamera_width/camera_height640 / 480rtx_pathtracingFalsenucleus_urlNoneEnvironment-variable overrides via
STRANDS_ISAAC_HEADLESS,STRANDS_ISAAC_RTX_PATHTRACING,STRANDS_ISAAC_NUCLEUS_URL— applied in__post_init__so caller-supplied values still win when explicitly set.R1 review-fix carry-over
The
from_kwargsclassmethod that PR-4'sIsaacSimulation.__init__uses to validate caller-supplied kwargs is included in this slice. It surfaces unknown keys asTypeError— the R1 fix from commit32ef307("reject unknown kwargs in IsaacSimulation.init") that closed the silent-drop bug whereIsaacSimulation(IsaacConfig(num_envs=4), num_env=8)would run with default config rather than raising on the typo.CI signal
hatch run lint(black --check/isort --check-only/flake8overstrands_robots_sim/+examples/) — clean.from strands_robots_sim.isaac.config import IsaacConfig; IsaacConfig()— works.__init__.pyfrom PR-1 (feat(isaac): [isaac] extras + entry-point + PEP 562 lazy import (1/5 of #31 split) #44) now resolvesIsaacConfigto a real class when accessed — thefrom strands_robots_sim.isaac import IsaacConfigimport path becomes live in this slice.Why no test file in this slice
test_unit.py::TestIsaacConfig(~105 LOC, 7 test methods) lives in PR-4'stests/test_unit.pyalongside the simulation-availability and contract tests. Carving a 105-LOC test class out into its own file just to ship in PR-2 felt over-engineered for ~120 LOC of dataclass code; PR-4's test_unit.py covers it once the full backend lands.Dependency chain
PR-1 (#44, foundation) → PR-2 (this) → unblocks PR-4 (simulation) which imports
IsaacConfigfrom this module.Diff: 1 file, +116 / -0 LOC.
Original authorship
Original work by @cagataycali in #31 (
413ff15..32ef307). This slice cherry-picks justconfig.py. R1'sfrom_kwargsvalidation pin (commit32ef307) is already baked into this file. Per#42's review-thread bookkeeping note, the original review threads onconfig.py(if any) carry over to this PR for resolution.