From 69137c8154632cf4adb69ac0ab5068adc9032502 Mon Sep 17 00:00:00 2001 From: Max Buckley Date: Tue, 4 Aug 2026 11:37:05 +0200 Subject: [PATCH] chore: configure ruff lint rules and fix what they surfaced 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) Signed-off-by: Max Buckley --- examples/camera_viz/pipeline/runner.py | 2 +- examples/camera_viz/sources/oakd.py | 4 +- examples/camera_viz/sources/synthetic.py | 36 +++++----- examples/camera_viz/sources/zed.py | 2 +- .../camera_viz/transports/rtp_h264_sender.py | 2 +- examples/noitom/noitom_retargeting.py | 4 +- .../python/wuji_hand_retargeter_demo.py | 3 +- .../python/message_channel_example.py | 6 +- pyproject.toml | 69 +++++++++++++++++++ scripts/check_copyright_year.py | 2 + scripts/check_lfs_pointers.py | 1 + src/core/cloudxr_tests/python/test_runtime.py | 6 +- .../python/test_base_retargeter.py | 2 +- .../python/test_haptic_sink.py | 7 +- .../python/test_parameter_state.py | 13 ++-- .../python/test_teleop_session.py | 6 +- .../detect_jumpy_hand_from_world_results.py | 2 +- src/python/isaacteleop/cloudxr/env_config.py | 3 +- src/python/isaacteleop/cloudxr/launcher.py | 1 + .../isaacteleop/cloudxr/oob_teleop_adb.py | 4 +- .../isaacteleop/haptic_devices/controller.py | 4 +- .../isaacteleop/haptic_devices/push_tensor.py | 4 +- .../retargeters/dex_hand_retargeter.py | 4 +- .../deviceio_source_nodes/interface.py | 2 - .../interface/base_retargeter.py | 3 - .../interface/tensor_type.py | 2 - .../interface/tunable_parameter.py | 4 -- .../multi_retargeter_tuning_ui.py | 2 +- src/python/isaacteleop/rig/launcher.py | 1 + 29 files changed, 133 insertions(+), 68 deletions(-) diff --git a/examples/camera_viz/pipeline/runner.py b/examples/camera_viz/pipeline/runner.py index 59273e31c..1ce37fda1 100644 --- a/examples/camera_viz/pipeline/runner.py +++ b/examples/camera_viz/pipeline/runner.py @@ -200,7 +200,7 @@ def _record_error(self, exc: BaseException, where: str) -> None: with self._error_lock: if self._error is None: self._error = exc - logger.error("VizRunner %s thread failed: %s", where, exc, exc_info=True) + logger.error("VizRunner %s thread failed: %s", where, exc, exc_info=exc) self._stop.set() with self._data_cond: self._data_cond.notify_all() diff --git a/examples/camera_viz/sources/oakd.py b/examples/camera_viz/sources/oakd.py index 6d732f7d1..a6217913f 100644 --- a/examples/camera_viz/sources/oakd.py +++ b/examples/camera_viz/sources/oakd.py @@ -30,7 +30,7 @@ import threading import time from dataclasses import dataclass, field -from typing import List, Optional +from typing import ClassVar, List, Optional import numpy as np @@ -120,7 +120,7 @@ class _OakdDevice: closes it. """ - _SOCKET_MAP = { + _SOCKET_MAP: ClassVar[dict[str, str]] = { "RGB": "CAM_A", "CAM_A": "CAM_A", "LEFT": "CAM_B", diff --git a/examples/camera_viz/sources/synthetic.py b/examples/camera_viz/sources/synthetic.py index 25c5ed414..e0c30fd03 100644 --- a/examples/camera_viz/sources/synthetic.py +++ b/examples/camera_viz/sources/synthetic.py @@ -234,27 +234,29 @@ def _produce_loop(self) -> None: diag_l = (x_grid_l + y_grid) / float(w + h) diag_r = (x_grid_r + y_grid) / float(w + h) + # Defined once, above the loop: taking phase as a parameter avoids + # rebuilding a closure over it on every generated frame. + def fill(buf, diag, phase): + r = (cp.sin((diag + phase) * 6.2831853) * 127.0 + 128.0).astype( + cp.uint8 + ) + g = ( + cp.sin((diag + phase + 0.3333) * 6.2831853) * 127.0 + 128.0 + ).astype(cp.uint8) + b = ( + cp.sin((diag + phase + 0.6667) * 6.2831853) * 127.0 + 128.0 + ).astype(cp.uint8) + buf[..., 0] = r + buf[..., 1] = g + buf[..., 2] = b + buf[..., 3] = 255 + while not self._stop.is_set(): t = (time.monotonic_ns() - self._t0_ns) * 1e-9 phase = (t * self._hue_speed_hz) % 1.0 - def fill(buf, diag): - r = (cp.sin((diag + phase) * 6.2831853) * 127.0 + 128.0).astype( - cp.uint8 - ) - g = ( - cp.sin((diag + phase + 0.3333) * 6.2831853) * 127.0 + 128.0 - ).astype(cp.uint8) - b = ( - cp.sin((diag + phase + 0.6667) * 6.2831853) * 127.0 + 128.0 - ).astype(cp.uint8) - buf[..., 0] = r - buf[..., 1] = g - buf[..., 2] = b - buf[..., 3] = 255 - - fill(self._left[self._write_idx], diag_l) - fill(self._right[self._write_idx], diag_r) + fill(self._left[self._write_idx], diag_l, phase) + fill(self._right[self._write_idx], diag_r, phase) cp.cuda.Stream.null.synchronize() with self._lock: diff --git a/examples/camera_viz/sources/zed.py b/examples/camera_viz/sources/zed.py index f49679bb2..33514640f 100644 --- a/examples/camera_viz/sources/zed.py +++ b/examples/camera_viz/sources/zed.py @@ -310,7 +310,7 @@ def _hint(): # Pyzed Mats are pitched GPU buffers — one per eye, reused every # grab() / retrieve_image() pair. - for eye, slot in self._slots.items(): + for slot in self._slots.values(): slot.zed_mat = sl.Mat() self._camera = camera diff --git a/examples/camera_viz/transports/rtp_h264_sender.py b/examples/camera_viz/transports/rtp_h264_sender.py index c4a906233..538a5f3d3 100644 --- a/examples/camera_viz/transports/rtp_h264_sender.py +++ b/examples/camera_viz/transports/rtp_h264_sender.py @@ -252,7 +252,7 @@ def _send_loop(self) -> None: raise RuntimeError( f"RtpH264Sender: encode failed {consecutive_encode_failures} " f"times in a row; surfacing to supervisor for full restart" - ) + ) from e continue for pkt in packets: diff --git a/examples/noitom/noitom_retargeting.py b/examples/noitom/noitom_retargeting.py index 2264f46ab..32193a173 100644 --- a/examples/noitom/noitom_retargeting.py +++ b/examples/noitom/noitom_retargeting.py @@ -1264,7 +1264,7 @@ def compute_robot_reference_positions( parsed = _parse_upper_body(frame) if parsed is None: return {} - torso, left, right, _pelvis_world = parsed + _torso, left, right, _pelvis_world = parsed yaw_delta = _resolve_yaw_delta(current_yaw - calib.body_yaw_isaac, settings) anchor = settings.robot_pelvis_world.astype(np.float64) positions: dict[int, np.ndarray] = {int(BodyJoint.PELVIS): anchor.copy()} @@ -1754,7 +1754,7 @@ def _solve_wrist_target( yaw_delta = _resolve_yaw_delta(_compute_torso_yaw(torso) - calib_yaw, settings) if settings.use_posture_based_arms: - shoulder_robot, elbow_robot, wrist_robot = _arm_fk_robot_blended( + _shoulder_robot, elbow_robot, wrist_robot = _arm_fk_robot_blended( arm, neutral, settings, yaw_delta, is_left ) forearm = wrist_robot - elbow_robot diff --git a/examples/retargeting/python/wuji_hand_retargeter_demo.py b/examples/retargeting/python/wuji_hand_retargeter_demo.py index 4996981d2..2b7d5ad12 100755 --- a/examples/retargeting/python/wuji_hand_retargeter_demo.py +++ b/examples/retargeting/python/wuji_hand_retargeter_demo.py @@ -54,6 +54,7 @@ import signal import sys import time +from typing import ClassVar import numpy as np @@ -147,7 +148,7 @@ class _ReplayUnpickler(pickle.Unpickler): recording may have been produced elsewhere or shared. """ - _ALLOWED = { + _ALLOWED: ClassVar[set[tuple[str, str]]] = { ("numpy", "ndarray"), ("numpy", "dtype"), ("numpy.core.multiarray", "_reconstruct"), # numpy < 2 diff --git a/examples/teleop_session_manager/python/message_channel_example.py b/examples/teleop_session_manager/python/message_channel_example.py index e5c607089..d2faf4b66 100755 --- a/examples/teleop_session_manager/python/message_channel_example.py +++ b/examples/teleop_session_manager/python/message_channel_example.py @@ -30,7 +30,9 @@ def _positive_int(value: str) -> int: try: n = int(value) except ValueError: - raise argparse.ArgumentTypeError(f"expected a positive integer, got {value!r}") + raise argparse.ArgumentTypeError( + f"expected a positive integer, got {value!r}" + ) from None if n <= 0: raise argparse.ArgumentTypeError(f"must be a positive integer, got {n}") return n @@ -44,7 +46,7 @@ def _parse_uuid_bytes(uuid_text: str) -> bytes: raise argparse.ArgumentTypeError( f"--channel-uuid: invalid UUID {uuid_text!r} (expected canonical form, " "e.g. 550e8400-e29b-41d4-a716-446655440000)" - ) + ) from None def _enqueue_outbound_message(sink, payload: bytes) -> None: diff --git a/pyproject.toml b/pyproject.toml index 5d93e2ab7..384664ef3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -132,3 +132,72 @@ packages = { isaacteleop = "src/python/isaacteleop" } [tool.scikit-build.editable] mode = "redirect" rebuild = false + +# ============================================================================== +# Ruff +# ============================================================================== +# The ruff + ruff-format pre-commit hooks previously ran with no configuration at +# all, i.e. only ruff's built-in default rules (E4/E7/E9 + F). The `select` below +# widens that to the correctness-oriented rule groups; formatting stays on +# ruff-format's defaults (88 cols), which is what the tree is already formatted to. +# +# Everything in `select` is currently clean, so the hook is enforceable as-is. +# Each `ignore` is a deliberate call, not a backlog marker -- see its comment. +[tool.ruff] +target-version = "py311" + +[tool.ruff.lint] +select = [ + "E4", "E7", "E9", # pycodestyle errors (ruff's default subset) + "F", # pyflakes + "B", # flake8-bugbear -- real bug shapes + "C4", # flake8-comprehensions + "PIE", # flake8-pie + "PERF", # perflint + "PLE", "PLW", # pylint errors + warnings + "LOG", "G", # logging correctness + "ASYNC", # async pitfalls + "RUF", # ruff-specific +] + +ignore = [ + # Adding strict= to a zip() converts a silent truncation into a runtime + # exception, so it is a behaviour change per call site, not a mechanical fix. + # Worth adopting deliberately (15 sites) rather than in a lint sweep. + "B905", + # Style-only rewrites of code that is already correct and readable: + # RUF005 turns `a + [b]` into `[*a, b]`, C408 turns dict(a=1) into {"a": 1}, + # and RUF007 turns zip(x[:-1], x[1:]) into itertools.pairwise(x). Enforcing + # any of them means churning working call sites for no behaviour change, so + # they are left to author preference. + "RUF005", "C408", "RUF007", + # Sorting __all__ alphabetically destroys the semantic grouping comments the + # package __init__ files use ("# Hand types.", "# Controller types.", ...). + "RUF022", + # RUF100 is evaluated against `select` above, so it flags every noqa written + # for a rule this config does not (yet) enable -- BLE001, PLC0415, N803 and + # friends. Re-enable once those groups are adopted. + "RUF100", + # The tree deliberately uses en/em dashes and arrows in prose and comments. + "RUF001", "RUF002", "RUF003", + # Module-level singletons (EnvConfig, the CloudXR runtime handles) are an + # intentional design choice here. + "PLW0603", + # Rebinding a loop variable to its normalized form, and small wrapper lambdas, + # both read better than the suggested rewrites in the places they occur. + "PLW2901", "PLW0108", + # The suggested comprehensions inline filtering logic that is clearer as an + # explicit loop (see EnvConfig._merge_env). + "PERF401", "PERF403", + # Targets trio/anyio. The one flagged call is a sub-millisecond os.path.isfile + # in an asyncio poll loop that already sleeps between iterations; routing it + # through an executor would cost more than the stat it replaces. + "ASYNC240", + # Flags any async def taking a `timeout` parameter; that is the established + # signature for the adb/runtime helpers and their callers. + "ASYNC109", +] + +[tool.ruff.lint.per-file-ignores] +# Tests legitimately assert on private state and re-import inside test bodies. +"**/tests/**" = ["SLF001"] diff --git a/scripts/check_copyright_year.py b/scripts/check_copyright_year.py index fe8c6b8c9..361f081d9 100644 --- a/scripts/check_copyright_year.py +++ b/scripts/check_copyright_year.py @@ -30,6 +30,7 @@ def git_last_modified_year(path: Path) -> int | None: text=True, cwd=path.parent, timeout=5, + check=False, ) if result.returncode != 0: return None @@ -41,6 +42,7 @@ def git_last_modified_year(path: Path) -> int | None: text=True, cwd=root, timeout=5, + check=False, ) if result.returncode != 0 or not result.stdout.strip(): return None diff --git a/scripts/check_lfs_pointers.py b/scripts/check_lfs_pointers.py index 3bcd3775e..7f8f04896 100755 --- a/scripts/check_lfs_pointers.py +++ b/scripts/check_lfs_pointers.py @@ -37,6 +37,7 @@ def _staged_content(path: str) -> bytes | None: proc = subprocess.run( ["git", "show", f":{path}"], capture_output=True, + check=False, ) if proc.returncode != 0: return None diff --git a/src/core/cloudxr_tests/python/test_runtime.py b/src/core/cloudxr_tests/python/test_runtime.py index 42dbfd39e..e58a3f03e 100644 --- a/src/core/cloudxr_tests/python/test_runtime.py +++ b/src/core/cloudxr_tests/python/test_runtime.py @@ -166,7 +166,7 @@ def test_explicit_exp_missing_raises(self, monkeypatch): "isaacteleop.cloudxr.runtime._is_exp_available", lambda: False ) with pytest.raises( - RuntimeError, match="cloudxr_exp|ENABLE_CLOUDXR_EXP" + RuntimeError, match=r"cloudxr_exp|ENABLE_CLOUDXR_EXP" ) as excinfo: resolve_cloudxr_runtime_module() # The message must blame the missing *artifact*: cloudxr_exp is authored @@ -240,7 +240,7 @@ def test_auto_t234_missing_raises(self, monkeypatch): monkeypatch.setattr( "isaacteleop.cloudxr.runtime._is_exp_available", lambda: False ) - with pytest.raises(RuntimeError, match="cloudxr_exp|ENABLE_CLOUDXR_EXP"): + with pytest.raises(RuntimeError, match=r"cloudxr_exp|ENABLE_CLOUDXR_EXP"): resolve_cloudxr_runtime_module() @@ -270,7 +270,7 @@ def test_missing_raises(self, tmp_path, monkeypatch): self._select(monkeypatch, "isaacteleop.cloudxr") _patch_find_spec(monkeypatch, {"isaacteleop.cloudxr": roots}) - with pytest.raises(RuntimeError, match="libcloudxr.so") as excinfo: + with pytest.raises(RuntimeError, match=r"libcloudxr\.so") as excinfo: get_sdk_path() for root in roots: assert root in str(excinfo.value) diff --git a/src/core/retargeting_engine_tests/python/test_base_retargeter.py b/src/core/retargeting_engine_tests/python/test_base_retargeter.py index 1788413f5..0abba9964 100644 --- a/src/core/retargeting_engine_tests/python/test_base_retargeter.py +++ b/src/core/retargeting_engine_tests/python/test_base_retargeter.py @@ -508,7 +508,7 @@ def test_output_selector_repr(self): class ParametricScaleRetargeter(BaseRetargeter): """Retargeter with a tunable scale parameter.""" - def __init__(self, name: str, config_file: str = None) -> None: + def __init__(self, name: str, config_file: str | None = None) -> None: # Create parameter with sync function parameters = [ FloatParameter( diff --git a/src/core/retargeting_engine_tests/python/test_haptic_sink.py b/src/core/retargeting_engine_tests/python/test_haptic_sink.py index 762b1ab68..21ae6801a 100644 --- a/src/core/retargeting_engine_tests/python/test_haptic_sink.py +++ b/src/core/retargeting_engine_tests/python/test_haptic_sink.py @@ -122,9 +122,10 @@ def test_connect_rejects_mismatched_upstream_type(self) -> None: sink = HapticSink("sink", device) leaf = ValueInput("upstream", TactileVector(5)) - with pytest.raises(Exception): - # Compatibility check raises a TypeError or AssertionError depending - # on which TensorType detects the mismatch first; either is fine. + # Which exception surfaces depends on which TensorType detects the mismatch + # first; NDArrayType currently raises ValueError. Naming the accepted set + # beats a bare Exception, which would also swallow an unrelated failure. + with pytest.raises((TypeError, AssertionError, ValueError)): sink.connect({"left": leaf.output("value")}) diff --git a/src/core/retargeting_engine_tests/python/test_parameter_state.py b/src/core/retargeting_engine_tests/python/test_parameter_state.py index 566c8d6da..9965fc747 100644 --- a/src/core/retargeting_engine_tests/python/test_parameter_state.py +++ b/src/core/retargeting_engine_tests/python/test_parameter_state.py @@ -158,13 +158,12 @@ def test_save_and_load_to_file(self): def test_load_nonexistent_file_doesnt_error(self): """Test loading from nonexistent file doesn't error.""" param = FloatParameter(name="value", description="Value", default_value=1.0) - try: - ParameterState( - name="test", parameters=[param], config_file="/nonexistent/path.json" - ) - except Exception as e: - # Should not raise error during construction - assert False, f"Unexpected exception raised: {e}" + # Must not raise during construction; letting any exception propagate + # fails the test with the original traceback (and unlike `assert False`, + # this still holds under `python -O`). + ParameterState( + name="test", parameters=[param], config_file="/nonexistent/path.json" + ) def test_load_from_file_after_save(self): """Test loading from saved file.""" diff --git a/src/core/teleop_session_manager_tests/python/test_teleop_session.py b/src/core/teleop_session_manager_tests/python/test_teleop_session.py index 586b58033..ed7d24939 100644 --- a/src/core/teleop_session_manager_tests/python/test_teleop_session.py +++ b/src/core/teleop_session_manager_tests/python/test_teleop_session.py @@ -603,8 +603,6 @@ def poll_tracker(self, deviceio_session): class MockOpenXRHandles: """Mock OpenXR session handles.""" - pass - class MockOpenXRSession: """Mock OpenXR session that supports context manager protocol.""" @@ -985,7 +983,7 @@ def test_external_leaves_missing_all_inputs(self): config = make_config(pipeline) session = TeleopSession(config) - with pytest.raises(ValueError, match="external.*non-DeviceIO"): + with pytest.raises(ValueError, match=r"external.*non-DeviceIO"): session._validate_external_inputs(None) def test_external_leaves_missing_some_inputs(self): @@ -1573,7 +1571,7 @@ def test_step_raises_on_missing_external_inputs(self): with mock_session_dependencies(): session = TeleopSession(config) with session: - with pytest.raises(ValueError, match="external.*non-DeviceIO"): + with pytest.raises(ValueError, match=r"external.*non-DeviceIO"): session.step() def test_step_checks_plugin_health_every_60_frames(self): diff --git a/src/postprocessing/egocentric_hand_reconstruction/quality_control/detect_jumpy_hand_from_world_results.py b/src/postprocessing/egocentric_hand_reconstruction/quality_control/detect_jumpy_hand_from_world_results.py index 8a23cf745..83b4b51f2 100755 --- a/src/postprocessing/egocentric_hand_reconstruction/quality_control/detect_jumpy_hand_from_world_results.py +++ b/src/postprocessing/egocentric_hand_reconstruction/quality_control/detect_jumpy_hand_from_world_results.py @@ -379,7 +379,7 @@ def _load_track_info_detection_lost( for b, s in enumerate(stats): # Find vis_mask for this track: track_info has track_id -> {index, vis_mask}; index matches our b vis_mask = None - for tid, info in tracks.items(): + for info in tracks.values(): if info.get("index") == b: vis_mask = info.get("vis_mask") break diff --git a/src/python/isaacteleop/cloudxr/env_config.py b/src/python/isaacteleop/cloudxr/env_config.py index 4408f6809..227b7cc4f 100644 --- a/src/python/isaacteleop/cloudxr/env_config.py +++ b/src/python/isaacteleop/cloudxr/env_config.py @@ -13,6 +13,7 @@ import shlex import warnings from pathlib import Path +from typing import ClassVar class EnvConfig: @@ -43,7 +44,7 @@ class EnvConfig: ) # Default env var name -> default value. Empty string or None means "resolve later" - _DEFAULT_ENV: dict[str, str | None] = { + _DEFAULT_ENV: ClassVar[dict[str, str | None]] = { "XR_RUNTIME_JSON": None, # resolved from openxr_run_dir() "NV_CXR_RUNTIME_DIR": None, # resolved from openxr_run_dir() "NV_CXR_OUTPUT_DIR": None, # resolved from ensure_logs_dir() diff --git a/src/python/isaacteleop/cloudxr/launcher.py b/src/python/isaacteleop/cloudxr/launcher.py index 0236b8e15..33b0f65ba 100644 --- a/src/python/isaacteleop/cloudxr/launcher.py +++ b/src/python/isaacteleop/cloudxr/launcher.py @@ -589,6 +589,7 @@ def _cleanup_stale_runtime(env_cfg: EnvConfig) -> None: ["fuser", "-k", "-TERM", ipc_socket], capture_output=True, timeout=5, + check=False, ) if result.returncode == 0: time.sleep(1) diff --git a/src/python/isaacteleop/cloudxr/oob_teleop_adb.py b/src/python/isaacteleop/cloudxr/oob_teleop_adb.py index 4405b56cd..727155dd0 100644 --- a/src/python/isaacteleop/cloudxr/oob_teleop_adb.py +++ b/src/python/isaacteleop/cloudxr/oob_teleop_adb.py @@ -637,7 +637,9 @@ def open_url_on_headset(url: str) -> tuple[int, str]: redact_control_token(" ".join(shlex.quote(c) for c in full)), ) try: - proc = subprocess.run(full, capture_output=True, text=True, timeout=30) + proc = subprocess.run( + full, capture_output=True, text=True, timeout=30, check=False + ) except subprocess.TimeoutExpired as e: partial = ( (e.stderr or e.stdout or b"") diff --git a/src/python/isaacteleop/haptic_devices/controller.py b/src/python/isaacteleop/haptic_devices/controller.py index d78355c9a..91cc9ed82 100644 --- a/src/python/isaacteleop/haptic_devices/controller.py +++ b/src/python/isaacteleop/haptic_devices/controller.py @@ -79,9 +79,7 @@ def __init__( ) # Latest-wins per endpoint within a frame; emitted and cleared by flush. self._pending: dict[Endpoint, _Pulse] = {} - self._error_logged: dict[Endpoint, bool] = { - endpoint: False for endpoint in self._endpoints - } + self._error_logged: dict[Endpoint, bool] = dict.fromkeys(self._endpoints, False) def accepted_type(self) -> TensorGroupType: return ControllerHapticPulse() diff --git a/src/python/isaacteleop/haptic_devices/push_tensor.py b/src/python/isaacteleop/haptic_devices/push_tensor.py index 73f262d5e..fd988dcf2 100644 --- a/src/python/isaacteleop/haptic_devices/push_tensor.py +++ b/src/python/isaacteleop/haptic_devices/push_tensor.py @@ -86,9 +86,7 @@ def __init__( ) # Latest-wins per endpoint within a frame; emitted and cleared by flush. self._pending: dict[Endpoint, list[float]] = {} - self._error_logged: dict[Endpoint, bool] = { - endpoint: False for endpoint in self._endpoints - } + self._error_logged: dict[Endpoint, bool] = dict.fromkeys(self._endpoints, False) def accepted_type(self) -> TensorGroupType: return self._accepted_type diff --git a/src/python/isaacteleop/retargeters/dex_hand_retargeter.py b/src/python/isaacteleop/retargeters/dex_hand_retargeter.py index b056de146..e0555398c 100644 --- a/src/python/isaacteleop/retargeters/dex_hand_retargeter.py +++ b/src/python/isaacteleop/retargeters/dex_hand_retargeter.py @@ -323,7 +323,7 @@ def _update_yaml(self, yaml_path: str, urdf_path: str) -> Optional[str]: return None except Exception as e: - logger.error(f"Error updating YAML {yaml_path}: {e}") + logger.error("Error updating YAML %s: %s", yaml_path, e) return None def _compute_hand(self, poses: Dict[str, np.ndarray]) -> np.ndarray: @@ -418,7 +418,7 @@ def _compute_hand(self, poses: Dict[str, np.ndarray]) -> np.ndarray: with torch.enable_grad(), torch.inference_mode(False): return self._dex_hand.retarget(ref_value) # type: ignore except Exception as e: - logger.error(f"Error in retargeting: {e}") + logger.error("Error in retargeting: %s", e) return np.zeros(len(self._dex_hand.optimizer.robot.dof_joint_names)) diff --git a/src/python/isaacteleop/retargeting_engine/deviceio_source_nodes/interface.py b/src/python/isaacteleop/retargeting_engine/deviceio_source_nodes/interface.py index 35849b147..c8e5e32e0 100644 --- a/src/python/isaacteleop/retargeting_engine/deviceio_source_nodes/interface.py +++ b/src/python/isaacteleop/retargeting_engine/deviceio_source_nodes/interface.py @@ -71,7 +71,6 @@ def get_tracker(self) -> "ITracker": Returns: The ITracker instance (e.g., HeadTracker, HandTracker, ControllerTracker) """ - pass @abstractmethod def poll_tracker(self, deviceio_session: Any) -> RetargeterIO: @@ -87,4 +86,3 @@ def poll_tracker(self, deviceio_session: Any) -> RetargeterIO: Dict mapping input names to TensorGroups containing raw tracker data, matching this source's input_spec(). """ - pass diff --git a/src/python/isaacteleop/retargeting_engine/interface/base_retargeter.py b/src/python/isaacteleop/retargeting_engine/interface/base_retargeter.py index 9e7bce35a..e1d033d60 100644 --- a/src/python/isaacteleop/retargeting_engine/interface/base_retargeter.py +++ b/src/python/isaacteleop/retargeting_engine/interface/base_retargeter.py @@ -111,7 +111,6 @@ def input_spec(self) -> RetargeterIOType: Returns: Dict[str, TensorGroupType] - Input specification """ - pass @abstractmethod def output_spec(self) -> RetargeterIOType: @@ -121,7 +120,6 @@ def output_spec(self) -> RetargeterIOType: Returns: Dict[str, TensorGroupType] - Output specification """ - pass @abstractmethod def _compute_fn( @@ -153,7 +151,6 @@ def _compute_fn( outputs: Output tensor groups to populate (Dict[str, TensorGroup]) context: Compute context containing graph time and future metadata """ - pass # ======================================================================== # Public convenience API — not part of any abstract contract diff --git a/src/python/isaacteleop/retargeting_engine/interface/tensor_type.py b/src/python/isaacteleop/retargeting_engine/interface/tensor_type.py index e2c7b75e0..8315645b1 100644 --- a/src/python/isaacteleop/retargeting_engine/interface/tensor_type.py +++ b/src/python/isaacteleop/retargeting_engine/interface/tensor_type.py @@ -66,7 +66,6 @@ def _check_instance_compatibility(self, other: "TensorType") -> bool: Returns: True if the instances are compatible, False otherwise """ - pass @abstractmethod def validate_value(self, value: Any) -> None: @@ -83,7 +82,6 @@ def validate_value(self, value: Any) -> None: Raises: TypeError: If the value does not conform to this tensor type """ - pass def __repr__(self) -> str: return f"{self.__class__.__name__}(name='{self._name}')" diff --git a/src/python/isaacteleop/retargeting_engine/interface/tunable_parameter.py b/src/python/isaacteleop/retargeting_engine/interface/tunable_parameter.py index c2dbecfd8..236554170 100644 --- a/src/python/isaacteleop/retargeting_engine/interface/tunable_parameter.py +++ b/src/python/isaacteleop/retargeting_engine/interface/tunable_parameter.py @@ -27,22 +27,18 @@ class ParameterSpec(ABC): @abstractmethod def validate(self, value: Any) -> bool: """Check if a value is valid for this parameter.""" - pass @abstractmethod def get_default_value(self) -> Any: """Get the default value for this parameter.""" - pass @abstractmethod def serialize(self, value: Any) -> Any: """Serialize a value to a JSON-compatible format.""" - pass @abstractmethod def deserialize(self, value: Any) -> Any: """Deserialize a value from a JSON-compatible format.""" - pass @dataclass diff --git a/src/python/isaacteleop/retargeting_engine_ui/multi_retargeter_tuning_ui.py b/src/python/isaacteleop/retargeting_engine_ui/multi_retargeter_tuning_ui.py index 661babfb0..523dadd29 100644 --- a/src/python/isaacteleop/retargeting_engine_ui/multi_retargeter_tuning_ui.py +++ b/src/python/isaacteleop/retargeting_engine_ui/multi_retargeter_tuning_ui.py @@ -368,7 +368,7 @@ def _render_floating_layout(self) -> None: imgui.set_next_window_size(400, 500, imgui.FIRST_USE_EVER) # Create independent floating window - expanded, opened = imgui.begin(f"{name}##floating", closable=False) + expanded, _opened = imgui.begin(f"{name}##floating", closable=False) if expanded: self._render_retargeter_params(name, param_state) imgui.end() diff --git a/src/python/isaacteleop/rig/launcher.py b/src/python/isaacteleop/rig/launcher.py index 0881f84f2..4c643fe58 100644 --- a/src/python/isaacteleop/rig/launcher.py +++ b/src/python/isaacteleop/rig/launcher.py @@ -111,6 +111,7 @@ def _check_python_can_import_cloudxr(env: Mapping[str, str]) -> None: [sys.executable, "-c", "import isaacteleop.cloudxr"], env=dict(env), capture_output=True, + check=False, ) if probe.returncode != 0: raise PreflightError(