Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite it in Rust: Ruff as alternative Linter #28

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ repos:
- id: end-of-file-fixer
exclude: '\.ipynb$'
- id: check-toml
exclude: '{{cookiecutter.repo_name}}/pyproject.toml'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess you're excluding this, as we now use some templating in the file, correct?

To still make sure that the generated files pass our own quality standards, we should extend the tests with one variation that uses ruff.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From my understanding, the check-toml pre-commit hook tries to parse the toml within the project directory as-is, which leads to a flag because it still contains template strings. This is not necessarily related to ruff/pylint, but to the way the check itself is executed. Let's discuss how to solve this :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Following up our discussion, I have implemented that the tests now execute the pre-commit hooks. On top of that, I've added the option to specify different configurations to run the tests in. With these changes in place, I'd suggest to:

  1. Exclude everything on "{{cookiecutter.repo_name}}" from the top-level pre-commit configuration, as these are now validated in the tests (after cookiecutter initialized the repo)
  2. Add some configuration with Ruff to our permutations.

- id: check-xml
- id: check-case-conflict
- id: detect-aws-credentials
Expand Down
1 change: 1 addition & 0 deletions cookiecutter.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@
"repo_url": "URL to repository",
"env_name": "{{ cookiecutter.project_name.lower().replace(' ', '-') + '-env' }}",
"install_jupyter": ["yes", "no"],
"linter_name": ["pylint", "ruff"],
"cicd_configuration": ["none", "gitlab"]
}
5 changes: 3 additions & 2 deletions environment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ channels:
- conda-forge
dependencies:
- black=23.1.0
- python=3.10.9
- cookiecutter=2.1.1
- pre-commit=3.0.4
- pylint=2.16.1
- pytest=7.2.1
- cookiecutter=2.1.1
- pytest=7.2.1
- python=3.10.9
- ruff=0.1.4
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that we need ruff in the top-level environment, as we're only using one linter for the template repository itself. Let's stick with Pylint for now, so I guess you can revert all changes to that file.

11 changes: 11 additions & 0 deletions hooks/post_gen_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import subprocess
from abc import ABC, abstractmethod

import yaml


# as cookiecutter is currently (v2.1.1) unable to support local imports in hooks
# the environment management code has to be included here
Expand Down Expand Up @@ -105,6 +107,14 @@ def copy_chosen_files(self) -> None:
else:
shutil.copy2(src, dst)

def remove_unused_linter_files(self):
with open(f"{self.temp_files_dir}/.manifest.yaml", "r", encoding="utf-8") as f:
manifest = yaml.safe_load(f)
for feature in manifest["features"]:
if not feature["enabled"]:
for resource in feature["resources"]:
os.remove(resource)


def get_ci_cd_file_manager(ci_cd_options: str) -> ConditionalFileManager:
template_root_dir = pathlib.Path.cwd()
Expand Down Expand Up @@ -147,6 +157,7 @@ def get_ci_cd_file_manager(ci_cd_options: str) -> ConditionalFileManager:
print("copying")
CICD_FILE_MANAGER.copy_chosen_files()
print("cleaning")
CICD_FILE_MANAGER.remove_unused_linter_files()
CICD_FILE_MANAGER.clean_temp_dir()
print("after")

Expand Down
9 changes: 9 additions & 0 deletions {{cookiecutter.repo_name}}/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,15 @@ venv.bak/
.idea
.vscode

# System files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db

