From c96241b5a5052af466894ef90c7a7c749ba872eb Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Wed, 15 Mar 2023 13:58:25 +0100 Subject: [PATCH] Replace bandit, flake8, isort, and pyupgrade with ruff (#8178) * Replace bandit, flake8, isort, and pyupgrade with ruff * Comment on ruff rules * updating DIRECTORY.md --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .flake8 | 10 --- .github/workflows/ruff.yml | 16 ++++ .pre-commit-config.yaml | 78 +++++-------------- arithmetic_analysis/newton_raphson.py | 2 +- arithmetic_analysis/newton_raphson_new.py | 2 +- data_structures/heap/heap_generic.py | 1 - dynamic_programming/min_distance_up_bottom.py | 9 +-- dynamic_programming/minimum_tickets_cost.py | 4 +- dynamic_programming/word_break.py | 4 +- hashes/sha1.py | 12 +-- machine_learning/support_vector_machines.py | 4 +- maths/eulers_totient.py | 34 ++++---- maths/fibonacci.py | 4 +- maths/pythagoras.py | 6 +- other/quine.py | 1 + project_euler/problem_075/sol1.py | 3 +- pyproject.toml | 59 ++++++++++++-- sorts/external_sort.py | 2 +- strings/check_anagrams.py | 3 +- strings/word_occurrence.py | 3 +- web_programming/currency_converter.py | 2 +- 21 files changed, 127 insertions(+), 132 deletions(-) delete mode 100644 .flake8 create mode 100644 .github/workflows/ruff.yml diff --git a/.flake8 b/.flake8 deleted file mode 100644 index b68ee8533..000000000 --- a/.flake8 +++ /dev/null @@ -1,10 +0,0 @@ -[flake8] -max-line-length = 88 -# max-complexity should be 10 -max-complexity = 19 -extend-ignore = - # Formatting style for `black` - # E203 is whitespace before ':' - E203, - # W503 is line break occurred before a binary operator - W503 diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml new file mode 100644 index 000000000..ca2d5be47 --- /dev/null +++ b/.github/workflows/ruff.yml @@ -0,0 +1,16 @@ +# https://beta.ruff.rs +name: ruff +on: + push: + branches: + - master + pull_request: + branches: + - master +jobs: + ruff: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - run: pip install --user ruff + - run: ruff --format=github . diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9aa965e42..82aad6c65 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,6 +3,7 @@ repos: rev: v4.4.0 hooks: - id: check-executables-have-shebangs + - id: check-toml - id: check-yaml - id: end-of-file-fixer types: [python] @@ -14,60 +15,41 @@ repos: hooks: - id: auto-walrus + - repo: https://github.com/charliermarsh/ruff-pre-commit + rev: v0.0.255 + hooks: + - id: ruff + - repo: https://github.com/psf/black rev: 23.1.0 hooks: - id: black - - repo: https://github.com/PyCQA/isort - rev: 5.12.0 + - repo: https://github.com/codespell-project/codespell + rev: v2.2.4 hooks: - - id: isort - args: - - --profile=black + - id: codespell + additional_dependencies: + - tomli - repo: https://github.com/tox-dev/pyproject-fmt rev: "0.9.2" hooks: - id: pyproject-fmt + - repo: local + hooks: + - id: validate-filenames + name: Validate filenames + entry: ./scripts/validate_filenames.py + language: script + pass_filenames: false + - repo: https://github.com/abravalheri/validate-pyproject rev: v0.12.1 hooks: - id: validate-pyproject - - repo: https://github.com/asottile/pyupgrade - rev: v3.3.1 - hooks: - - id: pyupgrade - args: - - --py311-plus - - - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.0.255 - hooks: - - id: ruff - args: - - --ignore=E741 - - - repo: https://github.com/PyCQA/flake8 - rev: 6.0.0 - hooks: - - id: flake8 # See .flake8 for args - additional_dependencies: &flake8-plugins - - flake8-bugbear - - flake8-builtins - # - flake8-broken-line - - flake8-comprehensions - - pep8-naming - - - repo: https://github.com/asottile/yesqa - rev: v1.4.0 - hooks: - - id: yesqa - additional_dependencies: - *flake8-plugins - - repo: https://github.com/pre-commit/mirrors-mypy rev: v1.1.1 hooks: @@ -77,25 +59,3 @@ repos: - --install-types # See mirrors-mypy README.md - --non-interactive additional_dependencies: [types-requests] - - - repo: https://github.com/codespell-project/codespell - rev: v2.2.4 - hooks: - - id: codespell - args: - - --ignore-words-list=3rt,ans,crate,damon,fo,followings,hist,iff,kwanza,mater,secant,som,sur,tim,zar - exclude: | - (?x)^( - ciphers/prehistoric_men.txt | - strings/dictionary.txt | - strings/words.txt | - project_euler/problem_022/p022_names.txt - )$ - - - repo: local - hooks: - - id: validate-filenames - name: Validate filenames - entry: ./scripts/validate_filenames.py - language: script - pass_filenames: false diff --git a/arithmetic_analysis/newton_raphson.py b/arithmetic_analysis/newton_raphson.py index 86ff9d350..aee2f07e5 100644 --- a/arithmetic_analysis/newton_raphson.py +++ b/arithmetic_analysis/newton_raphson.py @@ -5,7 +5,7 @@ from __future__ import annotations from decimal import Decimal -from math import * # noqa: F401, F403 +from math import * # noqa: F403 from sympy import diff diff --git a/arithmetic_analysis/newton_raphson_new.py b/arithmetic_analysis/newton_raphson_new.py index 472cb5b5a..f61841e2e 100644 --- a/arithmetic_analysis/newton_raphson_new.py +++ b/arithmetic_analysis/newton_raphson_new.py @@ -8,7 +8,7 @@ # Newton's Method - https://en.wikipedia.org/wiki/Newton's_method from sympy import diff, lambdify, symbols -from sympy.functions import * # noqa: F401, F403 +from sympy.functions import * # noqa: F403 def newton_raphson( diff --git a/data_structures/heap/heap_generic.py b/data_structures/heap/heap_generic.py index b4d7019f4..ee92149e2 100644 --- a/data_structures/heap/heap_generic.py +++ b/data_structures/heap/heap_generic.py @@ -166,7 +166,6 @@ def test_heap() -> None: >>> h.get_top() [9, -40] """ - pass if __name__ == "__main__": diff --git a/dynamic_programming/min_distance_up_bottom.py b/dynamic_programming/min_distance_up_bottom.py index 49c361f24..4870c7ef4 100644 --- a/dynamic_programming/min_distance_up_bottom.py +++ b/dynamic_programming/min_distance_up_bottom.py @@ -6,13 +6,13 @@ to find edit distance. The aim is to demonstate up bottom approach for solving the task. The implementation was tested on the leetcode: https://leetcode.com/problems/edit-distance/ -""" -""" Levinstein distance Dynamic Programming: up -> down. """ +import functools + def min_distance_up_bottom(word1: str, word2: str) -> int: """ @@ -25,13 +25,10 @@ def min_distance_up_bottom(word1: str, word2: str) -> int: >>> min_distance_up_bottom("zooicoarchaeologist", "zoologist") 10 """ - - from functools import lru_cache - len_word1 = len(word1) len_word2 = len(word2) - @lru_cache(maxsize=None) + @functools.cache def min_distance(index1: int, index2: int) -> int: # if first word index is overflow - delete all from the second word if index1 >= len_word1: diff --git a/dynamic_programming/minimum_tickets_cost.py b/dynamic_programming/minimum_tickets_cost.py index d07056d92..6790c21f1 100644 --- a/dynamic_programming/minimum_tickets_cost.py +++ b/dynamic_programming/minimum_tickets_cost.py @@ -22,7 +22,7 @@ Minimum Cost For Tickets Dynamic Programming: up -> down. """ -from functools import lru_cache +import functools def mincost_tickets(days: list[int], costs: list[int]) -> int: @@ -106,7 +106,7 @@ def mincost_tickets(days: list[int], costs: list[int]) -> int: days_set = set(days) - @lru_cache(maxsize=None) + @functools.cache def dynamic_programming(index: int) -> int: if index > 365: return 0 diff --git a/dynamic_programming/word_break.py b/dynamic_programming/word_break.py index 642ea0edf..4d7ac8690 100644 --- a/dynamic_programming/word_break.py +++ b/dynamic_programming/word_break.py @@ -20,7 +20,7 @@ Runtime: O(n * n) Space: O(n) """ -from functools import lru_cache +import functools from typing import Any @@ -80,7 +80,7 @@ def word_break(string: str, words: list[str]) -> bool: len_string = len(string) # Dynamic programming method - @lru_cache(maxsize=None) + @functools.cache def is_breakable(index: int) -> bool: """ >>> string = 'a' diff --git a/hashes/sha1.py b/hashes/sha1.py index b19e0cfaf..9f0437f20 100644 --- a/hashes/sha1.py +++ b/hashes/sha1.py @@ -26,7 +26,6 @@ Reference: https://deadhacker.com/2006/02/21/sha-1-illustrated/ import argparse import hashlib # hashlib is only used inside the Test class import struct -import unittest class SHA1Hash: @@ -128,14 +127,9 @@ class SHA1Hash: return "%08x%08x%08x%08x%08x" % tuple(self.h) -class SHA1HashTest(unittest.TestCase): - """ - Test class for the SHA1Hash class. Inherits the TestCase class from unittest - """ - - def testMatchHashes(self): # noqa: N802 - msg = bytes("Test String", "utf-8") - self.assertEqual(SHA1Hash(msg).final_hash(), hashlib.sha1(msg).hexdigest()) +def test_sha1_hash(): + msg = b"Test String" + assert SHA1Hash(msg).final_hash() == hashlib.sha1(msg).hexdigest() # noqa: S324 def main(): diff --git a/machine_learning/support_vector_machines.py b/machine_learning/support_vector_machines.py index caec10175..df854cc85 100644 --- a/machine_learning/support_vector_machines.py +++ b/machine_learning/support_vector_machines.py @@ -56,7 +56,7 @@ class SVC: *, regularization: float = np.inf, kernel: str = "linear", - gamma: float = 0, + gamma: float = 0.0, ) -> None: self.regularization = regularization self.gamma = gamma @@ -65,7 +65,7 @@ class SVC: elif kernel == "rbf": if self.gamma == 0: raise ValueError("rbf kernel requires gamma") - if not (isinstance(self.gamma, float) or isinstance(self.gamma, int)): + if not isinstance(self.gamma, (float, int)): raise ValueError("gamma must be float or int") if not self.gamma > 0: raise ValueError("gamma must be > 0") diff --git a/maths/eulers_totient.py b/maths/eulers_totient.py index 6a35e69bd..a15664703 100644 --- a/maths/eulers_totient.py +++ b/maths/eulers_totient.py @@ -1,5 +1,20 @@ # Eulers Totient function finds the number of relative primes of a number n from 1 to n def totient(n: int) -> list: + """ + >>> n = 10 + >>> totient_calculation = totient(n) + >>> for i in range(1, n): + ... print(f"{i} has {totient_calculation[i]} relative primes.") + 1 has 0 relative primes. + 2 has 1 relative primes. + 3 has 2 relative primes. + 4 has 2 relative primes. + 5 has 4 relative primes. + 6 has 2 relative primes. + 7 has 6 relative primes. + 8 has 4 relative primes. + 9 has 6 relative primes. + """ is_prime = [True for i in range(n + 1)] totients = [i - 1 for i in range(n + 1)] primes = [] @@ -20,25 +35,6 @@ def totient(n: int) -> list: return totients -def test_totient() -> None: - """ - >>> n = 10 - >>> totient_calculation = totient(n) - >>> for i in range(1, n): - ... print(f"{i} has {totient_calculation[i]} relative primes.") - 1 has 0 relative primes. - 2 has 1 relative primes. - 3 has 2 relative primes. - 4 has 2 relative primes. - 5 has 4 relative primes. - 6 has 2 relative primes. - 7 has 6 relative primes. - 8 has 4 relative primes. - 9 has 6 relative primes. - """ - pass - - if __name__ == "__main__": import doctest diff --git a/maths/fibonacci.py b/maths/fibonacci.py index d58c9fc68..e810add69 100644 --- a/maths/fibonacci.py +++ b/maths/fibonacci.py @@ -16,7 +16,7 @@ fib_memoization runtime: 0.0107 ms fib_binet runtime: 0.0174 ms """ -from functools import lru_cache +import functools from math import sqrt from time import time @@ -110,7 +110,7 @@ def fib_recursive_cached(n: int) -> list[int]: Exception: n is negative """ - @lru_cache(maxsize=None) + @functools.cache def fib_recursive_term(i: int) -> int: """ Calculates the i-th (0-indexed) Fibonacci number using recursion diff --git a/maths/pythagoras.py b/maths/pythagoras.py index 69a17731a..7770e981d 100644 --- a/maths/pythagoras.py +++ b/maths/pythagoras.py @@ -14,17 +14,13 @@ class Point: def distance(a: Point, b: Point) -> float: - return math.sqrt(abs((b.x - a.x) ** 2 + (b.y - a.y) ** 2 + (b.z - a.z) ** 2)) - - -def test_distance() -> None: """ >>> point1 = Point(2, -1, 7) >>> point2 = Point(1, -3, 5) >>> print(f"Distance from {point1} to {point2} is {distance(point1, point2)}") Distance from Point(2, -1, 7) to Point(1, -3, 5) is 3.0 """ - pass + return math.sqrt(abs((b.x - a.x) ** 2 + (b.y - a.y) ** 2 + (b.z - a.z) ** 2)) if __name__ == "__main__": diff --git a/other/quine.py b/other/quine.py index 01e03bbb0..500a351d3 100644 --- a/other/quine.py +++ b/other/quine.py @@ -1,4 +1,5 @@ #!/bin/python3 +# ruff: noqa """ Quine: diff --git a/project_euler/problem_075/sol1.py b/project_euler/problem_075/sol1.py index b57604d76..0ccaf5dee 100644 --- a/project_euler/problem_075/sol1.py +++ b/project_euler/problem_075/sol1.py @@ -29,7 +29,6 @@ Reference: https://en.wikipedia.org/wiki/Pythagorean_triple#Generating_a_triple from collections import defaultdict from math import gcd -from typing import DefaultDict def solution(limit: int = 1500000) -> int: @@ -43,7 +42,7 @@ def solution(limit: int = 1500000) -> int: >>> solution(50000) 5502 """ - frequencies: DefaultDict = defaultdict(int) + frequencies: defaultdict = defaultdict(int) euclid_m = 2 while 2 * euclid_m * (euclid_m + 1) <= limit: for euclid_n in range((euclid_m % 2) + 1, euclid_m, 2): diff --git a/pyproject.toml b/pyproject.toml index 5f9b1aa06..6552101d2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,8 +12,57 @@ addopts = [ omit = [".env/*"] sort = "Cover" -#[report] -#sort = Cover -#omit = -# .env/* -# backtracking/* +[tool.codespell] +ignore-words-list = "3rt,ans,crate,damon,fo,followings,hist,iff,kwanza,mater,secant,som,sur,tim,zar" +skip = "./.*,*.json,ciphers/prehistoric_men.txt,project_euler/problem_022/p022_names.txt,pyproject.toml,strings/dictionary.txt,strings/words.txt" + +[tool.ruff] +ignore = [ # `ruff rule S101` for a description of that rule + "B904", # B904: Within an `except` clause, raise exceptions with `raise ... from err` + "B905", # B905: `zip()` without an explicit `strict=` parameter + "E741", # E741: Ambiguous variable name 'l' + "G004", # G004 Logging statement uses f-string + "N999", # N999: Invalid module name + "PLC1901", # PLC1901: `{}` can be simplified to `{}` as an empty string is falsey + "PLR2004", # PLR2004: Magic value used in comparison + "PLR5501", # PLR5501: Consider using `elif` instead of `else` + "PLW0120", # PLW0120: `else` clause on loop without a `break` statement + "PLW060", # PLW060: Using global for `{name}` but no assignment is done -- DO NOT FIX + "PLW2901", # PLW2901: Redefined loop variable + "RUF00", # RUF00: Ambiguous unicode character -- DO NOT FIX + "RUF100", # RUF100: Unused `noqa` directive + "S101", # S101: Use of `assert` detected -- DO NOT FIX + "S105", # S105: Possible hardcoded password: 'password' + "S113", # S113: Probable use of requests call without timeout + "UP038", # UP038: Use `X | Y` in `{}` call instead of `(X, Y)` -- DO NOT FIX +] +select = [ # https://beta.ruff.rs/docs/rules + "A", # A: builtins + "B", # B: bugbear + "C40", # C40: comprehensions + "C90", # C90: mccabe code complexity + "E", # E: pycodestyle errors + "F", # F: pyflakes + "G", # G: logging format + "I", # I: isort + "N", # N: pep8 naming + "PL", # PL: pylint + "PIE", # PIE: pie + "PYI", # PYI: type hinting stub files + "RUF", # RUF: ruff + "S", # S: bandit + "TID", # TID: tidy imports + "UP", # UP: pyupgrade + "W", # W: pycodestyle warnings + "YTT", # YTT: year 2020 +] +target-version = "py311" + +[tool.ruff.mccabe] # DO NOT INCREASE THIS VALUE +max-complexity = 20 # default: 10 + +[tool.ruff.pylint] # DO NOT INCREASE THESE VALUES +max-args = 10 # default: 5 +max-branches = 20 # default: 12 +max-returns = 8 # default: 6 +max-statements = 88 # default: 50 diff --git a/sorts/external_sort.py b/sorts/external_sort.py index 7af7dc0a6..e6b0d47f7 100644 --- a/sorts/external_sort.py +++ b/sorts/external_sort.py @@ -104,7 +104,7 @@ class FileMerger: files = {} for i in range(len(filenames)): - files[i] = open(filenames[i], "r", buffer_size) + files[i] = open(filenames[i], "r", buffer_size) # noqa: UP015 return files diff --git a/strings/check_anagrams.py b/strings/check_anagrams.py index a364b9821..9dcdffcfb 100644 --- a/strings/check_anagrams.py +++ b/strings/check_anagrams.py @@ -2,7 +2,6 @@ wiki: https://en.wikipedia.org/wiki/Anagram """ from collections import defaultdict -from typing import DefaultDict def check_anagrams(first_str: str, second_str: str) -> bool: @@ -30,7 +29,7 @@ def check_anagrams(first_str: str, second_str: str) -> bool: return False # Default values for count should be 0 - count: DefaultDict[str, int] = defaultdict(int) + count: defaultdict[str, int] = defaultdict(int) # For each character in input strings, # increment count in the corresponding diff --git a/strings/word_occurrence.py b/strings/word_occurrence.py index 8260620c3..5a18ebf77 100644 --- a/strings/word_occurrence.py +++ b/strings/word_occurrence.py @@ -1,7 +1,6 @@ # Created by sarathkaul on 17/11/19 # Modified by Arkadip Bhattacharya(@darkmatter18) on 20/04/2020 from collections import defaultdict -from typing import DefaultDict def word_occurrence(sentence: str) -> dict: @@ -15,7 +14,7 @@ def word_occurrence(sentence: str) -> dict: >>> dict(word_occurrence("Two spaces")) {'Two': 1, 'spaces': 1} """ - occurrence: DefaultDict[str, int] = defaultdict(int) + occurrence: defaultdict[str, int] = defaultdict(int) # Creating a dictionary containing count of each word for word in sentence.split(): occurrence[word] += 1 diff --git a/web_programming/currency_converter.py b/web_programming/currency_converter.py index 6fcc60e8f..69f2a2c4d 100644 --- a/web_programming/currency_converter.py +++ b/web_programming/currency_converter.py @@ -8,7 +8,7 @@ import os import requests URL_BASE = "https://www.amdoren.com/api/currency.php" -TESTING = os.getenv("CI", False) +TESTING = os.getenv("CI", "") API_KEY = os.getenv("AMDOREN_API_KEY", "") if not API_KEY and not TESTING: