From 2bf33ccb985f2e5bffbad5a170f41c145aef7b93 Mon Sep 17 00:00:00 2001 From: Steven Palma Date: Sun, 12 Apr 2026 13:52:45 +0200 Subject: [PATCH] fix leaking imports in minimal testing --- docs/source/installation.mdx | 1 - pyproject.toml | 21 ++-- src/lerobot/motors/motors_bus.py | 18 ++- src/lerobot/policies/sarm/processor_sarm.py | 32 ++++- .../policies/wall_x/modeling_wall_x.py | 9 +- .../homunculus/homunculus_arm.py | 10 +- .../homunculus/homunculus_glove.py | 10 +- .../teleoperators/unitree_g1/exo_calib.py | 13 +- .../teleoperators/unitree_g1/exo_serial.py | 13 +- src/lerobot/utils/import_utils.py | 9 ++ tests/async_inference/test_e2e.py | 4 +- tests/async_inference/test_robot_client.py | 4 +- tests/conftest.py | 27 ++++- tests/datasets/test_aggregate.py | 6 +- tests/datasets/test_compute_stats.py | 2 + tests/datasets/test_dataset_metadata.py | 2 + tests/datasets/test_dataset_reader.py | 4 + tests/datasets/test_dataset_tools.py | 2 + tests/datasets/test_dataset_utils.py | 5 +- tests/datasets/test_dataset_writer.py | 2 + tests/datasets/test_datasets.py | 3 + tests/datasets/test_delta_timestamps.py | 2 + tests/datasets/test_image_transforms.py | 2 + tests/datasets/test_image_writer.py | 2 + tests/datasets/test_lerobot_dataset.py | 2 + .../test_quantiles_dataset_integration.py | 2 + tests/datasets/test_sampler.py | 5 +- tests/datasets/test_streaming.py | 2 + .../datasets/test_streaming_video_encoder.py | 5 +- tests/datasets/test_subtask_dataset.py | 5 +- tests/datasets/test_visualize_dataset.py | 2 + tests/motors/test_motors_bus.py | 2 + tests/policies/groot/test_groot_lerobot.py | 2 +- tests/policies/smolvla/test_smolvla_rtc.py | 10 +- tests/policies/test_policies.py | 3 + tests/policies/test_relative_actions.py | 2 + tests/processor/test_pipeline.py | 2 + tests/rl/test_actor.py | 3 + tests/rl/test_actor_learner.py | 3 + tests/scripts/test_edit_dataset_parsing.py | 2 + tests/test_control_robot.py | 4 + tests/training/test_multi_gpu.py | 2 + tests/training/test_visual_validation.py | 2 + tests/utils/test_replay_buffer.py | 1 + tests/utils/test_visualization_utils.py | 2 + uv.lock | 111 ++++++++---------- 46 files changed, 256 insertions(+), 121 deletions(-) diff --git a/docs/source/installation.mdx b/docs/source/installation.mdx index d3a45634d..1d772fc97 100644 --- a/docs/source/installation.mdx +++ b/docs/source/installation.mdx @@ -179,7 +179,6 @@ LeRobot provides **feature-scoped extras** that map to common workflows. If you | `training` | `dataset` + `accelerate`, `wandb` | Training policies | | `hardware` | `pynput`, `pyserial`, `deepdiff` | Connecting to real robots | | `viz` | `rerun-sdk` | Visualization during recording/eval | -| `build` | `cmake`, `setuptools` | Building native extensions | **Composite Extras** combine feature extras for common CLI scripts: diff --git a/pyproject.toml b/pyproject.toml index 3db911fb3..b6153f247 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -84,6 +84,10 @@ dependencies = [ "packaging>=24.2,<26.0", "termcolor>=2.4.0,<4.0.0", "tqdm>=4.66.0,<5.0.0", + + # Build tools (required by opencv-python-headless on some platforms) + "cmake>=3.29.0.1,<4.2.0", + "setuptools>=71.0.0,<81.0.0", ] # Optional dependencies @@ -111,11 +115,6 @@ hardware = [ viz = [ "rerun-sdk>=0.24.0,<0.27.0", ] -build = [ - "cmake>=3.29.0.1,<4.2.0", - "setuptools>=71.0.0,<81.0.0", -] - # ── User-facing composite extras (map to CLI scripts) ───── # lerobot-record, lerobot-replay, lerobot-calibrate, lerobot-teleoperate, etc. core_scripts = ["lerobot[dataset]", "lerobot[hardware]", "lerobot[viz]"] @@ -197,8 +196,8 @@ async = ["lerobot[grpcio-dep]", "lerobot[matplotlib-dep]"] peft = ["lerobot[transformers-dep]", "lerobot[peft-dep]"] # Development -dev = ["lerobot[dataset]", "lerobot[training]", "lerobot[hardware]", "lerobot[viz]", "pre-commit>=3.7.0,<5.0.0", "debugpy>=1.8.1,<1.9.0", "lerobot[grpcio-dep]", "grpcio-tools==1.73.1", "mypy>=1.19.1"] -test = ["lerobot[dataset]", "lerobot[training]", "lerobot[hardware]", "lerobot[viz]", "pytest>=8.1.0,<9.0.0", "pytest-timeout>=2.4.0,<3.0.0", "pytest-cov>=5.0.0,<8.0.0", "mock-serial>=0.0.1,<0.1.0 ; sys_platform != 'win32'"] +dev = ["pre-commit>=3.7.0,<5.0.0", "debugpy>=1.8.1,<1.9.0", "lerobot[grpcio-dep]", "grpcio-tools==1.73.1", "mypy>=1.19.1", "ruff>=0.14.1"] +test = ["pytest>=8.1.0,<9.0.0", "pytest-timeout>=2.4.0,<3.0.0", "pytest-cov>=5.0.0,<8.0.0", "mock-serial>=0.0.1,<0.1.0 ; sys_platform != 'win32'"] video_benchmark = ["scikit-image>=0.23.2,<0.26.0", "pandas>=2.2.2,<2.4.0"] # Simulation @@ -215,7 +214,6 @@ all = [ "lerobot[training]", "lerobot[hardware]", "lerobot[viz]", - "lerobot[build]", # NOTE(resolver hint): scipy is pulled in transitively via lerobot[scipy-dep] through # multiple extras (aloha, metaworld, pi, wallx, phone). Listing it explicitly # helps pip's resolver converge by constraining scipy early, before it encounters @@ -311,13 +309,8 @@ ignore = [ [tool.ruff.lint.per-file-ignores] "__init__.py" = ["F401", "F403", "E402"] -# E402: require_package() guards must precede the imports they protect -"src/lerobot/policies/wall_x/modeling_wall_x.py" = ["E402"] -"src/lerobot/policies/smolvla/smolvlm_with_expert.py" = ["E402"] -"src/lerobot/policies/xvla/modeling_xvla.py" = ["E402"] -"src/lerobot/policies/sarm/processor_sarm.py" = ["E402"] +# E402: conditional-import guards (TYPE_CHECKING / is_package_available) must precede the imports they protect "src/lerobot/scripts/convert_dataset_v21_to_v30.py" = ["E402"] -"src/lerobot/teleoperators/unitree_g1/exo_serial.py" = ["E402"] "src/lerobot/policies/wall_x/**" = ["N801", "N812", "SIM102", "SIM108", "SIM210", "SIM211", "B006", "B007", "SIM118"] # Supprese these as they are coming from original Qwen2_5_vl code TODO(pepijn): refactor original [tool.ruff.lint.isort] diff --git a/src/lerobot/motors/motors_bus.py b/src/lerobot/motors/motors_bus.py index 509f5e95f..209489bb9 100644 --- a/src/lerobot/motors/motors_bus.py +++ b/src/lerobot/motors/motors_bus.py @@ -29,12 +29,22 @@ from dataclasses import dataclass from enum import Enum from functools import cached_property from pprint import pformat -from typing import Protocol +from typing import TYPE_CHECKING, Protocol -import serial -from deepdiff import DeepDiff from tqdm import tqdm +from lerobot.utils.import_utils import _deepdiff_available, _serial_available, require_package + +if TYPE_CHECKING or _serial_available: + import serial +else: + serial = None # type: ignore[assignment] + +if TYPE_CHECKING or _deepdiff_available: + from deepdiff import DeepDiff +else: + DeepDiff = None # type: ignore[assignment, misc] + from lerobot.utils.decorators import check_if_already_connected, check_if_not_connected from lerobot.utils.utils import enter_pressed, move_cursor_up @@ -346,6 +356,8 @@ class SerialMotorsBus(MotorsBusBase): motors: dict[str, Motor], calibration: dict[str, MotorCalibration] | None = None, ): + require_package("pyserial", extra="hardware", import_name="serial") + require_package("deepdiff", extra="hardware") super().__init__(port, motors, calibration) self.port_handler: PortHandler diff --git a/src/lerobot/policies/sarm/processor_sarm.py b/src/lerobot/policies/sarm/processor_sarm.py index 57fb7c8af..e939b3485 100644 --- a/src/lerobot/policies/sarm/processor_sarm.py +++ b/src/lerobot/policies/sarm/processor_sarm.py @@ -16,20 +16,37 @@ """SARM Processor for encoding images/text and generating stage+tau targets.""" +from __future__ import annotations + import random -from typing import Any +from typing import TYPE_CHECKING, Any import numpy as np -import pandas as pd import torch -from faker import Faker from PIL import Image -from lerobot.utils.import_utils import require_package +from lerobot.utils.import_utils import ( + _faker_available, + _pandas_available, + _transformers_available, + require_package, +) -require_package("transformers", extra="sarm") +if TYPE_CHECKING or _transformers_available: + from transformers import CLIPModel, CLIPProcessor +else: + CLIPModel = None # type: ignore[assignment, misc] + CLIPProcessor = None # type: ignore[assignment, misc] -from transformers import CLIPModel, CLIPProcessor +if TYPE_CHECKING or _pandas_available: + import pandas as pd +else: + pd = None # type: ignore[assignment] + +if TYPE_CHECKING or _faker_available: + from faker import Faker +else: + Faker = None # type: ignore[assignment, misc] from lerobot.configs import FeatureType, PipelineFeatureType, PolicyFeature from lerobot.processor import ( @@ -65,6 +82,9 @@ class SARMEncodingProcessorStep(ProcessorStep): dataset_meta=None, dataset_stats: dict | None = None, ): + require_package("transformers", extra="sarm") + require_package("faker", extra="sarm") + require_package("pandas", extra="dataset") super().__init__() self.config = config self.image_key = image_key or config.image_key diff --git a/src/lerobot/policies/wall_x/modeling_wall_x.py b/src/lerobot/policies/wall_x/modeling_wall_x.py index 968d94129..bfecf3852 100644 --- a/src/lerobot/policies/wall_x/modeling_wall_x.py +++ b/src/lerobot/policies/wall_x/modeling_wall_x.py @@ -51,10 +51,7 @@ from torch.nn import CrossEntropyLoss from lerobot.utils.constants import ACTION, OBS_STATE from lerobot.utils.import_utils import ( - _peft_available, - _qwen_vl_utils_available, - _torchdiffeq_available, - _transformers_available, + _wallx_deps_available, require_package, ) @@ -72,10 +69,6 @@ from .constant import ( TOKENIZER_MAX_LENGTH, ) -_wallx_deps_available = ( - _transformers_available and _peft_available and _torchdiffeq_available and _qwen_vl_utils_available -) - if TYPE_CHECKING or _wallx_deps_available: from peft import LoraConfig, get_peft_model from qwen_vl_utils.vision_process import smart_resize diff --git a/src/lerobot/teleoperators/homunculus/homunculus_arm.py b/src/lerobot/teleoperators/homunculus/homunculus_arm.py index 178eed544..225235b59 100644 --- a/src/lerobot/teleoperators/homunculus/homunculus_arm.py +++ b/src/lerobot/teleoperators/homunculus/homunculus_arm.py @@ -18,11 +18,16 @@ import logging import threading from collections import deque from pprint import pformat - -import serial +from typing import TYPE_CHECKING from lerobot.motors.motors_bus import MotorCalibration, MotorNormMode from lerobot.utils.decorators import check_if_already_connected, check_if_not_connected +from lerobot.utils.import_utils import _serial_available, require_package + +if TYPE_CHECKING or _serial_available: + import serial +else: + serial = None # type: ignore[assignment] from lerobot.utils.utils import enter_pressed, move_cursor_up from ..teleoperator import Teleoperator @@ -40,6 +45,7 @@ class HomunculusArm(Teleoperator): name = "homunculus_arm" def __init__(self, config: HomunculusArmConfig): + require_package("pyserial", extra="hardware", import_name="serial") super().__init__(config) self.config = config self.serial = serial.Serial(config.port, config.baud_rate, timeout=1) diff --git a/src/lerobot/teleoperators/homunculus/homunculus_glove.py b/src/lerobot/teleoperators/homunculus/homunculus_glove.py index a58e35025..655bae726 100644 --- a/src/lerobot/teleoperators/homunculus/homunculus_glove.py +++ b/src/lerobot/teleoperators/homunculus/homunculus_glove.py @@ -18,12 +18,17 @@ import logging import threading from collections import deque from pprint import pformat - -import serial +from typing import TYPE_CHECKING from lerobot.motors import MotorCalibration from lerobot.motors.motors_bus import MotorNormMode from lerobot.utils.decorators import check_if_already_connected, check_if_not_connected +from lerobot.utils.import_utils import _serial_available, require_package + +if TYPE_CHECKING or _serial_available: + import serial +else: + serial = None # type: ignore[assignment] from lerobot.utils.utils import enter_pressed, move_cursor_up from ..teleoperator import Teleoperator @@ -66,6 +71,7 @@ class HomunculusGlove(Teleoperator): name = "homunculus_glove" def __init__(self, config: HomunculusGloveConfig): + require_package("pyserial", extra="hardware", import_name="serial") super().__init__(config) self.config = config self.serial = serial.Serial(config.port, config.baud_rate, timeout=1) diff --git a/src/lerobot/teleoperators/unitree_g1/exo_calib.py b/src/lerobot/teleoperators/unitree_g1/exo_calib.py index b90e8fd7e..05f5180ff 100644 --- a/src/lerobot/teleoperators/unitree_g1/exo_calib.py +++ b/src/lerobot/teleoperators/unitree_g1/exo_calib.py @@ -22,15 +22,24 @@ and calculate arctan2 of the unit circle to get the joint angle. We then store the ellipse parameters and the zero offset for each joint to be used at runtime. """ +from __future__ import annotations + import json import logging import time from collections import deque from dataclasses import dataclass, field from pathlib import Path +from typing import TYPE_CHECKING import numpy as np -import serial + +from lerobot.utils.import_utils import _serial_available + +if TYPE_CHECKING or _serial_available: + import serial +else: + serial = None # type: ignore[assignment] logger = logging.getLogger(__name__) @@ -82,7 +91,7 @@ class ExoskeletonCalibration: } @classmethod - def from_dict(cls, data: dict) -> "ExoskeletonCalibration": + def from_dict(cls, data: dict) -> ExoskeletonCalibration: joints = [ ExoskeletonJointCalibration( name=j["name"], diff --git a/src/lerobot/teleoperators/unitree_g1/exo_serial.py b/src/lerobot/teleoperators/unitree_g1/exo_serial.py index deb813f4b..9b1c71891 100644 --- a/src/lerobot/teleoperators/unitree_g1/exo_serial.py +++ b/src/lerobot/teleoperators/unitree_g1/exo_serial.py @@ -14,16 +14,20 @@ # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import annotations + import json import logging from dataclasses import dataclass from pathlib import Path +from typing import TYPE_CHECKING -from lerobot.utils.import_utils import require_package +from lerobot.utils.import_utils import _serial_available, require_package -require_package("pyserial", extra="hardware", import_name="serial") - -import serial +if TYPE_CHECKING or _serial_available: + import serial +else: + serial = None # type: ignore[assignment] from .exo_calib import ExoskeletonCalibration, exo_raw_to_angles, run_exo_calibration @@ -72,6 +76,7 @@ class ExoskeletonArm: calibration: ExoskeletonCalibration | None = None def __post_init__(self): + require_package("pyserial", extra="hardware", import_name="serial") if self.calibration_fpath.is_file(): self._load_calibration() diff --git a/src/lerobot/utils/import_utils.py b/src/lerobot/utils/import_utils.py index 425bf44eb..8cd24b0fa 100644 --- a/src/lerobot/utils/import_utils.py +++ b/src/lerobot/utils/import_utils.py @@ -108,16 +108,25 @@ _diffusers_available = is_package_available("diffusers") _torchdiffeq_available = is_package_available("torchdiffeq") # Hardware SDKs +_serial_available = is_package_available("pyserial", import_name="serial") +_deepdiff_available = is_package_available("deepdiff") _dynamixel_sdk_available = is_package_available("dynamixel-sdk", import_name="dynamixel_sdk") _feetech_sdk_available = is_package_available("feetech-servo-sdk", import_name="scservo_sdk") _reachy2_sdk_available = is_package_available("reachy2_sdk") _can_available = is_package_available("python-can", "can") _unitree_sdk_available = is_package_available("unitree-sdk2py", "unitree_sdk2py") +# Data / serialization +_pandas_available = is_package_available("pandas") +_faker_available = is_package_available("faker") + # Misc _pynput_available = is_package_available("pynput") _pygame_available = is_package_available("pygame") _qwen_vl_utils_available = is_package_available("qwen-vl-utils", import_name="qwen_vl_utils") +_wallx_deps_available = ( + _transformers_available and _peft_available and _torchdiffeq_available and _qwen_vl_utils_available +) def make_device_from_device_class(config: ChoiceRegistry) -> Any: diff --git a/tests/async_inference/test_e2e.py b/tests/async_inference/test_e2e.py index 54ca29b48..8c5861a91 100644 --- a/tests/async_inference/test_e2e.py +++ b/tests/async_inference/test_e2e.py @@ -35,8 +35,10 @@ from concurrent import futures import pytest import torch -# Skip entire module if grpc is not available +# Skip entire module if required deps are not available pytest.importorskip("grpc") +pytest.importorskip("serial", reason="pyserial is required (install lerobot[hardware])") +pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])") # ----------------------------------------------------------------------------- # End-to-end test diff --git a/tests/async_inference/test_robot_client.py b/tests/async_inference/test_robot_client.py index d7ef5b350..e2d840358 100644 --- a/tests/async_inference/test_robot_client.py +++ b/tests/async_inference/test_robot_client.py @@ -25,8 +25,10 @@ from queue import Queue import pytest import torch -# Skip entire module if grpc is not available +# Skip entire module if required deps are not available pytest.importorskip("grpc") +pytest.importorskip("serial", reason="pyserial is required (install lerobot[hardware])") +pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])") # ----------------------------------------------------------------------------- # Test fixtures diff --git a/tests/conftest.py b/tests/conftest.py index 2fcf878ab..cadeaf0d3 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -17,24 +17,39 @@ import traceback import pytest -from serial import SerialException from lerobot.configs.types import FeatureType, PipelineFeatureType, PolicyFeature +from lerobot.utils.import_utils import is_package_available from tests.utils import DEVICE -# Import fixture modules as plugins +# Import fixture modules as plugins. +# Fixtures that depend on optional packages are only registered when those packages are available, +# so that tests can be collected and run even with a minimal install. pytest_plugins = [ - "tests.fixtures.dataset_factories", - "tests.fixtures.files", - "tests.fixtures.hub", "tests.fixtures.optimizers", ] +if is_package_available("datasets"): + pytest_plugins += [ + "tests.fixtures.dataset_factories", + "tests.fixtures.files", + "tests.fixtures.hub", + ] + def pytest_collection_finish(): print(f"\nTesting with {DEVICE=}") +def _is_serial_exception(exc: Exception) -> bool: + """Check if an exception is a SerialException without requiring pyserial.""" + if not is_package_available("pyserial", import_name="serial"): + return False + from serial import SerialException + + return isinstance(exc, SerialException) + + def _check_component_availability(component_type, available_components, make_component): """Generic helper to check if a hardware component is available""" if component_type not in available_components: @@ -53,7 +68,7 @@ def _check_component_availability(component_type, available_components, make_com if isinstance(e, ModuleNotFoundError): print(f"\nInstall module '{e.name}'") - elif isinstance(e, SerialException): + elif _is_serial_exception(e): print("\nNo physical device detected.") elif isinstance(e, ValueError) and "camera_index" in str(e): print("\nNo physical camera detected.") diff --git a/tests/datasets/test_aggregate.py b/tests/datasets/test_aggregate.py index 4ac7e001a..b74299311 100644 --- a/tests/datasets/test_aggregate.py +++ b/tests/datasets/test_aggregate.py @@ -16,7 +16,11 @@ from unittest.mock import patch -import datasets +import pytest + +pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])") + +import datasets # noqa: E402 import torch from lerobot.datasets.aggregate import aggregate_datasets diff --git a/tests/datasets/test_compute_stats.py b/tests/datasets/test_compute_stats.py index 973c80bd8..70ba42378 100644 --- a/tests/datasets/test_compute_stats.py +++ b/tests/datasets/test_compute_stats.py @@ -18,6 +18,8 @@ from unittest.mock import patch import numpy as np import pytest +pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])") + from lerobot.datasets.compute_stats import ( RunningQuantileStats, _assert_type_and_shape, diff --git a/tests/datasets/test_dataset_metadata.py b/tests/datasets/test_dataset_metadata.py index 3f3971e15..6db41d05c 100644 --- a/tests/datasets/test_dataset_metadata.py +++ b/tests/datasets/test_dataset_metadata.py @@ -20,6 +20,8 @@ import json import numpy as np import pytest +pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])") + from lerobot.datasets.dataset_metadata import LeRobotDatasetMetadata from lerobot.datasets.utils import INFO_PATH from tests.fixtures.constants import DEFAULT_FPS, DUMMY_ROBOT_TYPE diff --git a/tests/datasets/test_dataset_reader.py b/tests/datasets/test_dataset_reader.py index be3616922..bbe858b5d 100644 --- a/tests/datasets/test_dataset_reader.py +++ b/tests/datasets/test_dataset_reader.py @@ -15,6 +15,10 @@ # limitations under the License. """Contract tests for DatasetReader.""" +import pytest + +pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])") + from lerobot.datasets.dataset_reader import DatasetReader from lerobot.utils.import_utils import get_safe_default_codec diff --git a/tests/datasets/test_dataset_tools.py b/tests/datasets/test_dataset_tools.py index 5ed7aa1a3..0b0862f00 100644 --- a/tests/datasets/test_dataset_tools.py +++ b/tests/datasets/test_dataset_tools.py @@ -21,6 +21,8 @@ import numpy as np import pytest import torch +pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])") + from lerobot.datasets.dataset_tools import ( add_features, delete_episodes, diff --git a/tests/datasets/test_dataset_utils.py b/tests/datasets/test_dataset_utils.py index 0cf9bf4d4..bf705ba81 100644 --- a/tests/datasets/test_dataset_utils.py +++ b/tests/datasets/test_dataset_utils.py @@ -16,7 +16,10 @@ import pytest import torch -from datasets import Dataset + +pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])") + +from datasets import Dataset # noqa: E402 from huggingface_hub import DatasetCard from lerobot.datasets.io_utils import hf_transform_to_torch diff --git a/tests/datasets/test_dataset_writer.py b/tests/datasets/test_dataset_writer.py index 8c6ee68bd..8d2bc0373 100644 --- a/tests/datasets/test_dataset_writer.py +++ b/tests/datasets/test_dataset_writer.py @@ -23,6 +23,8 @@ import pytest import torch from PIL import Image +pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])") + from lerobot.datasets.dataset_writer import _encode_video_worker from lerobot.datasets.lerobot_dataset import LeRobotDataset from lerobot.datasets.utils import DEFAULT_IMAGE_PATH diff --git a/tests/datasets/test_datasets.py b/tests/datasets/test_datasets.py index 0a00c7acb..6d4c41aaa 100644 --- a/tests/datasets/test_datasets.py +++ b/tests/datasets/test_datasets.py @@ -21,6 +21,9 @@ from pathlib import Path import numpy as np import pytest import torch + +pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])") + from huggingface_hub import HfApi from PIL import Image from safetensors.torch import load_file diff --git a/tests/datasets/test_delta_timestamps.py b/tests/datasets/test_delta_timestamps.py index 8d9529f68..e4e5cf4f3 100644 --- a/tests/datasets/test_delta_timestamps.py +++ b/tests/datasets/test_delta_timestamps.py @@ -13,6 +13,8 @@ # limitations under the License. import pytest +pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])") + from lerobot.datasets.feature_utils import ( check_delta_timestamps, get_delta_indices, diff --git a/tests/datasets/test_image_transforms.py b/tests/datasets/test_image_transforms.py index 399204c26..4310274e4 100644 --- a/tests/datasets/test_image_transforms.py +++ b/tests/datasets/test_image_transforms.py @@ -21,6 +21,8 @@ from safetensors.torch import load_file from torchvision.transforms import v2 from torchvision.transforms.v2 import functional as F # noqa: N812 +pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])") + from lerobot.scripts.lerobot_imgtransform_viz import ( save_all_transforms, save_each_transform, diff --git a/tests/datasets/test_image_writer.py b/tests/datasets/test_image_writer.py index 55419473f..916b8f017 100644 --- a/tests/datasets/test_image_writer.py +++ b/tests/datasets/test_image_writer.py @@ -20,6 +20,8 @@ import numpy as np import pytest from PIL import Image +pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])") + from lerobot.datasets.image_writer import ( AsyncImageWriter, image_array_to_pil_image, diff --git a/tests/datasets/test_lerobot_dataset.py b/tests/datasets/test_lerobot_dataset.py index 5c3c24f99..49efa84d9 100644 --- a/tests/datasets/test_lerobot_dataset.py +++ b/tests/datasets/test_lerobot_dataset.py @@ -25,6 +25,8 @@ from unittest.mock import Mock import pytest import torch +pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])") + import lerobot.datasets.dataset_metadata as dataset_metadata_module import lerobot.datasets.lerobot_dataset as lerobot_dataset_module from lerobot.datasets.dataset_metadata import LeRobotDatasetMetadata diff --git a/tests/datasets/test_quantiles_dataset_integration.py b/tests/datasets/test_quantiles_dataset_integration.py index 4df7fab06..b0e8a0e3c 100644 --- a/tests/datasets/test_quantiles_dataset_integration.py +++ b/tests/datasets/test_quantiles_dataset_integration.py @@ -19,6 +19,8 @@ import numpy as np import pytest +pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])") + from lerobot.datasets.lerobot_dataset import LeRobotDataset diff --git a/tests/datasets/test_sampler.py b/tests/datasets/test_sampler.py index 18fb1c8ac..8bb3be8e9 100644 --- a/tests/datasets/test_sampler.py +++ b/tests/datasets/test_sampler.py @@ -17,7 +17,10 @@ import logging import pytest import torch -from datasets import Dataset + +pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])") + +from datasets import Dataset # noqa: E402 from lerobot.datasets.io_utils import ( hf_transform_to_torch, diff --git a/tests/datasets/test_streaming.py b/tests/datasets/test_streaming.py index 1bd4c1787..db167f657 100644 --- a/tests/datasets/test_streaming.py +++ b/tests/datasets/test_streaming.py @@ -17,6 +17,8 @@ import numpy as np import pytest import torch +pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])") + from lerobot.datasets.streaming_dataset import StreamingLeRobotDataset from lerobot.datasets.utils import safe_shard from lerobot.utils.constants import ACTION diff --git a/tests/datasets/test_streaming_video_encoder.py b/tests/datasets/test_streaming_video_encoder.py index f7e63b06f..8b7a1540f 100644 --- a/tests/datasets/test_streaming_video_encoder.py +++ b/tests/datasets/test_streaming_video_encoder.py @@ -20,10 +20,13 @@ import queue import threading from unittest.mock import patch -import av import numpy as np import pytest +pytest.importorskip("av", reason="av is required (install lerobot[dataset])") + +import av # noqa: E402 + from lerobot.datasets.video_utils import ( VALID_VIDEO_CODECS, StreamingVideoEncoder, diff --git a/tests/datasets/test_subtask_dataset.py b/tests/datasets/test_subtask_dataset.py index f80a6c72d..bb77b77d1 100644 --- a/tests/datasets/test_subtask_dataset.py +++ b/tests/datasets/test_subtask_dataset.py @@ -23,8 +23,11 @@ These tests verify that: - Subtask handling gracefully handles missing data """ -import pandas as pd import pytest + +pytest.importorskip("pandas", reason="pandas is required (install lerobot[dataset])") + +import pandas as pd # noqa: E402 import torch from lerobot.datasets.lerobot_dataset import LeRobotDataset diff --git a/tests/datasets/test_visualize_dataset.py b/tests/datasets/test_visualize_dataset.py index 8e92ec82e..3bf94e6cb 100644 --- a/tests/datasets/test_visualize_dataset.py +++ b/tests/datasets/test_visualize_dataset.py @@ -15,6 +15,8 @@ # limitations under the License. import pytest +pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])") + from lerobot.scripts.lerobot_dataset_viz import visualize_dataset diff --git a/tests/motors/test_motors_bus.py b/tests/motors/test_motors_bus.py index 27650ef1b..60ecaeabb 100644 --- a/tests/motors/test_motors_bus.py +++ b/tests/motors/test_motors_bus.py @@ -19,6 +19,8 @@ from unittest.mock import patch import pytest +pytest.importorskip("serial", reason="pyserial is required (install lerobot[hardware])") + from lerobot.motors.motors_bus import ( Motor, MotorNormMode, diff --git a/tests/policies/groot/test_groot_lerobot.py b/tests/policies/groot/test_groot_lerobot.py index e299a34e2..788935d4f 100644 --- a/tests/policies/groot/test_groot_lerobot.py +++ b/tests/policies/groot/test_groot_lerobot.py @@ -31,7 +31,7 @@ from lerobot.policies.groot.processor_groot import make_groot_pre_post_processor from lerobot.processor import PolicyProcessorPipeline from lerobot.types import PolicyAction from lerobot.utils.device_utils import auto_select_torch_device -from tests.utils import require_cuda # noqa: E402 +from tests.utils import require_cuda pytest.importorskip("transformers") diff --git a/tests/policies/smolvla/test_smolvla_rtc.py b/tests/policies/smolvla/test_smolvla_rtc.py index 85da92a34..8c64c8a6c 100644 --- a/tests/policies/smolvla/test_smolvla_rtc.py +++ b/tests/policies/smolvla/test_smolvla_rtc.py @@ -19,12 +19,12 @@ import pytest import torch -from lerobot.configs.types import FeatureType, PolicyFeature, RTCAttentionSchedule # noqa: E402 -from lerobot.policies.factory import make_pre_post_processors # noqa: E402 -from lerobot.policies.rtc.configuration_rtc import RTCConfig # noqa: E402 +from lerobot.configs.types import FeatureType, PolicyFeature, RTCAttentionSchedule +from lerobot.policies.factory import make_pre_post_processors +from lerobot.policies.rtc.configuration_rtc import RTCConfig from lerobot.policies.smolvla.configuration_smolvla import SmolVLAConfig # noqa: F401 -from lerobot.utils.random_utils import set_seed # noqa: E402 -from tests.utils import require_cuda, skip_if_package_missing # noqa: E402 +from lerobot.utils.random_utils import set_seed +from tests.utils import require_cuda, skip_if_package_missing @skip_if_package_missing("transformers") diff --git a/tests/policies/test_policies.py b/tests/policies/test_policies.py index 46396b6c5..2d50446fe 100644 --- a/tests/policies/test_policies.py +++ b/tests/policies/test_policies.py @@ -20,6 +20,9 @@ from pathlib import Path import einops import pytest import torch + +pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])") + from packaging import version from safetensors.torch import load_file diff --git a/tests/policies/test_relative_actions.py b/tests/policies/test_relative_actions.py index 64c2ee9c4..15ef0a31b 100644 --- a/tests/policies/test_relative_actions.py +++ b/tests/policies/test_relative_actions.py @@ -10,6 +10,8 @@ import numpy as np import pytest import torch +pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])") + from lerobot.configs.types import FeatureType, NormalizationMode, PolicyFeature from lerobot.datasets.compute_stats import get_feature_stats from lerobot.datasets.lerobot_dataset import LeRobotDataset diff --git a/tests/processor/test_pipeline.py b/tests/processor/test_pipeline.py index a335c2b4b..2c41de22c 100644 --- a/tests/processor/test_pipeline.py +++ b/tests/processor/test_pipeline.py @@ -25,6 +25,8 @@ import pytest import torch import torch.nn as nn +pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])") + from lerobot.configs.types import FeatureType, PipelineFeatureType, PolicyFeature from lerobot.datasets.pipeline_features import aggregate_pipeline_dataset_features from lerobot.processor import ( diff --git a/tests/rl/test_actor.py b/tests/rl/test_actor.py index 688d59842..08746ec91 100644 --- a/tests/rl/test_actor.py +++ b/tests/rl/test_actor.py @@ -19,6 +19,9 @@ from unittest.mock import patch import pytest import torch + +pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])") + from torch.multiprocessing import Event, Queue from lerobot.utils.constants import OBS_STR diff --git a/tests/rl/test_actor_learner.py b/tests/rl/test_actor_learner.py index 4ce27a757..3978dfffd 100644 --- a/tests/rl/test_actor_learner.py +++ b/tests/rl/test_actor_learner.py @@ -20,6 +20,9 @@ import time import pytest import torch + +pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])") + from torch.multiprocessing import Event, Queue from lerobot.configs.train import TrainRLServerPipelineConfig diff --git a/tests/scripts/test_edit_dataset_parsing.py b/tests/scripts/test_edit_dataset_parsing.py index 4d758ae35..83ed5a78b 100644 --- a/tests/scripts/test_edit_dataset_parsing.py +++ b/tests/scripts/test_edit_dataset_parsing.py @@ -17,6 +17,8 @@ import draccus import pytest +pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])") + from lerobot.scripts.lerobot_edit_dataset import ( ConvertImageToVideoConfig, DeleteEpisodesConfig, diff --git a/tests/test_control_robot.py b/tests/test_control_robot.py index 772588467..ab28de103 100644 --- a/tests/test_control_robot.py +++ b/tests/test_control_robot.py @@ -16,6 +16,10 @@ from unittest.mock import patch +import pytest + +pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])") + from lerobot.scripts.lerobot_calibrate import CalibrateConfig, calibrate from lerobot.scripts.lerobot_record import DatasetRecordConfig, RecordConfig, record from lerobot.scripts.lerobot_replay import DatasetReplayConfig, ReplayConfig, replay diff --git a/tests/training/test_multi_gpu.py b/tests/training/test_multi_gpu.py index bb234e2e7..638dc3131 100644 --- a/tests/training/test_multi_gpu.py +++ b/tests/training/test_multi_gpu.py @@ -33,6 +33,8 @@ from pathlib import Path import pytest import torch +pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])") + from lerobot.datasets.lerobot_dataset import LeRobotDataset diff --git a/tests/training/test_visual_validation.py b/tests/training/test_visual_validation.py index 89351e3c2..1df8006b2 100644 --- a/tests/training/test_visual_validation.py +++ b/tests/training/test_visual_validation.py @@ -31,6 +31,8 @@ from pathlib import Path import numpy as np import pytest +pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])") + from lerobot.configs.default import DatasetConfig from lerobot.configs.policies import PreTrainedConfig from lerobot.configs.train import TrainPipelineConfig diff --git a/tests/utils/test_replay_buffer.py b/tests/utils/test_replay_buffer.py index 6085fb7fb..1b2af39f1 100644 --- a/tests/utils/test_replay_buffer.py +++ b/tests/utils/test_replay_buffer.py @@ -20,6 +20,7 @@ from collections.abc import Callable import pytest pytest.importorskip("grpc") +pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])") import torch # noqa: E402 diff --git a/tests/utils/test_visualization_utils.py b/tests/utils/test_visualization_utils.py index 711bbc8c0..63ff76c77 100644 --- a/tests/utils/test_visualization_utils.py +++ b/tests/utils/test_visualization_utils.py @@ -21,6 +21,8 @@ from types import SimpleNamespace import numpy as np import pytest +pytest.importorskip("rerun", reason="rerun-sdk is required (install lerobot[viz])") + from lerobot.types import TransitionKey from lerobot.utils.constants import OBS_STATE diff --git a/uv.lock b/uv.lock index a596f0c95..affeb5265 100644 --- a/uv.lock +++ b/uv.lock @@ -829,7 +829,7 @@ name = "cuda-bindings" version = "12.9.4" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cuda-pathfinder", marker = "platform_machine != 'aarch64' and platform_machine != 'arm64' and platform_machine != 'armv7l' and platform_machine != 's390x' and sys_platform == 'linux'" }, + { name = "cuda-pathfinder", marker = "platform_machine != 'aarch64' and platform_machine != 'arm64' and platform_machine != 'armv7l' and sys_platform == 'linux'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/a9/c1/dabe88f52c3e3760d861401bb994df08f672ec893b8f7592dc91626adcf3/cuda_bindings-12.9.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fda147a344e8eaeca0c6ff113d2851ffca8f7dfc0a6c932374ee5c47caa649c8", size = 12151019, upload-time = "2025-10-21T14:51:43.167Z" }, @@ -916,7 +916,7 @@ name = "decord" version = "0.6.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy", marker = "(platform_machine != 'aarch64' and platform_machine != 'arm64' and platform_machine != 'armv7l' and platform_machine != 's390x') or (platform_machine != 's390x' and sys_platform != 'linux')" }, + { name = "numpy", marker = "(platform_machine != 'aarch64' and platform_machine != 'arm64' and platform_machine != 'armv7l') or sys_platform != 'linux'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/11/79/936af42edf90a7bd4e41a6cac89c913d4b47fa48a26b042d5129a9242ee3/decord-0.6.0-py3-none-manylinux2010_x86_64.whl", hash = "sha256:51997f20be8958e23b7c4061ba45d0efcd86bffd5fe81c695d0befee0d442976", size = 13602299, upload-time = "2021-06-14T21:30:55.486Z" }, @@ -2205,6 +2205,7 @@ name = "lerobot" version = "0.5.2" source = { editable = "." } dependencies = [ + { name = "cmake" }, { name = "draccus" }, { name = "einops" }, { name = "gymnasium" }, @@ -2215,6 +2216,7 @@ dependencies = [ { name = "pillow" }, { name = "requests" }, { name = "safetensors" }, + { name = "setuptools" }, { name = "termcolor" }, { name = "torch" }, { name = "torchvision" }, @@ -2225,7 +2227,6 @@ dependencies = [ all = [ { name = "accelerate" }, { name = "av" }, - { name = "cmake" }, { name = "contourpy" }, { name = "datasets" }, { name = "debugpy" }, @@ -2270,9 +2271,9 @@ all = [ { name = "qwen-vl-utils" }, { name = "reachy2-sdk" }, { name = "rerun-sdk" }, + { name = "ruff" }, { name = "scikit-image" }, { name = "scipy" }, - { name = "setuptools" }, { name = "teleop" }, { name = "torchcodec", marker = "(platform_machine != 'aarch64' and platform_machine != 'arm64' and platform_machine != 'armv7l' and sys_platform == 'linux') or (platform_machine != 'x86_64' and sys_platform == 'darwin') or (sys_platform != 'darwin' and sys_platform != 'linux' and sys_platform != 'win32')" }, { name = "torchdiffeq" }, @@ -2298,10 +2299,6 @@ async = [ av-dep = [ { name = "av" }, ] -build = [ - { name = "cmake" }, - { name = "setuptools" }, -] can-dep = [ { name = "python-can" }, ] @@ -2338,24 +2335,13 @@ dataset-viz = [ { name = "torchcodec", marker = "(platform_machine != 'aarch64' and platform_machine != 'arm64' and platform_machine != 'armv7l' and sys_platform == 'linux') or (platform_machine != 'x86_64' and sys_platform == 'darwin') or (sys_platform != 'darwin' and sys_platform != 'linux' and sys_platform != 'win32')" }, ] dev = [ - { name = "accelerate" }, - { name = "av" }, - { name = "datasets" }, { name = "debugpy" }, - { name = "deepdiff" }, { name = "grpcio" }, { name = "grpcio-tools" }, - { name = "jsonlines" }, { name = "mypy" }, - { name = "pandas" }, { name = "pre-commit" }, { name = "protobuf" }, - { name = "pyarrow" }, - { name = "pynput" }, - { name = "pyserial" }, - { name = "rerun-sdk" }, - { name = "torchcodec", marker = "(platform_machine != 'aarch64' and platform_machine != 'arm64' and platform_machine != 'armv7l' and sys_platform == 'linux') or (platform_machine != 'x86_64' and sys_platform == 'darwin') or (sys_platform != 'darwin' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "wandb" }, + { name = "ruff" }, ] diffusers-dep = [ { name = "diffusers" }, @@ -2509,22 +2495,10 @@ smolvla = [ { name = "transformers" }, ] test = [ - { name = "accelerate" }, - { name = "av" }, - { name = "datasets" }, - { name = "deepdiff" }, - { name = "jsonlines" }, { name = "mock-serial", marker = "sys_platform != 'win32'" }, - { name = "pandas" }, - { name = "pyarrow" }, - { name = "pynput" }, - { name = "pyserial" }, { name = "pytest" }, { name = "pytest-cov" }, { name = "pytest-timeout" }, - { name = "rerun-sdk" }, - { name = "torchcodec", marker = "(platform_machine != 'aarch64' and platform_machine != 'arm64' and platform_machine != 'armv7l' and sys_platform == 'linux') or (platform_machine != 'x86_64' and sys_platform == 'darwin') or (sys_platform != 'darwin' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "wandb" }, ] training = [ { name = "accelerate" }, @@ -2571,7 +2545,7 @@ requires-dist = [ { name = "accelerate", marker = "extra == 'smolvla'", specifier = ">=1.7.0,<2.0.0" }, { name = "accelerate", marker = "extra == 'training'", specifier = ">=1.10.0,<2.0.0" }, { name = "av", marker = "extra == 'av-dep'", specifier = ">=15.0.0,<16.0.0" }, - { name = "cmake", marker = "extra == 'build'", specifier = ">=3.29.0.1,<4.2.0" }, + { name = "cmake", specifier = ">=3.29.0.1,<4.2.0" }, { name = "contourpy", marker = "extra == 'matplotlib-dep'", specifier = ">=1.3.0,<2.0.0" }, { name = "datasets", marker = "extra == 'dataset'", specifier = ">=4.0.0,<5.0.0" }, { name = "debugpy", marker = "extra == 'dev'", specifier = ">=1.8.1,<1.9.0" }, @@ -2601,7 +2575,6 @@ requires-dist = [ { name = "lerobot", extras = ["async"], marker = "extra == 'all'" }, { name = "lerobot", extras = ["av-dep"], marker = "extra == 'dataset'" }, { name = "lerobot", extras = ["av-dep"], marker = "extra == 'evaluation'" }, - { name = "lerobot", extras = ["build"], marker = "extra == 'all'" }, { name = "lerobot", extras = ["can-dep"], marker = "extra == 'damiao'" }, { name = "lerobot", extras = ["can-dep"], marker = "extra == 'robstride'" }, { name = "lerobot", extras = ["damiao"], marker = "extra == 'all'" }, @@ -2610,11 +2583,9 @@ requires-dist = [ { name = "lerobot", extras = ["dataset"], marker = "extra == 'aloha'" }, { name = "lerobot", extras = ["dataset"], marker = "extra == 'core-scripts'" }, { name = "lerobot", extras = ["dataset"], marker = "extra == 'dataset-viz'" }, - { name = "lerobot", extras = ["dataset"], marker = "extra == 'dev'" }, { name = "lerobot", extras = ["dataset"], marker = "extra == 'libero'" }, { name = "lerobot", extras = ["dataset"], marker = "extra == 'metaworld'" }, { name = "lerobot", extras = ["dataset"], marker = "extra == 'pusht'" }, - { name = "lerobot", extras = ["dataset"], marker = "extra == 'test'" }, { name = "lerobot", extras = ["dataset"], marker = "extra == 'training'" }, { name = "lerobot", extras = ["dev"], marker = "extra == 'all'" }, { name = "lerobot", extras = ["diffusers-dep"], marker = "extra == 'diffusion'" }, @@ -2631,8 +2602,6 @@ requires-dist = [ { name = "lerobot", extras = ["grpcio-dep"], marker = "extra == 'hilserl'" }, { name = "lerobot", extras = ["hardware"], marker = "extra == 'all'" }, { name = "lerobot", extras = ["hardware"], marker = "extra == 'core-scripts'" }, - { name = "lerobot", extras = ["hardware"], marker = "extra == 'dev'" }, - { name = "lerobot", extras = ["hardware"], marker = "extra == 'test'" }, { name = "lerobot", extras = ["hilserl"], marker = "extra == 'all'" }, { name = "lerobot", extras = ["hopejr"], marker = "extra == 'all'" }, { name = "lerobot", extras = ["intelrealsense"], marker = "extra == 'all'" }, @@ -2671,8 +2640,6 @@ requires-dist = [ { name = "lerobot", extras = ["smolvla"], marker = "extra == 'all'" }, { name = "lerobot", extras = ["test"], marker = "extra == 'all'" }, { name = "lerobot", extras = ["training"], marker = "extra == 'all'" }, - { name = "lerobot", extras = ["training"], marker = "extra == 'dev'" }, - { name = "lerobot", extras = ["training"], marker = "extra == 'test'" }, { name = "lerobot", extras = ["transformers-dep"], marker = "extra == 'groot'" }, { name = "lerobot", extras = ["transformers-dep"], marker = "extra == 'hilserl'" }, { name = "lerobot", extras = ["transformers-dep"], marker = "extra == 'libero'" }, @@ -2687,8 +2654,6 @@ requires-dist = [ { name = "lerobot", extras = ["viz"], marker = "extra == 'all'" }, { name = "lerobot", extras = ["viz"], marker = "extra == 'core-scripts'" }, { name = "lerobot", extras = ["viz"], marker = "extra == 'dataset-viz'" }, - { name = "lerobot", extras = ["viz"], marker = "extra == 'dev'" }, - { name = "lerobot", extras = ["viz"], marker = "extra == 'test'" }, { name = "lerobot", extras = ["wallx"], marker = "extra == 'all'" }, { name = "lerobot", extras = ["xvla"], marker = "extra == 'all'" }, { name = "matplotlib", marker = "extra == 'matplotlib-dep'", specifier = ">=3.10.3,<4.0.0" }, @@ -2728,11 +2693,12 @@ requires-dist = [ { name = "reachy2-sdk", marker = "extra == 'reachy2'", specifier = ">=1.0.15,<1.1.0" }, { name = "requests", specifier = ">=2.32.0,<3.0.0" }, { name = "rerun-sdk", marker = "extra == 'viz'", specifier = ">=0.24.0,<0.27.0" }, + { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.14.1" }, { name = "safetensors", specifier = ">=0.4.3,<1.0.0" }, { name = "scikit-image", marker = "extra == 'video-benchmark'", specifier = ">=0.23.2,<0.26.0" }, { name = "scipy", marker = "extra == 'all'", specifier = ">=1.14.0,<2.0.0" }, { name = "scipy", marker = "extra == 'scipy-dep'", specifier = ">=1.14.0,<2.0.0" }, - { name = "setuptools", marker = "extra == 'build'", specifier = ">=71.0.0,<81.0.0" }, + { name = "setuptools", specifier = ">=71.0.0,<81.0.0" }, { name = "teleop", marker = "extra == 'phone'", specifier = ">=0.1.0,<0.2.0" }, { name = "termcolor", specifier = ">=2.4.0,<4.0.0" }, { name = "timm", marker = "extra == 'groot'", specifier = ">=1.0.0,<1.1.0" }, @@ -2744,7 +2710,7 @@ requires-dist = [ { name = "transformers", marker = "extra == 'transformers-dep'", specifier = "==5.3.0" }, { name = "wandb", marker = "extra == 'training'", specifier = ">=0.24.0,<0.25.0" }, ] -provides-extras = ["dataset", "training", "hardware", "viz", "build", "core-scripts", "evaluation", "dataset-viz", "av-dep", "pygame-dep", "placo-dep", "transformers-dep", "grpcio-dep", "can-dep", "peft-dep", "scipy-dep", "diffusers-dep", "qwen-vl-utils-dep", "matplotlib-dep", "feetech", "dynamixel", "damiao", "robstride", "openarms", "gamepad", "hopejr", "lekiwi", "unitree-g1", "reachy2", "kinematics", "intelrealsense", "phone", "diffusion", "wallx", "pi", "smolvla", "multi-task-dit", "groot", "sarm", "xvla", "hilserl", "async", "peft", "dev", "test", "video-benchmark", "aloha", "pusht", "libero", "metaworld", "all"] +provides-extras = ["dataset", "training", "hardware", "viz", "core-scripts", "evaluation", "dataset-viz", "av-dep", "pygame-dep", "placo-dep", "transformers-dep", "grpcio-dep", "can-dep", "peft-dep", "scipy-dep", "diffusers-dep", "qwen-vl-utils-dep", "matplotlib-dep", "feetech", "dynamixel", "damiao", "robstride", "openarms", "gamepad", "hopejr", "lekiwi", "unitree-g1", "reachy2", "kinematics", "intelrealsense", "phone", "diffusion", "wallx", "pi", "smolvla", "multi-task-dit", "groot", "sarm", "xvla", "hilserl", "async", "peft", "dev", "test", "video-benchmark", "aloha", "pusht", "libero", "metaworld", "all"] [[package]] name = "librt" @@ -3529,7 +3495,7 @@ name = "nvidia-cudnn-cu12" version = "9.10.2.21" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-cublas-cu12", marker = "platform_machine != 'aarch64' and platform_machine != 'arm64' and platform_machine != 'armv7l' and platform_machine != 's390x' and sys_platform == 'linux'" }, + { name = "nvidia-cublas-cu12", marker = "platform_machine != 'aarch64' and platform_machine != 'arm64' and platform_machine != 'armv7l' and sys_platform == 'linux'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/ba/51/e123d997aa098c61d029f76663dedbfb9bc8dcf8c60cbd6adbe42f76d049/nvidia_cudnn_cu12-9.10.2.21-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:949452be657fa16687d0930933f032835951ef0892b37d2d53824d1a84dc97a8", size = 706758467, upload-time = "2025-06-06T21:54:08.597Z" }, @@ -3540,7 +3506,7 @@ name = "nvidia-cufft-cu12" version = "11.3.3.83" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-nvjitlink-cu12", marker = "platform_machine != 'aarch64' and platform_machine != 'arm64' and platform_machine != 'armv7l' and platform_machine != 's390x' and sys_platform == 'linux'" }, + { name = "nvidia-nvjitlink-cu12", marker = "platform_machine != 'aarch64' and platform_machine != 'arm64' and platform_machine != 'armv7l' and sys_platform == 'linux'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/1f/13/ee4e00f30e676b66ae65b4f08cb5bcbb8392c03f54f2d5413ea99a5d1c80/nvidia_cufft_cu12-11.3.3.83-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d2dd21ec0b88cf61b62e6b43564355e5222e4a3fb394cac0db101f2dd0d4f74", size = 193118695, upload-time = "2025-03-07T01:45:27.821Z" }, @@ -3567,9 +3533,9 @@ name = "nvidia-cusolver-cu12" version = "11.7.3.90" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-cublas-cu12", marker = "platform_machine != 'aarch64' and platform_machine != 'arm64' and platform_machine != 'armv7l' and platform_machine != 's390x' and sys_platform == 'linux'" }, - { name = "nvidia-cusparse-cu12", marker = "platform_machine != 'aarch64' and platform_machine != 'arm64' and platform_machine != 'armv7l' and platform_machine != 's390x' and sys_platform == 'linux'" }, - { name = "nvidia-nvjitlink-cu12", marker = "platform_machine != 'aarch64' and platform_machine != 'arm64' and platform_machine != 'armv7l' and platform_machine != 's390x' and sys_platform == 'linux'" }, + { name = "nvidia-cublas-cu12", marker = "platform_machine != 'aarch64' and platform_machine != 'arm64' and platform_machine != 'armv7l' and sys_platform == 'linux'" }, + { name = "nvidia-cusparse-cu12", marker = "platform_machine != 'aarch64' and platform_machine != 'arm64' and platform_machine != 'armv7l' and sys_platform == 'linux'" }, + { name = "nvidia-nvjitlink-cu12", marker = "platform_machine != 'aarch64' and platform_machine != 'arm64' and platform_machine != 'armv7l' and sys_platform == 'linux'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/85/48/9a13d2975803e8cf2777d5ed57b87a0b6ca2cc795f9a4f59796a910bfb80/nvidia_cusolver_cu12-11.7.3.90-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:4376c11ad263152bd50ea295c05370360776f8c3427b30991df774f9fb26c450", size = 267506905, upload-time = "2025-03-07T01:47:16.273Z" }, @@ -3580,7 +3546,7 @@ name = "nvidia-cusparse-cu12" version = "12.5.8.93" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-nvjitlink-cu12", marker = "platform_machine != 'aarch64' and platform_machine != 'arm64' and platform_machine != 'armv7l' and platform_machine != 's390x' and sys_platform == 'linux'" }, + { name = "nvidia-nvjitlink-cu12", marker = "platform_machine != 'aarch64' and platform_machine != 'arm64' and platform_machine != 'armv7l' and sys_platform == 'linux'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/c2/f5/e1854cb2f2bcd4280c44736c93550cc300ff4b8c95ebe370d0aa7d2b473d/nvidia_cusparse_cu12-12.5.8.93-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ec05d76bbbd8b61b06a80e1eaf8cf4959c3d4ce8e711b65ebd0443bb0ebb13b", size = 288216466, upload-time = "2025-03-07T01:48:13.779Z" }, @@ -4401,10 +4367,10 @@ name = "pyobjc-framework-applicationservices" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "(platform_machine != 's390x' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "pyobjc-framework-cocoa", marker = "(platform_machine != 's390x' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "pyobjc-framework-coretext", marker = "(platform_machine != 's390x' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "pyobjc-framework-quartz", marker = "(platform_machine != 's390x' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, + { name = "pyobjc-core", marker = "sys_platform != 'emscripten' and sys_platform != 'linux'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform != 'emscripten' and sys_platform != 'linux'" }, + { name = "pyobjc-framework-coretext", marker = "sys_platform != 'emscripten' and sys_platform != 'linux'" }, + { name = "pyobjc-framework-quartz", marker = "sys_platform != 'emscripten' and sys_platform != 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/be/6a/d4e613c8e926a5744fc47a9e9fea08384a510dc4f27d844f7ad7a2d793bd/pyobjc_framework_applicationservices-12.1.tar.gz", hash = "sha256:c06abb74f119bc27aeb41bf1aef8102c0ae1288aec1ac8665ea186a067a8945b", size = 103247, upload-time = "2025-11-14T10:08:52.18Z" } wheels = [ @@ -4420,7 +4386,7 @@ name = "pyobjc-framework-cocoa" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "(platform_machine != 's390x' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, + { name = "pyobjc-core", marker = "sys_platform != 'emscripten' and sys_platform != 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/02/a3/16ca9a15e77c061a9250afbae2eae26f2e1579eb8ca9462ae2d2c71e1169/pyobjc_framework_cocoa-12.1.tar.gz", hash = "sha256:5556c87db95711b985d5efdaaf01c917ddd41d148b1e52a0c66b1a2e2c5c1640", size = 2772191, upload-time = "2025-11-14T10:13:02.069Z" } wheels = [ @@ -4436,9 +4402,9 @@ name = "pyobjc-framework-coretext" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "(platform_machine != 's390x' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "pyobjc-framework-cocoa", marker = "(platform_machine != 's390x' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "pyobjc-framework-quartz", marker = "(platform_machine != 's390x' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, + { name = "pyobjc-core", marker = "sys_platform != 'emscripten' and sys_platform != 'linux'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform != 'emscripten' and sys_platform != 'linux'" }, + { name = "pyobjc-framework-quartz", marker = "sys_platform != 'emscripten' and sys_platform != 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/29/da/682c9c92a39f713bd3c56e7375fa8f1b10ad558ecb075258ab6f1cdd4a6d/pyobjc_framework_coretext-12.1.tar.gz", hash = "sha256:e0adb717738fae395dc645c9e8a10bb5f6a4277e73cba8fa2a57f3b518e71da5", size = 90124, upload-time = "2025-11-14T10:14:38.596Z" } wheels = [ @@ -4454,8 +4420,8 @@ name = "pyobjc-framework-quartz" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "(platform_machine != 's390x' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "pyobjc-framework-cocoa", marker = "(platform_machine != 's390x' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, + { name = "pyobjc-core", marker = "sys_platform != 'emscripten' and sys_platform != 'linux'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform != 'emscripten' and sys_platform != 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/94/18/cc59f3d4355c9456fc945eae7fe8797003c4da99212dd531ad1b0de8a0c6/pyobjc_framework_quartz-12.1.tar.gz", hash = "sha256:27f782f3513ac88ec9b6c82d9767eef95a5cf4175ce88a1e5a65875fee799608", size = 3159099, upload-time = "2025-11-14T10:21:24.31Z" } wheels = [ @@ -5058,6 +5024,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/85/70/92482ccffb96f5441aab93e26c4d66489eb599efdcf96fad90c14bbfb976/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40", size = 556030, upload-time = "2025-11-30T20:24:10.956Z" }, ] +[[package]] +name = "ruff" +version = "0.15.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/d9/aa3f7d59a10ef6b14fe3431706f854dbf03c5976be614a9796d36326810c/ruff-0.15.10.tar.gz", hash = "sha256:d1f86e67ebfdef88e00faefa1552b5e510e1d35f3be7d423dc7e84e63788c94e", size = 4631728, upload-time = "2026-04-09T14:06:09.884Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/00/a1c2fdc9939b2c03691edbda290afcd297f1f389196172826b03d6b6a595/ruff-0.15.10-py3-none-linux_armv6l.whl", hash = "sha256:0744e31482f8f7d0d10a11fcbf897af272fefdfcb10f5af907b18c2813ff4d5f", size = 10563362, upload-time = "2026-04-09T14:06:21.189Z" }, + { url = "https://files.pythonhosted.org/packages/5c/15/006990029aea0bebe9d33c73c3e28c80c391ebdba408d1b08496f00d422d/ruff-0.15.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b1e7c16ea0ff5a53b7c2df52d947e685973049be1cdfe2b59a9c43601897b22e", size = 10951122, upload-time = "2026-04-09T14:06:02.236Z" }, + { url = "https://files.pythonhosted.org/packages/f2/c0/4ac978fe874d0618c7da647862afe697b281c2806f13ce904ad652fa87e4/ruff-0.15.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:93cc06a19e5155b4441dd72808fdf84290d84ad8a39ca3b0f994363ade4cebb1", size = 10314005, upload-time = "2026-04-09T14:06:00.026Z" }, + { url = "https://files.pythonhosted.org/packages/da/73/c209138a5c98c0d321266372fc4e33ad43d506d7e5dd817dd89b60a8548f/ruff-0.15.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83e1dd04312997c99ea6965df66a14fb4f03ba978564574ffc68b0d61fd3989e", size = 10643450, upload-time = "2026-04-09T14:05:42.137Z" }, + { url = "https://files.pythonhosted.org/packages/ec/76/0deec355d8ec10709653635b1f90856735302cb8e149acfdf6f82a5feb70/ruff-0.15.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8154d43684e4333360fedd11aaa40b1b08a4e37d8ffa9d95fee6fa5b37b6fab1", size = 10379597, upload-time = "2026-04-09T14:05:49.984Z" }, + { url = "https://files.pythonhosted.org/packages/dc/be/86bba8fc8798c081e28a4b3bb6d143ccad3fd5f6f024f02002b8f08a9fa3/ruff-0.15.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ab88715f3a6deb6bde6c227f3a123410bec7b855c3ae331b4c006189e895cef", size = 11146645, upload-time = "2026-04-09T14:06:12.246Z" }, + { url = "https://files.pythonhosted.org/packages/a8/89/140025e65911b281c57be1d385ba1d932c2366ca88ae6663685aed8d4881/ruff-0.15.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a768ff5969b4f44c349d48edf4ab4f91eddb27fd9d77799598e130fb628aa158", size = 12030289, upload-time = "2026-04-09T14:06:04.776Z" }, + { url = "https://files.pythonhosted.org/packages/88/de/ddacca9545a5e01332567db01d44bd8cf725f2db3b3d61a80550b48308ea/ruff-0.15.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ee3ef42dab7078bda5ff6a1bcba8539e9857deb447132ad5566a038674540d0", size = 11496266, upload-time = "2026-04-09T14:05:55.485Z" }, + { url = "https://files.pythonhosted.org/packages/bc/bb/7ddb00a83760ff4a83c4e2fc231fd63937cc7317c10c82f583302e0f6586/ruff-0.15.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51cb8cc943e891ba99989dd92d61e29b1d231e14811db9be6440ecf25d5c1609", size = 11256418, upload-time = "2026-04-09T14:05:57.69Z" }, + { url = "https://files.pythonhosted.org/packages/dc/8d/55de0d35aacf6cd50b6ee91ee0f291672080021896543776f4170fc5c454/ruff-0.15.10-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:e59c9bdc056a320fb9ea1700a8d591718b8faf78af065484e801258d3a76bc3f", size = 11288416, upload-time = "2026-04-09T14:05:44.695Z" }, + { url = "https://files.pythonhosted.org/packages/68/cf/9438b1a27426ec46a80e0a718093c7f958ef72f43eb3111862949ead3cc1/ruff-0.15.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:136c00ca2f47b0018b073f28cb5c1506642a830ea941a60354b0e8bc8076b151", size = 10621053, upload-time = "2026-04-09T14:05:52.782Z" }, + { url = "https://files.pythonhosted.org/packages/4c/50/e29be6e2c135e9cd4cb15fbade49d6a2717e009dff3766dd080fcb82e251/ruff-0.15.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8b80a2f3c9c8a950d6237f2ca12b206bccff626139be9fa005f14feb881a1ae8", size = 10378302, upload-time = "2026-04-09T14:06:14.361Z" }, + { url = "https://files.pythonhosted.org/packages/18/2f/e0b36a6f99c51bb89f3a30239bc7bf97e87a37ae80aa2d6542d6e5150364/ruff-0.15.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:e3e53c588164dc025b671c9df2462429d60357ea91af7e92e9d56c565a9f1b07", size = 10850074, upload-time = "2026-04-09T14:06:16.581Z" }, + { url = "https://files.pythonhosted.org/packages/11/08/874da392558ce087a0f9b709dc6ec0d60cbc694c1c772dab8d5f31efe8cb/ruff-0.15.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b0c52744cf9f143a393e284125d2576140b68264a93c6716464e129a3e9adb48", size = 11358051, upload-time = "2026-04-09T14:06:18.948Z" }, + { url = "https://files.pythonhosted.org/packages/e4/46/602938f030adfa043e67112b73821024dc79f3ab4df5474c25fa4c1d2d14/ruff-0.15.10-py3-none-win32.whl", hash = "sha256:d4272e87e801e9a27a2e8df7b21011c909d9ddd82f4f3281d269b6ba19789ca5", size = 10588964, upload-time = "2026-04-09T14:06:07.14Z" }, + { url = "https://files.pythonhosted.org/packages/25/b6/261225b875d7a13b33a6d02508c39c28450b2041bb01d0f7f1a83d569512/ruff-0.15.10-py3-none-win_amd64.whl", hash = "sha256:28cb32d53203242d403d819fd6983152489b12e4a3ae44993543d6fe62ab42ed", size = 11745044, upload-time = "2026-04-09T14:05:39.473Z" }, + { url = "https://files.pythonhosted.org/packages/58/ed/dea90a65b7d9e69888890fb14c90d7f51bf0c1e82ad800aeb0160e4bacfd/ruff-0.15.10-py3-none-win_arm64.whl", hash = "sha256:601d1610a9e1f1c2165a4f561eeaa2e2ea1e97f3287c5aa258d3dab8b57c6188", size = 11035607, upload-time = "2026-04-09T14:05:47.593Z" }, +] + [[package]] name = "safetensors" version = "0.7.0"