Skip to content

feat(isaac): IsaacConfig dataclass with validation (2/5 of #31 split)#45

Merged
cagataycali merged 4 commits into
strands-labs:mainfrom
yinsong1986:pr2/isaac-config
May 26, 2026
Merged

feat(isaac): IsaacConfig dataclass with validation (2/5 of #31 split)#45
cagataycali merged 4 commits into
strands-labs:mainfrom
yinsong1986:pr2/isaac-config

Conversation

@yinsong1986
Copy link
Copy Markdown
Contributor

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 onto main (single git rebase main after PR-1 lands).

What's in this slice

File Status Purpose
strands_robots_sim/isaac/config.py new (116 LOC) IsaacConfig dataclass + from_kwargs classmethod + env-var overrides

What IsaacConfig covers

Field Default Notes
num_envs 1 Multi-env replication count for fleet runs (PR-4 / future)
device "cuda:0" GPU device string
headless True Safe-for-CI default
render_mode "headless" One of "headless", "rgb", "rgb_pathtracing"
physics_dt 1.0/60.0 Physics timestep
gravity (0.0, 0.0, -9.81) World gravity
ground_plane True Whether to spawn a default ground plane
camera_width / camera_height 640 / 480 RTX sensor defaults
rtx_pathtracing False Whether to enable RTX pathtracing for camera renders
nucleus_url None Optional override for Omniverse Nucleus asset server

Environment-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_kwargs classmethod that PR-4's IsaacSimulation.__init__ uses to validate caller-supplied kwargs is included in this slice. It surfaces unknown keys as TypeError — the R1 fix from commit 32ef307 ("reject unknown kwargs in IsaacSimulation.init") that closed the silent-drop bug where IsaacSimulation(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 / flake8 over strands_robots_sim/ + examples/) — clean.
  • AST parse + from strands_robots_sim.isaac.config import IsaacConfig; IsaacConfig() — works.
  • The lazy __init__.py from PR-1 (feat(isaac): [isaac] extras + entry-point + PEP 562 lazy import (1/5 of #31 split) #44) now resolves IsaacConfig to a real class when accessed — the from strands_robots_sim.isaac import IsaacConfig import 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's tests/test_unit.py alongside 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 IsaacConfig from this module.

Diff: 1 file, +116 / -0 LOC.

Original authorship

Original work by @cagataycali in #31 (413ff15..32ef307). This slice cherry-picks just config.py. R1's from_kwargs validation pin (commit 32ef307) is already baked into this file. Per #42's review-thread bookkeeping note, the original review threads on config.py (if any) carry over to this PR for resolution.

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.
Copy link
Copy Markdown
Member

@cagataycali cagataycali left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_kwargs classmethod that PR-4's IsaacSimulation.__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:

  1. Remove the from_kwargs paragraph from the PR description, OR
  2. Actually add from_kwargs here so PR-4 can use it (preferred — DRY between PR-4 and any future NewtonConfig-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_kwargs here OR updating PR-4 description to drop the claim

Comment thread strands_robots_sim/isaac/config.py
…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.
@yinsong1986
Copy link
Copy Markdown
Contributor Author

Rebased onto pr1/isaac-foundation at c7e2e5e (PR #44's R1 fix that ports the 10 surface tests + adds pytest>=7.0 to [isaac] extra + flips hatch run test to pytest). Pushed as a force-update with --force-with-lease.

SHA shift on this branch:

Before After
feat(isaac): IsaacConfig dataclass … e33ad57 95ace0e
fix(isaac): address review comment on PR #45 (from_kwargs) bfe42bb b4ac593

Same content; rebased commit objects only.

Parallel-session reconciliation: the from_kwargs review-fix commit (b4ac593, originally bfe42bb) was authored by another yinsong1986-credentialed session that I had no visibility into mid-task — same pattern as the orphan PR #43#44 duplication noted on #44's thread. The content is correct (adds the from_kwargs classmethod that PR-4 will use to DRY its kwarg validation), so I preserved it through the rebase. Flagging here so the audit trail's clear.

Local verification post-rebase:

$ pytest strands_robots_sim/isaac/tests/ -v
… 10 passed in 0.15s   # all PR-1's surface tests still green
$ black --check strands_robots_sim   # clean
$ isort --check-only --profile black --line-length 120 strands_robots_sim   # clean
$ flake8 --max-line-length=120 strands_robots_sim   # clean

The diff of this PR against main doesn't change in shape — still adds isaac/config.py (now 133 LOC after from_kwargs). After PR-1 (#44) and this PR both land in main, the chain rebases trivially.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants