Skip to content

Commit 6d535ab

Browse files
authored
pre-commit: Add codespell and ruff (TheAlgorithms#189)
1 parent 717ed4d commit 6d535ab

23 files changed

+191
-117
lines changed

.flake8

-2
This file was deleted.

.pre-commit-config.yaml

+11-9
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,19 @@ repos:
77
types: [python]
88
- id: trailing-whitespace
99

10-
- repo: https://github.com/psf/black
11-
rev: 23.1.0
10+
- repo: https://github.com/codespell-project/codespell
11+
rev: v2.2.5
1212
hooks:
13-
- id: black
13+
- id: codespell
14+
additional_dependencies:
15+
- tomli
1416

15-
- repo: https://github.com/PyCQA/isort
16-
rev: 5.12.0
17+
- repo: https://github.com/psf/black
18+
rev: 23.7.0
1719
hooks:
18-
- id: isort
20+
- id: black
1921

20-
- repo: https://github.com/PyCQA/flake8
21-
rev: 6.0.0
22+
- repo: https://github.com/astral-sh/ruff-pre-commit
23+
rev: v0.0.285
2224
hooks:
23-
- id: flake8
25+
- id: ruff

algorithms_keeper/api.py

+11-9
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,14 @@ def _get_private_key() -> str:
3030
if private_key is None:
3131
private_key_path = os.getenv("GITHUB_PRIVATE_KEY_PATH")
3232
if private_key_path is None:
33-
raise RuntimeError(
33+
msg = (
3434
"Provide the private key using the GITHUB_PRIVATE_KEY environment "
35-
+ "variable or in a file using the GITHUB_PRIVATE_KEY_PATH "
36-
+ "environment variable. The path should either be absolute or "
37-
+ "relative to the repository root."
35+
"variable or in a file using the GITHUB_PRIVATE_KEY_PATH environment "
36+
"variable. The path should either be absolute or relative to the "
37+
"repository root."
3838
)
39-
with open(private_key_path, "r") as f:
39+
raise RuntimeError(msg)
40+
with open(private_key_path) as f: # noqa: PTH123
4041
private_key = f.read()
4142
return private_key
4243

@@ -93,10 +94,11 @@ def log(response: ClientResponse, body: bytes) -> None: # pragma: no cover
9394
if response.status in STATUS_OK:
9495
loggerlevel = logger.info
9596
# Comments and reviews are too long to be logged for INFO level
96-
if response.url.name in ("comments", "reviews"):
97-
data = response.url.name.upper()
98-
else:
99-
data = body.decode(UTF_8_CHARSET)
97+
data = (
98+
response.url.name.upper()
99+
if response.url.name in ("comments", "reviews")
100+
else body.decode(UTF_8_CHARSET)
101+
)
100102
else:
101103
loggerlevel = logger.error
102104
data = body.decode(UTF_8_CHARSET)

algorithms_keeper/parser/files_parser.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def validate_extension(self) -> str:
5353
filepath = file.path
5454
if not filepath.suffix:
5555
if ".github" not in filepath.parts:
56-
if filepath.parent.name:
56+
if filepath.parent.name: # noqa: SIM114
5757
invalid_filepath.append(file.name)
5858
# Dotfiles are not considered as invalid if they are present in the
5959
# root of the repository

algorithms_keeper/parser/python_parser.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,8 @@ class PythonParser(BaseFilesParser):
7474
".txt",
7575
# Good old Python file
7676
".py",
77-
) + DOCS_EXTENSIONS
77+
*DOCS_EXTENSIONS,
78+
)
7879

7980
def __init__(
8081
self,

algorithms_keeper/parser/record.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -83,12 +83,12 @@ def add_error(
8383
# It seems that ``ParserSyntaxError`` is not a subclass of ``SyntaxError``,
8484
# the same information is stored under a different attribute. There is no
8585
# filename information in ``ParserSyntaxError``, thus the parameter `filepath`.
86-
if isinstance(exc, SyntaxError): # pragma: no cover
86+
if isinstance(exc, SyntaxError): # noqa: SIM108, pragma: no cover
8787
lineno = exc.lineno or 1
8888
else:
8989
lineno = exc.raw_line
9090
body = (
91-
f"An error occured while parsing the file: `{filepath}`\n"
91+
f"An error occurred while parsing the file: `{filepath}`\n"
9292
f"```python\n{message}\n```"
9393
)
9494
self._comments.append(ReviewComment(body, filepath, lineno))

algorithms_keeper/parser/rules/naming_convention.py

+8-9
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@
1010

1111
INVALID_CAMEL_CASE_NAME_COMMENT: str = (
1212
"Class names should follow the [`CamelCase`]"
13-
+ "(https://en.wikipedia.org/wiki/Camel_case) naming convention. "
14-
+ "Please update the following name accordingly: `{nodename}`"
13+
"(https://en.wikipedia.org/wiki/Camel_case) naming convention. "
14+
"Please update the following name accordingly: `{nodename}`"
1515
)
1616

1717
INVALID_SNAKE_CASE_NAME_COMMENT: str = (
1818
"Variable and function names should follow the [`snake_case`]"
19-
+ "(https://en.wikipedia.org/wiki/Snake_case) naming convention. "
20-
+ "Please update the following name accordingly: `{nodename}`"
19+
"(https://en.wikipedia.org/wiki/Snake_case) naming convention. "
20+
"Please update the following name accordingly: `{nodename}`"
2121
)
2222

2323

@@ -35,9 +35,8 @@ def valid(self, name: str) -> bool:
3535
name = name.strip("_")
3636
if name[0].islower() or "_" in name:
3737
return False
38-
else:
39-
if name.lower() != name and name.upper() != name:
40-
return False
38+
elif name.lower() != name and name.upper() != name:
39+
return False
4140
return True
4241

4342

@@ -159,7 +158,7 @@ def visit_ClassDef(self, node: cst.ClassDef) -> None:
159158
def visit_Attribute(self, node: cst.Attribute) -> None:
160159
# The attribute node can come through other context as well but we only care
161160
# about the ones coming from assignments.
162-
if self._assigntarget_counter > 0:
161+
if self._assigntarget_counter > 0: # noqa: SIM102
163162
# We only care about assignment attribute to *self*.
164163
if m.matches(node, m.Attribute(value=m.Name(value="self"))):
165164
self._validate_nodename(
@@ -169,7 +168,7 @@ def visit_Attribute(self, node: cst.Attribute) -> None:
169168
def visit_Element(self, node: cst.Element) -> None:
170169
# We only care about elements in *List* or *Tuple* specifically coming from
171170
# inside the multiple assignments.
172-
if self._assigntarget_counter > 0:
171+
if self._assigntarget_counter > 0: # noqa: SIM102
173172
if m.matches(node, m.Element(value=m.Name())):
174173
nodename = cst.ensure_type(node.value, cst.Name).value
175174
self._validate_nodename(node, nodename, NamingConvention.SNAKE_CASE)

algorithms_keeper/parser/rules/require_doctest.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
MISSING_DOCTEST: str = (
1010
"As there is no test file in this pull request nor any test function or class in "
11-
+ "the file `{filepath}`, please provide doctest for the function `{nodename}`"
11+
"the file `{filepath}`, please provide doctest for the function `{nodename}`"
1212
)
1313

1414
INIT: str = "__init__"
@@ -184,7 +184,7 @@ def spam(self):
184184
pass
185185
"""
186186
),
187-
# Check that `_skip_doctest` attribute is reseted after leaving the class.
187+
# Check that `_skip_doctest` attribute is reset after leaving the class.
188188
Invalid(
189189
"""
190190
def bar():
@@ -221,7 +221,7 @@ def visit_ClassDef(self, node: cst.ClassDef) -> None:
221221
# Temporary storage of the ``skip_doctest`` value only during the class visit.
222222
# If the class-level docstring contains doctest, then the checks should only be
223223
# skipped for all its methods and not for other functions/class in the module.
224-
# After leaving the class, ``skip_doctest`` should be resetted to whatever the
224+
# After leaving the class, ``skip_doctest`` should be reset to whatever the
225225
# value was before.
226226
self._temporary = self._skip_doctest
227227
self._skip_doctest = self._has_doctest(node)

algorithms_keeper/parser/rules/require_type_hint.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77

88
MISSING_RETURN_TYPE_HINT: str = (
99
"Please provide return type hint for the function: `{nodename}`. "
10-
+ "**If the function does not return a value, please provide "
11-
+ "the type hint as:** `def function() -> None:`"
10+
"**If the function does not return a value, please provide "
11+
"the type hint as:** `def function() -> None:`"
1212
)
1313

1414
IGNORE_PARAM: set[str] = {"self", "cls"}

algorithms_keeper/parser/rules/use_fstring.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@
88
class UseFstringRule(CstLintRule):
99
MESSAGE: str = (
1010
"As mentioned in the [Contributing Guidelines]"
11-
+ "(https://github.com/TheAlgorithms/Python/blob/master/CONTRIBUTING.md), "
12-
+ "please do not use printf style formatting or `str.format()`. "
13-
+ "Use [f-string](https://realpython.com/python-f-strings/) instead to be "
14-
+ "more readable and efficient."
11+
"(https://github.com/TheAlgorithms/Python/blob/master/CONTRIBUTING.md), "
12+
"please do not use printf style formatting or `str.format()`. "
13+
"Use [f-string](https://realpython.com/python-f-strings/) instead to be "
14+
"more readable and efficient."
1515
)
1616

1717
VALID = [

algorithms_keeper/utils.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ async def get_user_open_pr_numbers(
142142
)
143143
pr_numbers = []
144144
async for pull in gh.getiter(search_url, oauth_token=await gh.access_token):
145-
pr_numbers.append(pull["number"])
145+
pr_numbers.append(pull["number"]) # noqa: PERF401
146146
return pr_numbers
147147

148148

@@ -211,7 +211,7 @@ async def get_pr_files(gh: GitHubAPI, *, pull_request: Mapping[str, Any]) -> lis
211211
async for data in gh.getiter(
212212
pull_request["url"] + "/files", oauth_token=await gh.access_token
213213
):
214-
files.append(
214+
files.append( # noqa: PERF401
215215
File(
216216
data["filename"],
217217
Path(data["filename"]),

pyproject.toml

+75-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
[tool.isort]
2-
profile = "black"
3-
41
[tool.mypy]
52
ignore_missing_imports = true
63
warn_unused_configs = true
@@ -19,6 +16,81 @@ module = "tests.data.*"
1916
disallow_untyped_defs = false
2017
check_untyped_defs = false
2118

19+
[tool.ruff]
20+
select = [
21+
"A", # flake8-builtins
22+
"AIR", # Airflow
23+
"ASYNC", # flake8-async
24+
"B", # flake8-bugbear
25+
"BLE", # flake8-blind-except
26+
"C4", # flake8-comprehensions
27+
"C90", # McCabe cyclomatic complexity
28+
"CPY", # flake8-copyright
29+
"DJ", # flake8-django
30+
"DTZ", # flake8-datetimez
31+
"E", # pycodestyle
32+
"EM", # flake8-errmsg
33+
"EXE", # flake8-executable
34+
"F", # Pyflakes
35+
"FLY", # flynt
36+
"G", # flake8-logging-format
37+
"I", # isort
38+
"ICN", # flake8-import-conventions
39+
"INP", # flake8-no-pep420
40+
"INT", # flake8-gettext
41+
"ISC", # flake8-implicit-str-concat
42+
"N", # pep8-naming
43+
"NPY", # NumPy-specific rules
44+
"PD", # pandas-vet
45+
"PERF", # Perflint
46+
"PGH", # pygrep-hooks
47+
"PIE", # flake8-pie
48+
"PL", # Pylint
49+
"PT", # flake8-pytest-style
50+
"PTH", # flake8-use-pathlib
51+
"PYI", # flake8-pyi
52+
"RSE", # flake8-raise
53+
"RUF", # Ruff-specific rules
54+
"S", # flake8-bandit
55+
"SIM", # flake8-simplify
56+
"SLF", # flake8-self
57+
"SLOT", # flake8-slots
58+
"T10", # flake8-debugger
59+
"TCH", # flake8-type-checking
60+
"TID", # flake8-tidy-imports
61+
"UP", # pyupgrade
62+
"W", # pycodestyle
63+
"YTT", # flake8-2020
64+
# "ANN", # flake8-annotations
65+
# "ARG", # flake8-unused-arguments
66+
# "COM", # flake8-commas
67+
# "D", # pydocstyle
68+
# "ERA", # eradicate
69+
# "FA", # flake8-future-annotations
70+
# "FBT", # flake8-boolean-trap
71+
# "FIX", # flake8-fixme
72+
# "Q", # flake8-quotes
73+
# "RET", # flake8-return
74+
# "T20", # flake8-print
75+
# "TD", # flake8-todos
76+
# "TRY", s# tryceratops
77+
]
78+
ignore = ["N802", "PGH003", "RUF012", "SLF001"]
79+
line-length = 88
80+
target-version = "py38"
81+
82+
[tool.ruff.mccabe]
83+
max-complexity = 13
84+
85+
[tool.ruff.per-file-ignores]
86+
"tests/*" = ["PT006", "PT007", "S101"]
87+
"tests/test_commands.py" = ["B008"]
88+
"tests/test_pull_requests.py" = ["B008"]
89+
90+
[tool.ruff.pylint]
91+
allow-magic-value-types = ["bytes", "int", "str"]
92+
max-args = 6 # Recommended: 5
93+
2294
[tool.pytest.ini_options]
2395
testpaths = "tests"
2496
addopts = """\

tests/data/descriptive_name.py

-2
Original file line numberDiff line numberDiff line change
@@ -66,5 +66,3 @@ def c(self, a: int = 10) -> None:
6666

6767
class C:
6868
"""A class which requires descriptive names"""
69-
70-
pass

tests/test_api.py

+6-5
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import pytest
55
import pytest_asyncio
66
from gidgethub import apps, sansio
7-
from pytest import MonkeyPatch
87

98
from algorithms_keeper.api import GitHubAPI, token_cache
109

@@ -23,7 +22,7 @@ async def github_api() -> AsyncGenerator[GitHubAPI, None]:
2322
assert session.closed is True
2423

2524

26-
@pytest.mark.asyncio
25+
@pytest.mark.asyncio()
2726
async def test_initialization() -> None:
2827
async with aiohttp.ClientSession() as session:
2928
github_api = GitHubAPI(number, session, "algorithms-keeper")
@@ -35,8 +34,10 @@ async def test_initialization() -> None:
3534
assert github_api.requester == "algorithms-keeper"
3635

3736

38-
@pytest.mark.asyncio
39-
async def test_access_token(github_api: GitHubAPI, monkeypatch: MonkeyPatch) -> None:
37+
@pytest.mark.asyncio()
38+
async def test_access_token(
39+
github_api: GitHubAPI, monkeypatch: pytest.MonkeyPatch
40+
) -> None:
4041
monkeypatch.setattr(apps, "get_installation_access_token", mock_return)
4142
monkeypatch.setenv("GITHUB_APP_ID", "")
4243
monkeypatch.setenv("GITHUB_PRIVATE_KEY", "")
@@ -49,7 +50,7 @@ async def test_access_token(github_api: GitHubAPI, monkeypatch: MonkeyPatch) ->
4950
assert cached_token == token
5051

5152

52-
@pytest.mark.asyncio
53+
@pytest.mark.asyncio()
5354
async def test_headers_and_log(github_api: GitHubAPI) -> None:
5455
request_headers = sansio.create_headers("algorithms-keeper")
5556
resp = await github_api._request(

tests/test_check_runs.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
# Reminder: ``Event.delivery_id`` is used as a short description for the respective
2222
# test case and as a way to id the specific test case in the parametrized group.
23-
@pytest.mark.asyncio
23+
@pytest.mark.asyncio()
2424
@pytest.mark.parametrize(
2525
"event, gh, expected",
2626
(
@@ -187,7 +187,7 @@
187187
post_data=[{"labels": [Label.FAILED_TEST]}],
188188
),
189189
),
190-
# Check run complated and it's a failure, there is ``FAILED_TEST`` label on
190+
# Check run completed and it's a failure, there is ``FAILED_TEST`` label on
191191
# the pull request, so don't do anything.
192192
(
193193
Event(

0 commit comments

Comments
 (0)