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

Major Refactor: Add Ruff Linting, Nox, and Pre-Commit Hook #266

Open
wants to merge 139 commits into
base: main
Choose a base branch
from

Conversation

hmd101
Copy link
Contributor

@hmd101 hmd101 commented Jul 6, 2024

Summary:

This PR refactors the project by adding Ruff Linting and formatting, integrated with Nox and an optional Pre-commit hook. It enhances both code quality and development efficiency.

Key Changes:

  1. Ruff Linting:
  2. Ruff Formatter:
    • Compatible with the popular Black formatter but more performant.
  3. Nox Automation:
    • Nox has been introduced to automate the execution of our CI tasks, including Ruff linting and other testing steps. Nox provides a flexible way to manage and run our tasks in isolated virtual environments, improving the reproducibility and ease of running multiple environments and workflows.
  4. CI Pipeline Update:
    • Our CI configuration has been updated to include Ruff linting and formatting checks. From now on, Ruff will automatically lint all new commits and pull requests to ensure they adhere to our code quality standards.
  5. Optional Pre-commit Hook:
    • A pre-commit hook for Ruff is now available, ensuring developers can run lint checks locally before committing code. This helps catch and resolve linting issues early in the development process.

Next Steps & Open Questions:

  • Jupyter Notebooks: Should we include Jupyter notebooks in the Ruff linting process? -> Yes!
  • Are all errors raised by the linter fixed?
  • Contributing file updated?

Resolves issue #51


Configuration Details:

1. Ruff Linting Configuration:

The current Ruff configuration includes the most commonly used linting rules. (Future PRs will likely incorporate more linting rules, as discussed here).

  • Pycodestyle (E): Enforces style consistency based on PEP8.
  • Pyflakes (F): Detects basic errors such as undefined variables or missing imports.
  • Pyupgrade (UP): Ensures usage of modern Python syntax, making our code more future-proof.
  • Flake8-simplify (SIM): Improves code readability by suggesting simplified expressions. We have chosen to ignore rule SIM105 since our team prefers explicit boolean comparisons in some cases.
  • Isort (I): Ensures imports are organized and ordered consistently, improving code readability and maintainability.