# Data
data/*
!data/raw/
15 changes: 15 additions & 0 deletions {{cookiecutter.repo_name}}/.pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ repos:
additional_dependencies: ['@commitlint/config-conventional']
stages:
- commit-msg
{%- if cookiecutter.linter_name == "pylint" %}
- repo: https://github.com/pycqa/isort
rev: 5.12.0
hooks:
Expand All @@ -39,6 +40,12 @@ repos:
stages:
- commit
- manual
{%- elif cookiecutter.linter_name == "ruff" %}
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.1.4
hooks:
- id: ruff
{%- endif %}
- repo: local
hooks:
- id: black
Expand All @@ -52,6 +59,7 @@ repos:
entry: nbqa black
language: system
files: \.ipynb
{%- if cookiecutter.linter_name == "pylint" %}
- id: pylint
name: pylint
entry: pylint
Expand All @@ -65,6 +73,13 @@ repos:
args:
- --disable=pointless-statement,duplicate-code,expression-not-assigned
- --const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__)|([a-z_][a-z0-9_]{0,50}))$
{%- elif cookiecutter.linter_name == "ruff" %}
- id: nbqa-ruff
name: nbqa-ruff
entry: nbqa ruff
language: system
files: \.ipynb
{%- endif %}
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v2.7.1
hooks:
Expand Down
9 changes: 9 additions & 0 deletions {{cookiecutter.repo_name}}/.temp_ci_cd/.manifest.yaml
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really understand what this is good for. Can you explain? I guess it's somehow using one file or the other, but I don't understand how 🙈

Copy link
Author

@DavidSchischke DavidSchischke Nov 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is related to a change in post_gen_project:

def remove_unused_linter_files(self):
with open(f"{self.temp_files_dir}/.manifest.yaml", "r", encoding="utf-8") as f:
manifest = yaml.safe_load(f)
for feature in manifest["features"]:
if not feature["enabled"]:
for resource in feature["resources"]:
os.remove(resource)

Basically, after the project is created, the type of linter is determined from manifest.yaml and the config for the other linter is deleted prior to committing. The procedure is taken from this cookiecutter issue.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the main confusion here is the name of the folder .temp_ci_cd. I'd instead suggest something like .conditional_files and adjust the post generation script accordingly.

Instead of introducing yet another custom configuration file, we should move both configs (Pylint and Ruff) into that folder and adapt our ConditionalFileManager to only use the appropriate file. While we're at it, we might as well add the option to have no linter at all :)

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
features:
- name: pylint
enabled: {{cookiecutter.linter_name == "pylint"}}
resources:
- .pylintrc
- name: ruff
enabled: {{cookiecutter.linter_name == "ruff"}}
resources:
- ruff.toml
10 changes: 7 additions & 3 deletions {{cookiecutter.repo_name}}/environment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@ channels:
- conda-forge
dependencies:
- black=23.1.0
- nbqa=1.6.1
- python=3.10.9
- pre-commit=3.0.4
{%- if cookiecutter.install_jupyter == 'yes' %}
- jupyter=1.0.0
- nbqa=1.6.1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's a good idea to move "nbqa" into that conditional. However, if we do this, we also need to add a condition to the pre-commit config to avoid that the nbqa-tasks are executed when the dependency is not installed.

{%- endif %}
- python=3.10.9
- pre-commit=3.0.4
{%- if cookiecutter.linter_name == "pylint" %}
- pylint=2.16.1
{%- elif cookiecutter.linter_name == "ruff" %}
- ruff=0.1.4
{%- endif %}
4 changes: 3 additions & 1 deletion {{cookiecutter.repo_name}}/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ exclude = '''
)
'''

{% if cookiecutter.linter_name == "pylint" %}
[tool.isort]
profile='black'
profile = 'black'
line_length = 100
{% endif %}
33 changes: 33 additions & 0 deletions {{cookiecutter.repo_name}}/ruff.toml
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed, let's do some testing with some existing projects to see if these settings are reasonable.

Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# sync with black
line-length = 100

# No need for NBQA as ruff has native Jupyter support
extend-include = ["*.ipynb"]

# https://docs.astral.sh/ruff/rules/
ignore = [
# "E501", # Line too long, handled by black
# "W291", # Trailing whitespace, handled by black
# "W292", # Missing final newline, handled by black
# "PLR0904", # Too many public methods
# "PLR0911", # Too many return statements
# "PLR0913", # Too many arguments
# "PLC0415", # Import outside toplevel
# NA as checks yet but mentioned in github comments
# "C0305", # Trailing newlines, handled by black
# "C0114", # Missing module docstring
# "C0115", # Missing class docstring
# "C0116", # Missing function docstring
# "R0902", # Too many instance attributes
# "R0903", # Too few public methods
# "R0914", # Too many locals
# "W0124", # Confusing with statement
# "C0413", # Wrong import position
# "C0410", # Multiple imports
# "R1705", # No else return
# "W0201", # Attribute defined outside init
# "E1123", # Unexpected keyword arg
# "C0401", # Wrong spelling in comment
# "C0402", # Wrong spelling in docstring
# "C0403" # Invalid character in docstring
]
Loading