Skip to content

Commit

Permalink
Merge pull request #139 from mechmotum/all-ruff-rules
Browse files Browse the repository at this point in the history
Bump ruff version and add almost all rules
  • Loading branch information
tjstienstra authored Sep 11, 2024
2 parents d88d482 + 6fc0160 commit b07c45c
Show file tree
Hide file tree
Showing 80 changed files with 819 additions and 612 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
run: poetry install --with lint
- name: Run lint
run: |
poetry run ruff .
poetry run ruff check .
#-------------------------------------- tests ---------------------------------------#

Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ repos:
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.3.4
rev: v0.6.4
hooks:
- id: ruff
args: [ --fix, --show-fixes, --exit-non-zero-on-fix ]
Expand Down
9 changes: 5 additions & 4 deletions benchmarks/test_rolling_disc.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import itertools

from brim.bicycle import FlatGround, KnifeEdgeWheel, NonHolonomicTire
from brim.other.rolling_disc import RollingDisc
from brim.utilities.benchmarking import benchmark
from sympy import count_ops, cse, symbols
from sympy.physics.mechanics import (
KanesMethod,
Expand All @@ -15,6 +12,10 @@
inertia,
)

from brim.bicycle import FlatGround, KnifeEdgeWheel, NonHolonomicTire
from brim.other.rolling_disc import RollingDisc
from brim.utilities.benchmarking import benchmark

ROUNDS = 10


Expand Down Expand Up @@ -53,7 +54,7 @@ def rolling_disc_from_sympy_test_suite():
Dmc.v2pt_theory(C, N, R)

# This is a simple way to form the inertia dyadic.
I = inertia(L, *symbols("Ixx Iyy Ixx")) # noqa: N806 E741
I = inertia(L, *symbols("Ixx Iyy Ixx")) # noqa: E741, N806

# Kinematic differential equations; how the generalized coordinate time
# derivatives relate to generalized speeds.
Expand Down
29 changes: 12 additions & 17 deletions benchmarks/test_whipple_bicycle.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,3 @@
from brim.bicycle import (
FlatGround,
KnifeEdgeWheel,
NonHolonomicTire,
RigidFrontFrame,
RigidRearFrame,
WhippleBicycleMoore,
)
from brim.utilities.benchmarking import benchmark
from sympy import symbols
from sympy.physics.mechanics import (
KanesMethod,
Expand All @@ -19,6 +10,16 @@
inertia,
)

from brim.bicycle import (
FlatGround,
KnifeEdgeWheel,
NonHolonomicTire,
RigidFrontFrame,
RigidRearFrame,
WhippleBicycleMoore,
)
from brim.utilities.benchmarking import benchmark

ROUNDS = 3


Expand All @@ -42,14 +43,8 @@ def create_whipple_bicycle_moore_brim():
return system


def create_whipple_bicycle_moore_minimal_coords():
N = ReferenceFrame("N") # noqa: N806
A = ReferenceFrame("A") # noqa: N806
B = ReferenceFrame("B") # noqa: N806
C = ReferenceFrame("C") # noqa: N806
D = ReferenceFrame("D") # noqa: N806
E = ReferenceFrame("E") # noqa: N806
F = ReferenceFrame("F") # noqa: N806
def create_whipple_bicycle_moore_minimal_coords(): # noqa: PLR0915
N, A, B, C, D, E, F = symbols("N A:F", cls=ReferenceFrame) # noqa: N806
q1, q2, q3, q4 = dynamicsymbols("q1 q2 q3 q4")
q5, q6, q7, q8 = dynamicsymbols("q5 q6 q7 q8")

