Skip to content

chore: configure ruff lint rules and fix what they surfaced - #883

Open
maxwbuckley wants to merge 1 commit into
NVIDIA:mainfrom
maxwbuckley:chore/ruff-lint-rules
Open

chore: configure ruff lint rules and fix what they surfaced#883
maxwbuckley wants to merge 1 commit into
NVIDIA:mainfrom
maxwbuckley:chore/ruff-lint-rules

Conversation

@maxwbuckley

@maxwbuckley maxwbuckley commented Aug 4, 2026

Copy link
Copy Markdown

Description

The ruff and ruff-format pre-commit hooks were running with no configuration at
all, so only ruff's built-in defaults (E4/E7/E9 + F) were ever enforced. Adds a
[tool.ruff] section selecting the correctness-oriented groups: B, C4, PIE, PERF,
PLE, PLW, LOG, G, ASYNC and RUF. Formatting is unchanged (ruff-format defaults, which
the tree already matches).

Everything in select is currently clean, so the hook is enforceable as-is. Each entry
in ignore is a deliberate call with its reason inline; the notable ones:

  • B905 (zip strict=) is a per-call-site behaviour change, turning a silent
    truncation into a runtime exception. Worth adopting deliberately, not in a lint sweep.
  • RUF005, C408 and RUF007 are style-only rewrites of code that is already correct
    and readable (a + [b][*a, b], dict(a=1){"a": 1},
    zip(x[:-1], x[1:])itertools.pairwise(x)). Enforcing them means churning working
    call sites for no behaviour change, so they are left to author preference.
  • RUF022 sorts __all__ alphabetically, which scrambles the semantic grouping
    comments the schema/package __init__ files rely on.
  • RUF100 is evaluated against select, so it flags every noqa written for a rule not
    yet enabled (BLE001, PLC0415, N803, ...). Re-enable once those groups are adopted.

No live defects were found. Every rule fired on code that behaves correctly today;
what follows removes fragility, not bugs.

Correct today, fragile to a later change

  • LOG014: _record_error passes exc_info=True, which reads the ambient
    sys.exc_info(). Both of its callers invoke it from inside an except block, so the
    traceback is logged correctly; the rule is lexical and fires because the logging call
    sits in a helper rather than in the handler itself. Passing the exception explicitly
    makes it independent of the caller's context.
  • B023: a closure in the frame loop captured the loop variable phase by reference.
    It is called immediately in the same iteration, so the value was always correct.
    fill is hoisted above the loop and takes phase as a parameter, which also stops
    re-creating the function object every frame. Binding it as a default argument would
    satisfy the rule equally.
  • B011: assert False in a test, which python -O strips, turning a failure into a
    silent pass. The suite is not run under -O today. Replaced by calling the
    constructor directly, so an exception fails the test with its own traceback.
  • B017: a blind pytest.raises(Exception) that would also accept an unrelated
    failure. The test passes for the right reason today; naming the accepted exception set
    keeps it that way.
  • RUF043: pytest match="libcloudxr.so" treats . as a regex wildcard where a literal
    filename was meant. The real message contains the literal, so the assertion passes
    correctly, but it is weaker than it reads. The remaining patterns are intentional
    regexes and are now raw strings.

Typing and explicitness

  • RUF012: three mutable class attributes annotated ClassVar, one of them on the
    EnvConfig singleton.
  • RUF013: implicit Optional spelled out as str | None.
  • B904: raise ... from on re-raises, so the original cause is not lost.
  • G004: log calls take %s arguments rather than eagerly formatted f-strings.
  • PLW1510: subprocess.run calls that inspect returncode say check=False. The
    tree already spelled this out at 10 call sites, 8 of them in oob_teleop_adb.py; this
    covers the stragglers.

TRY004 (ValueErrorTypeError in TeleopSessionConfig validation) was left
alone: it is a public API behaviour change, not a lint fix.

Python only — no C++ or CMake is touched. A companion PR adds the C++ warning set; the
two are independent and can land in either order.

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Testing

Ubuntu 24.04 / x86_64, Python 3.12

  • ruff check and ruff format --check both clean at v0.15.1, the version pinned in
    .pre-commit-config.yaml (268 files).
  • SKIP=check-copyright-year pre-commit run --all-files: all hooks pass.
  • ctest: 309/310. The one failure, cloudxr_test_launcher, is the missing CloudXR SDK
    (no NGC key on this host, so the download 404s and get_sdk_path() raises) — an
    environment gap, not a code failure.