Note that a couple of linting rules were ignored with #noqa <ruff rule> (ignoring a single line) and #ruff: noqa <ruff rule> (ignoring entire file):

  • E501 for too long lines has been ignored several times to ensure for example that links will still work or flow charts remain readable

  • I001 sorting imports has been ignored once in src/plenoptic/__init__.py as the import order matters to avoid circular dependencies and tests to fail

  • F401 unused imports

    • # ruff: noqa: F401 is used in several __init__.py files to ignore unused imports. When unused imports are removed, tests fail.
  • SIM105 : Checks for try-except-pass blocks that can be replaced with the contextlib.suppress context manager.

    • currently ignored globally in pyproject.toml
  • F403 flags wildcard imports * , which is bad practice.

    To remove wildcard (*) imports, issue Improve API #246 should be considered. 2 recommended approaches are:

    1. Explicit Imports in the __init__.py File:
      • Manually specify the functions and classes to be imported.
      • Example: from .validate import remove_grad
      • This approach aligns with the flat hierarchy recommendation from the OpenSci review (see issue Improve API #246)
    2. Nested Structure with Subpackages/Modules:
      • Organize the code into subpackages and import modules explicitly.
      • Example: plenoptic.tools.validate
        • Use from . import validate within the subpackage.
      • In files like validate.py, define a list called __all__ = ["remove_grad", ...], which specifies what can be imported from the file.
        • This can live in either the module file itself or the __init__.py, and it controls the public API of that module.

2. Ruff Formatting

Currently, formatting is enforced to emulate Black in pyproject.toml:

indent-width = 4 # same as Black
[tool.ruff.format]
# Like Black, use double quotes for strings.
quote-style = "double"

# Like Black, indent with spaces, rather than tabs.
indent-style = "space"

# Like Black, respect magic trailing commas.
skip-magic-trailing-comma = false

# Like Black, automatically detect the appropriate line ending.
line-ending = "auto"

Nox & Pre-commit Hooks and their Configurations:

TL;DR:

  • Both tools are complementary and contribute to better maintainability by promoting consistent coding practices and making it easier to identify and fix issues early in the development process. Both are recommended by the Scientific Python Library Development Guide.
  • Nox: A task-runner automating testing across different environments after commits, ensuring code works well in varied setups specified in noxfile.py.
  • Pre-commit: Ensures code quality before commits by enforcing checks like formatting and linting. For this to work, pre-commit needs to be installed with pip install pre-commit . Pre-commit checks can be ignored, by adding --no-verify to your commit message.

2. Nox Configuration:

Nox is a Python-based task runner that is used to automate various development workflows, such as testing, building, and deploying applications. It allows you to define sessions, which are Python functions in the noxfile.py decorated with @nox.session with various parameters, such as name, by which these sessions can then be executed via the command line. This easily allows to test code by running pre-defined tasks with a single command across multiple Python versions without manually setting up environments.

Example: Based on the following noxfile.py , the command nox -s tests would run the session named "tests” below. nox without any commands would run all the sessions defined in the noxfile.py .

@nox.session(name="tests", python=["3.10", "3.11", "3.12"])
def tests(session):
    # run tests
    session.install("pytest")
    session.run("pytest")

Here are the current Nox sessions available for execution:

  • nox -s lint
  • nox -s tests
  • nox -s coverage

3. Pre-Commit Configurations:

Hook:
A pre-commit hook can be added to run Ruff linting on code before it is committed. This ensures that developers catch and fix linting issues early in the development process. It only runs when installed and can be bypassed with git commit -m <my commit message> --no-verify.

Considered Configurations: (in .pre-commit.yaml:
(Legend: ✅: included, ❓: recommended, but not included, possibly in future PR)

  • check-added-large-files
    • Purpose: Checks for large files added to the repository, typically to prevent accidental inclusion of large binaries or datasets.
  • check-case-conflict
    • Purpose: Detects potential filename conflicts due to case-insensitive filesystems (e.g., Windows) where File.txt and file.txt would be considered the same
  • check-merge-conflict
    • Purpose: Checks for leftover merge conflict markers (<<<<<<<, =======, >>>>>>>) in files.
  • check-symlinks
    • Purpose: Verifies that symbolic links are valid and do not point to non-existent targets.
  • check-yaml
    • Purpose: Validates YAML files for syntax errors.
  • debug-statements
    • Purpose: Detects debug statements (e.g., print, console.log) left in code.
  • end-of-file-fixer
    • Purpose: Ensures files have a newline at the end.
  • trailing-whitespace
    • Purpose: Removes trailing whitespace characters from files.

CI Pipeline:

Finally, the CI configuration ci.yml has been updated to include a step each for running Ruff formatting and linting check on all new commits and pull requests. Two separate actions were chosen to signal the developer which combination of the two might be causing an error.

Next steps:

0. update contributing file

1.Ruff Linting

Include the following linting rules (in pyproject.toml ) and resolve linting errors:

  • B (flake8-bugbear):
    Checks for common programming errors, style issues, or patterns that are error-prone
  • ARG (flake8-unused-arguments):
    Flags unused function arguments.
  • C4 (flake8-comprehensions):
    Checks for issues in list comprehensions and generator expressions.
  • EM (flake8-errmsg):
    Lints error message formatting.
  • ICN (flake8-import-conventions):
    Ensures imports are formatted according to best practices.
  • PTH (flake8-use-pathlib):
    Encourages the use of pathlib instead of os.path.
  • RET (flake8-return):
    Checks for issues related to return statements.
  • SIM (flake8-simplify):
    Suggests simplifications for code.
  • T20 (flake8-print):
    Flags print statements.
  • UP (pyupgrade):
    Upgrades syntax to modern Python standards.
  • YTT (flake8-2020):
    Enforces Python 3.8+ compatibility.

2. Nox:

We might want to consider to include nox sessions for:

  • format: Runs code formatting too
  • docs: Builds and verifies the project's documentation to ensure it's up to date and properly rendered.
  • type check: uses mypy or a similar tool to perform static type checking, to catch type-related errors early.

3. Pre-commit

We might want to add the following to .pre-commit-config.yaml :

  • mixed-line-ending
    • Purpose: Checks for files with mixed line endings (e.g., both LF and CRLF).
      • Pros: Prevents compatibility issues across platforms, ensures consistent line endings.
      • Cons: Can require adjustments for projects that intentionally use mixed line endings for specific reasons.
  • requirements-txt-fixer
    • Purpose: Standardizes the format of requirements.txt files, ensuring they follow conventions and are easy to manage.
    • Requires adjustments for projects with complex dependency requirements.

@hmd101 hmd101 self-assigned this Jul 6, 2024
@hmd101 hmd101 marked this pull request as ready for review July 6, 2024 17:45
@hmd101 hmd101 requested a review from billbrod as a code owner July 6, 2024 17:45
billbrod
billbrod previously approved these changes Jul 9, 2024
@hmd101 hmd101 marked this pull request as draft July 18, 2024 20:06
Copy link

codecov bot commented Jul 19, 2024

Codecov Report

Attention: Patch coverage is 87.11180% with 83 lines in your changes missing coverage. Please review.

Project coverage is 88.54%. Comparing base (b80af32) to head (5b76b6a).
Report is 11 commits behind head on main.

Files Patch % Lines
src/plenoptic/tools/external.py 4.76% 20 Missing ⚠️
src/plenoptic/tools/display.py 68.08% 15 Missing ⚠️
src/plenoptic/synthesize/metamer.py 93.12% 9 Missing ⚠️
src/plenoptic/synthesize/mad_competition.py 92.92% 8 Missing ⚠️
src/plenoptic/synthesize/simple_metamer.py 22.22% 7 Missing ⚠️
...e/canonical_computations/steerable_pyramid_freq.py 80.00% 5 Missing ⚠️
src/plenoptic/synthesize/synthesis.py 81.48% 5 Missing ⚠️
src/plenoptic/metric/perceptual_distance.py 80.95% 4 Missing ⚠️
src/plenoptic/synthesize/geodesic.py 92.85% 3 Missing ⚠️
...c/plenoptic/simulate/models/portilla_simoncelli.py 97.87% 2 Missing ⚠️
... and 4 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #266      +/-   ##
==========================================
- Coverage   88.60%   88.54%   -0.07%     
==========================================
  Files          38       38              
  Lines        3405     3386      -19     
==========================================
- Hits         3017     2998      -19     
  Misses        388      388              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@hmd101 hmd101 changed the title Code formatting and linting with ruff Major Refactor: Add Ruff Linting, Nox, and Pre-Commit Hook Oct 1, 2024
@hmd101 hmd101 marked this pull request as ready for review October 1, 2024 19:29
Copy link
Collaborator

@billbrod billbrod left a comment

Choose a reason for hiding this comment

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

Thanks for putting together this (huge) PR Hanna!

  • It would be good to run pre-commit as part of the CI too -- when I pulled down the branch and ran it, it made some additional changes (not related to ruff, but the other hooks, which I think we'd also want to run regularly). From poking around, it looks like it's impossible to run something like "pre-commit check", which would check whether the hooks would pass and fail, but not change anything. Therefore, I think the only real CI option is pre-commit.ci, which will also push back to the repo. It's not ideal (I'd like to have as much as possible explicit in the git repo, rather than involving stuff that has to be configured through separate websites), but still fine I think. Does that match your understanding as well?
  • The contributing document has gotten complex as part of this. I think it needs a run through to improve the flow, but that's not part of this PR
  • Can you add your notes about the wildcard imports and the two approaches to Improve API #246?
  • it looks like the right way to handle F401 in init files is with __all__, which we should do anyway. I added a note to Improve API #246.
  • Can you move the next steps of your PR note to a relevant issue? maybe Consistent formatting (Python Black) #51 ?
  • you have several comments of the form
    # ignore F401
    # ruff: noqa: F401
    or similar. is the ignore line doing anything there? I don't think it adds any info beyond the noqa line (which we do explain in contributing), so if it's not doing anything functional, it should be removed
  • There are several changes that I think were not made by ruff or pre-commit? I flagged them, asking "why this change". I'm not necessarily opposed to them, but want to double-check whether they were made directly by you or by ruff and, if by you, want to understand your thought process

.github/workflows/deploy.yml Show resolved Hide resolved
CONTRIBUTING.md Outdated Show resolved Hide resolved
CONTRIBUTING.md Outdated Show resolved Hide resolved
CONTRIBUTING.md Outdated Show resolved Hide resolved
CONTRIBUTING.md Outdated Show resolved Hide resolved
device = torch.device("cpu")
else:
device = std.device
device = torch.device("cpu") if isinstance(std, float) else std.device
Copy link
Collaborator

Choose a reason for hiding this comment

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

this is preferred?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This if-else block is flagged by SIM108 (from the flake8-simplify linter). Ternary operators are preferred as it is more concise.

@@ -339,10 +360,15 @@ def __init__(
):
super().__init__()
if pretrained:
assert kernel_size in [31, (31, 31)], "pretrained model has kernel_size (31, 31)"
assert kernel_size in [
Copy link
Collaborator

Choose a reason for hiding this comment

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

this is ugly. I should probably just rewrite it to use if so it formats nicer...

@@ -331,7 +366,7 @@ def _check_convergence(self, stop_criterion, stop_iters_to_check):
Have we been synthesizing for ``stop_iters_to_check`` iterations?
| |
no yes
| '---->Is ``abs(synth.loss[-1] - synth.losses[-stop_iters_to_check]) < stop_criterion``?
| '---->Is abs(synth.loss[-1] - synth.loss[-stop_iters_to_check]) < stop_crit?
Copy link
Collaborator

Choose a reason for hiding this comment

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

why this change?

@@ -274,7 +298,7 @@ def _check_convergence(self, stop_criterion, stop_iters_to_check):
Have we been synthesizing for ``stop_iters_to_check`` iterations?
| |
no yes
| '---->Is ``abs(synth.loss[-1] - synth.losses[-stop_iters_to_check]) < stop_criterion``?
| '----> Is the change in loss < stop_criterion over ``stop_iters_to_check``?
Copy link
Collaborator

Choose a reason for hiding this comment

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

why this change?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Those changes were made to honor the line-length limit. Alternatively, we can ignore too long lines here with #noqa: E501.

Comment on lines +894 to +897
| '---->Is the change in loss < stop_criterion over ``stop_iters_to_check``?
| no |
| | yes
|-------' '---->Have we synthesized all scales and done so for ``ctf_iters_to_check`` iterations?
|-------' '---->Are all scales synthesized for `ctf_iters_to_check` iterations?
Copy link
Collaborator

Choose a reason for hiding this comment

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

why this change?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Those changes were made to honor the line-length limit. Alternatively, we can ignore too long lines here with #noqa: E501.

hmd101 and others added 20 commits October 14, 2024 10:54
Co-authored-by: William F. Broderick <[email protected]>
Co-authored-by: William F. Broderick <[email protected]>
Co-authored-by: William F. Broderick <[email protected]>
Co-authored-by: William F. Broderick <[email protected]>
Co-authored-by: William F. Broderick <[email protected]>
Co-authored-by: William F. Broderick <[email protected]>
Co-authored-by: William F. Broderick <[email protected]>
Co-authored-by: William F. Broderick <[email protected]>
Co-authored-by: William F. Broderick <[email protected]>
…eplacing the None, None expression, for better readability
Co-authored-by: William F. Broderick <[email protected]>
Co-authored-by: William F. Broderick <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants