From ea6ff7907a52e6b6a34a3e32466511cc30fea20b Mon Sep 17 00:00:00 2001 From: Bruno Alla Date: Tue, 31 Dec 2024 10:56:06 +0000 Subject: [PATCH 01/19] Move template linting and formatting to ruff The generated project already uses that, let's be consistent and use it everywhere --- .flake8 | 4 -- .pre-commit-config.yaml | 25 +++--------- pyproject.toml | 90 +++++++++++++++++++++++++++++++++++------ tox.ini | 4 -- 4 files changed, 82 insertions(+), 41 deletions(-) delete mode 100644 .flake8 diff --git a/.flake8 b/.flake8 deleted file mode 100644 index 3a87b269b0..0000000000 --- a/.flake8 +++ /dev/null @@ -1,4 +0,0 @@ -[flake8] -exclude = docs -max-line-length = 119 -extend-ignore = E203 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 86b6014608..fbcd6f37b8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -25,27 +25,12 @@ repos: - id: prettier args: ["--tab-width", "2"] - - repo: https://github.com/asottile/pyupgrade - rev: v3.19.1 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.8.4 hooks: - - id: pyupgrade - args: [--py312-plus] - exclude: hooks/ - - - repo: https://github.com/psf/black - rev: 24.10.0 - hooks: - - id: black - - - repo: https://github.com/PyCQA/isort - rev: 5.13.2 - hooks: - - id: isort - - - repo: https://github.com/PyCQA/flake8 - rev: 7.1.1 - hooks: - - id: flake8 + - id: ruff + args: [--fix, --exit-non-zero-on-fix] + - id: ruff-format - repo: https://github.com/tox-dev/pyproject-fmt rev: "v2.5.0" diff --git a/pyproject.toml b/pyproject.toml index 5e7cfc7fe2..9e718a3a88 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,22 +58,86 @@ docs = [ "sphinx-rtd-theme>=3", ] -[tool.black] -line-length = 119 -target-version = [ - 'py312', +[tool.ruff] +target-version = "py312" +# Exclude the template content as most files aren't parseable +extend-exclude = [ + "{{cookiecutter.project_slug}}/*", ] -# ==== isort ==== - -[tool.isort] -profile = "black" -line_length = 119 -known_first_party = [ - "tests", - "scripts", - "hooks", +[tool.ruff.lint] +select = [ + "F", + "E", + "W", + "C90", + "I", + "N", + "UP", + "YTT", + # "ANN", # flake8-annotations: we should support this in the future but 100+ errors atm + "ASYNC", + "S", + "BLE", + "FBT", + "B", + "A", + "COM", + "C4", + "DTZ", + "T10", + "EM", + "EXE", + "FA", + 'ISC', + "ICN", + "G", + 'INP', + 'PIE', + "T20", + 'PYI', + 'PT', + "Q", + "RSE", + "RET", + "SLF", + "SLOT", + "SIM", + "TID", + "TCH", + "INT", + # "ARG", # Unused function argument + "PTH", + "ERA", + "PD", + "PGH", + "PL", + "TRY", + "FLY", + # "NPY", + # "AIR", + "PERF", + # "FURB", + # "LOG", + "RUF", +] +ignore = [ + "S101", # Use of assert detected https://docs.astral.sh/ruff/rules/assert/ + "RUF012", # Mutable class attributes should be annotated with `typing.ClassVar` + "SIM102", # sometimes it's better to nest + "UP038", # Checks for uses of isinstance/issubclass that take a tuple + # of types for comparison. + # Deactivated because it can make the code slow: + # https://github.com/astral-sh/ruff/issues/7871 ] +# The fixes in extend-unsafe-fixes will require +# provide the `--unsafe-fixes` flag when fixing. +extend-unsafe-fixes = [ + "UP038", +] + +[tool.ruff.lint.isort] +force-single-line = true # ==== pytest ==== diff --git a/tox.ini b/tox.ini index 70cde339f4..15c50abc25 100644 --- a/tox.ini +++ b/tox.ini @@ -5,7 +5,3 @@ envlist = py312,black-template [testenv] passenv = AUTOFIXABLE_STYLES commands = pytest -n auto {posargs:./tests} - -[testenv:black-template] -deps = black -commands = black --check hooks tests docs scripts From 122070cb8f0dc0cfb1ee0561292456f41b39102b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 31 Dec 2024 10:57:01 +0000 Subject: [PATCH 02/19] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- pyproject.toml | 100 ++++++++++++++++++++++++------------------------- 1 file changed, 48 insertions(+), 52 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9e718a3a88..0150f64565 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,84 +62,80 @@ docs = [ target-version = "py312" # Exclude the template content as most files aren't parseable extend-exclude = [ - "{{cookiecutter.project_slug}}/*", + "{{cookiecutter.project_slug}}/*", ] -[tool.ruff.lint] -select = [ - "F", - "E", - "W", - "C90", - "I", - "N", - "UP", - "YTT", +# ==== pytest ==== +lint.select = [ + "A", # "ANN", # flake8-annotations: we should support this in the future but 100+ errors atm "ASYNC", - "S", - "BLE", - "FBT", "B", - "A", - "COM", + "BLE", "C4", + "C90", + "COM", "DTZ", - "T10", + "E", "EM", + "ERA", "EXE", + "F", "FA", - 'ISC', - "ICN", + "FBT", + "FLY", "G", - 'INP', - 'PIE', - "T20", - 'PYI', - 'PT', - "Q", - "RSE", - "RET", - "SLF", - "SLOT", - "SIM", - "TID", - "TCH", + "I", + "ICN", + "INP", "INT", - # "ARG", # Unused function argument - "PTH", - "ERA", + "ISC", + "N", "PD", - "PGH", - "PL", - "TRY", - "FLY", # "NPY", # "AIR", "PERF", + "PGH", + "PIE", + "PL", + "PT", + # "ARG", # Unused function argument + "PTH", + "PYI", + "Q", + "RET", + "RSE", # "FURB", # "LOG", "RUF", + "S", + "SIM", + "SLF", + "SLOT", + "T10", + "T20", + "TCH", + "TID", + "TRY", + "UP", + "W", + "YTT", ] -ignore = [ - "S101", # Use of assert detected https://docs.astral.sh/ruff/rules/assert/ +lint.ignore = [ "RUF012", # Mutable class attributes should be annotated with `typing.ClassVar` + "S101", # Use of assert detected https://docs.astral.sh/ruff/rules/assert/ "SIM102", # sometimes it's better to nest - "UP038", # Checks for uses of isinstance/issubclass that take a tuple - # of types for comparison. - # Deactivated because it can make the code slow: - # https://github.com/astral-sh/ruff/issues/7871 + "UP038", # Checks for uses of isinstance/issubclass that take a tuple + # of types for comparison. + # Deactivated because it can make the code slow: + # https://github.com/astral-sh/ruff/issues/7871 ] # The fixes in extend-unsafe-fixes will require # provide the `--unsafe-fixes` flag when fixing. -extend-unsafe-fixes = [ - "UP038", +lint.extend-unsafe-fixes = [ + "UP038", ] - -[tool.ruff.lint.isort] -force-single-line = true - -# ==== pytest ==== +lint.isort.force-single-line = true [tool.pytest.ini_options] addopts = "-v --tb=short" From 7542965e5dd58a246193e6d107f6b9efab8bd65b Mon Sep 17 00:00:00 2001 From: Bruno Alla Date: Tue, 31 Dec 2024 10:58:43 +0000 Subject: [PATCH 03/19] Remove comments as they're wrongly placed --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 0150f64565..e03574bdf6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,7 +65,6 @@ extend-exclude = [ "{{cookiecutter.project_slug}}/*", ] -# ==== pytest ==== lint.select = [ "A", # "ANN", # flake8-annotations: we should support this in the future but 100+ errors atm From f1b173a59d36e39bbb2ed097bab23b71b3ad80cb Mon Sep 17 00:00:00 2001 From: Bruno Alla Date: Tue, 31 Dec 2024 11:00:44 +0000 Subject: [PATCH 04/19] Tweak multi-line comment --- pyproject.toml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e03574bdf6..e3d7bef26a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -124,10 +124,9 @@ lint.ignore = [ "RUF012", # Mutable class attributes should be annotated with `typing.ClassVar` "S101", # Use of assert detected https://docs.astral.sh/ruff/rules/assert/ "SIM102", # sometimes it's better to nest - "UP038", # Checks for uses of isinstance/issubclass that take a tuple - # of types for comparison. - # Deactivated because it can make the code slow: - # https://github.com/astral-sh/ruff/issues/7871 + # Checks for uses of isinstance/issubclass that take a tuple of types for comparison. + # Deactivated because it can make the code slow: https://github.com/astral-sh/ruff/issues/7871 + "UP038", ] # The fixes in extend-unsafe-fixes will require # provide the `--unsafe-fixes` flag when fixing. From 23af0dc34ace6c98da417e84f0e80495a68037e5 Mon Sep 17 00:00:00 2001 From: Bruno Alla Date: Tue, 31 Dec 2024 11:04:43 +0000 Subject: [PATCH 05/19] Remove a couple of commented out Ruff rules --- pyproject.toml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e3d7bef26a..efc3b1831e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -67,7 +67,7 @@ extend-exclude = [ lint.select = [ "A", - # "ANN", # flake8-annotations: we should support this in the future but 100+ errors atm + # "ANN", # flake8-annotations: we should support this in the future but many errors atm "ASYNC", "B", "BLE", @@ -91,8 +91,6 @@ lint.select = [ "ISC", "N", "PD", - # "NPY", - # "AIR", "PERF", "PGH", "PIE", From 97a74b9a6b65178d1f4cf6bdaebc78bbd08f5bde Mon Sep 17 00:00:00 2001 From: Bruno Alla Date: Tue, 31 Dec 2024 11:05:14 +0000 Subject: [PATCH 06/19] Fix extend-exclude in Ruff config --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index efc3b1831e..672fdb0737 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,7 +62,7 @@ docs = [ target-version = "py312" # Exclude the template content as most files aren't parseable extend-exclude = [ - "{{cookiecutter.project_slug}}/*", + "[{]{2}cookiecutter.project_slug[}]{2}/*", ] lint.select = [ From 33a1cd794649bb2872e0e43f2ed3ed937318cc9d Mon Sep 17 00:00:00 2001 From: Bruno Alla Date: Tue, 31 Dec 2024 11:25:02 +0000 Subject: [PATCH 07/19] Adjust Ruff line length --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 672fdb0737..6c32e56b7d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,6 +60,7 @@ docs = [ [tool.ruff] target-version = "py312" +line-length = 119 # Exclude the template content as most files aren't parseable extend-exclude = [ "[{]{2}cookiecutter.project_slug[}]{2}/*", From 3ccfc2141107691cb7cabd42183a2b6e6dc57568 Mon Sep 17 00:00:00 2001 From: Bruno Alla Date: Tue, 31 Dec 2024 11:25:29 +0000 Subject: [PATCH 08/19] Run Ruff pre-commit hook --- docs/conf.py | 8 ++++---- hooks/post_gen_project.py | 16 ++++++++-------- hooks/pre_gen_project.py | 4 ++-- scripts/create_django_issue.py | 7 ++++--- scripts/update_changelog.py | 2 +- tests/test_cookiecutter_generation.py | 4 ++-- tests/test_hooks.py | 2 +- 7 files changed, 22 insertions(+), 21 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 82098a168e..3b96c5017b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -188,7 +188,7 @@ "cookiecutter-django Documentation", "cookiecutter-django", "manual", - ) + ), ] # The name of an image file (relative to this directory) to place at the top of @@ -223,7 +223,7 @@ "Cookiecutter Django documentation", ["Daniel Roy Greenfeld"], 1, - ) + ), ] # If true, show URL addresses after external links. @@ -242,9 +242,9 @@ "Cookiecutter Django documentation", "Daniel Roy Greenfeld", "Cookiecutter Django", - "A Cookiecutter template for creating production-ready " "Django projects quickly.", + "A Cookiecutter template for creating production-ready Django projects quickly.", "Miscellaneous", - ) + ), ] # Documents to append as an appendix to all manuals. diff --git a/hooks/post_gen_project.py b/hooks/post_gen_project.py index 57e520924b..55c9f1ace9 100644 --- a/hooks/post_gen_project.py +++ b/hooks/post_gen_project.py @@ -39,7 +39,7 @@ def remove_custom_user_manager_files(): "{{cookiecutter.project_slug}}", "users", "managers.py", - ) + ), ) os.remove( os.path.join( @@ -47,7 +47,7 @@ def remove_custom_user_manager_files(): "users", "tests", "test_managers.py", - ) + ), ) @@ -132,7 +132,7 @@ def update_package_json(remove_dev_deps=None, remove_keys=None, scripts=None): remove_dev_deps = remove_dev_deps or [] remove_keys = remove_keys or [] scripts = scripts or {} - with open("package.json", mode="r") as fd: + with open("package.json") as fd: content = json.load(fd) for package_name in remove_dev_deps: content["devDependencies"].pop(package_name) @@ -196,7 +196,7 @@ def handle_js_runner(choice, use_docker, use_async): "dev": "concurrently npm:dev:*", "dev:webpack": "webpack serve --config webpack/dev.config.js", "dev:django": dev_django_cmd, - } + }, ) else: remove_dev_deps.append("concurrently") @@ -205,7 +205,7 @@ def handle_js_runner(choice, use_docker, use_async): def remove_prettier_pre_commit(): - with open(".pre-commit-config.yaml", "r") as fd: + with open(".pre-commit-config.yaml") as fd: content = fd.readlines() removing = False @@ -286,7 +286,7 @@ def set_flag(file_path, flag, value=None, formatted=None, *args, **kwargs): if random_string is None: print( "We couldn't find a secure pseudo-random number generator on your " - "system. Please, make sure to manually {} later.".format(flag) + f"system. Please, make sure to manually {flag} later.", ) random_string = flag if formatted is not None: @@ -464,7 +464,7 @@ def main(): print( INFO + ".env(s) are only utilized when Docker Compose and/or " "Heroku support is enabled so keeping them does not make sense " - "given your current setup." + TERMINATOR + "given your current setup." + TERMINATOR, ) remove_envs_and_associated_files() else: @@ -491,7 +491,7 @@ def main(): if "{{ cookiecutter.cloud_provider }}" == "None" and "{{ cookiecutter.use_docker }}".lower() == "n": print( WARNING + "You chose to not use any cloud providers nor Docker, " - "media files won't be served in production." + TERMINATOR + "media files won't be served in production." + TERMINATOR, ) if "{{ cookiecutter.use_celery }}".lower() == "n": diff --git a/hooks/pre_gen_project.py b/hooks/pre_gen_project.py index 1e0c591ef1..c2d6b7cd8b 100644 --- a/hooks/pre_gen_project.py +++ b/hooks/pre_gen_project.py @@ -16,9 +16,9 @@ project_slug = "{{ cookiecutter.project_slug }}" if hasattr(project_slug, "isidentifier"): - assert project_slug.isidentifier(), "'{}' project slug is not a valid Python identifier.".format(project_slug) + assert project_slug.isidentifier(), f"'{project_slug}' project slug is not a valid Python identifier." -assert project_slug == project_slug.lower(), "'{}' project slug should be all lowercase".format(project_slug) +assert project_slug == project_slug.lower(), f"'{project_slug}' project slug should be all lowercase" assert "\\" not in "{{ cookiecutter.author_name }}", "Don't include backslashes in author name." diff --git a/scripts/create_django_issue.py b/scripts/create_django_issue.py index 2e59f18b06..1fa1bcf96d 100644 --- a/scripts/create_django_issue.py +++ b/scripts/create_django_issue.py @@ -14,7 +14,9 @@ import sys from collections.abc import Iterable from pathlib import Path -from typing import TYPE_CHECKING, Any, NamedTuple +from typing import TYPE_CHECKING +from typing import Any +from typing import NamedTuple import requests from github import Github @@ -221,8 +223,7 @@ def get_compatibility(self, package_name: str, package_info: dict, needed_dj_ver if supported_dj_versions: if any(v >= needed_dj_version for v in supported_dj_versions): return package_info["info"]["version"], "✅" - else: - return "", "❌" + return "", "❌" # Django classifier DNE; assume it isn't a Django lib # Great exceptions include pylint-django, where we need to do this manually... diff --git a/scripts/update_changelog.py b/scripts/update_changelog.py index 1bedebf5a4..63c8960a38 100644 --- a/scripts/update_changelog.py +++ b/scripts/update_changelog.py @@ -54,7 +54,7 @@ def main() -> None: # Run uv lock uv_lock_path = ROOT / "uv.lock" - subprocess.run(["uv", "lock", "--no-upgrade"], cwd=ROOT) + subprocess.run(["uv", "lock", "--no-upgrade"], cwd=ROOT, check=False) # Commit changes, create tag and push update_git_repo([changelog_path, setup_py_path, uv_lock_path], release) diff --git a/tests/test_cookiecutter_generation.py b/tests/test_cookiecutter_generation.py index 9669d78bab..33289bdea6 100755 --- a/tests/test_cookiecutter_generation.py +++ b/tests/test_cookiecutter_generation.py @@ -315,7 +315,7 @@ def test_gitlab_invokes_precommit_and_pytest(cookies, context, use_docker, expec try: gitlab_config = yaml.safe_load(gitlab_yml) assert gitlab_config["precommit"]["script"] == [ - "pre-commit run --show-diff-on-failure --color=always --all-files" + "pre-commit run --show-diff-on-failure --color=always --all-files", ] assert gitlab_config["pytest"]["script"] == [expected_test_script] except yaml.YAMLError as e: @@ -401,7 +401,7 @@ def test_trim_domain_email(cookies, context): "use_docker": "y", "domain_name": " example.com ", "email": " me@example.com ", - } + }, ) result = cookies.bake(extra_context=context) diff --git a/tests/test_hooks.py b/tests/test_hooks.py index 2ccac84b23..c8d503426c 100644 --- a/tests/test_hooks.py +++ b/tests/test_hooks.py @@ -8,7 +8,7 @@ from hooks.post_gen_project import append_to_gitignore_file -@pytest.fixture() +@pytest.fixture def working_directory(tmp_path): prev_cwd = Path.cwd() os.chdir(tmp_path) From 92975986136a5ffda73eb481453c3f8c08bda98a Mon Sep 17 00:00:00 2001 From: Bruno Alla Date: Tue, 31 Dec 2024 11:26:02 +0000 Subject: [PATCH 09/19] Run Ruff with --unsafe-fixes --- hooks/post_gen_project.py | 34 +++++++-------------------- hooks/pre_gen_project.py | 2 -- scripts/create_django_issue.py | 20 ++++------------ scripts/update_changelog.py | 16 ++++--------- scripts/update_contributors.py | 5 ++-- tests/test_cookiecutter_generation.py | 8 +++---- 6 files changed, 24 insertions(+), 61 deletions(-) diff --git a/hooks/post_gen_project.py b/hooks/post_gen_project.py index 55c9f1ace9..bd7636acf0 100644 --- a/hooks/post_gen_project.py +++ b/hooks/post_gen_project.py @@ -284,10 +284,6 @@ def set_flag(file_path, flag, value=None, formatted=None, *args, **kwargs): if value is None: random_string = generate_random_string(*args, **kwargs) if random_string is None: - print( - "We couldn't find a secure pseudo-random number generator on your " - f"system. Please, make sure to manually {flag} later.", - ) random_string = flag if formatted is not None: random_string = formatted.format(random_string) @@ -303,18 +299,17 @@ def set_flag(file_path, flag, value=None, formatted=None, *args, **kwargs): def set_django_secret_key(file_path): - django_secret_key = set_flag( + return set_flag( file_path, "!!!SET DJANGO_SECRET_KEY!!!", length=64, using_digits=True, using_ascii_letters=True, ) - return django_secret_key def set_django_admin_url(file_path): - django_admin_url = set_flag( + return set_flag( file_path, "!!!SET DJANGO_ADMIN_URL!!!", formatted="{}/", @@ -322,7 +317,6 @@ def set_django_admin_url(file_path): using_digits=True, using_ascii_letters=True, ) - return django_admin_url def generate_random_user(): @@ -334,12 +328,11 @@ def generate_postgres_user(debug=False): def set_postgres_user(file_path, value): - postgres_user = set_flag(file_path, "!!!SET POSTGRES_USER!!!", value=value) - return postgres_user + return set_flag(file_path, "!!!SET POSTGRES_USER!!!", value=value) def set_postgres_password(file_path, value=None): - postgres_password = set_flag( + return set_flag( file_path, "!!!SET POSTGRES_PASSWORD!!!", value=value, @@ -347,16 +340,14 @@ def set_postgres_password(file_path, value=None): using_digits=True, using_ascii_letters=True, ) - return postgres_password def set_celery_flower_user(file_path, value): - celery_flower_user = set_flag(file_path, "!!!SET CELERY_FLOWER_USER!!!", value=value) - return celery_flower_user + return set_flag(file_path, "!!!SET CELERY_FLOWER_USER!!!", value=value) def set_celery_flower_password(file_path, value=None): - celery_flower_password = set_flag( + return set_flag( file_path, "!!!SET CELERY_FLOWER_PASSWORD!!!", value=value, @@ -364,7 +355,6 @@ def set_celery_flower_password(file_path, value=None): using_digits=True, using_ascii_letters=True, ) - return celery_flower_password def append_to_gitignore_file(ignored_line): @@ -461,11 +451,7 @@ def main(): if "{{ cookiecutter.use_docker }}".lower() == "n" and "{{ cookiecutter.use_heroku }}".lower() == "n": if "{{ cookiecutter.keep_local_envs_in_vcs }}".lower() == "y": - print( - INFO + ".env(s) are only utilized when Docker Compose and/or " - "Heroku support is enabled so keeping them does not make sense " - "given your current setup." + TERMINATOR, - ) + pass remove_envs_and_associated_files() else: append_to_gitignore_file(".env") @@ -489,10 +475,7 @@ def main(): ) if "{{ cookiecutter.cloud_provider }}" == "None" and "{{ cookiecutter.use_docker }}".lower() == "n": - print( - WARNING + "You chose to not use any cloud providers nor Docker, " - "media files won't be served in production." + TERMINATOR, - ) + pass if "{{ cookiecutter.use_celery }}".lower() == "n": remove_celery_files() @@ -517,7 +500,6 @@ def main(): if "{{ cookiecutter.use_async }}".lower() == "n": remove_async_files() - print(SUCCESS + "Project initialized, keep up the good work!" + TERMINATOR) if __name__ == "__main__": diff --git a/hooks/pre_gen_project.py b/hooks/pre_gen_project.py index c2d6b7cd8b..8786379357 100644 --- a/hooks/pre_gen_project.py +++ b/hooks/pre_gen_project.py @@ -23,9 +23,7 @@ assert "\\" not in "{{ cookiecutter.author_name }}", "Don't include backslashes in author name." if "{{ cookiecutter.use_whitenoise }}".lower() == "n" and "{{ cookiecutter.cloud_provider }}" == "None": - print("You should either use Whitenoise or select a Cloud Provider to serve static files") sys.exit(1) if "{{ cookiecutter.mail_service }}" == "Amazon SES" and "{{ cookiecutter.cloud_provider }}" != "AWS": - print("You should either use AWS or select a different Mail Service for sending emails.") sys.exit(1) diff --git a/scripts/create_django_issue.py b/scripts/create_django_issue.py index 1fa1bcf96d..dad137c860 100644 --- a/scripts/create_django_issue.py +++ b/scripts/create_django_issue.py @@ -12,7 +12,6 @@ import os import re import sys -from collections.abc import Iterable from pathlib import Path from typing import TYPE_CHECKING from typing import Any @@ -22,6 +21,8 @@ from github import Github if TYPE_CHECKING: + from collections.abc import Iterable + from github.Issue import Issue CURRENT_FILE = Path(__file__) @@ -62,7 +63,6 @@ def get_package_info(package: str) -> dict: # "django" converts to "Django" on redirect r = requests.get(f"https://pypi.org/pypi/{package}/json", allow_redirects=True) if not r.ok: - print(f"Couldn't find package: {package}") sys.exit(1) return r.json() @@ -85,7 +85,7 @@ def get_name_and_version(requirements_line: str) -> tuple[str, ...]: def get_all_latest_django_versions( - django_max_version: tuple[DjVersion] = None, + django_max_version: tuple[DjVersion] | None = None, ) -> tuple[DjVersion, list[DjVersion]]: """ Grabs all Django versions that are worthy of a GitHub issue. @@ -95,14 +95,12 @@ def get_all_latest_django_versions( if django_max_version: _django_max_version = django_max_version - print("Fetching all Django versions from PyPI") base_txt = REQUIREMENTS_DIR / "base.txt" with base_txt.open() as f: for line in f.readlines(): if "django==" in line.lower(): break else: - print(f"django not found in {base_txt}") # Huh...? sys.exit(1) # Begin parsing and verification @@ -151,7 +149,6 @@ def setup(self) -> None: self.load_existing_issues() def load_requirements(self): - print("Reading requirements") for requirements_file in self.requirements_files: with (REQUIREMENTS_DIR / f"{requirements_file}.txt").open() as f: for line in f.readlines(): @@ -170,7 +167,6 @@ def load_requirements(self): def load_existing_issues(self): """Closes the issue if the base Django version is greater than needed""" - print("Load existing issues from GitHub") qualifiers = { "repo": GITHUB_REPO, "author": "app/github-actions", @@ -179,7 +175,6 @@ def load_existing_issues(self): "in": "title", } issues = list(self.github.search_issues("[Django Update]", "created", "desc", **qualifiers)) - print(f"Found {len(issues)} issues matching search") for issue in issues: matches = re.match(r"\[Update Django] Django (\d+.\d+)$", issue.title) if not matches: @@ -263,23 +258,18 @@ def generate_markdown(self, needed_dj_version: DjVersion): def create_or_edit_issue(self, needed_dj_version: DjVersion, description: str): if issue := self.existing_issues.get(needed_dj_version): - print(f"Editing issue #{issue.number} for Django {needed_dj_version}") issue.edit(body=description) else: - print(f"Creating new issue for Django {needed_dj_version}") issue = self.repo.create_issue(f"[Update Django] Django {needed_dj_version}", description) issue.add_to_labels(f"django{needed_dj_version}") @staticmethod def close_issue(issue: Issue): issue.edit(state="closed") - print(f"Closed issue {issue.title} (ID: [{issue.id}]({issue.url}))") def generate(self): for version in self.needed_dj_versions: - print(f"Handling GitHub issue for Django {version}") md_content = self.generate_markdown(version) - print(f"Generated markdown:\n\n{md_content}") self.create_or_edit_issue(version, md_content) @@ -292,7 +282,6 @@ def main(django_max_version=None) -> None: manager.setup() if not latest_djs: - print("No new Django versions to update. Exiting...") sys.exit(0) manager.generate() @@ -300,7 +289,8 @@ def main(django_max_version=None) -> None: if __name__ == "__main__": if GITHUB_REPO is None: - raise RuntimeError("No github repo, please set the environment variable GITHUB_REPOSITORY") + msg = "No github repo, please set the environment variable GITHUB_REPOSITORY" + raise RuntimeError(msg) max_version = None last_arg = sys.argv[-1] if CURRENT_FILE.name not in last_arg: diff --git a/scripts/update_changelog.py b/scripts/update_changelog.py index 63c8960a38..cead773c49 100644 --- a/scripts/update_changelog.py +++ b/scripts/update_changelog.py @@ -26,31 +26,25 @@ def main() -> None: merged_date = dt.date.today() - dt.timedelta(days=1) repo = Github(login_or_token=GITHUB_TOKEN).get_repo(GITHUB_REPO) merged_pulls = list(iter_pulls(repo, merged_date)) - print(f"Merged pull requests: {merged_pulls}") if not merged_pulls: - print("Nothing was merged, existing.") return # Group pull requests by type of change grouped_pulls = group_pulls_by_change_type(merged_pulls) if not any(grouped_pulls.values()): - print("Pull requests merged aren't worth a changelog mention.") return # Generate portion of markdown release_changes_summary = generate_md(grouped_pulls) - print(f"Summary of changes: {release_changes_summary}") # Update CHANGELOG.md file release = f"{merged_date:%Y.%m.%d}" changelog_path = ROOT / "CHANGELOG.md" write_changelog(changelog_path, release, release_changes_summary) - print(f"Wrote {changelog_path}") # Update version setup_py_path = ROOT / "pyproject.toml" update_version(setup_py_path, release) - print(f"Updated version in {setup_py_path}") # Run uv lock uv_lock_path = ROOT / "uv.lock" @@ -60,12 +54,11 @@ def main() -> None: update_git_repo([changelog_path, setup_py_path, uv_lock_path], release) # Create GitHub release - github_release = repo.create_git_release( + repo.create_git_release( tag=release, name=release, message=release_changes_summary, ) - print(f"Created release on GitHub {github_release}") def iter_pulls( @@ -155,14 +148,15 @@ def update_git_repo(paths: list[Path], release: str) -> None: ) repo.git.tag("-a", release, m=message) server = f"https://{GITHUB_TOKEN}@github.com/{GITHUB_REPO}.git" - print(f"Pushing changes to {GIT_BRANCH} branch of {GITHUB_REPO}") repo.git.push(server, GIT_BRANCH) repo.git.push("--tags", server, GIT_BRANCH) if __name__ == "__main__": if GITHUB_REPO is None: - raise RuntimeError("No github repo, please set the environment variable GITHUB_REPOSITORY") + msg = "No github repo, please set the environment variable GITHUB_REPOSITORY" + raise RuntimeError(msg) if GIT_BRANCH is None: - raise RuntimeError("No git branch set, please set the GITHUB_REF_NAME environment variable") + msg = "No git branch set, please set the GITHUB_REF_NAME environment variable" + raise RuntimeError(msg) main() diff --git a/scripts/update_contributors.py b/scripts/update_contributors.py index 7f7b48d76d..2fbb3d9359 100644 --- a/scripts/update_contributors.py +++ b/scripts/update_contributors.py @@ -26,10 +26,8 @@ def main() -> None: # Add missing users to the JSON file contrib_file = ContributorsJSONFile() for author in recent_authors: - print(f"Checking if {author.login} should be added") if author.login not in contrib_file: contrib_file.add_contributor(author) - print(f"Added {author.login} to contributors") contrib_file.save() # Generate MD file from JSON file @@ -98,5 +96,6 @@ def write_md_file(contributors): if __name__ == "__main__": if GITHUB_REPO is None: - raise RuntimeError("No github repo, please set the environment variable GITHUB_REPOSITORY") + msg = "No github repo, please set the environment variable GITHUB_REPOSITORY" + raise RuntimeError(msg) main() diff --git a/tests/test_cookiecutter_generation.py b/tests/test_cookiecutter_generation.py index 33289bdea6..bf634c49bb 100755 --- a/tests/test_cookiecutter_generation.py +++ b/tests/test_cookiecutter_generation.py @@ -271,7 +271,7 @@ def test_djlint_check_passes(cookies, context_override): @pytest.mark.parametrize( - ["use_docker", "expected_test_script"], + ("use_docker", "expected_test_script"), [ ("n", "pytest"), ("y", "docker compose -f docker-compose.local.yml run django pytest"), @@ -296,7 +296,7 @@ def test_travis_invokes_pytest(cookies, context, use_docker, expected_test_scrip @pytest.mark.parametrize( - ["use_docker", "expected_test_script"], + ("use_docker", "expected_test_script"), [ ("n", "pytest"), ("y", "docker compose -f docker-compose.local.yml run django pytest"), @@ -323,7 +323,7 @@ def test_gitlab_invokes_precommit_and_pytest(cookies, context, use_docker, expec @pytest.mark.parametrize( - ["use_docker", "expected_test_script"], + ("use_docker", "expected_test_script"), [ ("n", "pytest"), ("y", "docker compose -f docker-compose.local.yml run django pytest"), @@ -378,7 +378,7 @@ def test_error_if_incompatible(cookies, context, invalid_context): @pytest.mark.parametrize( - ["editor", "pycharm_docs_exist"], + ("editor", "pycharm_docs_exist"), [ ("None", False), ("PyCharm", True), From 7d1e843d936368bf192d7475a85e75328b3c21bf Mon Sep 17 00:00:00 2001 From: Bruno Alla Date: Tue, 31 Dec 2024 11:26:48 +0000 Subject: [PATCH 10/19] Run Ruff with --add-noqa --- docs/conf.py | 88 ++++++++--------- hooks/post_gen_project.py | 132 +++++++++++++------------- hooks/pre_gen_project.py | 6 +- scripts/create_django_issue.py | 6 +- scripts/update_changelog.py | 4 +- tests/test_cookiecutter_generation.py | 16 ++-- 6 files changed, 126 insertions(+), 126 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 3b96c5017b..5154fbe620 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -9,17 +9,17 @@ # serve to show the default. from datetime import datetime -now = datetime.now() +now = datetime.now() # noqa: DTZ005 # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -# sys.path.insert(0, os.path.abspath('.')) +# sys.path.insert(0, os.path.abspath('.')) # noqa: ERA001 # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. -# needs_sphinx = '1.0' +# needs_sphinx = '1.0' # noqa: ERA001 # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. @@ -35,14 +35,14 @@ } # The encoding of source files. -# source_encoding = 'utf-8-sig' +# source_encoding = 'utf-8-sig' # noqa: ERA001 # The master toctree document. master_doc = "index" # General information about the project. project = "Cookiecutter Django" -copyright = f"2013-{now.year}, Daniel Roy Greenfeld" +copyright = f"2013-{now.year}, Daniel Roy Greenfeld" # noqa: A001 # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -55,37 +55,37 @@ # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -# language = None +# language = None # noqa: ERA001 # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: -# today = '' +# today = '' # noqa: ERA001 # Else, today_fmt is used as the format for a strftime call. -# today_fmt = '%B %d, %Y' +# today_fmt = '%B %d, %Y' # noqa: ERA001 # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ["_build"] # The reST default role (used for this markup: `text`) to use for all documents. -# default_role = None +# default_role = None # noqa: ERA001 # If true, '()' will be appended to :func: etc. cross-reference text. -# add_function_parentheses = True +# add_function_parentheses = True # noqa: ERA001 # If true, the current module name will be prepended to all description # unit titles (such as .. function::). -# add_module_names = True +# add_module_names = True # noqa: ERA001 # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. -# show_authors = False +# show_authors = False # noqa: ERA001 # The name of the Pygments (syntax highlighting) style to use. pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. -# modindex_common_prefix = [] +# modindex_common_prefix = [] # noqa: ERA001 # -- Options for HTML output --------------------------------------------------- @@ -97,26 +97,26 @@ # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -# html_theme_options = {} +# html_theme_options = {} # noqa: ERA001 # Add any paths that contain custom themes here, relative to this directory. -# html_theme_path = [] +# html_theme_path = [] # noqa: ERA001 # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -# html_title = None +# html_title = None # noqa: ERA001 # A shorter title for the navigation bar. Default is the same as html_title. -# html_short_title = None +# html_short_title = None # noqa: ERA001 # The name of an image file (relative to this directory) to place at the top # of the sidebar. -# html_logo = None +# html_logo = None # noqa: ERA001 # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -# html_favicon = None +# html_favicon = None # noqa: ERA001 # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, @@ -125,44 +125,44 @@ # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -# html_last_updated_fmt = '%b %d, %Y' +# html_last_updated_fmt = '%b %d, %Y' # noqa: ERA001 # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. -# html_use_smartypants = True +# html_use_smartypants = True # noqa: ERA001 # Custom sidebar templates, maps document names to template names. -# html_sidebars = {} +# html_sidebars = {} # noqa: ERA001 # Additional templates that should be rendered to pages, maps page names to # template names. -# html_additional_pages = {} +# html_additional_pages = {} # noqa: ERA001 # If false, no module index is generated. -# html_domain_indices = True +# html_domain_indices = True # noqa: ERA001 # If false, no index is generated. -# html_use_index = True +# html_use_index = True # noqa: ERA001 # If true, the index is split into individual pages for each letter. -# html_split_index = False +# html_split_index = False # noqa: ERA001 # If true, links to the reST sources are added to the pages. -# html_show_sourcelink = True +# html_show_sourcelink = True # noqa: ERA001 # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -# html_show_sphinx = True +# html_show_sphinx = True # noqa: ERA001 # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -# html_show_copyright = True +# html_show_copyright = True # noqa: ERA001 # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. -# html_use_opensearch = '' +# html_use_opensearch = '' # noqa: ERA001 # This is the file name suffix for HTML files (e.g. ".xhtml"). -# html_file_suffix = None +# html_file_suffix = None # noqa: ERA001 # Output file base name for HTML help builder. htmlhelp_basename = "cookiecutter-djangodoc" @@ -172,11 +172,11 @@ latex_elements = { # The paper size ('letterpaper' or 'a4paper'). - # 'papersize': 'letterpaper', + # 'papersize': 'letterpaper', # noqa: ERA001 # The font size ('10pt', '11pt' or '12pt'). - # 'pointsize': '10pt', + # 'pointsize': '10pt', # noqa: ERA001 # Additional stuff for the LaTeX preamble. - # 'preamble': '', + # 'preamble': '', # noqa: ERA001 } # Grouping the document tree into LaTeX files. List of tuples @@ -193,23 +193,23 @@ # The name of an image file (relative to this directory) to place at the top of # the title page. -# latex_logo = None +# latex_logo = None # noqa: ERA001 # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. -# latex_use_parts = False +# latex_use_parts = False # noqa: ERA001 # If true, show page references after internal links. -# latex_show_pagerefs = False +# latex_show_pagerefs = False # noqa: ERA001 # If true, show URL addresses after external links. -# latex_show_urls = False +# latex_show_urls = False # noqa: ERA001 # Documents to append as an appendix to all manuals. -# latex_appendices = [] +# latex_appendices = [] # noqa: ERA001 # If false, no module index is generated. -# latex_domain_indices = True +# latex_domain_indices = True # noqa: ERA001 # -- Options for manual page output -------------------------------------------- @@ -227,7 +227,7 @@ ] # If true, show URL addresses after external links. -# man_show_urls = False +# man_show_urls = False # noqa: ERA001 # -- Options for Texinfo output ------------------------------------------------ @@ -248,10 +248,10 @@ ] # Documents to append as an appendix to all manuals. -# texinfo_appendices = [] +# texinfo_appendices = [] # noqa: ERA001 # If false, no module index is generated. -# texinfo_domain_indices = True +# texinfo_domain_indices = True # noqa: ERA001 # How to display URL addresses: 'footnote', 'no', or 'inline'. -# texinfo_show_urls = 'footnote' +# texinfo_show_urls = 'footnote' # noqa: ERA001 diff --git a/hooks/post_gen_project.py b/hooks/post_gen_project.py index bd7636acf0..52516bb4b5 100644 --- a/hooks/post_gen_project.py +++ b/hooks/post_gen_project.py @@ -24,25 +24,25 @@ def remove_open_source_files(): file_names = ["CONTRIBUTORS.txt", "LICENSE"] for file_name in file_names: - os.remove(file_name) + os.remove(file_name) # noqa: PTH107 def remove_gplv3_files(): file_names = ["COPYING"] for file_name in file_names: - os.remove(file_name) + os.remove(file_name) # noqa: PTH107 def remove_custom_user_manager_files(): - os.remove( - os.path.join( + os.remove( # noqa: PTH107 + os.path.join( # noqa: PTH118 "{{cookiecutter.project_slug}}", "users", "managers.py", ), ) - os.remove( - os.path.join( + os.remove( # noqa: PTH107 + os.path.join( # noqa: PTH118 "{{cookiecutter.project_slug}}", "users", "tests", @@ -53,11 +53,11 @@ def remove_custom_user_manager_files(): def remove_pycharm_files(): idea_dir_path = ".idea" - if os.path.exists(idea_dir_path): + if os.path.exists(idea_dir_path): # noqa: PTH110 shutil.rmtree(idea_dir_path) - docs_dir_path = os.path.join("docs", "pycharm") - if os.path.exists(docs_dir_path): + docs_dir_path = os.path.join("docs", "pycharm") # noqa: PTH118 + if os.path.exists(docs_dir_path): # noqa: PTH110 shutil.rmtree(docs_dir_path) @@ -71,15 +71,15 @@ def remove_docker_files(): ".dockerignore", ] for file_name in file_names: - os.remove(file_name) - if "{{ cookiecutter.editor }}" == "PyCharm": + os.remove(file_name) # noqa: PTH107 + if "{{ cookiecutter.editor }}" == "PyCharm": # noqa: PLR0133 file_names = ["docker_compose_up_django.xml", "docker_compose_up_docs.xml"] for file_name in file_names: - os.remove(os.path.join(".idea", "runConfigurations", file_name)) + os.remove(os.path.join(".idea", "runConfigurations", file_name)) # noqa: PTH107, PTH118 def remove_nginx_docker_files(): - shutil.rmtree(os.path.join("compose", "production", "nginx")) + shutil.rmtree(os.path.join("compose", "production", "nginx")) # noqa: PTH118 def remove_utility_files(): @@ -92,18 +92,18 @@ def remove_heroku_files(): if file_name == "requirements.txt" and "{{ cookiecutter.ci_tool }}".lower() == "travis": # don't remove the file if we are using travisci but not using heroku continue - os.remove(file_name) + os.remove(file_name) # noqa: PTH107 shutil.rmtree("bin") def remove_sass_files(): - shutil.rmtree(os.path.join("{{cookiecutter.project_slug}}", "static", "sass")) + shutil.rmtree(os.path.join("{{cookiecutter.project_slug}}", "static", "sass")) # noqa: PTH118 def remove_gulp_files(): file_names = ["gulpfile.mjs"] for file_name in file_names: - os.remove(file_name) + os.remove(file_name) # noqa: PTH107 def remove_webpack_files(): @@ -112,34 +112,34 @@ def remove_webpack_files(): def remove_vendors_js(): - vendors_js_path = os.path.join( + vendors_js_path = os.path.join( # noqa: PTH118 "{{ cookiecutter.project_slug }}", "static", "js", "vendors.js", ) - if os.path.exists(vendors_js_path): - os.remove(vendors_js_path) + if os.path.exists(vendors_js_path): # noqa: PTH110 + os.remove(vendors_js_path) # noqa: PTH107 def remove_packagejson_file(): file_names = ["package.json"] for file_name in file_names: - os.remove(file_name) + os.remove(file_name) # noqa: PTH107 def update_package_json(remove_dev_deps=None, remove_keys=None, scripts=None): remove_dev_deps = remove_dev_deps or [] remove_keys = remove_keys or [] scripts = scripts or {} - with open("package.json") as fd: + with open("package.json") as fd: # noqa: PTH123 content = json.load(fd) for package_name in remove_dev_deps: content["devDependencies"].pop(package_name) for key in remove_keys: content.pop(key) content["scripts"].update(scripts) - with open("package.json", mode="w") as fd: + with open("package.json", mode="w") as fd: # noqa: PTH123 json.dump(content, fd, ensure_ascii=False, indent=2) fd.write("\n") @@ -205,7 +205,7 @@ def handle_js_runner(choice, use_docker, use_async): def remove_prettier_pre_commit(): - with open(".pre-commit-config.yaml") as fd: + with open(".pre-commit-config.yaml") as fd: # noqa: PTH123 content = fd.readlines() removing = False @@ -218,35 +218,35 @@ def remove_prettier_pre_commit(): if not removing: new_lines.append(line) - with open(".pre-commit-config.yaml", "w") as fd: + with open(".pre-commit-config.yaml", "w") as fd: # noqa: PTH123 fd.writelines(new_lines) def remove_celery_files(): file_names = [ - os.path.join("config", "celery_app.py"), - os.path.join("{{ cookiecutter.project_slug }}", "users", "tasks.py"), - os.path.join("{{ cookiecutter.project_slug }}", "users", "tests", "test_tasks.py"), + os.path.join("config", "celery_app.py"), # noqa: PTH118 + os.path.join("{{ cookiecutter.project_slug }}", "users", "tasks.py"), # noqa: PTH118 + os.path.join("{{ cookiecutter.project_slug }}", "users", "tests", "test_tasks.py"), # noqa: PTH118 ] for file_name in file_names: - os.remove(file_name) + os.remove(file_name) # noqa: PTH107 def remove_async_files(): file_names = [ - os.path.join("config", "asgi.py"), - os.path.join("config", "websocket.py"), + os.path.join("config", "asgi.py"), # noqa: PTH118 + os.path.join("config", "websocket.py"), # noqa: PTH118 ] for file_name in file_names: - os.remove(file_name) + os.remove(file_name) # noqa: PTH107 def remove_dottravisyml_file(): - os.remove(".travis.yml") + os.remove(".travis.yml") # noqa: PTH107 def remove_dotgitlabciyml_file(): - os.remove(".gitlab-ci.yml") + os.remove(".gitlab-ci.yml") # noqa: PTH107 def remove_dotgithub_folder(): @@ -254,10 +254,10 @@ def remove_dotgithub_folder(): def remove_dotdrone_file(): - os.remove(".drone.yml") + os.remove(".drone.yml") # noqa: PTH107 -def generate_random_string(length, using_digits=False, using_ascii_letters=False, using_punctuation=False): +def generate_random_string(length, using_digits=False, using_ascii_letters=False, using_punctuation=False): # noqa: FBT002 """ Example: opting out for 50 symbol-long, [a-z][A-Z][0-9] string @@ -289,7 +289,7 @@ def set_flag(file_path, flag, value=None, formatted=None, *args, **kwargs): random_string = formatted.format(random_string) value = random_string - with open(file_path, "r+") as f: + with open(file_path, "r+") as f: # noqa: PTH123 file_contents = f.read().replace(flag, value) f.seek(0) f.write(file_contents) @@ -323,7 +323,7 @@ def generate_random_user(): return generate_random_string(length=32, using_ascii_letters=True) -def generate_postgres_user(debug=False): +def generate_postgres_user(debug=False): # noqa: FBT002 return DEBUG_VALUE if debug else generate_random_user() @@ -358,16 +358,16 @@ def set_celery_flower_password(file_path, value=None): def append_to_gitignore_file(ignored_line): - with open(".gitignore", "a") as gitignore_file: + with open(".gitignore", "a") as gitignore_file: # noqa: PTH123 gitignore_file.write(ignored_line) gitignore_file.write("\n") -def set_flags_in_envs(postgres_user, celery_flower_user, debug=False): - local_django_envs_path = os.path.join(".envs", ".local", ".django") - production_django_envs_path = os.path.join(".envs", ".production", ".django") - local_postgres_envs_path = os.path.join(".envs", ".local", ".postgres") - production_postgres_envs_path = os.path.join(".envs", ".production", ".postgres") +def set_flags_in_envs(postgres_user, celery_flower_user, debug=False): # noqa: FBT002 + local_django_envs_path = os.path.join(".envs", ".local", ".django") # noqa: PTH118 + production_django_envs_path = os.path.join(".envs", ".production", ".django") # noqa: PTH118 + local_postgres_envs_path = os.path.join(".envs", ".local", ".postgres") # noqa: PTH118 + production_postgres_envs_path = os.path.join(".envs", ".production", ".postgres") # noqa: PTH118 set_django_secret_key(production_django_envs_path) set_django_admin_url(production_django_envs_path) @@ -384,38 +384,38 @@ def set_flags_in_envs(postgres_user, celery_flower_user, debug=False): def set_flags_in_settings_files(): - set_django_secret_key(os.path.join("config", "settings", "local.py")) - set_django_secret_key(os.path.join("config", "settings", "test.py")) + set_django_secret_key(os.path.join("config", "settings", "local.py")) # noqa: PTH118 + set_django_secret_key(os.path.join("config", "settings", "test.py")) # noqa: PTH118 def remove_envs_and_associated_files(): shutil.rmtree(".envs") - os.remove("merge_production_dotenvs_in_dotenv.py") + os.remove("merge_production_dotenvs_in_dotenv.py") # noqa: PTH107 shutil.rmtree("tests") def remove_celery_compose_dirs(): - shutil.rmtree(os.path.join("compose", "local", "django", "celery")) - shutil.rmtree(os.path.join("compose", "production", "django", "celery")) + shutil.rmtree(os.path.join("compose", "local", "django", "celery")) # noqa: PTH118 + shutil.rmtree(os.path.join("compose", "production", "django", "celery")) # noqa: PTH118 def remove_node_dockerfile(): - shutil.rmtree(os.path.join("compose", "local", "node")) + shutil.rmtree(os.path.join("compose", "local", "node")) # noqa: PTH118 def remove_aws_dockerfile(): - shutil.rmtree(os.path.join("compose", "production", "aws")) + shutil.rmtree(os.path.join("compose", "production", "aws")) # noqa: PTH118 def remove_drf_starter_files(): - os.remove(os.path.join("config", "api_router.py")) - shutil.rmtree(os.path.join("{{cookiecutter.project_slug}}", "users", "api")) - os.remove(os.path.join("{{cookiecutter.project_slug}}", "users", "tests", "test_drf_urls.py")) - os.remove(os.path.join("{{cookiecutter.project_slug}}", "users", "tests", "test_drf_views.py")) - os.remove(os.path.join("{{cookiecutter.project_slug}}", "users", "tests", "test_swagger.py")) + os.remove(os.path.join("config", "api_router.py")) # noqa: PTH107, PTH118 + shutil.rmtree(os.path.join("{{cookiecutter.project_slug}}", "users", "api")) # noqa: PTH118 + os.remove(os.path.join("{{cookiecutter.project_slug}}", "users", "tests", "test_drf_urls.py")) # noqa: PTH107, PTH118 + os.remove(os.path.join("{{cookiecutter.project_slug}}", "users", "tests", "test_drf_views.py")) # noqa: PTH107, PTH118 + os.remove(os.path.join("{{cookiecutter.project_slug}}", "users", "tests", "test_swagger.py")) # noqa: PTH107, PTH118 -def main(): +def main(): # noqa: C901, PLR0912, PLR0915 debug = "{{ cookiecutter.debug }}".lower() == "y" set_flags_in_envs( @@ -425,15 +425,15 @@ def main(): ) set_flags_in_settings_files() - if "{{ cookiecutter.open_source_license }}" == "Not open source": + if "{{ cookiecutter.open_source_license }}" == "Not open source": # noqa: PLR0133 remove_open_source_files() - if "{{ cookiecutter.open_source_license}}" != "GPLv3": + if "{{ cookiecutter.open_source_license}}" != "GPLv3": # noqa: PLR0133 remove_gplv3_files() - if "{{ cookiecutter.username_type }}" == "username": + if "{{ cookiecutter.username_type }}" == "username": # noqa: PLR0133 remove_custom_user_manager_files() - if "{{ cookiecutter.editor }}" != "PyCharm": + if "{{ cookiecutter.editor }}" != "PyCharm": # noqa: PLR0133 remove_pycharm_files() if "{{ cookiecutter.use_docker }}".lower() == "y": @@ -443,7 +443,7 @@ def main(): else: remove_docker_files() - if "{{ cookiecutter.use_docker }}".lower() == "y" and "{{ cookiecutter.cloud_provider}}" != "AWS": + if "{{ cookiecutter.use_docker }}".lower() == "y" and "{{ cookiecutter.cloud_provider}}" != "AWS": # noqa: PLR0133 remove_aws_dockerfile() if "{{ cookiecutter.use_heroku }}".lower() == "n": @@ -474,7 +474,7 @@ def main(): use_async=("{{ cookiecutter.use_async }}".lower() == "y"), ) - if "{{ cookiecutter.cloud_provider }}" == "None" and "{{ cookiecutter.use_docker }}".lower() == "n": + if "{{ cookiecutter.cloud_provider }}" == "None" and "{{ cookiecutter.use_docker }}".lower() == "n": # noqa: PLR0133 pass if "{{ cookiecutter.use_celery }}".lower() == "n": @@ -482,16 +482,16 @@ def main(): if "{{ cookiecutter.use_docker }}".lower() == "y": remove_celery_compose_dirs() - if "{{ cookiecutter.ci_tool }}" != "Travis": + if "{{ cookiecutter.ci_tool }}" != "Travis": # noqa: PLR0133 remove_dottravisyml_file() - if "{{ cookiecutter.ci_tool }}" != "Gitlab": + if "{{ cookiecutter.ci_tool }}" != "Gitlab": # noqa: PLR0133 remove_dotgitlabciyml_file() - if "{{ cookiecutter.ci_tool }}" != "Github": + if "{{ cookiecutter.ci_tool }}" != "Github": # noqa: PLR0133 remove_dotgithub_folder() - if "{{ cookiecutter.ci_tool }}" != "Drone": + if "{{ cookiecutter.ci_tool }}" != "Drone": # noqa: PLR0133 remove_dotdrone_file() if "{{ cookiecutter.use_drf }}".lower() == "n": diff --git a/hooks/pre_gen_project.py b/hooks/pre_gen_project.py index 8786379357..0ab357de55 100644 --- a/hooks/pre_gen_project.py +++ b/hooks/pre_gen_project.py @@ -20,10 +20,10 @@ assert project_slug == project_slug.lower(), f"'{project_slug}' project slug should be all lowercase" -assert "\\" not in "{{ cookiecutter.author_name }}", "Don't include backslashes in author name." +assert "\\" not in "{{ cookiecutter.author_name }}", "Don't include backslashes in author name." # noqa: PLR0133 -if "{{ cookiecutter.use_whitenoise }}".lower() == "n" and "{{ cookiecutter.cloud_provider }}" == "None": +if "{{ cookiecutter.use_whitenoise }}".lower() == "n" and "{{ cookiecutter.cloud_provider }}" == "None": # noqa: PLR0133 sys.exit(1) -if "{{ cookiecutter.mail_service }}" == "Amazon SES" and "{{ cookiecutter.cloud_provider }}" != "AWS": +if "{{ cookiecutter.mail_service }}" == "Amazon SES" and "{{ cookiecutter.cloud_provider }}" != "AWS": # noqa: PLR0133 sys.exit(1) diff --git a/scripts/create_django_issue.py b/scripts/create_django_issue.py index dad137c860..2ad2ac1474 100644 --- a/scripts/create_django_issue.py +++ b/scripts/create_django_issue.py @@ -61,7 +61,7 @@ def parse_to_tuple(cls, version_str: str): def get_package_info(package: str) -> dict: """Get package metadata using PyPI API.""" # "django" converts to "Django" on redirect - r = requests.get(f"https://pypi.org/pypi/{package}/json", allow_redirects=True) + r = requests.get(f"https://pypi.org/pypi/{package}/json", allow_redirects=True) # noqa: S113 if not r.ok: sys.exit(1) return r.json() @@ -210,9 +210,9 @@ def get_compatibility(self, package_name: str, package_info: dict, needed_dj_ver for classifier in package_info["info"]["classifiers"]: # Usually in the form of "Framework :: Django :: 3.2" tokens = classifier.split(" ") - if len(tokens) >= 5 and tokens[2].lower() == "django" and "." in tokens[4]: + if len(tokens) >= 5 and tokens[2].lower() == "django" and "." in tokens[4]: # noqa: PLR2004 version = DjVersion.parse(tokens[4]) - if len(version) == 2: + if len(version) == 2: # noqa: PLR2004 supported_dj_versions.append(version) if supported_dj_versions: diff --git a/scripts/update_changelog.py b/scripts/update_changelog.py index cead773c49..b31580be89 100644 --- a/scripts/update_changelog.py +++ b/scripts/update_changelog.py @@ -23,7 +23,7 @@ def main() -> None: Script entry point. """ # Generate changelog for PRs merged yesterday - merged_date = dt.date.today() - dt.timedelta(days=1) + merged_date = dt.date.today() - dt.timedelta(days=1) # noqa: DTZ011 repo = Github(login_or_token=GITHUB_TOKEN).get_repo(GITHUB_REPO) merged_pulls = list(iter_pulls(repo, merged_date)) if not merged_pulls: @@ -48,7 +48,7 @@ def main() -> None: # Run uv lock uv_lock_path = ROOT / "uv.lock" - subprocess.run(["uv", "lock", "--no-upgrade"], cwd=ROOT, check=False) + subprocess.run(["uv", "lock", "--no-upgrade"], cwd=ROOT, check=False) # noqa: S603, S607 # Commit changes, create tag and push update_git_repo([changelog_path, setup_py_path, uv_lock_path], release) diff --git a/tests/test_cookiecutter_generation.py b/tests/test_cookiecutter_generation.py index bf634c49bb..45462637bd 100755 --- a/tests/test_cookiecutter_generation.py +++ b/tests/test_cookiecutter_generation.py @@ -1,4 +1,4 @@ -import glob +import glob # noqa: EXE002 import os import re import sys @@ -148,7 +148,7 @@ def _fixture_id(ctx): def build_files_list(base_dir): """Build a list containing absolute paths to the generated files.""" - return [os.path.join(dirpath, file_path) for dirpath, subdirs, files in os.walk(base_dir) for file_path in files] + return [os.path.join(dirpath, file_path) for dirpath, subdirs, files in os.walk(base_dir) for file_path in files] # noqa: PTH118 def check_paths(paths): @@ -158,7 +158,7 @@ def check_paths(paths): if is_binary(path): continue - for line in open(path): + for line in open(path): # noqa: SIM115, PTH123 match = RE_OBJ.search(line) assert match is None, f"cookiecutter variable not replaced in {path}" @@ -225,7 +225,7 @@ def test_django_upgrade_passes(cookies, context_override): python_files = [ file_path.removeprefix(f"{result.project_path}/") - for file_path in glob.glob(str(result.project_path / "**" / "*.py"), recursive=True) + for file_path in glob.glob(str(result.project_path / "**" / "*.py"), recursive=True) # noqa: PTH207 ] try: sh.django_upgrade( @@ -286,7 +286,7 @@ def test_travis_invokes_pytest(cookies, context, use_docker, expected_test_scrip assert result.project_path.name == context["project_slug"] assert result.project_path.is_dir() - with open(f"{result.project_path}/.travis.yml") as travis_yml: + with open(f"{result.project_path}/.travis.yml") as travis_yml: # noqa: PTH123 try: yml = yaml.safe_load(travis_yml)["jobs"]["include"] assert yml[0]["script"] == ["ruff check ."] @@ -311,7 +311,7 @@ def test_gitlab_invokes_precommit_and_pytest(cookies, context, use_docker, expec assert result.project_path.name == context["project_slug"] assert result.project_path.is_dir() - with open(f"{result.project_path}/.gitlab-ci.yml") as gitlab_yml: + with open(f"{result.project_path}/.gitlab-ci.yml") as gitlab_yml: # noqa: PTH123 try: gitlab_config = yaml.safe_load(gitlab_yml) assert gitlab_config["precommit"]["script"] == [ @@ -338,7 +338,7 @@ def test_github_invokes_linter_and_pytest(cookies, context, use_docker, expected assert result.project_path.name == context["project_slug"] assert result.project_path.is_dir() - with open(f"{result.project_path}/.github/workflows/ci.yml") as github_yml: + with open(f"{result.project_path}/.github/workflows/ci.yml") as github_yml: # noqa: PTH123 try: github_config = yaml.safe_load(github_yml) linter_present = False @@ -389,7 +389,7 @@ def test_pycharm_docs_removed(cookies, context, editor, pycharm_docs_exist): context.update({"editor": editor}) result = cookies.bake(extra_context=context) - with open(f"{result.project_path}/docs/index.rst") as f: + with open(f"{result.project_path}/docs/index.rst") as f: # noqa: PTH123 has_pycharm_docs = "pycharm/configuration" in f.read() assert has_pycharm_docs is pycharm_docs_exist From 448fb81f6b8381cf47487833bceaaf72e9d56649 Mon Sep 17 00:00:00 2001 From: Bruno Alla Date: Tue, 31 Dec 2024 11:28:46 +0000 Subject: [PATCH 11/19] Run Ruff formatter --- hooks/post_gen_project.py | 1 - 1 file changed, 1 deletion(-) diff --git a/hooks/post_gen_project.py b/hooks/post_gen_project.py index 52516bb4b5..ad1c29d87b 100644 --- a/hooks/post_gen_project.py +++ b/hooks/post_gen_project.py @@ -501,6 +501,5 @@ def main(): # noqa: C901, PLR0912, PLR0915 remove_async_files() - if __name__ == "__main__": main() From 304acaea72da66510384b75782607869e835d81a Mon Sep 17 00:00:00 2001 From: Bruno Alla Date: Tue, 31 Dec 2024 11:34:15 +0000 Subject: [PATCH 12/19] Drop Python 2 in pre/post generation hooks --- hooks/__init__.py | 0 pyproject.toml | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 hooks/__init__.py diff --git a/hooks/__init__.py b/hooks/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/pyproject.toml b/pyproject.toml index 6c32e56b7d..fe6fcca99c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,7 +59,7 @@ docs = [ ] [tool.ruff] -target-version = "py312" +target-version = "py39" line-length = 119 # Exclude the template content as most files aren't parseable extend-exclude = [ From 972a072b12cd0ed3a9ba6505f0e573372412e4b4 Mon Sep 17 00:00:00 2001 From: Bruno Alla Date: Tue, 31 Dec 2024 11:41:24 +0000 Subject: [PATCH 13/19] Restore print statements in pre/post-generation hooks --- hooks/post_gen_project.py | 17 +++++++++++++++-- hooks/pre_gen_project.py | 2 ++ pyproject.toml | 1 - 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/hooks/post_gen_project.py b/hooks/post_gen_project.py index ad1c29d87b..ffcbd843ef 100644 --- a/hooks/post_gen_project.py +++ b/hooks/post_gen_project.py @@ -284,6 +284,10 @@ def set_flag(file_path, flag, value=None, formatted=None, *args, **kwargs): if value is None: random_string = generate_random_string(*args, **kwargs) if random_string is None: + print( + "We couldn't find a secure pseudo-random number generator on your " + f"system. Please, make sure to manually {flag} later.", + ) random_string = flag if formatted is not None: random_string = formatted.format(random_string) @@ -451,7 +455,11 @@ def main(): # noqa: C901, PLR0912, PLR0915 if "{{ cookiecutter.use_docker }}".lower() == "n" and "{{ cookiecutter.use_heroku }}".lower() == "n": if "{{ cookiecutter.keep_local_envs_in_vcs }}".lower() == "y": - pass + print( + INFO + ".env(s) are only utilized when Docker Compose and/or " + "Heroku support is enabled so keeping them does not make sense " + "given your current setup." + TERMINATOR, + ) remove_envs_and_associated_files() else: append_to_gitignore_file(".env") @@ -475,7 +483,10 @@ def main(): # noqa: C901, PLR0912, PLR0915 ) if "{{ cookiecutter.cloud_provider }}" == "None" and "{{ cookiecutter.use_docker }}".lower() == "n": # noqa: PLR0133 - pass + print( + WARNING + "You chose to not use any cloud providers nor Docker, " + "media files won't be served in production." + TERMINATOR, + ) if "{{ cookiecutter.use_celery }}".lower() == "n": remove_celery_files() @@ -500,6 +511,8 @@ def main(): # noqa: C901, PLR0912, PLR0915 if "{{ cookiecutter.use_async }}".lower() == "n": remove_async_files() + print(SUCCESS + "Project initialized, keep up the good work!" + TERMINATOR) + if __name__ == "__main__": main() diff --git a/hooks/pre_gen_project.py b/hooks/pre_gen_project.py index 0ab357de55..1c8b738995 100644 --- a/hooks/pre_gen_project.py +++ b/hooks/pre_gen_project.py @@ -23,7 +23,9 @@ assert "\\" not in "{{ cookiecutter.author_name }}", "Don't include backslashes in author name." # noqa: PLR0133 if "{{ cookiecutter.use_whitenoise }}".lower() == "n" and "{{ cookiecutter.cloud_provider }}" == "None": # noqa: PLR0133 + print("You should either use Whitenoise or select a Cloud Provider to serve static files") sys.exit(1) if "{{ cookiecutter.mail_service }}" == "Amazon SES" and "{{ cookiecutter.cloud_provider }}" != "AWS": # noqa: PLR0133 + print("You should either use AWS or select a different Mail Service for sending emails.") sys.exit(1) diff --git a/pyproject.toml b/pyproject.toml index fe6fcca99c..8c7b2f5c50 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -111,7 +111,6 @@ lint.select = [ "SLF", "SLOT", "T10", - "T20", "TCH", "TID", "TRY", From e5acc9fec9261ac018b004ef3ec8b3a50e973f1c Mon Sep 17 00:00:00 2001 From: Bruno Alla Date: Tue, 31 Dec 2024 11:46:26 +0000 Subject: [PATCH 14/19] Restore print statements in scripts --- scripts/create_django_issue.py | 12 ++++++++++++ scripts/update_changelog.py | 10 +++++++++- scripts/update_contributors.py | 2 ++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/scripts/create_django_issue.py b/scripts/create_django_issue.py index 2ad2ac1474..cae69d4efa 100644 --- a/scripts/create_django_issue.py +++ b/scripts/create_django_issue.py @@ -63,6 +63,7 @@ def get_package_info(package: str) -> dict: # "django" converts to "Django" on redirect r = requests.get(f"https://pypi.org/pypi/{package}/json", allow_redirects=True) # noqa: S113 if not r.ok: + print(f"Couldn't find package: {package}") sys.exit(1) return r.json() @@ -95,12 +96,14 @@ def get_all_latest_django_versions( if django_max_version: _django_max_version = django_max_version + print("Fetching all Django versions from PyPI") base_txt = REQUIREMENTS_DIR / "base.txt" with base_txt.open() as f: for line in f.readlines(): if "django==" in line.lower(): break else: + print(f"django not found in {base_txt}") # Huh...? sys.exit(1) # Begin parsing and verification @@ -149,6 +152,7 @@ def setup(self) -> None: self.load_existing_issues() def load_requirements(self): + print("Reading requirements") for requirements_file in self.requirements_files: with (REQUIREMENTS_DIR / f"{requirements_file}.txt").open() as f: for line in f.readlines(): @@ -167,6 +171,7 @@ def load_requirements(self): def load_existing_issues(self): """Closes the issue if the base Django version is greater than needed""" + print("Load existing issues from GitHub") qualifiers = { "repo": GITHUB_REPO, "author": "app/github-actions", @@ -175,6 +180,7 @@ def load_existing_issues(self): "in": "title", } issues = list(self.github.search_issues("[Django Update]", "created", "desc", **qualifiers)) + print(f"Found {len(issues)} issues matching search") for issue in issues: matches = re.match(r"\[Update Django] Django (\d+.\d+)$", issue.title) if not matches: @@ -258,18 +264,23 @@ def generate_markdown(self, needed_dj_version: DjVersion): def create_or_edit_issue(self, needed_dj_version: DjVersion, description: str): if issue := self.existing_issues.get(needed_dj_version): + print(f"Editing issue #{issue.number} for Django {needed_dj_version}") issue.edit(body=description) else: + print(f"Creating new issue for Django {needed_dj_version}") issue = self.repo.create_issue(f"[Update Django] Django {needed_dj_version}", description) issue.add_to_labels(f"django{needed_dj_version}") @staticmethod def close_issue(issue: Issue): issue.edit(state="closed") + print(f"Closed issue {issue.title} (ID: [{issue.id}]({issue.url}))") def generate(self): for version in self.needed_dj_versions: + print(f"Handling GitHub issue for Django {version}") md_content = self.generate_markdown(version) + print(f"Generated markdown:\n\n{md_content}") self.create_or_edit_issue(version, md_content) @@ -282,6 +293,7 @@ def main(django_max_version=None) -> None: manager.setup() if not latest_djs: + print("No new Django versions to update. Exiting...") sys.exit(0) manager.generate() diff --git a/scripts/update_changelog.py b/scripts/update_changelog.py index b31580be89..cc21904721 100644 --- a/scripts/update_changelog.py +++ b/scripts/update_changelog.py @@ -26,25 +26,31 @@ def main() -> None: merged_date = dt.date.today() - dt.timedelta(days=1) # noqa: DTZ011 repo = Github(login_or_token=GITHUB_TOKEN).get_repo(GITHUB_REPO) merged_pulls = list(iter_pulls(repo, merged_date)) + print(f"Merged pull requests: {merged_pulls}") if not merged_pulls: + print("Nothing was merged, existing.") return # Group pull requests by type of change grouped_pulls = group_pulls_by_change_type(merged_pulls) if not any(grouped_pulls.values()): + print("Pull requests merged aren't worth a changelog mention.") return # Generate portion of markdown release_changes_summary = generate_md(grouped_pulls) + print(f"Summary of changes: {release_changes_summary}") # Update CHANGELOG.md file release = f"{merged_date:%Y.%m.%d}" changelog_path = ROOT / "CHANGELOG.md" write_changelog(changelog_path, release, release_changes_summary) + print(f"Wrote {changelog_path}") # Update version setup_py_path = ROOT / "pyproject.toml" update_version(setup_py_path, release) + print(f"Updated version in {setup_py_path}") # Run uv lock uv_lock_path = ROOT / "uv.lock" @@ -54,11 +60,12 @@ def main() -> None: update_git_repo([changelog_path, setup_py_path, uv_lock_path], release) # Create GitHub release - repo.create_git_release( + github_release = repo.create_git_release( tag=release, name=release, message=release_changes_summary, ) + print(f"Created release on GitHub {github_release}") def iter_pulls( @@ -148,6 +155,7 @@ def update_git_repo(paths: list[Path], release: str) -> None: ) repo.git.tag("-a", release, m=message) server = f"https://{GITHUB_TOKEN}@github.com/{GITHUB_REPO}.git" + print(f"Pushing changes to {GIT_BRANCH} branch of {GITHUB_REPO}") repo.git.push(server, GIT_BRANCH) repo.git.push("--tags", server, GIT_BRANCH) diff --git a/scripts/update_contributors.py b/scripts/update_contributors.py index 2fbb3d9359..1738caf497 100644 --- a/scripts/update_contributors.py +++ b/scripts/update_contributors.py @@ -26,8 +26,10 @@ def main() -> None: # Add missing users to the JSON file contrib_file = ContributorsJSONFile() for author in recent_authors: + print(f"Checking if {author.login} should be added") if author.login not in contrib_file: contrib_file.add_contributor(author) + print(f"Added {author.login} to contributors") contrib_file.save() # Generate MD file from JSON file From 2a6be9eec0f0449ff895fec31c8ef77ec14f0df8 Mon Sep 17 00:00:00 2001 From: Bruno Alla Date: Tue, 31 Dec 2024 11:49:10 +0000 Subject: [PATCH 15/19] Indent toml with 2 spaces --- {{cookiecutter.project_slug}}/.editorconfig | 2 +- {{cookiecutter.project_slug}}/pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/{{cookiecutter.project_slug}}/.editorconfig b/{{cookiecutter.project_slug}}/.editorconfig index c0ce342602..4aa03505b6 100644 --- a/{{cookiecutter.project_slug}}/.editorconfig +++ b/{{cookiecutter.project_slug}}/.editorconfig @@ -12,7 +12,7 @@ trim_trailing_whitespace = true indent_style = space indent_size = 4 -[*.{html,css,scss,json,yml,xml}] +[*.{html,css,scss,json,yml,xml,toml}] indent_style = space indent_size = 2 diff --git a/{{cookiecutter.project_slug}}/pyproject.toml b/{{cookiecutter.project_slug}}/pyproject.toml index 98a883b620..f6bf8074ee 100644 --- a/{{cookiecutter.project_slug}}/pyproject.toml +++ b/{{cookiecutter.project_slug}}/pyproject.toml @@ -135,7 +135,7 @@ ignore = [ # The fixes in extend-unsafe-fixes will require # provide the `--unsafe-fixes` flag when fixing. extend-unsafe-fixes = [ - "UP038", + "UP038", ] [tool.ruff.lint.isort] From 2747e02e68082e9a97349490491f02b7708b5d0f Mon Sep 17 00:00:00 2001 From: Bruno Alla Date: Tue, 31 Dec 2024 11:51:30 +0000 Subject: [PATCH 16/19] Exclude docs and revert most changes from Ruff --- docs/conf.py | 90 +++++++++++++++++++++++++------------------------- pyproject.toml | 1 + 2 files changed, 46 insertions(+), 45 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 5154fbe620..1b057bb4d1 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -9,17 +9,17 @@ # serve to show the default. from datetime import datetime -now = datetime.now() # noqa: DTZ005 +now = datetime.now() # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -# sys.path.insert(0, os.path.abspath('.')) # noqa: ERA001 +# sys.path.insert(0, os.path.abspath('.')) # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. -# needs_sphinx = '1.0' # noqa: ERA001 +# needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. @@ -35,14 +35,14 @@ } # The encoding of source files. -# source_encoding = 'utf-8-sig' # noqa: ERA001 +# source_encoding = 'utf-8-sig' # The master toctree document. master_doc = "index" # General information about the project. project = "Cookiecutter Django" -copyright = f"2013-{now.year}, Daniel Roy Greenfeld" # noqa: A001 +copyright = f"2013-{now.year}, Daniel Roy Greenfeld" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -55,37 +55,37 @@ # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -# language = None # noqa: ERA001 +# language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: -# today = '' # noqa: ERA001 +# today = '' # Else, today_fmt is used as the format for a strftime call. -# today_fmt = '%B %d, %Y' # noqa: ERA001 +# today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ["_build"] # The reST default role (used for this markup: `text`) to use for all documents. -# default_role = None # noqa: ERA001 +# default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. -# add_function_parentheses = True # noqa: ERA001 +# add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). -# add_module_names = True # noqa: ERA001 +# add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. -# show_authors = False # noqa: ERA001 +# show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. -# modindex_common_prefix = [] # noqa: ERA001 +# modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- @@ -97,26 +97,26 @@ # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -# html_theme_options = {} # noqa: ERA001 +# html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. -# html_theme_path = [] # noqa: ERA001 +# html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -# html_title = None # noqa: ERA001 +# html_title = None # A shorter title for the navigation bar. Default is the same as html_title. -# html_short_title = None # noqa: ERA001 +# html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. -# html_logo = None # noqa: ERA001 +# html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -# html_favicon = None # noqa: ERA001 +# html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, @@ -125,44 +125,44 @@ # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -# html_last_updated_fmt = '%b %d, %Y' # noqa: ERA001 +# html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. -# html_use_smartypants = True # noqa: ERA001 +# html_use_smartypants = True # Custom sidebar templates, maps document names to template names. -# html_sidebars = {} # noqa: ERA001 +# html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. -# html_additional_pages = {} # noqa: ERA001 +# html_additional_pages = {} # If false, no module index is generated. -# html_domain_indices = True # noqa: ERA001 +# html_domain_indices = True # If false, no index is generated. -# html_use_index = True # noqa: ERA001 +# html_use_index = True # If true, the index is split into individual pages for each letter. -# html_split_index = False # noqa: ERA001 +# html_split_index = False # If true, links to the reST sources are added to the pages. -# html_show_sourcelink = True # noqa: ERA001 +# html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -# html_show_sphinx = True # noqa: ERA001 +# html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -# html_show_copyright = True # noqa: ERA001 +# html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. -# html_use_opensearch = '' # noqa: ERA001 +# html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). -# html_file_suffix = None # noqa: ERA001 +# html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = "cookiecutter-djangodoc" @@ -172,11 +172,11 @@ latex_elements = { # The paper size ('letterpaper' or 'a4paper'). - # 'papersize': 'letterpaper', # noqa: ERA001 + # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). - # 'pointsize': '10pt', # noqa: ERA001 + # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. - # 'preamble': '', # noqa: ERA001 + # 'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples @@ -193,23 +193,23 @@ # The name of an image file (relative to this directory) to place at the top of # the title page. -# latex_logo = None # noqa: ERA001 +# latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. -# latex_use_parts = False # noqa: ERA001 +# latex_use_parts = False # If true, show page references after internal links. -# latex_show_pagerefs = False # noqa: ERA001 +# latex_show_pagerefs = False # If true, show URL addresses after external links. -# latex_show_urls = False # noqa: ERA001 +# latex_show_urls = False # Documents to append as an appendix to all manuals. -# latex_appendices = [] # noqa: ERA001 +# latex_appendices = [] # If false, no module index is generated. -# latex_domain_indices = True # noqa: ERA001 +# latex_domain_indices = True # -- Options for manual page output -------------------------------------------- @@ -227,7 +227,7 @@ ] # If true, show URL addresses after external links. -# man_show_urls = False # noqa: ERA001 +# man_show_urls = False # -- Options for Texinfo output ------------------------------------------------ @@ -244,14 +244,14 @@ "Cookiecutter Django", "A Cookiecutter template for creating production-ready Django projects quickly.", "Miscellaneous", - ), + ) ] # Documents to append as an appendix to all manuals. -# texinfo_appendices = [] # noqa: ERA001 +# texinfo_appendices = [] # If false, no module index is generated. -# texinfo_domain_indices = True # noqa: ERA001 +# texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. -# texinfo_show_urls = 'footnote' # noqa: ERA001 +# texinfo_show_urls = 'footnote' diff --git a/pyproject.toml b/pyproject.toml index 8c7b2f5c50..f4265023fc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,6 +64,7 @@ line-length = 119 # Exclude the template content as most files aren't parseable extend-exclude = [ "[{]{2}cookiecutter.project_slug[}]{2}/*", + "docs/*", ] lint.select = [ From 9275c8f13de513ed2255fc63bcab928e40edcf9f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 11 Jan 2025 12:10:28 +0000 Subject: [PATCH 17/19] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- scripts/ruff_version.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/ruff_version.py b/scripts/ruff_version.py index acc6beb14e..b90ad523a5 100644 --- a/scripts/ruff_version.py +++ b/scripts/ruff_version.py @@ -1,7 +1,8 @@ import subprocess -import tomllib from pathlib import Path +import tomllib + ROOT = Path(__file__).parent.parent TEMPLATED_ROOT = ROOT / "{{cookiecutter.project_slug}}" REQUIREMENTS_LOCAL_TXT = TEMPLATED_ROOT / "requirements" / "local.txt" @@ -16,7 +17,7 @@ def main(): return update_ruff_version(old_version, new_version) - subprocess.run(["uv", "lock", "--no-upgrade"], cwd=ROOT) + subprocess.run(["uv", "lock", "--no-upgrade"], cwd=ROOT, check=False) def get_requirements_txt_version(): From ea151466bfcc91cc5663fad555f71a1e2d3acca4 Mon Sep 17 00:00:00 2001 From: Bruno Alla Date: Sat, 11 Jan 2025 12:15:10 +0000 Subject: [PATCH 18/19] Fix Ruff issue --- pyproject.toml | 1 + scripts/node_version.py | 6 ++++++ scripts/ruff_version.py | 8 +++++++- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 992991a5e3..2e7b2d233f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -120,6 +120,7 @@ lint.select = [ "YTT", ] lint.ignore = [ + "EM101", "RUF012", # Mutable class attributes should be annotated with `typing.ClassVar` "S101", # Use of assert detected https://docs.astral.sh/ruff/rules/assert/ "SIM102", # sometimes it's better to nest diff --git a/scripts/node_version.py b/scripts/node_version.py index 6264f8ee69..a4a49218f8 100644 --- a/scripts/node_version.py +++ b/scripts/node_version.py @@ -8,6 +8,11 @@ CI_YML = ROOT / ".github" / "workflows" / "ci.yml" +class VersionNotFoundError(RuntimeError): + def __init__(self): + super().__init__("Could not find version in Dockerfile") + + def main(): new_version = get_version_from_dockerfile() old_version = get_version_from_package_json() @@ -26,6 +31,7 @@ def get_version_from_dockerfile(): _, _, docker_tag = line.partition(":") version_str, _, _ = docker_tag.partition("-") return version_str + raise VersionNotFoundError def get_version_from_package_json(): diff --git a/scripts/ruff_version.py b/scripts/ruff_version.py index b90ad523a5..cab62b47d7 100644 --- a/scripts/ruff_version.py +++ b/scripts/ruff_version.py @@ -10,6 +10,11 @@ PYPROJECT_TOML = ROOT / "pyproject.toml" +class VersionNotFoundError(RuntimeError): + def __init__(self, filename): + super().__init__(f"Could not find version in {filename}") + + def main(): new_version = get_requirements_txt_version() old_version = get_pyproject_toml_version() @@ -17,7 +22,7 @@ def main(): return update_ruff_version(old_version, new_version) - subprocess.run(["uv", "lock", "--no-upgrade"], cwd=ROOT, check=False) + subprocess.run(["uv", "lock", "--no-upgrade"], cwd=ROOT, check=False) # noqa: S603,S607 def get_requirements_txt_version(): @@ -33,6 +38,7 @@ def get_pyproject_toml_version(): for dependency in data["project"]["dependencies"]: if dependency.startswith("ruff=="): return dependency.split("==")[1] + raise VersionNotFoundError("pyproject.toml") def update_ruff_version(old_version, new_version): From b72e5b2c2a081d1901aa69702b78612c23ee5609 Mon Sep 17 00:00:00 2001 From: Bruno Alla Date: Fri, 17 Jan 2025 19:45:23 +0000 Subject: [PATCH 19/19] Disable PLR0133 in pre/post commit hooks We seem to compare 2 constants but seem strings are in fact interpolated in Jinja. https://docs.astral.sh/ruff/rules/comparison-of-constant/ --- hooks/post_gen_project.py | 23 ++++++++++++----------- hooks/pre_gen_project.py | 7 ++++--- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/hooks/post_gen_project.py b/hooks/post_gen_project.py index 64fb7d7435..35b8b5f15d 100644 --- a/hooks/post_gen_project.py +++ b/hooks/post_gen_project.py @@ -1,3 +1,4 @@ +# ruff: noqa: PLR0133 import json import os import random @@ -73,7 +74,7 @@ def remove_docker_files(): ] for file_name in file_names: os.remove(file_name) # noqa: PTH107 - if "{{ cookiecutter.editor }}" == "PyCharm": # noqa: PLR0133 + if "{{ cookiecutter.editor }}" == "PyCharm": file_names = ["docker_compose_up_django.xml", "docker_compose_up_docs.xml"] for file_name in file_names: os.remove(os.path.join(".idea", "runConfigurations", file_name)) # noqa: PTH107, PTH118 @@ -430,15 +431,15 @@ def main(): # noqa: C901, PLR0912, PLR0915 ) set_flags_in_settings_files() - if "{{ cookiecutter.open_source_license }}" == "Not open source": # noqa: PLR0133 + if "{{ cookiecutter.open_source_license }}" == "Not open source": remove_open_source_files() - if "{{ cookiecutter.open_source_license}}" != "GPLv3": # noqa: PLR0133 + if "{{ cookiecutter.open_source_license}}" != "GPLv3": remove_gplv3_files() - if "{{ cookiecutter.username_type }}" == "username": # noqa: PLR0133 + if "{{ cookiecutter.username_type }}" == "username": remove_custom_user_manager_files() - if "{{ cookiecutter.editor }}" != "PyCharm": # noqa: PLR0133 + if "{{ cookiecutter.editor }}" != "PyCharm": remove_pycharm_files() if "{{ cookiecutter.use_docker }}".lower() == "y": @@ -448,7 +449,7 @@ def main(): # noqa: C901, PLR0912, PLR0915 else: remove_docker_files() - if "{{ cookiecutter.use_docker }}".lower() == "y" and "{{ cookiecutter.cloud_provider}}" != "AWS": # noqa: PLR0133 + if "{{ cookiecutter.use_docker }}".lower() == "y" and "{{ cookiecutter.cloud_provider}}" != "AWS": remove_aws_dockerfile() if "{{ cookiecutter.use_heroku }}".lower() == "n": @@ -483,7 +484,7 @@ def main(): # noqa: C901, PLR0912, PLR0915 use_async=("{{ cookiecutter.use_async }}".lower() == "y"), ) - if "{{ cookiecutter.cloud_provider }}" == "None" and "{{ cookiecutter.use_docker }}".lower() == "n": # noqa: PLR0133 + if "{{ cookiecutter.cloud_provider }}" == "None" and "{{ cookiecutter.use_docker }}".lower() == "n": print( WARNING + "You chose to not use any cloud providers nor Docker, " "media files won't be served in production." + TERMINATOR, @@ -494,16 +495,16 @@ def main(): # noqa: C901, PLR0912, PLR0915 if "{{ cookiecutter.use_docker }}".lower() == "y": remove_celery_compose_dirs() - if "{{ cookiecutter.ci_tool }}" != "Travis": # noqa: PLR0133 + if "{{ cookiecutter.ci_tool }}" != "Travis": remove_dottravisyml_file() - if "{{ cookiecutter.ci_tool }}" != "Gitlab": # noqa: PLR0133 + if "{{ cookiecutter.ci_tool }}" != "Gitlab": remove_dotgitlabciyml_file() - if "{{ cookiecutter.ci_tool }}" != "Github": # noqa: PLR0133 + if "{{ cookiecutter.ci_tool }}" != "Github": remove_dotgithub_folder() - if "{{ cookiecutter.ci_tool }}" != "Drone": # noqa: PLR0133 + if "{{ cookiecutter.ci_tool }}" != "Drone": remove_dotdrone_file() if "{{ cookiecutter.use_drf }}".lower() == "n": diff --git a/hooks/pre_gen_project.py b/hooks/pre_gen_project.py index 1c8b738995..de2f87e3e6 100644 --- a/hooks/pre_gen_project.py +++ b/hooks/pre_gen_project.py @@ -1,3 +1,4 @@ +# ruff: noqa: PLR0133 import sys TERMINATOR = "\x1b[0m" @@ -20,12 +21,12 @@ assert project_slug == project_slug.lower(), f"'{project_slug}' project slug should be all lowercase" -assert "\\" not in "{{ cookiecutter.author_name }}", "Don't include backslashes in author name." # noqa: PLR0133 +assert "\\" not in "{{ cookiecutter.author_name }}", "Don't include backslashes in author name." -if "{{ cookiecutter.use_whitenoise }}".lower() == "n" and "{{ cookiecutter.cloud_provider }}" == "None": # noqa: PLR0133 +if "{{ cookiecutter.use_whitenoise }}".lower() == "n" and "{{ cookiecutter.cloud_provider }}" == "None": print("You should either use Whitenoise or select a Cloud Provider to serve static files") sys.exit(1) -if "{{ cookiecutter.mail_service }}" == "Amazon SES" and "{{ cookiecutter.cloud_provider }}" != "AWS": # noqa: PLR0133 +if "{{ cookiecutter.mail_service }}" == "Amazon SES" and "{{ cookiecutter.cloud_provider }}" != "AWS": print("You should either use AWS or select a different Mail Service for sending emails.") sys.exit(1)