Forward-compat note, not addressed here: at ruff 0.16 format --check wants to reflow
Python code blocks embedded in three Markdown files (examples/teleop_ros2/README.md,
src/plugins/oak/README.md, and one other) — newer ruff formats fenced code in Markdown,
which 0.15.1 does not. None of those files are touched by this PR, but it will surface
whenever the pre-commit rev is bumped past 0.16.

Checklist

  • I have read and understood the contribution guidelines
  • I have run the linter and formatter with SKIP=check-copyright-year pre-commit run --all-files
  • I have made corresponding changes to the documentation
  • I have added tests that prove my fix/feature works (or explained why not)
  • I have signed off all my commits (git commit -s) per the DCO

Documentation: no user-facing behaviour changes, so no doc updates. Every non-obvious
ignore entry is documented inline in pyproject.toml.

Tests: no new tests — this is a lint-configuration change plus the fixes it surfaced,
and it is exercised by the existing suite passing.

Summary by CodeRabbit

  • Bug Fixes

    • Improved error reporting with preserved exception details and clearer diagnostics.
    • CloudXR headset URL and import checks now report command failures without unexpected exceptions.
    • Refined validation and error matching for more reliable test and runtime behavior.
  • Quality Improvements

    • Added expanded Python linting and bug-detection rules.
    • Improved type annotations, logging consistency, and handling of abstract interfaces.
    • Simplified internal example and tooling code without changing behavior.

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The pull request adds project-wide Ruff configuration and applies related Python cleanup. Changes include explicit class-variable and optional-argument typing, abstract-method simplification, unused-variable cleanup, parameterized logging, exception chaining, subprocess return-code handling, and equivalent iteration changes. Tests update exception types and regular-expression patterns. Camera frame output and other existing behavior remain unchanged.

Estimated code review effort: 3 (Moderate) | ~20 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 57.58% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main changes: configuring Ruff lint rules and fixing the issues they identified.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

📝 Docs preview is not auto-deployed for fork PRs.

A maintainer with write access to NVIDIA/IsaacTeleop can deploy a preview by
commenting /preview-docs on this PR. Once deployed, the preview
will live at:

https://nvidia.github.io/IsaacTeleop/preview/pr-883/

@maxwbuckley
maxwbuckley force-pushed the chore/ruff-lint-rules branch 2 times, most recently from 60dd35f to 69137c8 Compare August 4, 2026 14:26
@nv-jakob

nv-jakob commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Hey we quickly looked over this, general feel is good. What are you feeling about merging this?

@jiwenc-nv
jiwenc-nv requested a review from aristarkhovNV August 7, 2026 20:46
The ruff and ruff-format pre-commit hooks were running with no configuration
at all, so only ruff's built-in defaults (E4/E7/E9 + F) were ever enforced.
Adds a [tool.ruff] section selecting the correctness-oriented groups:
B, C4, PIE, PERF, PLE, PLW, LOG, G, ASYNC and RUF. Formatting is unchanged
(ruff-format defaults, which the tree already matches).

Everything in `select` is currently clean, so the hook is enforceable as-is.
Each entry in `ignore` is a deliberate call with its reason inline; the
notable ones:

- B905 (zip strict=) is a per-call-site behaviour change, turning a silent
  truncation into a runtime exception. Worth adopting deliberately, not in a
  lint sweep.
- RUF005, C408 and RUF007 are style-only rewrites of code that is already
  correct and readable (`a + [b]` -> `[*a, b]`, dict(a=1) -> {"a": 1},
  zip(x[:-1], x[1:]) -> itertools.pairwise(x)). Enforcing them means churning
  working call sites for no behaviour change, so they are left to author
  preference.
- RUF022 sorts __all__ alphabetically, which scrambles the semantic grouping
  comments the schema/package __init__ files rely on.
- RUF100 is evaluated against `select`, so it flags every noqa written for a
  rule not yet enabled (BLE001, PLC0415, N803, ...). Re-enable once those
  groups are adopted.

No live defects were found. Every rule fired on code that behaves correctly
today; what follows removes fragility, not bugs.

Correct today, fragile to a later change:

- LOG014: _record_error passes exc_info=True, which reads the ambient
  sys.exc_info(). Both of its callers invoke it from inside an except block,
  so the traceback is logged correctly; the rule is lexical and fires because
  the logging call sits in a helper rather than in the handler itself.
  Passing the exception explicitly makes it independent of the caller's
  context.
