Skip to content

pre-commit: Add codespell and ruff #189

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

Merged
merged 3 commits into from
Aug 22, 2023
Merged
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
2 changes: 0 additions & 2 deletions .flake8

This file was deleted.

20 changes: 11 additions & 9 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,19 @@ repos:
types: [python]
- id: trailing-whitespace

- repo: https://github.com/psf/black
rev: 23.1.0
- repo: https://github.com/codespell-project/codespell
rev: v2.2.5
hooks:
- id: black
- id: codespell
additional_dependencies:
- tomli

- repo: https://github.com/PyCQA/isort
rev: 5.12.0
- repo: https://github.com/psf/black
rev: 23.7.0
hooks:
- id: isort
- id: black

- repo: https://github.com/PyCQA/flake8
rev: 6.0.0
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.0.285
hooks:
- id: flake8
- id: ruff
20 changes: 11 additions & 9 deletions algorithms_keeper/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,14 @@ def _get_private_key() -> str:
if private_key is None:
private_key_path = os.getenv("GITHUB_PRIVATE_KEY_PATH")
if private_key_path is None:
raise RuntimeError(
msg = (
"Provide the private key using the GITHUB_PRIVATE_KEY environment "
+ "variable or in a file using the GITHUB_PRIVATE_KEY_PATH "
+ "environment variable. The path should either be absolute or "
+ "relative to the repository root."
"variable or in a file using the GITHUB_PRIVATE_KEY_PATH environment "
"variable. The path should either be absolute or relative to the "
"repository root."
)
with open(private_key_path, "r") as f:
raise RuntimeError(msg)
with open(private_key_path) as f: # noqa: PTH123
private_key = f.read()
return private_key

Expand Down Expand Up @@ -93,10 +94,11 @@ def log(response: ClientResponse, body: bytes) -> None: # pragma: no cover
if response.status in STATUS_OK:
loggerlevel = logger.info
# Comments and reviews are too long to be logged for INFO level
if response.url.name in ("comments", "reviews"):
data = response.url.name.upper()
else:
data = body.decode(UTF_8_CHARSET)
data = (
response.url.name.upper()
if response.url.name in ("comments", "reviews")
else body.decode(UTF_8_CHARSET)
)
else:
loggerlevel = logger.error
data = body.decode(UTF_8_CHARSET)
Expand Down
2 changes: 1 addition & 1 deletion algorithms_keeper/parser/files_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def validate_extension(self) -> str:
filepath = file.path
if not filepath.suffix:
if ".github" not in filepath.parts:
if filepath.parent.name:
if filepath.parent.name: # noqa: SIM114
invalid_filepath.append(file.name)
# Dotfiles are not considered as invalid if they are present in the
# root of the repository
Expand Down
3 changes: 2 additions & 1 deletion algorithms_keeper/parser/python_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ class PythonParser(BaseFilesParser):
".txt",
# Good old Python file
".py",
) + DOCS_EXTENSIONS
*DOCS_EXTENSIONS,
)

