-
-
Notifications
You must be signed in to change notification settings - Fork 5k
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
MAINT: add (optional) pre-commit hook #17812
Merged
Merged
Changes from 12 commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
76ef708
Add (optional) pre-commit hook
stefanv 0b97f7d
Use `tools/lint.py` in `dev.py`
stefanv 46683cb
Use flake8-force to also lint Cython files
stefanv aa1b0b4
Unpin flake8
stefanv fe2aceb
Switch from flake8 to ruff and cython-lint
stefanv 0941cd1
Do not try to lint deleted files
stefanv c784c7a
Fix dependency versions in Azure pipelines
stefanv 1dbff90
conda: install ruff from pypa
stefanv 399f5ab
Prepare for cython-lint supporting pxi, pxd
stefanv 2142275
Recommend dev.py linting instead of directly via tools/lint.py
stefanv 23eaa07
dev.py: rename linting task
stefanv d542b93
Use cp instead of ln, since not available on Windows
stefanv 48f460b
Use standard linter in runtests
stefanv 42d8864
Fix typo in lint.toml
stefanv 374f24f
Use ruff config
stefanv 213291d
Remove flake8 from Azure Pipelines
stefanv 89c3ff7
Add UP rules; ignore long lines
stefanv 2085bec
Make linter happy on dev.py
stefanv 4637f2d
Update ruff config so we get a clean report
stefanv File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
#!/usr/bin/env python | ||
import os | ||
import sys | ||
import subprocess | ||
from argparse import ArgumentParser | ||
|
||
|
||
CONFIG = os.path.join( | ||
os.path.abspath(os.path.dirname(__file__)), | ||
'lint.ini', | ||
) | ||
|
||
|
||
def rev_list(branch, num_commits): | ||
"""List commits in reverse chronological order. | ||
|
||
Only the first `num_commits` are shown. | ||
|
||
""" | ||
res = subprocess.run( | ||
[ | ||
'git', | ||
'rev-list', | ||
'--max-count', | ||
f'{num_commits}', | ||
'--first-parent', | ||
branch | ||
], | ||
stdout=subprocess.PIPE, | ||
encoding='utf-8', | ||
) | ||
res.check_returncode() | ||
return res.stdout.rstrip('\n').split('\n') | ||
|
||
|
||
def find_branch_point(branch): | ||
"""Find when the current branch split off from the given branch. | ||
|
||
It is based off of this Stackoverflow post: | ||
|
||
https://stackoverflow.com/questions/1527234/finding-a-branch-point-with-git#4991675 | ||
|
||
""" | ||
branch_commits = rev_list('HEAD', 1000) | ||
main_commits = set(rev_list(branch, 1000)) | ||
for branch_commit in branch_commits: | ||
if branch_commit in main_commits: | ||
return branch_commit | ||
|
||
# If a branch split off over 1000 commits ago we will fail to find | ||
# the ancestor. | ||
raise RuntimeError( | ||
'Failed to find a common ancestor in the last 1000 commits' | ||
) | ||
|
||
|
||
def diff_files(sha): | ||
"""Find the diff since the given SHA.""" | ||
res = subprocess.run( | ||
['git', 'diff', '--name-only', '--diff-filter=ACMR', '-z', sha, '--', | ||
'*.py', '*.pyx', '*.pxd', '*.pxi'], | ||
stdout=subprocess.PIPE, | ||
encoding='utf-8' | ||
) | ||
res.check_returncode() | ||
return [f for f in res.stdout.split('\0') if f] | ||
|
||
|
||
def run_ruff(files, fix): | ||
if not files: | ||
return 0, "" | ||
args = ['--fix'] if fix else [] | ||
res = subprocess.run( | ||
['ruff'] + args + files, | ||
stdout=subprocess.PIPE, | ||
encoding='utf-8' | ||
) | ||
return res.returncode, res.stdout | ||
|
||
|
||
def run_cython_lint(files): | ||
if not files: | ||
return 0, "" | ||
res = subprocess.run( | ||
['cython-lint'] + files, | ||
stdout=subprocess.PIPE, | ||
encoding='utf-8' | ||
) | ||
return res.returncode, res.stdout | ||
|
||
|
||
def main(): | ||
parser = ArgumentParser(description="Also see `pre-commit-hook.sh` which " | ||
"lints all files staged in git.") | ||
# In Python 3.9, can use: argparse.BooleanOptionalAction | ||
parser.add_argument("--fix", action='store_true', | ||
help='Attempt to fix linting violations') | ||
parser.add_argument("--diff-against", dest='branch', | ||
type=str, default=None, | ||
help="Diff against " | ||
"this branch and lint modified files. Use either " | ||
"`--diff-against` or `--files`, but not both.") | ||
parser.add_argument("--files", nargs='*', | ||
help="Lint these files or directories; " | ||
"use **/*.py to lint all files") | ||
|
||
args = parser.parse_args() | ||
|
||
if not ((args.files is None) ^ (args.branch is None)): | ||
print('Specify either `--diff-against` or `--files`. Aborting.') | ||
sys.exit(1) | ||
|
||
if args.branch: | ||
branch_point = find_branch_point(args.branch) | ||
files = diff_files(branch_point) | ||
else: | ||
files = args.files | ||
|
||
cython_exts = ['.pyx'] # Add '.pxd', '.pxi' once cython-lint supports it | ||
cython_files = [f for f in files if any(f.endswith(ext) for ext in cython_exts)] | ||
other_files = [f for f in files if not f.endswith('.pyx')] | ||
|
||
rc_cy, errors = run_cython_lint(cython_files) | ||
if errors: | ||
print(errors) | ||
|
||
rc, errors = run_ruff(other_files, fix=args.fix) | ||
if errors: | ||
print(errors) | ||
|
||
if rc == 0 and rc_cy != 0: | ||
rc = rc_cy | ||
|
||
sys.exit(rc) | ||
|
||
|
||
if __name__ == '__main__': | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
[tool.ruff] | ||
line-length = 88 | ||
|
||
# Enable Pyflakes `E` and `F` codes by default. | ||
select = ["E", "F"] | ||
ignore = [ | ||
"E121", "E122", "E123", "E125", "E126", "E127", "E128", "E226", | ||
"E251", "E265", "E266", "E302", "E402", "E712", "E721", "E731", | ||
"E741", "W291", "W293", "W391", "W503", "W504" | ||
] | ||
excludes = [ | ||
'scipy/datasets/_registry.py' | ||
] | ||
per-file-ignores = {'**/__init.py', ['F401', 'F403']} | ||
|
||
# Allow unused variables when underscore-prefixed. | ||
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" | ||
|
||
# Assume Python 3.10. | ||
target-version = "py38" | ||
|
||
[tool.ruff.mccabe] | ||
# Unlike Flake8, default to a complexity level of 10. | ||
max-complexity = 10 |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we update the
target-version
to match the comment/newer Python above?One more question about this file--where is
lint.toml
used? Is it just that you can useruff
with any.toml
file at appropriate location?A quick browse of their GitHub page used other filenames so wasn't sure:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, Tyler.
lint.py
explicitly sets--config=lint.toml
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Recommended entrypoints for the linter are:
python dev.py lint
ortools/lint.py
.I think if we wanted it to pick up
ruff.toml
by default, we'd have to move it to the root of the repo, or include its config in ourpyproject.toml
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I didn't catch that in the diff--just to confirm, this is separate from the usage of
lint.ini
intools/lint.py
? Or does it remap to to thetoml
file from that?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Scratch all my comments:
ruff
expectsruff.toml
(even if--config
is used), andlint.py
didn't specify it correctly.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately we do need the file to either be named
ruff.toml
orpyproject.toml
. Otherwise, we have no way of knowing which schema to enforce. (The schemas are the ~same betweenruff.toml
andpyproject.toml
, except that all the Ruff settings are nested under[tool.ruff]
.)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @charliermarsh. It sounds like you may need two config options then,
--config
and--config-pyproject
or similar. I think it's quite confusing not to honor the--config
option just because the file is named incorrectly.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As an alternative, if the file isn't named
pyproject.toml
, I could just treat it as inruff.toml
format. That would at least be an improvement, I think, though would still have the potential to cause confusion as you describe.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@stefanv - This is changed in the next release (anything not named
pyproject.toml
is assumed to be inruff.toml
format).