This commit is contained in:
Bruno Alla 2025-03-20 14:50:10 +01:00 committed by GitHub
commit 5c9327c955
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 119 additions and 80 deletions

View File

@ -1,4 +0,0 @@
[flake8]
exclude = docs
max-line-length = 119
extend-ignore = E203

View File

@ -26,27 +26,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.11.0
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"

0
hooks/__init__.py Normal file
View File

View File

@ -1,3 +1,4 @@
# ruff: noqa: PLR0133
import json
import random
import shutil
@ -79,7 +80,7 @@ def remove_heroku_files():
file_names = ["Procfile", "requirements.txt"]
for file_name in file_names:
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
# Don't remove the file if we are using Travis CI but not using Heroku
continue
Path(file_name).unlink()
shutil.rmtree("bin")
@ -179,7 +180,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")
@ -239,7 +240,7 @@ def remove_dotdrone_file():
Path(".drone.yml").unlink()
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
@ -268,7 +269,7 @@ def set_flag(file_path: 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:
@ -285,18 +286,17 @@ def set_flag(file_path: Path, flag, value=None, formatted=None, *args, **kwargs)
def set_django_secret_key(file_path: 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: Path):
django_admin_url = set_flag(
return set_flag(
file_path,
"!!!SET DJANGO_ADMIN_URL!!!",
formatted="{}/",
@ -304,24 +304,22 @@ def set_django_admin_url(file_path: Path):
using_digits=True,
using_ascii_letters=True,
)
return django_admin_url
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()
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,
@ -329,16 +327,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,
@ -346,7 +342,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):
@ -355,7 +350,7 @@ def append_to_gitignore_file(ignored_line):
gitignore_file.write("\n")
def set_flags_in_envs(postgres_user, celery_flower_user, debug=False):
def set_flags_in_envs(postgres_user, celery_flower_user, debug=False): # noqa: FBT002
local_django_envs_path = Path(".envs", ".local", ".django")
production_django_envs_path = Path(".envs", ".production", ".django")
local_postgres_envs_path = Path(".envs", ".local", ".postgres")
@ -405,7 +400,7 @@ def remove_drf_starter_files():
shutil.rmtree(Path("{{cookiecutter.project_slug}}", "users", "tests", "api"))
def main():
def main(): # noqa: C901, PLR0912, PLR0915
debug = "{{ cookiecutter.debug }}".lower() == "y"
set_flags_in_envs(
@ -444,7 +439,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:
@ -471,7 +466,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":

View File

@ -1,3 +1,4 @@
# ruff: noqa: PLR0133
import sys
TERMINATOR = "\x1b[0m"
@ -16,9 +17,9 @@ SUCCESS = "\x1b[1;32m [SUCCESS]: "
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."

View File

@ -58,22 +58,83 @@ docs = [
"sphinx-rtd-theme>=3",
]
[tool.black]
[tool.ruff]
target-version = "py39"
line-length = 119
target-version = [
'py312',
# Exclude the template content as most files aren't parseable
extend-exclude = [
"[{]{2}cookiecutter.project_slug[}]{2}/*",
"docs/*",
]
# ==== isort ====
[tool.isort]
profile = "black"
line_length = 119
known_first_party = [
"tests",
"scripts",
"hooks",
lint.select = [
"A",
# "ANN", # flake8-annotations: we should support this in the future but many errors atm
"ASYNC",
"B",
"BLE",
"C4",
"C90",
"COM",
"DTZ",
"E",
"EM",
"ERA",
"EXE",
"F",
"FA",
"FBT",
"FLY",
"G",
"I",
"ICN",
"INP",
"INT",
"ISC",
"N",
"PD",
"PERF",
"PGH",
"PIE",
"PL",
"PT",
# "ARG", # Unused function argument
"PTH",
"PYI",
"Q",
"RET",
"RSE",
# "FURB",
# "LOG",
"RUF",
"S",
"SIM",
"SLF",
"SLOT",
"T10",
"TC",
"TID",
"TRY",
"UP",
"W",
"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
"TRY003", # Avoid specifying long messages outside the exception class
# 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.
lint.extend-unsafe-fixes = [
"UP038",
]
lint.isort.force-single-line = true
[tool.pyproject-fmt]
keep_full_version = true

View File

@ -13,7 +13,9 @@ import os
import re
import sys
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
@ -59,7 +61,7 @@ class DjVersion(NamedTuple):
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:
print(f"Couldn't find package: {package}")
sys.exit(1)
@ -214,9 +216,9 @@ class GitHubManager:
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:

View File

@ -1,12 +1,14 @@
from __future__ import annotations
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"
TEMPLATE_PRE_COMMIT_CONFIG = ROOT / ".pre-commit-config.yaml"
PRE_COMMIT_CONFIG = TEMPLATED_ROOT / ".pre-commit-config.yaml"
PYPROJECT_TOML = ROOT / "pyproject.toml"
@ -18,7 +20,7 @@ def main() -> None:
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) # noqa: S603,S607
def get_requirements_txt_version() -> str:
@ -44,12 +46,13 @@ def update_ruff_version(old_version: str, new_version: str) -> None:
f"ruff=={new_version}",
)
PYPROJECT_TOML.write_text(new_content)
# Update pre-commit config
new_content = PRE_COMMIT_CONFIG.read_text().replace(
f"repo: https://github.com/astral-sh/ruff-pre-commit\n rev: v{old_version}",
f"repo: https://github.com/astral-sh/ruff-pre-commit\n rev: v{new_version}",
)
PRE_COMMIT_CONFIG.write_text(new_content)
# Update pre-commit configs
for config_file in [PRE_COMMIT_CONFIG, TEMPLATE_PRE_COMMIT_CONFIG]:
new_content = config_file.read_text().replace(
f"repo: https://github.com/astral-sh/ruff-pre-commit\n rev: v{old_version}",
f"repo: https://github.com/astral-sh/ruff-pre-commit\n rev: v{new_version}",
)
config_file.write_text(new_content)
if __name__ == "__main__":

View File

@ -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))
print(f"Merged pull requests: {merged_pulls}")
@ -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) # noqa: S603, S607
# Commit changes, create tag and push
update_git_repo([changelog_path, setup_py_path, uv_lock_path], release)

View File

@ -1,4 +1,4 @@
import glob
import glob # noqa: EXE002
import os
import re
import sys
@ -227,7 +227,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(

View File

@ -1,11 +1,7 @@
[tox]
skipsdist = true
envlist = py312,black-template
envlist = py312
[testenv]
passenv = AUTOFIXABLE_STYLES
commands = pytest -n auto {posargs:./tests}
[testenv:black-template]
deps = black
commands = black --check hooks tests docs scripts