# Copyright 2024 The HuggingFace Inc. team. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"

[project.urls]
homepage = "https://huggingface.co/lerobot"
documentation = "https://huggingface.co/docs/lerobot/index"
source = "https://github.com/huggingface/lerobot"
issues = "https://github.com/huggingface/lerobot/issues"
discord = "https://discord.gg/s3KuuzsPFb"

[project]
name = "lerobot"
version = "0.6.2"
description = "🤗 LeRobot: State-of-the-art Machine Learning for Real-World Robotics in Pytorch"
dynamic = ["readme"]
license = { text = "Apache-2.0" }
requires-python = ">=3.12"
authors = [
    { name = "Rémi Cadène", email = "re.cadene@gmail.com" },
    { name = "Simon Alibert", email = "alibert.sim@gmail.com" },
    { name = "Alexander Soare", email = "alexander.soare159@gmail.com" },
    { name = "Quentin Gallouédec", email = "quentin.gallouedec@ec-lyon.fr" },
    { name = "Steven Palma", email = "imstevenpmwork@ieee.org" },
    { name = "Pepijn Kooijmans", email = "pepijnkooijmans@outlook.com"},
    { name = "Michel Aractingi", email = "michel.aractingi@gmail.com"},
    { name = "Adil Zouitine", email = "adilzouitinegm@gmail.com" },
    { name = "Dana Aubakirova", email = "danaaubakirova17@gmail.com"},
    { name = "Caroline Pascal", email = "caroline8.pascal@gmail.com"},
    { name = "Martino Russi", email = "nopyeps@gmail.com"},
    { name = "Thomas Wolf", email = "thomaswolfcontact@gmail.com" },
]
classifiers = [
    "Development Status :: 3 - Alpha",
    "Intended Audience :: Developers",
    "Intended Audience :: Education",
    "Intended Audience :: Science/Research",
    "License :: OSI Approved :: Apache Software License",
    "Programming Language :: Python :: 3.12",
    "Programming Language :: Python :: 3.13",
    "Topic :: Software Development :: Build Tools",
    "Topic :: Scientific/Engineering :: Artificial Intelligence",
]
keywords = ["lerobot", "huggingface", "robotics",  "machine learning", "artificial intelligence"]

dependencies = [
    # Core ML
    "torch>=2.7,<2.12.0",
    "torchvision>=0.22.0,<0.27.0",
    "numpy>=2.0.0,<2.3.0", # NOTE: Explicitly listing numpy helps the resolver converge faster. Upper bound imposed by opencv-python-headless.
    "opencv-python-headless>=4.9.0,<4.14.0",
    "Pillow>=10.0.0,<13.0.0",
    "einops>=0.8.0,<0.9.0",

    # Config & Hub
    "draccus>=0.11.6,<0.12.0",
    "huggingface-hub>=1.6.0,<2.0.0",
    "filelock>=3.12.0,<4.0.0",
    "fsspec>=2023.5.0,<2027.0.0",
    "httpx>=0.27.0,<1.0.0",
    "requests>=2.32.0,<3.0.0",

    # Environments
    # NOTE: gymnasium is used in lerobot.envs (lerobot-train, lerobot-eval), policies/factory,
    # and robots/unitree. Moving it to an optional extra would require import guards across many
    # tightly-coupled modules. Candidate for a future refactor to decouple envs from the core.
    "gymnasium>=1.1.1,<2.0.0",

    # Serialization & checkpointing
    "safetensors>=0.4.3,<1.0.0",

    # Lightweight utilities
    "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,<82.0.0",  # torch 2.11 requires setuptools<82; a higher cap makes the resolver downgrade torch
]

# Optional dependencies
[project.optional-dependencies]