- B023: a closure in the frame loop captured the loop variable `phase` by
  reference. It is called immediately in the same iteration, so the value was
  always correct. `fill` is hoisted above the loop and takes `phase` as a
  parameter, which also stops re-creating the function object every frame.
  Binding it as a default argument would satisfy the rule equally.
- B011: `assert False` in a test, which python -O strips, turning a failure
  into a silent pass. The suite is not run under -O today. Replaced by calling
  the constructor directly, so an exception fails the test with its own
  traceback.
- B017: a blind pytest.raises(Exception) that would also accept an unrelated
  failure. The test passes for the right reason today; naming the accepted
  exception set keeps it that way.
- RUF043: pytest match="libcloudxr.so" treats '.' as a regex wildcard where a
  literal filename was meant. The real message contains the literal, so the
  assertion passes correctly, but it is weaker than it reads. The remaining
  patterns are intentional regexes and are now raw strings.

Typing and explicitness:

- RUF012: three mutable class attributes annotated ClassVar, one of them on
  the EnvConfig singleton.
- RUF013: implicit Optional spelled out as `str | None`.
- B904: `raise ... from` on re-raises, so the original cause is not lost.
- G004: log calls take %s arguments rather than eagerly formatted f-strings.
- PLW1510: subprocess.run calls that inspect returncode say check=False.
  The tree already spelled this out at 10 call sites, 8 of them in
  oob_teleop_adb.py; this covers the stragglers.

TRY004 (ValueError -> TypeError in TeleopSessionConfig validation) was left
alone: it is a public API behaviour change, not a lint fix.

Verified on Ubuntu 24.04 / Python 3.12: ruff check and ruff format --check
both clean at v0.15.1, the version pinned in .pre-commit-config.yaml, and
SKIP=check-copyright-year pre-commit run --all-files passes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Max Buckley <maxwbuckley@gmail.com>
@maxwbuckley
maxwbuckley force-pushed the chore/ruff-lint-rules branch from 69137c8 to 13e7dca Compare August 10, 2026 19:29
@maxwbuckley
maxwbuckley marked this pull request as ready for review August 10, 2026 19:29
@maxwbuckley

Copy link
Copy Markdown
Author

Thanks for looking! I'm happy to merge this — it's rebased onto current main and out of draft now.

One thing worth calling out from the rebase. The only conflict was in cloudxr/launcher.py, and it resolved in main's favour: 53a1d7a ("refuse to start over a live runtime") replaced _cleanup_stale_runtime's fuser call with the is_runtime_live guard, which deleted the exact subprocess.run this PR was adding check=False to. That fix is obsolete, so I dropped it — cloudxr/launcher.py is now byte-identical to main and the PR touches 28 files instead of 29. Nothing in the start_wss_proxy deprecation or the health_check docs change is affected.

Re-verified after the rebase, at the ruff version pinned in .pre-commit-config.yaml (v0.15.1):

  • ruff check . and ruff format --check . both clean.
  • SKIP=check-copyright-year pre-commit run --all-files passes.
  • The rule set is still load-bearing: applied to unmodified main it reports 46 violations, all of which this PR fixes.
  • The Python suites (cloudxr, schema, retargeting_engine, teleop_session_manager, camera_viz) give identical results on this branch and on main — no regressions. I wasn't able to run the full ctest suite locally this time as I don't have the CloudXR SDK/CUDA build configured on this machine, so CI is the real check.

Which is the ask: the workflow runs are all sitting at action_required (the fork-PR approval gate), so CI has never actually run on this PR. Could you hit "Approve and run workflows" on the Checks tab when you get a chance? Happy to fix up anything it turns red.

@nv-jakob

Copy link
Copy Markdown
Contributor

Thanks for looking! I'm happy to merge this — it's rebased onto current main and out of draft now.

One thing worth calling out from the rebase. The only conflict was in cloudxr/launcher.py, and it resolved in main's favour: 53a1d7a ("refuse to start over a live runtime") replaced _cleanup_stale_runtime's fuser call with the is_runtime_live guard, which deleted the exact subprocess.run this PR was adding check=False to. That fix is obsolete, so I dropped it — cloudxr/launcher.py is now byte-identical to main and the PR touches 28 files instead of 29. Nothing in the start_wss_proxy deprecation or the health_check docs change is affected.

@jiwenc-nv this is okay right?

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants