diff --git a/.github/workflows/style.yml b/.github/workflows/style.yml deleted file mode 100644 index 9ee3e50..0000000 --- a/.github/workflows/style.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: Code Style - -on: - push: - branches: [ "**" ] - tags-ignore: [ "**" ] - pull_request: - workflow_dispatch: - -jobs: - - black: - - name: black - runs-on: ubuntu-latest - - steps: - - - name: "🔀 Checkout repository" - uses: actions/checkout@v2 - - - name: '🐍 Initialize Python' - uses: actions/setup-python@v2 - with: - python-version: "3.10" - - - name: "📝 Black Code Formatter" - uses: psf/black@stable - with: - options: --check --diff --color - - isort: - - name: isort - runs-on: ubuntu-latest - - steps: - - - name: "🔀 Checkout repository" - uses: actions/checkout@v2 - - - name: '🐍 Initialize Python' - uses: actions/setup-python@v2 - with: - python-version: "3.10" - - # Workaround for https://github.com/isort/isort-action/issues/70 - - run: echo "colorama" >> /tmp/isort.requirements.txt - - - name: "📝 isort" - uses: isort/isort-action@master - with: - configuration: --check --diff --color - requirements-files: /tmp/isort.requirements.txt diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..c985051 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,34 @@ +ci: + autofix_prs: false + autoupdate_schedule: quarterly + submodules: false + +default_language_version: + python: python3 + +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: check-ast + - id: check-merge-conflict + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace + + - repo: https://github.com/psf/black-pre-commit-mirror + rev: 24.8.0 + hooks: + - id: black + args: ["--check", "--diff"] + + - repo: https://github.com/pycqa/isort + rev: 5.13.2 + hooks: + - id: isort + args: ["--check", "--diff"] + + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.6.9 + hooks: + - id: ruff diff --git a/LICENSE b/LICENSE index 3c486a4..2755199 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ BSD 3-Clause License -Copyright (c) 2022, Artificial and Mechanical Intelligence +Copyright (c) 2022, Artificial and Mechanical Intelligence All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/README.md b/README.md index f7c3fa3..4b2b58c 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ Installing ROD using `conda` is the recommended way to obtain a complete install conda install rod -c conda-forge ``` -This will automatically install `sdformat` and `gz-tools`. +This will automatically install `sdformat` and `gz-tools`. @@ -242,7 +242,7 @@ print(urdf_string) ## Contributing -Pull requests are welcome. +Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. ## Maintainers diff --git a/pyproject.toml b/pyproject.toml index 5dd01a3..2073c98 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,3 +18,47 @@ multi_line_output = 3 [tool.cibuildwheel] build-frontend = "build" + + +# ================== +# Ruff configuration +# ================== + +[tool.ruff] +exclude = [ + ".git", + ".pytest_cache", + ".ruff_cache", + ".vscode", + ".devcontainer", + "__pycache__", +] +preview = true + +[tool.ruff.lint] +# https://docs.astral.sh/ruff/rules/ +select = [ + "B", + "E", + "F", + "I", + "W", + "RUF", + "UP", + "YTT", +] + +ignore = [ + "B008", # Function call in default argument + "B024", # Abstract base class without abstract methods + "E402", # Module level import not at top of file + "E501", # Line too long + "E731", # Do not assign a `lambda` expression, use a `def` + "E741", # Ambiguous variable name + "I001", # Import block is unsorted or unformatted + "RUF003", # Ambigous unicode character in comment +] + +[tool.ruff.lint.per-file-ignores] +# Ignore `E402` (import violations) in all `__init__.py` files +"__init__.py" = ["F401", "F821"] diff --git a/src/rod/builder/primitive_builder.py b/src/rod/builder/primitive_builder.py index 3ef60c9..1fc0633 100644 --- a/src/rod/builder/primitive_builder.py +++ b/src/rod/builder/primitive_builder.py @@ -121,7 +121,7 @@ def add_inertial( pose: rod.Pose | None = None, inertial: rod.Inertial | None = None, ) -> PrimitiveBuilder: - if not isinstance(self.element, (rod.Model, rod.Link)): + if not isinstance(self.element, rod.Model | rod.Link): raise ValueError(type(self.element)) if isinstance(self.element, rod.Model): @@ -151,7 +151,7 @@ def add_visual( pose: rod.Pose | None = None, visual: rod.Visual | None = None, ) -> PrimitiveBuilder: - if not isinstance(self.element, (rod.Model, rod.Link)): + if not isinstance(self.element, rod.Model | rod.Link): raise ValueError(type(self.element)) if isinstance(self.element, rod.Model): @@ -187,7 +187,7 @@ def add_collision( pose: rod.Pose | None = None, collision: rod.Collision | None = None, ) -> PrimitiveBuilder: - if not isinstance(self.element, (rod.Model, rod.Link)): + if not isinstance(self.element, rod.Model | rod.Link): raise ValueError(type(self.element)) if isinstance(self.element, rod.Model): diff --git a/src/rod/kinematics/kinematic_tree.py b/src/rod/kinematics/kinematic_tree.py index 168237c..abbe68f 100644 --- a/src/rod/kinematics/kinematic_tree.py +++ b/src/rod/kinematics/kinematic_tree.py @@ -3,7 +3,7 @@ import copy import dataclasses import functools -from typing import Sequence +from collections.abc import Sequence import numpy as np diff --git a/src/rod/kinematics/tree_transforms.py b/src/rod/kinematics/tree_transforms.py index 00f4e8a..49b9ef7 100644 --- a/src/rod/kinematics/tree_transforms.py +++ b/src/rod/kinematics/tree_transforms.py @@ -13,7 +13,9 @@ @dataclasses.dataclass class TreeTransforms: - kinematic_tree: KinematicTree = dataclasses.dataclass(init=False) + kinematic_tree: KinematicTree = dataclasses.field( + default_factory=dataclasses.dataclass(init=False) + ) _transform_cache: dict[str, npt.NDArray] = dataclasses.field(default_factory=dict) @staticmethod diff --git a/src/rod/pretty_printer.py b/src/rod/pretty_printer.py index 7e064ff..5b238d3 100644 --- a/src/rod/pretty_printer.py +++ b/src/rod/pretty_printer.py @@ -13,7 +13,7 @@ def list_to_string(obj: list[Any], level: int = 1) -> str: if not isinstance(obj, list): raise TypeError(obj, type(obj)) - if all(isinstance(el, (numbers.Number, str)) for el in obj): + if all(isinstance(el, numbers.Number | str) for el in obj): return str(obj) spacing = " " * 4 diff --git a/src/rod/sdf/geometry.py b/src/rod/sdf/geometry.py index de55d58..d3e9b98 100644 --- a/src/rod/sdf/geometry.py +++ b/src/rod/sdf/geometry.py @@ -2,10 +2,12 @@ import dataclasses import types -from typing import ClassVar, Union +from typing import ClassVar import mashumaro +from rod import logging + from .element import Element diff --git a/src/rod/sdf/joint.py b/src/rod/sdf/joint.py index eeae4c5..7da1261 100644 --- a/src/rod/sdf/joint.py +++ b/src/rod/sdf/joint.py @@ -1,5 +1,4 @@ import dataclasses -from typing import Optional import mashumaro @@ -10,32 +9,32 @@ @dataclasses.dataclass class Limit(Element): - lower: Optional[float] = dataclasses.field( + lower: float | None = dataclasses.field( default=None, metadata=mashumaro.field_options(serialize=Element.serialize_float), ) - upper: Optional[float] = dataclasses.field( + upper: float | None = dataclasses.field( default=None, metadata=mashumaro.field_options(serialize=Element.serialize_float), ) - effort: Optional[float] = dataclasses.field( + effort: float | None = dataclasses.field( default=None, metadata=mashumaro.field_options(serialize=Element.serialize_float), ) - velocity: Optional[float] = dataclasses.field( + velocity: float | None = dataclasses.field( default=None, metadata=mashumaro.field_options(serialize=Element.serialize_float), ) - stiffness: Optional[float] = dataclasses.field( + stiffness: float | None = dataclasses.field( default=None, metadata=mashumaro.field_options(serialize=Element.serialize_float), ) - dissipation: Optional[float] = dataclasses.field( + dissipation: float | None = dataclasses.field( default=None, metadata=mashumaro.field_options(serialize=Element.serialize_float), ) @@ -51,12 +50,12 @@ class Dynamics(Element): metadata=mashumaro.field_options(serialize=Element.serialize_float), ) - damping: Optional[float] = dataclasses.field( + damping: float | None = dataclasses.field( default=None, metadata=mashumaro.field_options(serialize=Element.serialize_float), ) - friction: Optional[float] = dataclasses.field( + friction: float | None = dataclasses.field( default=None, metadata=mashumaro.field_options(serialize=Element.serialize_float), ) @@ -66,7 +65,7 @@ class Dynamics(Element): class Axis(Element): xyz: Xyz limit: Limit - dynamics: Optional[Dynamics] = dataclasses.field(default=None) + dynamics: Dynamics | None = dataclasses.field(default=None) @dataclasses.dataclass @@ -78,4 +77,4 @@ class Joint(Element): child: str pose: Pose | None = dataclasses.field(default=None) - axis: Optional[Axis] = dataclasses.field(default=None) + axis: Axis | None = dataclasses.field(default=None) diff --git a/src/rod/sdf/physics.py b/src/rod/sdf/physics.py index 7b78d98..7636619 100644 --- a/src/rod/sdf/physics.py +++ b/src/rod/sdf/physics.py @@ -1,5 +1,4 @@ import dataclasses -from typing import Optional import mashumaro @@ -39,7 +38,7 @@ class Physics(Element): metadata=mashumaro.field_options(serialize=Element.serialize_float), ) - max_contacts: Optional[float] = dataclasses.field( + max_contacts: float | None = dataclasses.field( default=None, metadata=mashumaro.field_options(serialize=Element.serialize_float), ) diff --git a/src/rod/sdf/visual.py b/src/rod/sdf/visual.py index f4273d3..675cf9e 100644 --- a/src/rod/sdf/visual.py +++ b/src/rod/sdf/visual.py @@ -1,5 +1,4 @@ import dataclasses -from typing import Optional import mashumaro @@ -17,4 +16,4 @@ class Visual(Element): pose: Pose | None = dataclasses.field(default=None) - material: Optional[Material] = dataclasses.field(default=None) + material: Material | None = dataclasses.field(default=None) diff --git a/src/rod/tree/directed_tree.py b/src/rod/tree/directed_tree.py index 5720e47..423d718 100644 --- a/src/rod/tree/directed_tree.py +++ b/src/rod/tree/directed_tree.py @@ -1,13 +1,13 @@ -import collections.abc import dataclasses import functools -from typing import Any, Callable, Iterable +from collections.abc import Callable, Iterable, Sequence +from typing import Any from .tree_elements import DirectedTreeNode @dataclasses.dataclass(frozen=True) -class DirectedTree(collections.abc.Sequence): +class DirectedTree(Sequence): root: DirectedTreeNode def __post_init__(self): diff --git a/src/rod/urdf/exporter.py b/src/rod/urdf/exporter.py index 06ecdc8..bc031f7 100644 --- a/src/rod/urdf/exporter.py +++ b/src/rod/urdf/exporter.py @@ -1,7 +1,7 @@ import abc import copy import dataclasses -from typing import Any, ClassVar, Set +from typing import Any, ClassVar import numpy as np import xmltodict @@ -25,7 +25,7 @@ class UrdfExporter(abc.ABC): # If a list of strings is passed, only the listed fixed joints will be preserved. gazebo_preserve_fixed_joints: bool | list[str] = False - SupportedSdfJointTypes: ClassVar[Set[str]] = { + SupportedSdfJointTypes: ClassVar[set[str]] = { "revolute", "continuous", "prismatic", diff --git a/src/rod/utils/frame_convention.py b/src/rod/utils/frame_convention.py index 697b607..7a6a652 100644 --- a/src/rod/utils/frame_convention.py +++ b/src/rod/utils/frame_convention.py @@ -193,9 +193,7 @@ def switch_frame_convention( # Adjust the reference frames of all sub-models for sub_model in model.models(): logging.info( - "Model composition not yet supported, ignoring '{}/{}'".format( - model.name, sub_model.name - ) + f"Model composition not yet supported, ignoring '{model.name}/{sub_model.name}'" ) # Adjust the reference frames of all joints @@ -312,7 +310,7 @@ def find_parent_link_of_frame(frame: rod.Frame, model: rod.Model) -> str: ) # At this point, the parent is either a link or another frame. - assert isinstance(parent, (rod.Link, rod.Frame)) + assert isinstance(parent, rod.Link | rod.Frame) match parent: # If the parent is a link, can stop searching.