# ── Feature-scoped extras ──────────────────────────────────
lancedb = [
    "lerobot[dataset]",
    "lancedb>=0.37.1,<0.40.0",
]
lancedb-convert = [
    "lerobot[lancedb]",
    "lerobot-lancedb>=0.3.1,<0.4.0",
]
dataset = [
    "datasets>=4.8.0,<5.0.0",
    "pandas>=2.0.0,<3.0.0", # NOTE: Transitive dependency of datasets
    "pyarrow>=21.0.0,<30.0.0", # NOTE: Transitive dependency of datasets
    "lerobot[av-dep]",

    # NOTE: torchcodec wheel availability matrix (PyPI):
    #   - linux x86_64/amd64 + macOS arm64 : wheels since 0.3.0 (the historic supported set).
    #   - win32 x86_64                     : wheels since 0.7.0  (needs torch>=2.8).
    #   - linux aarch64/arm64              : wheels since 0.11.0 (needs torch>=2.11).
    #   - macOS x86_64 (Intel) and linux armv7l: no wheels in any released version -> fall through to the PyAV decoder.
    # Each platform gets its own line so the resolver picks the minimum version that has a wheel for it.

    # Other torch/torchcodec pairings (informational): 0.8.1 = ffmpeg>=8 support, 0.10 = system-wide ffmpeg support, 0.12 needs torch==2.12.
    "torchcodec>=0.3.0,<0.12.0; (sys_platform == 'linux' and (platform_machine == 'x86_64' or platform_machine == 'AMD64')) or (sys_platform == 'darwin' and platform_machine == 'arm64')",
    "torchcodec>=0.7.0,<0.12.0; sys_platform == 'win32'",
    "torchcodec>=0.11.0,<0.12.0; sys_platform == 'linux' and (platform_machine == 'aarch64' or platform_machine == 'arm64')",
    "jsonlines>=4.0.0,<5.0.0",
]
training = [
    "lerobot[dataset]",
    "wandb>=0.24.0,<0.28.0",
    "lerobot[accelerate-dep]",
]
hardware = [
    "lerobot[pynput-dep]",
    "lerobot[pyserial-dep]",
    "lerobot[deepdiff-dep]",
]
viz = [
    "rerun-sdk>=0.24.0,<0.34.0",
    "foxglove-sdk>=0.25.1,<0.26.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]"]
# lerobot-eval -- base evaluation framework. You also need the policy's extra (e.g., lerobot[pi])
# and the environment's extra (e.g., lerobot[pusht]) if evaluating in simulation.
evaluation = ["lerobot[av-dep]"]
# lerobot-dataset-viz, lerobot-imgtransform-viz
dataset_viz = ["lerobot[dataset]", "lerobot[viz]"]

# Common
av-dep = ["av>=15.0.0,<16.0.0"]
pygame-dep = ["pygame>=2.5.1,<2.7.0"]
# NOTE: 0.9.16 links against liburdfdom_sensor.so.4, which is unavailable on Ubuntu 24.04
# (noble ships urdfdom 3.x). Cap below 0.9.16 until system urdfdom 4.x is broadly available.
#
# NOTE: placo pulls in pin (Pinocchio), whose binary wheels dlopen specific cmeel sonames
# (liburdfdom_sensor.so.4.0, libtinyxml2.so.10) but declare only `>=` floors on their cmeel
# packages. The 2026-05-21 major bumps (cmeel-urdfdom 6.0.0 -> .so.6, cmeel-tinyxml2 11.0.0
# -> .so.11) ship newer sonames, so left unpinned the resolver grabs them and `import placo`
# fails at load with "liburdfdom_sensor.so.4.0: cannot open shared object file" (see #3755).
# There is no cmeel-urdfdom 5.x; <5 selects the 4.x ABI the placo/pin wheels are built against.
placo-dep = ["placo>=0.9.6,<0.9.16", "cmeel-urdfdom>=4,<5", "cmeel-tinyxml2<11"]
transformers-dep = ["transformers>=5.4.0,<5.6.0"]
sentencepiece-dep = ["sentencepiece>=0.2.0,<0.3.0"] # FAST action tokenizer backend (fineart_vla)
grpcio-dep = ["grpcio>=1.73.1,<2.0.0", "protobuf>=6.31.1,<8.0.0"]
accelerate-dep = ["accelerate>=1.14.0,<2.0.0"]
can-dep = ["python-can>=4.2.0,<5.0.0"]
peft-dep = ["peft>=0.18.0,<1.0.0"]
scipy-dep = ["scipy>=1.14.0,<2.0.0"]
diffusers-dep = ["diffusers>=0.38.0,<0.40.0"]
qwen-vl-utils-dep = ["qwen-vl-utils>=0.0.11,<0.1.0"]
matplotlib-dep = ["matplotlib>=3.10.3,<4.0.0", "contourpy>=1.3.0,<2.0.0"] # NOTE: Explicitly listing contourpy helps the resolver converge faster.
pyserial-dep = ["pyserial>=3.5,<4.0"]
deepdiff-dep = ["deepdiff>=7.0.1,<9.0.0"]
pynput-dep = ["pynput>=1.7.8,<1.9.0"]
pyzmq-dep = ["pyzmq>=26.2.1,<28.0.0"]
motorbridge-dep = ["motorbridge>=0.5,<0.6"]
motorbridge-smart-servo-dep = ["motorbridge-smart-servo>=0.0.4,<0.1.0"]
timm-dep = ["timm>=1.0.0,<1.1.0"]