def __init__(
self,
Expand Down
4 changes: 2 additions & 2 deletions algorithms_keeper/parser/record.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,12 @@ def add_error(
# It seems that ``ParserSyntaxError`` is not a subclass of ``SyntaxError``,
# the same information is stored under a different attribute. There is no
# filename information in ``ParserSyntaxError``, thus the parameter `filepath`.
if isinstance(exc, SyntaxError): # pragma: no cover
if isinstance(exc, SyntaxError): # noqa: SIM108, pragma: no cover
lineno = exc.lineno or 1
else:
lineno = exc.raw_line
body = (
f"An error occured while parsing the file: `{filepath}`\n"
f"An error occurred while parsing the file: `{filepath}`\n"
f"```python\n{message}\n```"
)
self._comments.append(ReviewComment(body, filepath, lineno))
Expand Down
17 changes: 8 additions & 9 deletions algorithms_keeper/parser/rules/naming_convention.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@

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

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


Expand All @@ -35,9 +35,8 @@ def valid(self, name: str) -> bool:
name = name.strip("_")
if name[0].islower() or "_" in name:
return False
else:
if name.lower() != name and name.upper() != name:
return False
elif name.lower() != name and name.upper() != name:
return False
return True


Expand Down Expand Up @@ -159,7 +158,7 @@ def visit_ClassDef(self, node: cst.ClassDef) -> None:
def visit_Attribute(self, node: cst.Attribute) -> None:
# The attribute node can come through other context as well but we only care
# about the ones coming from assignments.
if self._assigntarget_counter > 0:
if self._assigntarget_counter > 0: # noqa: SIM102
# We only care about assignment attribute to *self*.
if m.matches(node, m.Attribute(value=m.Name(value="self"))):
self._validate_nodename(
Expand All @@ -169,7 +168,7 @@ def visit_Attribute(self, node: cst.Attribute) -> None:
def visit_Element(self, node: cst.Element) -> None:
# We only care about elements in *List* or *Tuple* specifically coming from
# inside the multiple assignments.
if self._assigntarget_counter > 0:
if self._assigntarget_counter > 0: # noqa: SIM102
if m.matches(node, m.Element(value=m.Name())):
nodename = cst.ensure_type(node.value, cst.Name).value
self._validate_nodename(node, nodename, NamingConvention.SNAKE_CASE)
Expand Down
6 changes: 3 additions & 3 deletions algorithms_keeper/parser/rules/require_doctest.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

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

INIT: str = "__init__"
Expand Down Expand Up @@ -184,7 +184,7 @@ def spam(self):
pass
"""
),
# Check that `_skip_doctest` attribute is reseted after leaving the class.
# Check that `_skip_doctest` attribute is reset after leaving the class.
Invalid(
"""
def bar():
Expand Down Expand Up @@ -221,7 +221,7 @@ def visit_ClassDef(self, node: cst.ClassDef) -> None:
# Temporary storage of the ``skip_doctest`` value only during the class visit.
# If the class-level docstring contains doctest, then the checks should only be
# skipped for all its methods and not for other functions/class in the module.
# After leaving the class, ``skip_doctest`` should be resetted to whatever the
# After leaving the class, ``skip_doctest`` should be reset to whatever the
# value was before.
self._temporary = self._skip_doctest
self._skip_doctest = self._has_doctest(node)
Expand Down
4 changes: 2 additions & 2 deletions algorithms_keeper/parser/rules/require_type_hint.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@

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

IGNORE_PARAM: set[str] = {"self", "cls"}
Expand Down
8 changes: 4 additions & 4 deletions algorithms_keeper/parser/rules/use_fstring.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
class UseFstringRule(CstLintRule):
MESSAGE: str = (
"As mentioned in the [Contributing Guidelines]"
+ "(https://github.com/TheAlgorithms/Python/blob/master/CONTRIBUTING.md), "
+ "please do not use printf style formatting or `str.format()`. "
+ "Use [f-string](https://realpython.com/python-f-strings/) instead to be "
+ "more readable and efficient."
"(https://github.com/TheAlgorithms/Python/blob/master/CONTRIBUTING.md), "
"please do not use printf style formatting or `str.format()`. "
"Use [f-string](https://realpython.com/python-f-strings/) instead to be "
"more readable and efficient."
)

VALID = [
Expand Down
4 changes: 2 additions & 2 deletions algorithms_keeper/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ async def get_user_open_pr_numbers(
)
pr_numbers = []
async for pull in gh.getiter(search_url, oauth_token=await gh.access_token):
pr_numbers.append(pull["number"])
pr_numbers.append(pull["number"]) # noqa: PERF401
return pr_numbers


Expand Down Expand Up @@ -211,7 +211,7 @@ async def get_pr_files(gh: GitHubAPI, *, pull_request: Mapping[str, Any]) -> lis
async for data in gh.getiter(
pull_request["url"] + "/files", oauth_token=await gh.access_token
):
files.append(
files.append( # noqa: PERF401
File(
data["filename"],
Path(data["filename"]),
Expand Down
78 changes: 75 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
[tool.isort]
profile = "black"

[tool.mypy]
ignore_missing_imports = true
warn_unused_configs = true
Expand All @@ -19,6 +16,81 @@ module = "tests.data.*"
disallow_untyped_defs = false
check_untyped_defs = false

[tool.ruff]
select = [
"A", # flake8-builtins
"AIR", # Airflow
"ASYNC", # flake8-async
"B", # flake8-bugbear
"BLE", # flake8-blind-except
"C4", # flake8-comprehensions
"C90", # McCabe cyclomatic complexity
"CPY", # flake8-copyright
"DJ", # flake8-django
"DTZ", # flake8-datetimez
"E", # pycodestyle
"EM", # flake8-errmsg
"EXE", # flake8-executable
"F", # Pyflakes
"FLY", # flynt
"G", # flake8-logging-format
"I", # isort
"ICN", # flake8-import-conventions
"INP", # flake8-no-pep420
"INT", # flake8-gettext
"ISC", # flake8-implicit-str-concat
"N", # pep8-naming
"NPY", # NumPy-specific rules
"PD", # pandas-vet
"PERF", # Perflint
"PGH", # pygrep-hooks
"PIE", # flake8-pie
"PL", # Pylint
"PT", # flake8-pytest-style
"PTH", # flake8-use-pathlib
"PYI", # flake8-pyi
"RSE", # flake8-raise
"RUF", # Ruff-specific rules
"S", # flake8-bandit
"SIM", # flake8-simplify
"SLF", # flake8-self
"SLOT", # flake8-slots
"T10", # flake8-debugger
"TCH", # flake8-type-checking
"TID", # flake8-tidy-imports
"UP", # pyupgrade
"W", # pycodestyle
"YTT", # flake8-2020
# "ANN", # flake8-annotations
# "ARG", # flake8-unused-arguments
# "COM", # flake8-commas
# "D", # pydocstyle
# "ERA", # eradicate
# "FA", # flake8-future-annotations
# "FBT", # flake8-boolean-trap
# "FIX", # flake8-fixme
# "Q", # flake8-quotes
# "RET", # flake8-return
# "T20", # flake8-print
# "TD", # flake8-todos
# "TRY", s# tryceratops
]
ignore = ["N802", "PGH003", "RUF012", "SLF001"]
line-length = 88
target-version = "py38"

[tool.ruff.mccabe]
max-complexity = 13

[tool.ruff.per-file-ignores]
"tests/*" = ["PT006", "PT007", "S101"]
"tests/test_commands.py" = ["B008"]
"tests/test_pull_requests.py" = ["B008"]

[tool.ruff.pylint]
allow-magic-value-types = ["bytes", "int", "str"]
max-args = 6 # Recommended: 5

[tool.pytest.ini_options]
testpaths = "tests"
addopts = """\
Expand Down
2 changes: 0 additions & 2 deletions tests/data/descriptive_name.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,5 +66,3 @@ def c(self, a: int = 10) -> None:

class C:
"""A class which requires descriptive names"""

pass
11 changes: 6 additions & 5 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import pytest
import pytest_asyncio
from gidgethub import apps, sansio
from pytest import MonkeyPatch

from algorithms_keeper.api import GitHubAPI, token_cache

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


@pytest.mark.asyncio
@pytest.mark.asyncio()
async def test_initialization() -> None:
async with aiohttp.ClientSession() as session:
github_api = GitHubAPI(number, session, "algorithms-keeper")
Expand All @@ -35,8 +34,10 @@ async def test_initialization() -> None:
assert github_api.requester == "algorithms-keeper"


@pytest.mark.asyncio
async def test_access_token(github_api: GitHubAPI, monkeypatch: MonkeyPatch) -> None:
@pytest.mark.asyncio()
async def test_access_token(
github_api: GitHubAPI, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setattr(apps, "get_installation_access_token", mock_return)
monkeypatch.setenv("GITHUB_APP_ID", "")
monkeypatch.setenv("GITHUB_PRIVATE_KEY", "")
Expand All @@ -49,7 +50,7 @@ async def test_access_token(github_api: GitHubAPI, monkeypatch: MonkeyPatch) ->
assert cached_token == token


@pytest.mark.asyncio
@pytest.mark.asyncio()
async def test_headers_and_log(github_api: GitHubAPI) -> None:
request_headers = sansio.create_headers("algorithms-keeper")
resp = await github_api._request(
Expand Down
4 changes: 2 additions & 2 deletions tests/test_check_runs.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

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