diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7175346..ddc9111 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,14 +3,4 @@ repos: rev: "22.8.0" hooks: - id: black - exclude: ^{{cookiecutter.project_name}} - - - repo: https://github.com/PyCQA/isort - rev: "5.10.1" - hooks: - - id: isort - - - repo: https://github.com/PyCQA/flake8 - rev: "5.0.4" - hooks: - - id: flake8 \ No newline at end of file + exclude: ^{{cookiecutter.project_name}} \ No newline at end of file diff --git a/Makefile b/Makefile index b96036c..1f0bd8e 100644 --- a/Makefile +++ b/Makefile @@ -35,6 +35,8 @@ check: ## Run code quality tools. @poetry lock --check @echo "🚀 Linting code: Running pre-commit" @poetry run pre-commit run -a + @echo "🚀 Linting with ruff" + @poetry run ruff hooks tests cookiecutter_poetry --config pyproject.toml @echo "🚀 Static type checking: Running mypy" @poetry run mypy @echo "🚀 Checking for obsolete dependencies: Running deptry" diff --git a/README.md b/README.md index 01199c8..acf2bf9 100644 --- a/README.md +++ b/README.md @@ -27,8 +27,8 @@ repository to generate the file structure for a Python project that uses - [Poetry](https://python-poetry.org/) for dependency management - CI/CD with [GitHub Actions](https://github.com/features/actions) - Pre-commit hooks with [pre-commit](https://pre-commit.com/) -- Formatting with [black](https://pypi.org/project/black/) and [isort](https://pycqa.github.io/isort/index.html) -- Linting with [flake8](https://flake8.pycqa.org/en/latest/) +- Formatting with [black](https://pypi.org/project/black/). +- Linting with [ruff](https://github.com/charliermarsh/ruff) - Static type checking with [mypy](https://mypy.readthedocs.io/en/stable/) - Dependency checking with [cookiecutter-poetry](https://fpgmaas.github.io/cookiecutter-poetry/) - Publishing to [Pypi](https://pypi.org) or [Artifactory](https://jfrog.com/artifactory) by creating a new release on GitHub diff --git a/docs/features/linting.md b/docs/features/linting.md index a9a53d2..4e04556 100644 --- a/docs/features/linting.md +++ b/docs/features/linting.md @@ -10,15 +10,6 @@ Note that this requires the pre-commit hooks to be installed. This command will run the following tools: -## isort - -[isort](https://pycqa.github.io/isort/index.html) is run to sort the imports and it is configured through `pyproject.toml`: - -```toml -[tool.isort] -profile = "black" -``` - ## black [black](https://pypi.org/project/black/) is used to format the code, and it is configured through `pyproject.toml`: @@ -34,30 +25,58 @@ fast = true To exclude directories or files, add an `exclude` argument to `pre-commit-config.yaml`. Note that adding an `exclude` argument to `pyproject.toml` will not work, see also [here](https://stackoverflow.com/a/61046953/8037249). -## flake8 +## ruff -[flake8](https://flake8.pycqa.org/en/latest/) is used to check the code style, and it is configured through `tox.ini`: +[ruff](https://github.com/charliermarsh/ruff) is used to check the code style, and it is configured through `pyproject.toml`: ``` -[flake8] -per-file-ignores = __init__.py:F401 -# PEP-8 The following are ignored: -# E731 do not assign a lambda expression, use a def -# E203 whitespace before ':' -# E501 line too long -# W503 line break before binary operator -# W605 invalid escape sequence -ignore = E731, E203, E501, W503, W605 -exclude = - .git, - __pycache__, - docs/source/conf.py, - old, - build, - dist, - .venv, -max-complexity = 10 -max-line-length = 120 +[tool.ruff] +target-version = "py37" +line-length = 120 +fix = false +select = [ + # flake8-2020 + "YTT", + # flake8-bandit + "S", + # flake8-bugbear + "B", + # flake8-builtins + "A", + # flake8-comprehensions + "C4", + # flake8-debugger + "T10", + # flake8-print + "T20", + # flake8-simplify + "SIM", + # isort + "I", + # mccabe + "C90", + # pycodestyle + "E", "W", + # pyflakes + "F", + # pygrep-hooks + "PGH", + # pyupgrade + "UP", + # ruff + "RUF", + # tryceratops + "TRY", +] +ignore = [ + # LineTooLong + "E501", + # DoNotAssignLambda + "E731", +] + +[tool.ruff.per-file-ignores] +"tests/*" = ["S101"] ``` # mypy diff --git a/docs/features/makefile.md b/docs/features/makefile.md index 62efeb1..9caba0f 100644 --- a/docs/features/makefile.md +++ b/docs/features/makefile.md @@ -7,7 +7,7 @@ available: ``` install Install the poetry environment and install the pre-commit hooks -check Lint and check code by running black, isort, flake8, mypy and deptry. +check Lint and check code by running black, ruff, mypy and deptry. test Test the code with pytest build Build wheel file using poetry clean-build clean build artifacts diff --git a/docs/index.md b/docs/index.md index b7d93cf..84c72bd 100644 --- a/docs/index.md +++ b/docs/index.md @@ -26,8 +26,8 @@ A project generated with ``cookiecutter-poetry`` supports the following features - [Poetry](https://python-poetry.org/) for dependency management - CI/CD with [GitHub Actions](https://github.com/features/actions) - Pre-commit hooks with [pre-commit](https://pre-commit.com/) -- Formatting with [black](https://pypi.org/project/black/) and [isort](https://pycqa.github.io/isort/index.html) -- Linting with [flake8](https://flake8.pycqa.org/en/latest/) +- Formatting with [black](https://pypi.org/project/black/). +- Linting with [ruff](https://github.com/charliermarsh/ruff) - Static type checking with [mypy](https://mypy.readthedocs.io/en/stable/) - Dependency checking with [cookiecutter-poetry](https://fpgmaas.github.io/cookiecutter-poetry/) - Publishing to [Pypi](https://pypi.org) or [Artifactory](https://jfrog.com/artifactory) by creating a new release on GitHub diff --git a/poetry.lock b/poetry.lock index b3ef0bc..9efcc4d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -551,6 +551,14 @@ urllib3 = ">=1.21.1,<1.27" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "ruff" +version = "0.0.235" +description = "An extremely fast Python linter, written in Rust." +category = "dev" +optional = false +python-versions = ">=3.7" + [[package]] name = "setuptools" version = "65.6.3" @@ -682,7 +690,7 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools" [metadata] lock-version = "1.1" python-versions = ">=3.8,<4.0" -content-hash = "756d04439feca402c21a8a6e8d3cc5f76bf221f8613e7b4cec55a28d7de9ea29" +content-hash = "0507a1a9e287e3acc294ec7e5277c94f55714e0b3ea9625b252f2a74be22b390" [metadata.files] arrow = [ @@ -1021,6 +1029,24 @@ requests = [ {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, ] +ruff = [ + {file = "ruff-0.0.235-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:50327fe28aa914c4b2e3d06c3e41f47bcfbd595843a26f5f7fda30ca5318755f"}, + {file = "ruff-0.0.235-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:d29966029ff77a1c336004ff3e1effd33db8554ad9ec9f87ff339d0f3d44ae35"}, + {file = "ruff-0.0.235-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50baf2635584b93c09d1e69bca51041eb4ff584b20b0a443124feb7019591a4e"}, + {file = "ruff-0.0.235-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cc67f4e8095ad4af9bdd81f76db9cdc4e07533aeb91037dc3548d1384200de0f"}, + {file = "ruff-0.0.235-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fa9d2ba750180e3d7c23ee0151c52f1900e601be54ab516283ada368b1bb1672"}, + {file = "ruff-0.0.235-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:fbd10fbb7643a8334e0f6ca1095a877e2f1fb240bbd0ee23f8196592e0c092d3"}, + {file = "ruff-0.0.235-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8738cabb41216d467ac92d747380c6c943d74dd4d7d1bf8a3106787ecccbd36f"}, + {file = "ruff-0.0.235-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64de46e14c30a6eb9c6a458c62048b1711c46e45ff0468f14118c4d24d2fa750"}, + {file = "ruff-0.0.235-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9b475d800a6f356a7e7afae89a8ce1297e06f365eaa23b9eb80e6cb16a0915f"}, + {file = "ruff-0.0.235-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ed8771ab7bbaa9b350eb64a3d6d6628e800800cb15c5c3cc6e3e3217ff67703d"}, + {file = "ruff-0.0.235-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e60c855babdc3d8df77ac044fb3f893c2084efebc606726ecb078edc9d3c5702"}, + {file = "ruff-0.0.235-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9efb9b87b92deeaeb707581a884e1764343165df0d37c3bdc4dc297edd837dce"}, + {file = "ruff-0.0.235-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:856ec6bfda0912f8010e15ffe04c33f2793971452379dfc8bd1f30b849483ede"}, + {file = "ruff-0.0.235-py3-none-win32.whl", hash = "sha256:82cf33ce2a998d1762517cc2e4ec0f79bbd985d005b312f31674411100c41899"}, + {file = "ruff-0.0.235-py3-none-win_amd64.whl", hash = "sha256:4a8b0284d52ea7b486894899cf5ba705c7b03a9d5fa780d55ac99ab64d3967ad"}, + {file = "ruff-0.0.235.tar.gz", hash = "sha256:270c0c83c01d00370851813edfd1502f2146a0a0b4e75b723e0c388252840f5a"}, +] setuptools = [ {file = "setuptools-65.6.3-py3-none-any.whl", hash = "sha256:57f6f22bde4e042978bcd50176fdb381d7c21a9efa4041202288d3737a0c6a54"}, {file = "setuptools-65.6.3.tar.gz", hash = "sha256:a7620757bf984b58deaf32fc8a4577a9bbc0850cf92c20e1ce41c38c19e5fb75"}, diff --git a/pyproject.toml b/pyproject.toml index 1f60bf5..9740d09 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,6 +28,7 @@ tox = "^3.25.1" deptry = "^0.6.4" mypy = "^0.991" pytest-cov = "^4.0.0" +ruff = "^0.0.235" [tool.poetry.group.docs.dependencies] mkdocs = "^1.4.2" @@ -42,10 +43,6 @@ line-length = 120 target-version = ['py37'] preview = true -[tool.isort] -profile = "black" -skip = ["{{cookiecutter.project_name}}",".venv"] - [tool.poetry.scripts] ccp = 'cookiecutter_poetry.cli:main' @@ -70,4 +67,50 @@ show_error_codes = "True" extend_exclude = [ "{{cookiecutter.project_name}}" ] -ignore_obsolete = ["cookiecutter"] \ No newline at end of file +ignore_obsolete = ["cookiecutter"] + +[tool.ruff] +target-version = "py37" +line-length = 120 +fix = true +select = [ + # flake8-2020 + "YTT", + # flake8-bandit + "S", + # flake8-bugbear + "B", + # flake8-builtins + "A", + # flake8-comprehensions + "C4", + # flake8-debugger + "T10", + # flake8-simplify + "SIM", + # isort + "I", + # mccabe + "C90", + # pycodestyle + "E", "W", + # pyflakes + "F", + # pygrep-hooks + "PGH", + # pyupgrade + "UP", + # ruff + "RUF", + # tryceratops + "TRY", +] +ignore = [ + # LineTooLong + "E501", + # DoNotAssignLambda + "E731", +] + +[tool.ruff.per-file-ignores] +"tests/*" = ["S101"] \ No newline at end of file diff --git a/tests/test_cookiecutter.py b/tests/test_cookiecutter.py index 17c9182..a328b72 100644 --- a/tests/test_cookiecutter.py +++ b/tests/test_cookiecutter.py @@ -15,7 +15,8 @@ def run_within_dir(path: str): def file_contains_text(file: str, text: str) -> bool: - return open(file, "r").read().find(text) != -1 + with open(file) as f: + return f.read().find(text) != -1 def test_bake_project(cookies): @@ -39,8 +40,8 @@ def test_using_pytest(cookies, tmp_path): # Install the poetry environment and run the tests. with run_within_dir(str(result.project_path)): - subprocess.check_call(shlex.split("poetry install --no-interaction")) == 0 - subprocess.check_call(shlex.split("poetry run make test")) == 0 + assert subprocess.check_call(shlex.split("poetry install --no-interaction")) == 0 + assert subprocess.check_call(shlex.split("poetry run make test")) == 0 def test_cicd_contains_artifactory_secrets(cookies, tmp_path): diff --git a/tox.ini b/tox.ini index c3dbff1..951ae45 100644 --- a/tox.ini +++ b/tox.ini @@ -11,29 +11,8 @@ python = [testenv] passenv = PYTHON_VERSION -whitelist_externals = poetry +allowlist_externals = poetry commands = poetry install -v pytest --doctest-modules tests --cov --cov-config=pyproject.toml --cov-report=xml - mypy - -[flake8] -per-file-ignores = __init__.py:F401 -# PEP-8 The following are ignored: -# E731 do not assign a lambda expression, use a def -# E203 whitespace before ':' -# E501 line too long -# W503 line break before binary operator -# W605 invalid escape sequence -ignore = E731, E203, E501, W503, W605 -exclude = - .git, - __pycache__, - docs/source/conf.py, - old, - build, - dist, - .venv, - *cookiecutter.project_name* -max-complexity = 10 -max-line-length = 120 \ No newline at end of file + mypy \ No newline at end of file diff --git a/{{cookiecutter.project_name}}/.pre-commit-config.yaml b/{{cookiecutter.project_name}}/.pre-commit-config.yaml index 7cf3d54..65dda4d 100644 --- a/{{cookiecutter.project_name}}/.pre-commit-config.yaml +++ b/{{cookiecutter.project_name}}/.pre-commit-config.yaml @@ -1,19 +1,10 @@ repos: + - repo: https://github.com/charliermarsh/ruff-pre-commit + rev: "v0.0.230" + hooks: + - id: ruff + - repo: https://github.com/psf/black rev: "22.8.0" hooks: - - id: black - - - repo: https://github.com/PyCQA/isort - rev: "5.10.1" - hooks: - - id: isort - - - repo: https://github.com/PyCQA/flake8 - rev: "5.0.4" - hooks: - - id: flake8 - additional_dependencies: - - flake8-bugbear==22.9.23 - - flake8-comprehensions==3.10.0 - - flake8-simplify==0.19.3 \ No newline at end of file + - id: black \ No newline at end of file diff --git a/{{cookiecutter.project_name}}/pyproject.toml b/{{cookiecutter.project_name}}/pyproject.toml index 7f9fea4..163e560 100644 --- a/{{cookiecutter.project_name}}/pyproject.toml +++ b/{{cookiecutter.project_name}}/pyproject.toml @@ -41,9 +41,6 @@ line-length = 120 target-version = ['py37'] preview = true -[tool.isort] -profile = "black" - [tool.mypy] files = ["{{cookiecutter.project_slug}}"] disallow_untyped_defs = "True" @@ -54,6 +51,52 @@ warn_return_any = "True" warn_unused_ignores = "True" show_error_codes = "True" +[tool.ruff] +target-version = "py37" +line-length = 120 +fix = true +select = [ + # flake8-2020 + "YTT", + # flake8-bandit + "S", + # flake8-bugbear + "B", + # flake8-builtins + "A", + # flake8-comprehensions + "C4", + # flake8-debugger + "T10", + # flake8-simplify + "SIM", + # isort + "I", + # mccabe + "C90", + # pycodestyle + "E", "W", + # pyflakes + "F", + # pygrep-hooks + "PGH", + # pyupgrade + "UP", + # ruff + "RUF", + # tryceratops + "TRY", +] +ignore = [ + # LineTooLong + "E501", + # DoNotAssignLambda + "E731", +] + +[tool.ruff.per-file-ignores] +"tests/*" = ["S101"] + {% if cookiecutter.codecov == "y"-%} [tool.coverage.report] skip_empty = true @@ -62,3 +105,4 @@ skip_empty = true branch = true source = ["{{cookiecutter.project_slug}}"] {% endif%} + diff --git a/{{cookiecutter.project_name}}/tox.ini b/{{cookiecutter.project_name}}/tox.ini index 6d9ceae..ed3e35c 100644 --- a/{{cookiecutter.project_name}}/tox.ini +++ b/{{cookiecutter.project_name}}/tox.ini @@ -1,23 +1,3 @@ -[flake8] -per-file-ignores = __init__.py:F401 -# PEP-8 The following are ignored: -# E731 do not assign a lambda expression, use a def -# E203 whitespace before ':' -# E501 line too long -# W503 line break before binary operator -# W605 invalid escape sequence -ignore = E731, E203, E501, W503, W605 -exclude = - .git, - __pycache__, - docs/source/conf.py, - old, - build, - dist, - .venv, -max-complexity = 10 -max-line-length = 120 - [tox] skipsdist = true envlist = py38, py39, py310, py311 @@ -31,7 +11,7 @@ python = [testenv] passenv = PYTHON_VERSION -whitelist_externals = poetry +allowlist_externals = poetry commands = poetry install -v pytest --doctest-modules tests --cov --cov-config=pyproject.toml --cov-report=xml diff --git a/{{cookiecutter.project_name}}/{{cookiecutter.project_slug}}/foo.py b/{{cookiecutter.project_name}}/{{cookiecutter.project_slug}}/foo.py index 4aa893b..35fd0ac 100644 --- a/{{cookiecutter.project_name}}/{{cookiecutter.project_slug}}/foo.py +++ b/{{cookiecutter.project_name}}/{{cookiecutter.project_slug}}/foo.py @@ -14,4 +14,4 @@ def foo() -> str: if __name__ == "__main__": # pragma: no cover - print(foo()) + pass