# Motors
feetech = ["feetech-servo-sdk>=1.0.0,<2.0.0", "lerobot[pyserial-dep]", "lerobot[deepdiff-dep]"]
dynamixel = ["dynamixel-sdk>=3.7.31,<3.9.0", "lerobot[pyserial-dep]", "lerobot[deepdiff-dep]"]
damiao = ["lerobot[can-dep]"]
robstride = ["lerobot[can-dep]"]

# Robots
openarms = ["lerobot[damiao]"]
gamepad = ["lerobot[pygame-dep]", "hidapi>=0.14.0,<0.15.0"]
hopejr = ["lerobot[feetech]", "lerobot[pygame-dep]"]
lekiwi = ["lerobot[feetech]", "lerobot[pyzmq-dep]"]
unitree_g1 = [
    # "unitree-sdk2==1.0.1",
    "lerobot[pyzmq-dep]",
    "lerobot[pyserial-dep]",
    "onnxruntime>=1.16.0,<2.0.0",
    "onnx>=1.16.0,<2.0.0",
    "meshcat>=0.3.0,<0.4.0",
    "lerobot[matplotlib-dep]",
    "lerobot[pygame-dep]",
]
# reachy2-sdk caps grpcio<=1.73.1 and protobuf<=6.32.0; quarantined here so downstream users aren't held back. reachy2-sdk is unlikely to release new versions.
reachy2 = [
    "reachy2_sdk>=1.0.15,<1.1.0",
    "grpcio<=1.73.1",
    "protobuf<=6.32.0",
]
# Seeed Studio reBot B601 follower (motorbridge / CAN) + StarArm102 / reBot Arm 102
# leader (motorbridge-smart-servo / FashionStar UART servos).
rebot = ["lerobot[motorbridge-dep]", "lerobot[motorbridge-smart-servo-dep]"]
kinematics = ["lerobot[placo-dep]"]
intelrealsense = [
    "pyrealsense2>=2.55.1.6486,<2.57.0 ; sys_platform != 'darwin'",
    "pyrealsense2-macosx>=2.54,<2.57.0 ; sys_platform == 'darwin'",
]
phone = ["hebi-py>=2.8.0,<2.12.0", "teleop>=0.1.0,<0.2.0", "fastapi<1.0", "lerobot[scipy-dep]"]