Expand Down
6 changes: 3 additions & 3 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
For the full list of built-in configuration values, see the documentation:
https://www.sphinx-doc.org/en/master/usage/configuration.html
"""
import os
import sys
from pathlib import Path

# Add source folder to path for autodoc
sys.path.insert(0, os.path.dirname(__file__))
sys.path.insert(0, os.path.abspath("../src"))
sys.path.insert(0, str(Path(__file__).parent))
sys.path.insert(0, str(Path("../src").resolve()))

from process_tutorials import main as process_tutorials

Expand Down
2 changes: 1 addition & 1 deletion docs/guides/component_implementation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ Define Objects
in this stage, but they should not be oriented or positioned yet. ::

@property
def descriptions(self) -> dict[Any, str]:
def descriptions(self) -> dict[object, str]:
"""Descriptions of the attributes of the object."""
return {
**super().descriptions,
Expand Down
122 changes: 67 additions & 55 deletions docs/process_tutorials.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,22 @@
import shutil
import subprocess
import zipfile
from pathlib import Path
from typing import TYPE_CHECKING

import nbformat
from nbconvert.exporters import NotebookExporter
from nbconvert.exporters.exporter import ResourcesDict
from nbconvert.preprocessors import ClearOutputPreprocessor, Preprocessor
from traitlets import Unicode
from traitlets.config import Config

CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
MAIN_DIR = os.path.join(CURRENT_DIR, "..")
TUTORIALS_DIR = os.path.join(CURRENT_DIR, "tutorials")
EXERCISES_DIR = os.path.join(TUTORIALS_DIR, "exercises")
if TYPE_CHECKING:
from nbconvert.exporters.exporter import ResourcesDict

CURRENT_DIR = Path(__file__).resolve().parent
MAIN_DIR = CURRENT_DIR.parent
TUTORIALS_DIR = CURRENT_DIR / "tutorials"
EXERCISES_DIR = TUTORIALS_DIR / "exercises"


class ClearSolutionsPreProcessor(Preprocessor):
Expand All @@ -34,7 +38,7 @@ class ClearSolutionsPreProcessor(Preprocessor):
solution_code_subs = Unicode("###\n### YOUR CODE HERE\n###", config=True)
solution_text_subs = Unicode("YOUR ANSWER HERE", config=True)

def _replace_solution_regions(self, cell: nbformat.NotebookNode):
def _replace_solution_regions(self, cell: nbformat.NotebookNode) -> None:
"""Replace solution regions in a cell."""
if cell.cell_type == "code":
solution_subs = self.solution_code_subs.splitlines()
Expand All @@ -58,7 +62,7 @@ def _replace_solution_regions(self, cell: nbformat.NotebookNode):
cell.source = "\n".join(new_lines)

def preprocess_cell(self, cell: nbformat.NotebookNode, resources: ResourcesDict,
index: int) -> tuple[nbformat.NotebookNode, ResourcesDict]:
index: int) -> tuple[nbformat.NotebookNode, ResourcesDict]: # noqa: ARG002
"""Preprocess a cell."""
self._replace_solution_regions(cell)
return cell, resources
Expand All @@ -74,52 +78,57 @@ def preprocess(self, nb: nbformat.NotebookNode, resources: ResourcesDict
return super().preprocess(nb, resources)

def preprocess_cell(self, cell: nbformat.NotebookNode, resources: ResourcesDict,
index: int) -> tuple[nbformat.NotebookNode, ResourcesDict]:
index: int) -> tuple[nbformat.NotebookNode, ResourcesDict]: # noqa: ARG002
"""Preprocess a cell."""

def replace(match):
def replace(match: re.Match) -> str:
path = match.group(2)
if path.startswith("http"):
return match.group()
new_path = os.path.join(".", "images", os.path.basename(path))
new_path = Path() / "images" / Path(path).name
resources["required_files"][path] = new_path
return f'<img{match.group(1)} src="{new_path}"'

if cell.cell_type == "markdown":
# replace with multiline being true
cell.source = re.sub(r'<img([^>]+)src="([^"]+)"', replace, cell.source,
flags=re.M)
cell.source = re.sub(
r'<img([^>]+)src="([^"]+)"', replace, cell.source, flags=re.MULTILINE
)

return cell, resources


def convert_notebook(input_file: str, output_file: str
) -> tuple[nbformat.NotebookNode, ResourcesDict]:
def convert_notebook(
input_file: str, output_file: str
) -> tuple[nbformat.NotebookNode, ResourcesDict]:
"""Convert a solution notebook to an exercise notebook."""
with open(input_file, encoding="utf-8") as f:
with Path(input_file).open(encoding="utf-8") as f:
nb = nbformat.read(f, as_version=4)
c = Config()
c.NotebookExporter.preprocessors = [
ClearOutputPreprocessor, ClearSolutionsPreProcessor,
UpdateImagePathsPreprocessor]
ClearOutputPreprocessor,
ClearSolutionsPreProcessor,
UpdateImagePathsPreprocessor,
]
exporter = NotebookExporter(config=c)
body, resources = exporter.from_notebook_node(nb)
with open(output_file, "w") as f:
with Path(output_file).open("w") as f:
f.write(body)
return body, resources


def main():
def main() -> None:
"""Convert notebooks and create a zip file with exercises."""
required_files = {}

# Create a folder with exercise notebooks.
if not os.path.isdir(EXERCISES_DIR):
os.mkdir(EXERCISES_DIR)
if not EXERCISES_DIR.is_dir():
EXERCISES_DIR.mkdir()
notebooks = [f for f in os.listdir(TUTORIALS_DIR) if f.endswith(".ipynb")]
for notebook in notebooks:
body, resources = convert_notebook(os.path.join(TUTORIALS_DIR, notebook),
os.path.join(EXERCISES_DIR, notebook))
body, resources = convert_notebook(
TUTORIALS_DIR / notebook, EXERCISES_DIR / notebook
)
required_files.update(resources["required_files"])

# Create a zip file with exercise notebooks.
Expand All @@ -137,34 +146,32 @@ def main():
def create_zip(required_files: dict[str, str]) -> None:
"""Create a zip file with exercise notebooks."""
# Create a folder with files to be zipped.
zip_dir = os.path.join(TUTORIALS_DIR, "zip")
if os.path.isdir(zip_dir):
zip_dir = TUTORIALS_DIR / "zip"
if zip_dir.is_dir():
shutil.rmtree(zip_dir)
os.mkdir(zip_dir)
os.mkdir(os.path.join(zip_dir, "images"))
for file in os.listdir(EXERCISES_DIR):
shutil.copy(os.path.join(EXERCISES_DIR, file), zip_dir)
zip_dir.mkdir()
(zip_dir / "images").mkdir()
for file in EXERCISES_DIR.iterdir():
shutil.copy(EXERCISES_DIR / file, zip_dir)
for file, new_location in required_files.items():
shutil.copy(os.path.join(TUTORIALS_DIR, file),
os.path.join(zip_dir, new_location))
shutil.copy(TUTORIALS_DIR / file, zip_dir / new_location)
for file in ("README.md", "tutorials_environment.yml"):
shutil.copy(os.path.join(TUTORIALS_DIR, file), zip_dir)
shutil.copy(TUTORIALS_DIR / file, zip_dir)

# Create a zip file.
with zipfile.ZipFile(os.path.join(TUTORIALS_DIR, "tutorials.zip"), "w") as f:
for root, _dirs, files in os.walk(zip_dir):
for file in files:
f.write(os.path.join(root, file),
os.path.relpath(os.path.join(root, file), zip_dir))
with zipfile.ZipFile(TUTORIALS_DIR / "tutorials.zip", "w") as f:
for path in zip_dir.rglob("*"):
if path.is_file():
f.write(path, path.relative_to(zip_dir))

# Remove the folder with files to be zipped.
shutil.rmtree(zip_dir)


def notebook_is_executed(nb: str | nbformat.NotebookNode) -> bool:
def notebook_is_executed(nb: Path | str | nbformat.NotebookNode) -> bool:
"""Check if a notebook is executed."""
if isinstance(nb, str):
with open(nb, encoding="utf-8") as f:
if isinstance(nb, (Path, str)):
with Path(nb).open(encoding="utf-8") as f:
nb = nbformat.read(f, as_version=4)
for _cell in nb.cells:
# If statement is from the nbsphinx source code.
Expand All @@ -181,17 +188,19 @@ def notebook_is_executed(nb: str | nbformat.NotebookNode) -> bool:

def notebooks_to_execute() -> tuple[str]:
"""Return a list of notebooks that need to be executed."""
notebooks = [os.path.join(TUTORIALS_DIR, f)
for f in os.listdir(TUTORIALS_DIR) if f.endswith(".ipynb")]
notebooks = [
TUTORIALS_DIR / f for f in os.listdir(TUTORIALS_DIR) if f.endswith(".ipynb")
]
return tuple(nb for nb in notebooks if not notebook_is_executed(nb))


def get_tutorials_environment_name():
def get_tutorials_environment_name() -> str | None:
"""Return the name of the tutorials' environment."""
with open(os.path.join(TUTORIALS_DIR, "tutorials_environment.yml")) as f:
with (TUTORIALS_DIR / "tutorials_environment.yml").open() as f:
for line in f:
if line.startswith("name:"):
return line.split(":")[1].strip()
return None