# Policies
diffusion = ["lerobot[diffusers-dep]"]
wallx = [
    "lerobot[transformers-dep]",
    "lerobot[peft-dep]",
    "lerobot[scipy-dep]",
    "lerobot[qwen-vl-utils-dep]",
]
pi = ["lerobot[transformers-dep]", "lerobot[scipy-dep]", "lerobot[sentencepiece-dep]"]
dm05 = ["lerobot[transformers-dep]"]
molmoact2 = ["lerobot[transformers-dep]", "lerobot[peft-dep]", "lerobot[scipy-dep]"]
smolvla = ["lerobot[transformers-dep]", "num2words>=0.5.14,<0.6.0", "lerobot[accelerate-dep]"]
multi_task_dit = ["lerobot[transformers-dep]", "lerobot[diffusers-dep]"]
groot = [
    "lerobot[transformers-dep]",
    "lerobot[peft-dep]",
    "lerobot[diffusers-dep]",
    "lerobot[dataset]", # NOTE: processor_groot builds a LeRobotDataset for relative-action training stats
    "dm-tree>=0.1.8,<1.0.0",
    "lerobot[timm-dep]",
    "decord>=0.6.0,<1.0.0; (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
]
sarm = ["lerobot[transformers-dep]", "pydantic>=2.0.0,<3.0.0", "faker>=33.0.0,<35.0.0", "lerobot[matplotlib-dep]", "lerobot[qwen-vl-utils-dep]"]
robometer = ["lerobot[transformers-dep]", "lerobot[qwen-vl-utils-dep]", "lerobot[peft-dep]"]
topreward = ["lerobot[transformers-dep]"]
xvla = ["lerobot[transformers-dep]"]
eo1 = ["lerobot[transformers-dep]", "lerobot[qwen-vl-utils-dep]"]
fastwam = [
    "lerobot[transformers-dep]",
    "lerobot[diffusers-dep]",
]
# flux3 (FLUX 3 Action): Qwen3-VL text encoder via transformers. The video
# VAE additionally needs NATTEN (`natten`, torch-matched wheel, not on PyPI for every platform): see
# docs/source/flux3.mdx.
flux3 = [
    "lerobot[transformers-dep]",
]
evo1 = ["lerobot[transformers-dep]"]
# fineart_vla: PI0.5-based policy that also trains a language head. The optional
# fused training kernels (Liger Triton kernels, FlashRT Hub kernels) need Linux + CUDA.
fineart_vla = ["lerobot[pi]"]
fineart_vla_kernels = [
    "lerobot[fineart_vla]",
    "liger-kernel>=0.8.0,<0.9.0; sys_platform == 'linux'",
    # kernels>=0.15 breaks `import transformers.activations` with the pinned transformers.
    "kernels>=0.14.1,<0.15.0",
]
hilserl = ["lerobot[transformers-dep]", "lerobot[dataset]", "gym-hil>=0.1.14,<0.2.0", "lerobot[grpcio-dep]", "lerobot[placo-dep]"]
vla_jepa = ["lerobot[transformers-dep]", "lerobot[diffusers-dep]", "lerobot[qwen-vl-utils-dep]"]
lingbot_va = ["lerobot[transformers-dep]", "lerobot[diffusers-dep]", "lerobot[accelerate-dep]"]
lawam = [
    "lerobot[transformers-dep]",
    "lerobot[diffusers-dep]",
]

# Features
async = ["lerobot[grpcio-dep]", "lerobot[matplotlib-dep]"]
peft = ["lerobot[transformers-dep]", "lerobot[peft-dep]"]

# Annotation pipeline (lerobot-annotate). The only backend is ``openai``,
# which talks to any OpenAI-compatible server (``vllm serve`` /
# ``transformers serve`` / hosted). Distributed runs use Hugging Face Jobs
# (see examples/annotations/run_hf_job.py).
annotations = [
    "lerobot[dataset]",
    "lerobot[transformers-dep]",
    "openai>=1.40,<2.0",
    # ``vllm`` is intentionally NOT a hard dep: it pins an older torch, and
    # uv's single unified lock would then cap ``torch`` for every extra
    # (e.g. forcing 2.8 while ``torchcodec`` in [dataset] needs 2.11 -> ABI
    # break in CI). The HF Jobs image (``vllm/vllm-openai``) provides vLLM;
    # install it locally only if you run your own ``vllm serve``.
]

# Development
dev = ["pre-commit>=3.7.0,<5.0.0", "debugpy>=1.8.1,<1.9.0", "lerobot[grpcio-dep]", "grpcio-tools>=1.73.1,<2.0.0", "mypy>=2.0.0,<3.0.0", "ruff>=0.14.1", "lerobot[notebook]"]
notebook = ["jupyter>=1.0.0,<2.0.0", "ipykernel>=6.0.0,<7.0.0"]
test = ["pytest>=8.1.0,<10.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
# NOTE: Explicitly listing scipy helps flatten the dependecy tree.
aloha = ["lerobot[dataset]", "gym-aloha>=0.1.4,<0.2.0", "lerobot[scipy-dep]"]
pusht = ["lerobot[dataset]", "gym-pusht>=0.1.5,<0.2.0", "pymunk>=6.6.0,<7.0.0"] # TODO: Fix pymunk version in gym-pusht instead
libero = ["lerobot[dataset]", "lerobot[transformers-dep]", "hf-libero>=0.1.4,<0.2.0; sys_platform == 'linux'", "lerobot[scipy-dep]"]
metaworld = ["lerobot[dataset]", "metaworld==3.0.0", "lerobot[scipy-dep]"]
# NOTE: vlabench is NOT exposed as a `lerobot` extra. Its only distribution
# is the OpenMOSS/VLABench GitHub repo (package name `VLABench`, no PyPI
# release), so any `vlabench>=X` pip spec is unresolvable. Install it
# manually alongside MuJoCo / dm-control — see docs/source/vlabench.mdx
# for the recipe.
# NOTE: robomme is NOT a pyproject extra — mani-skill hard-pins numpy<2
# which conflicts with lerobot's numpy>=2 base pin, so the two trees can't
# resolve into a single env. Install it only in the RoboMME Docker image
# via `uv pip install --override` (see docker/Dockerfile.benchmark.robomme).
# NOTE: robocasa is NOT exposed as a `lerobot` extra. Its setup.py pins
# `lerobot==0.3.3` in install_requires, which cyclically shadows our own
# workspace `lerobot` and makes the graph unsolvable under any resolver
# (uv, pip). Install it manually alongside robosuite — see
# docs/source/robocasa.mdx for the recipe.

# All
all = [
    # Feature-scoped extras
    "lerobot[dataset]",
    "lerobot[lancedb]",
    "lerobot[training]",
    "lerobot[hardware]",
    "lerobot[viz]",
    # 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
    # the loose scipy requirements from transitive deps like dm-control and metaworld.
    "scipy>=1.14.0,<2.0.0",
    "lerobot[dynamixel]",
    "lerobot[feetech]",
    "lerobot[damiao]",
    "lerobot[robstride]",
    "lerobot[gamepad]",
    "lerobot[hopejr]",
    "lerobot[lekiwi]",
    "lerobot[openarms]",
    "lerobot[reachy2]",
    "lerobot[rebot]",
    "lerobot[kinematics]",
    "lerobot[intelrealsense]",
    "lerobot[diffusion]",
    "lerobot[multi_task_dit]",
    "lerobot[wallx]",
    "lerobot[pi]",
    "lerobot[dm05]",
    "lerobot[fineart_vla]",
    "lerobot[molmoact2]",
    "lerobot[smolvla]",
    "lerobot[fastwam]",
    "lerobot[flux3]",
    "lerobot[groot]",
    "lerobot[xvla]",
    "lerobot[evo1]",
    "lerobot[hilserl]",
    "lerobot[vla_jepa]",
    "lerobot[lingbot_va]",
    "lerobot[lawam]",
    "lerobot[async]",
    "lerobot[dev]",
    "lerobot[test]",
    "lerobot[video_benchmark]",
    "lerobot[aloha]",
    "lerobot[pusht]",
    "lerobot[phone]",
    "lerobot[libero]; sys_platform == 'linux'",
    "lerobot[metaworld]",
    "lerobot[sarm]",
    "lerobot[robometer]",
    "lerobot[topreward]",
    "lerobot[peft]",
    # "lerobot[unitree_g1]", TODO: Unitree requires specific installation instructions for unitree_sdk2
]

[project.scripts]
lerobot-build-mp4-sidecar="lerobot.scripts.lerobot_build_mp4_sidecar:main"
lerobot-calibrate="lerobot.scripts.lerobot_calibrate:main"
lerobot-find-cameras="lerobot.scripts.lerobot_find_cameras:main"
lerobot-find-port="lerobot.scripts.lerobot_find_port:main"
lerobot-record="lerobot.scripts.lerobot_record:main"
lerobot-replay="lerobot.scripts.lerobot_replay:main"
lerobot-setup-motors="lerobot.scripts.lerobot_setup_motors:main"
lerobot-teleoperate="lerobot.scripts.lerobot_teleoperate:main"
lerobot-convert-dcp="lerobot.scripts.lerobot_convert_dcp:main"
lerobot-eval="lerobot.scripts.lerobot_eval:main"
lerobot-train="lerobot.scripts.lerobot_train:main"
lerobot-train-tokenizer="lerobot.scripts.lerobot_train_tokenizer:main"
lerobot-dataset-viz="lerobot.scripts.lerobot_dataset_viz:main"
lerobot-info="lerobot.scripts.lerobot_info:main"
lerobot-find-joint-limits="lerobot.scripts.lerobot_find_joint_limits:main"
lerobot-imgtransform-viz="lerobot.scripts.lerobot_imgtransform_viz:main"
lerobot-edit-dataset="lerobot.scripts.lerobot_edit_dataset:main"
lerobot-setup-can="lerobot.scripts.lerobot_setup_can:main"
lerobot-annotate="lerobot.scripts.lerobot_annotate:main"
lerobot-rollout="lerobot.scripts.lerobot_rollout:main"

# ---------------- Tool Configurations ----------------

# cu128 wheels keep broad hardware reach; the driver floor is 570.86.
# To use a different CUDA variant, reinstall torch with an explicit index, e.g.:
#   uv pip install --force-reinstall torch torchvision \
#       --index-url https://download.pytorch.org/whl/cu130
[[tool.uv.index]]
name = "pytorch-cu128"
url = "https://download.pytorch.org/whl/cu128"
explicit = true

[tool.uv.sources]
torch = [{ index = "pytorch-cu128", marker = "sys_platform == 'linux'" }]
torchvision = [{ index = "pytorch-cu128", marker = "sys_platform == 'linux'" }]

[tool.setuptools.package-data]
lerobot = ["envs/*.json", "annotations/steerable_pipeline/prompts/*.txt", "configs/recipes/*.yaml"]

[tool.setuptools.packages.find]
where = ["src"]

[tool.ruff]
target-version = "py312"
line-length = 110
exclude = ["tests/artifacts/**/*.safetensors", "*_pb2.py", "*_pb2_grpc.py"]

[tool.ruff.lint]
# E, W: pycodestyle errors and warnings
# F: PyFlakes
# I: isort
# UP: pyupgrade
# B: flake8-bugbear (good practices, potential bugs)
# C4: flake8-comprehensions (more concise comprehensions)
# A: flake8-builtins (shadowing builtins)
# SIM: flake8-simplify
# RUF: Ruff-specific rules
# D: pydocstyle (for docstring style/formatting)
# S: flake8-bandit (some security checks, complements Bandit)
# T20: flake8-print (discourage print statements in production code)
# N: pep8-naming
# TODO: Uncomment rules when ready to use
select = [
    "E", "W", "F", "I", "B", "C4", "T20", "N", "UP", "SIM", "D" #, "A", "S", "RUF"
]
ignore = [
    "E501", # Line too long
    "T201", # Print statement found
    "T203", # Pprint statement found
    "B008", # Perform function call in argument defaults
]

[tool.ruff.lint.per-file-ignores]
"__init__.py" = ["F401", "F403", "E402", "D104"]
# 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"]

# D (pydocstyle) is enabled globally, but only holds for code that has been converted to the docstring
# standard in docs/source/writing_docstrings.mdx. Every module below is still on the old style; each entry
# is deleted as that module is converted, and this block can be removed once it is empty.
#
# Not part of the API reference and not planned for conversion: tests, examples, benchmarks, templates,
# CI helper scripts and the packaging shim.
"tests/**" = ["D"]
"examples/**" = ["D"]
"benchmarks/**" = ["D"]
"scripts/**" = ["D"]
"setup.py" = ["D"]
"src/lerobot/templates/**" = ["D"]
# Vendored from transformers; keeps its upstream docstring style so syncs stay clean.
"src/lerobot/policies/molmoact2/molmoact2_hf_model/**" = ["D"]
# Awaiting conversion, one PR per module.
"src/lerobot/annotations/**" = ["D"]
"src/lerobot/async_inference/**" = ["D"]
"src/lerobot/cameras/**" = ["D"]
"src/lerobot/common/**" = ["D"]
"src/lerobot/configs/**" = ["D"]
"src/lerobot/data_processing/**" = ["D"]
"src/lerobot/datasets/**" = ["D"]
"src/lerobot/distributed/**" = ["D"]
"src/lerobot/envs/**" = ["D"]
"src/lerobot/jobs/**" = ["D"]
"src/lerobot/model/**" = ["D"]
"src/lerobot/motors/**" = ["D"]
"src/lerobot/optim/**" = ["D"]
"src/lerobot/policies/**" = ["D"]
"src/lerobot/processor/**" = ["D"]
"src/lerobot/rewards/**" = ["D"]
"src/lerobot/rl/**" = ["D"]
"src/lerobot/robots/**" = ["D"]
"src/lerobot/rollout/**" = ["D"]
"src/lerobot/scripts/**" = ["D"]
"src/lerobot/teleoperators/**" = ["D"]
"src/lerobot/transforms/**" = ["D"]
"src/lerobot/transport/**" = ["D"]
"src/lerobot/utils/**" = ["D"]
"src/lerobot/lerobot_types.py" = ["D"]
# Package root: two one-line docstring fixes land with the docstring PR.
"src/lerobot/__init__.py" = ["D"]
"src/lerobot/__version__.py" = ["D"]
[tool.ruff.lint.isort]
combine-as-imports = true
known-first-party = ["lerobot"]

[tool.ruff.lint.pydocstyle]
convention = "google"

[tool.ruff.format]
quote-style = "double"
indent-style = "space"
skip-magic-trailing-comma = false
line-ending = "auto"
docstring-code-format = true

[tool.bandit]
exclude_dirs = [
    "tests",
    "benchmarks",
    "src/lerobot/datasets/push_dataset_to_hub",
]
skips = ["B101", "B311", "B404", "B603", "B615"]

[tool.typos]
default.extend-words = { trak = "trak", nd = "nd" }
default.extend-ignore-re = [
    "(?Rm)^.*(#|//)\\s*spellchecker:disable-line$",                      # spellchecker:disable-line
    "(?s)(#|//)\\s*spellchecker:off.*?\\n\\s*(#|//)\\s*spellchecker:on", # spellchecker:<on|off>
]
default.extend-ignore-identifiers-re = [
    # Add individual words here to ignore them
    "2nd",
    "pn",
    "ser",
    "ein",
    "thw",
    "inpt",
    "arange",
    "is_compileable",
    "ROBOTIS",
    "OT_VALUE",
    "VanderBilt",
    "seperated_timestep",
    "nd",  # einsum output axes "...nd" in the vendored FLUX 3 Action transformer
    "EmbedND",
]

# Docstring coverage gate. `fail-under` is a RATCHET, not a target: it is set just below the currently
# measured coverage so it passes today, and is raised in the same PR that documents a module. Never set it
# to a value that fails on main. The destination is 100; see docs/source/writing_docstrings.mdx.
[tool.interrogate]
ignore-init-module = true
ignore-init-method = true
ignore-nested-functions = false
ignore-magic = false
ignore-semiprivate = false
ignore-private = false
ignore-property-decorators = false
ignore-module = false
ignore-setters = false
fail-under = 52
output-format = "term-missing"
color = true
paths = ["src/lerobot"]
exclude = ["src/lerobot/policies/molmoact2/molmoact2_hf_model"]

[tool.pytest.ini_options]
markers = [
    "multigpu: distributed tests needing 2-4 GPUs (CI: docker_publish.yml lane)",
    "multigpu_heavy: 8-GPU sweeps and soak tests; never run in CI",
]

[tool.mypy]
python_version = "3.12"
ignore_missing_imports = true
follow_imports = "skip"
# Paths the hook checks as a whole (pass_filenames: false); keep its `files` pattern in sync.
files = ["src/lerobot", "scripts", "utils", "conftest.py", "setup.py"]
exclude = ['_pb2(_grpc)?\.py$']
exclude_gitignore = true
warn_redundant_casts = true
extra_checks = true
strict_bytes = true
strict_equality = true
local_partial_types = true
implicit_reexport = false
warn_unused_ignores = true
enable_error_code = [
    "deprecated",
    "exhaustive-match",
    "ignore-without-code",
    "redundant-self",
    "truthy-bool",
    "truthy-iterable",
    "unused-awaitable",
]
# Next candidates, each needing a fix PR first (error counts as of 2026-09):
# warn_unreachable = true          # 40: dead code, mostly unannotated `self.x = None` attributes
# strict_equality_for_none = true  # 40: keep None guards on config fields, `--field=null` yields None
# check_untyped_defs = true        # 195: most valuable; decide first how Optional dataset metadata is accessed

# One ruleset for all checked code. Excluded: lerobot.rl (not typed yet) and the vendored
# molmoact2_hf_model (kept verbatim so upstream syncs stay clean).
[[tool.mypy.overrides]]
module = ["lerobot.rl.*", "lerobot.policies.molmoact2.molmoact2_hf_model.*"]
ignore_errors = true