def get_command_environment() -> str:
Expand All @@ -204,31 +213,34 @@ def get_command_environment() -> str:
raise RuntimeError(f"{command.capitalize()} is not installed.")
continue
try: # If the command cannot be found, try using the full path.
subprocess.run([command])
subprocess.run([command], check=True)
except FileNotFoundError:
command = subprocess.run(
["where", "mamba"], capture_output=True).stdout.decode().strip()
if command == "mamba":
command = subprocess.run( # noqa: PLW2901
["where", "mamba"], capture_output=True, check=True
).stdout.decode().strip()
# Check if the tutorials' environment exists.
env = get_tutorials_environment_name()
if env in subprocess.run([command, "env", "list"], capture_output=True
).stdout.decode():
if env in subprocess.run(
[command, "env", "list"], capture_output=True, check=True
).stdout.decode():
return command
else:
if command == commands[-1]:
raise RuntimeError(f"The {command} environment '{env}' does not exist.")
raise RuntimeError(f"The {command} environment '{env}' does not exist.")


def install_local_brim_version(command: str) -> None:
"""Install a local version of BRiM."""
env = get_tutorials_environment_name()
subprocess.run([command, "uninstall", "-n", env, "brim", "-y"])
subprocess.run([command, "run", "-n", env, "pip", "install", "-e", MAIN_DIR])
subprocess.run([command, "uninstall", "-n", env, "brim", "-y"], check=False)
subprocess.run(
[command, "run", "-n", env, "pip", "install", "-e", MAIN_DIR], check=True
)


def execute_notebook(nb: str, command: str = "conda") -> None:
"""Execute a notebook."""
subprocess.run([command, "run", "-n", get_tutorials_environment_name(),
"jupyter", "nbconvert", nb, "--execute", "--inplace"])
"jupyter", "nbconvert", nb, "--execute", "--inplace"], check=True)


if __name__ == "__main__":
Expand Down
Loading

0 comments on commit b07c45c

Please sign in to comment.