mirror of
https://github.com/Crocmagnon/cookiecutter-django.git
synced 2024-11-05 06:23:54 +01:00
Introduce ruff to cookiecutter template
This commit is contained in:
parent
5fee1369e8
commit
a5dee60e98
14 changed files with 124 additions and 76 deletions
|
@ -2,6 +2,7 @@
|
||||||
"project_name": "My Awesome Project",
|
"project_name": "My Awesome Project",
|
||||||
"project_slug": "{{ cookiecutter.project_name.lower()|replace(' ', '_')|replace('-', '_')|replace('.', '_')|trim() }}",
|
"project_slug": "{{ cookiecutter.project_name.lower()|replace(' ', '_')|replace('-', '_')|replace('.', '_')|trim() }}",
|
||||||
"python_version": "3.11.1",
|
"python_version": "3.11.1",
|
||||||
|
"python_version_slug": "py{{ cookiecutter.python_version.split('.')[:2] | join('') }}",
|
||||||
"_copy_without_render": [
|
"_copy_without_render": [
|
||||||
"*.html",
|
"*.html",
|
||||||
"*/activation*.txt"
|
"*/activation*.txt"
|
||||||
|
|
|
@ -24,6 +24,8 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
pip install pip-tools
|
pip install pip-tools
|
||||||
pip-sync requirements.txt requirements-dev.txt
|
pip-sync requirements.txt requirements-dev.txt
|
||||||
|
- name: Ruff
|
||||||
|
run: ruff --format=github .
|
||||||
- name: Test
|
- name: Test
|
||||||
run: pytest --cov=. --cov-branch --cov-report term-missing:skip-covered
|
run: pytest --cov=. --cov-branch --cov-report term-missing:skip-covered
|
||||||
working-directory: ./src/
|
working-directory: ./src/
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
<option name="workingDir" value="" />
|
<option name="workingDir" value="" />
|
||||||
<envs />
|
<envs />
|
||||||
</TaskOptions>
|
</TaskOptions>
|
||||||
<TaskOptions isEnabled="true">
|
<TaskOptions isEnabled="false">
|
||||||
<option name="arguments" value="run flakeheaven --file $FilePath$" />
|
<option name="arguments" value="run flakeheaven --file $FilePath$" />
|
||||||
<option name="checkSyntaxErrors" value="true" />
|
<option name="checkSyntaxErrors" value="true" />
|
||||||
<option name="description" />
|
<option name="description" />
|
||||||
|
@ -41,5 +41,25 @@
|
||||||
<option name="workingDir" value="" />
|
<option name="workingDir" value="" />
|
||||||
<envs />
|
<envs />
|
||||||
</TaskOptions>
|
</TaskOptions>
|
||||||
|
<TaskOptions isEnabled="true">
|
||||||
|
<option name="arguments" value="run ruff --file $FilePath$" />
|
||||||
|
<option name="checkSyntaxErrors" value="true" />
|
||||||
|
<option name="description" />
|
||||||
|
<option name="exitCodeBehavior" value="ERROR" />
|
||||||
|
<option name="fileExtension" value="py" />
|
||||||
|
<option name="immediateSync" value="false" />
|
||||||
|
<option name="name" value="ruff" />
|
||||||
|
<option name="output" value="$FilePath$" />
|
||||||
|
<option name="outputFilters">
|
||||||
|
<array />
|
||||||
|
</option>
|
||||||
|
<option name="outputFromStdout" value="false" />
|
||||||
|
<option name="program" value="$USER_HOME$/.local/pipx/venvs/pre-commit/bin/pre-commit" />
|
||||||
|
<option name="runOnExternalChanges" value="false" />
|
||||||
|
<option name="scopeName" value="Project Files" />
|
||||||
|
<option name="trackOnlyRoot" value="false" />
|
||||||
|
<option name="workingDir" value="" />
|
||||||
|
<envs />
|
||||||
|
</TaskOptions>
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
|
@ -27,40 +27,26 @@ repos:
|
||||||
rev: v3.3.1
|
rev: v3.3.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: pyupgrade
|
- id: pyupgrade
|
||||||
args: [--py311-plus]
|
args: [--{{ cookiecutter.python_version_slug }}-plus]
|
||||||
- repo: https://github.com/adamchainz/django-upgrade
|
- repo: https://github.com/adamchainz/django-upgrade
|
||||||
rev: 1.12.0
|
rev: 1.12.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: django-upgrade
|
- id: django-upgrade
|
||||||
args: [--target-version, "4.1"]
|
args: [--target-version, "4.1"]
|
||||||
- repo: https://github.com/PyCQA/isort
|
|
||||||
rev: 5.11.4
|
|
||||||
hooks:
|
|
||||||
- id: isort
|
|
||||||
args: [--profile, black]
|
|
||||||
- repo: https://github.com/psf/black
|
- repo: https://github.com/psf/black
|
||||||
rev: 22.12.0
|
rev: 22.12.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
args: [--target-version, py311]
|
args: [--target-version, {{ cookiecutter.python_version_slug }}]
|
||||||
- repo: https://github.com/rtts/djhtml
|
- repo: https://github.com/rtts/djhtml
|
||||||
rev: v1.5.2
|
rev: v1.5.2
|
||||||
hooks:
|
hooks:
|
||||||
- id: djhtml
|
- id: djhtml
|
||||||
- repo: https://github.com/flakeheaven/flakeheaven
|
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||||
rev: 3.2.1
|
rev: 'v0.0.237'
|
||||||
hooks:
|
hooks:
|
||||||
- id: flakeheaven
|
- id: ruff
|
||||||
additional_dependencies:
|
args: [--fix]
|
||||||
- flake8-annotations-complexity
|
|
||||||
- flake8-bandit
|
|
||||||
- flake8-builtins
|
|
||||||
- flake8-bugbear
|
|
||||||
- flake8-comprehensions
|
|
||||||
- flake8-docstrings
|
|
||||||
- flake8-eradicate
|
|
||||||
- flake8-noqa
|
|
||||||
- pep8-naming
|
|
||||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||||
rev: v3.0.0-alpha.4
|
rev: v3.0.0-alpha.4
|
||||||
hooks:
|
hooks:
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
[tool.pytest.ini_options]
|
[tool.pytest.ini_options]
|
||||||
addopts = """
|
addopts = """
|
||||||
--html=test_reports/pytest_result/pytest.html --color=yes --durations 20
|
--html=test_reports/pytest_result/pytest.html --color=yes --durations 20
|
||||||
--no-cov-on-fail --strict-markers
|
--no-cov-on-fail --strict-markers --reuse-db
|
||||||
-W error
|
-W error
|
||||||
"""
|
"""
|
||||||
markers = []
|
markers = []
|
||||||
|
@ -27,41 +27,77 @@ python_files = [
|
||||||
]
|
]
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
# flake8 / flakeheaven
|
# ruff
|
||||||
###############################################################################
|
###############################################################################
|
||||||
[tool.flakeheaven]
|
[tool.ruff]
|
||||||
max_complexity = 10
|
src = ["src"]
|
||||||
format = "grouped"
|
target-version = "{{ cookiecutter.python_version_slug }}"
|
||||||
|
select = [
|
||||||
|
"F", # pyflakes
|
||||||
|
"E", "W", # pycodestyle
|
||||||
|
"C90", # mccabe
|
||||||
|
"I", # isort
|
||||||
|
"N", # pep8-naming
|
||||||
|
"D", # pydocstyle
|
||||||
|
"S", # flake8-bandit
|
||||||
|
"FBT", # flake8-boolean-trap
|
||||||
|
"B", # flake8-bugbear
|
||||||
|
"A", # flake8-builtins
|
||||||
|
"C4", # flake8-comprehensions
|
||||||
|
"DTZ", # flake8-datetimez
|
||||||
|
"T10", # flake8-debugger
|
||||||
|
"EXE", # flake8-executable
|
||||||
|
"ISC", # flake8-implicit-str-concat
|
||||||
|
"ICN", # flake8-import-conventions
|
||||||
|
"G", # flake8-logging-format
|
||||||
|
"INP", # flake8-no-pep420
|
||||||
|
"PIE", # flake8-pie
|
||||||
|
"T20", # flake8-print
|
||||||
|
"PT", # flake8-pytest-style
|
||||||
|
"RET", # flake8-return
|
||||||
|
"SIM", # flake8-simplify
|
||||||
|
"TID", # flake8-tidy-imports
|
||||||
|
"ARG", # flake8-unused-arguments
|
||||||
|
"PTH", # flake8-use-pathlib
|
||||||
|
"ERA", # eradicate
|
||||||
|
"PD", # pandas-vet
|
||||||
|
"PGH", # pygrep-hooks
|
||||||
|
"PL", # pylint
|
||||||
|
"TRY", # tryceratops
|
||||||
|
"RUF", # ruff-specific rules
|
||||||
|
]
|
||||||
|
unfixable = ["T20", "RUF001", "RUF002", "RUF003"]
|
||||||
|
|
||||||
# Base rules
|
ignore = [
|
||||||
#############################
|
"UP", # pyupgrade
|
||||||
[tool.flakeheaven.plugins]
|
"YTT", # flake8-2020
|
||||||
"*" = [
|
"ANN", # flake8-annotations
|
||||||
"+*",
|
"BLE", # flake8-blind-except
|
||||||
"-E501", # long lines
|
"COM", # flake8-commas
|
||||||
"-E203", # conflict with black on PEP8 interpretation
|
"EM", # flake8-errmsg
|
||||||
"-W503", # deprecated rule: https://www.flake8rules.com/rules/W503.html
|
"Q", # flake8-quotes
|
||||||
]
|
"TCH", # flake8-type-checking / TODO: revisit later ?
|
||||||
flake8-builtins = [
|
|
||||||
"+*",
|
"E501", # long lines
|
||||||
"-A003", # class attribute is shadowing a python builtin
|
"D1", # missing docstring
|
||||||
]
|
"TRY003", # Avoid specifying long messages outside the exception class
|
||||||
flake8-docstrings = [
|
|
||||||
"+*",
|
|
||||||
"-D1??", # missing docstring
|
|
||||||
]
|
|
||||||
flake8-bandit = [
|
|
||||||
"+*",
|
|
||||||
"-S308", # Use of mark_safe() may expose cross-site scripting vulnerabilities and should be reviewed.
|
|
||||||
"-S703", # Potential XSS on mark_safe function.
|
|
||||||
]
|
]
|
||||||
|
|
||||||
# Exceptions
|
[tool.ruff.per-file-ignores]
|
||||||
#############################
|
"**/tests/*" = [
|
||||||
[tool.flakeheaven.exceptions."**/tests/*"]
|
"S101", # Use of assert detected.
|
||||||
flake8-bandit = [
|
"S106", # Possible hardcoded password.
|
||||||
"+*",
|
"B011", # Do not call assert False since python -O removes these calls.
|
||||||
"-S101", # Use of assert detected.
|
"ARG001", # Unused function argument (mostly fixtures)
|
||||||
"-S106", # Possible hardcoded password.
|
"PLR2004", # Magic value used in comparison, consider replacing {value} with a constant variable
|
||||||
"-S311", # Standard pseudo-random generators are not suitable for security/cryptographic purposes.
|
|
||||||
]
|
]
|
||||||
|
# File {name} is part of an implicit namespace package. Add an `__init__.py`.
|
||||||
|
"tasks.py" = ["INP001"]
|
||||||
|
"src/conftest.py" = ["INP001"]
|
||||||
|
"src/manage.py" = ["INP001"]
|
||||||
|
|
||||||
|
[tool.ruff.pydocstyle]
|
||||||
|
convention = "pep257"
|
||||||
|
|
||||||
|
[tool.ruff.mccabe]
|
||||||
|
max-complexity = 10
|
||||||
|
|
|
@ -12,3 +12,6 @@ bpython>=0.22.1
|
||||||
invoke>=1.7.3
|
invoke>=1.7.3
|
||||||
hypothesis>=6.56.4
|
hypothesis>=6.56.4
|
||||||
django-browser-reload>=1.6.0
|
django-browser-reload>=1.6.0
|
||||||
|
black>=22.12.0
|
||||||
|
pip-tools>=6.0
|
||||||
|
ruff>=0.0.237
|
||||||
|
|
|
@ -3,5 +3,3 @@ from django.contrib.auth.models import AbstractUser
|
||||||
|
|
||||||
class User(AbstractUser):
|
class User(AbstractUser):
|
||||||
"""Default custom user model for {{ cookiecutter.project_name }}."""
|
"""Default custom user model for {{ cookiecutter.project_name }}."""
|
||||||
|
|
||||||
pass
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from http import HTTPStatus
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
@ -9,17 +11,17 @@ class TestUserAdmin:
|
||||||
def test_changelist(self, admin_client):
|
def test_changelist(self, admin_client):
|
||||||
url = reverse("admin:common_user_changelist")
|
url = reverse("admin:common_user_changelist")
|
||||||
response = admin_client.get(url)
|
response = admin_client.get(url)
|
||||||
assert response.status_code == 200
|
assert response.status_code == HTTPStatus.OK
|
||||||
|
|
||||||
def test_search(self, admin_client):
|
def test_search(self, admin_client):
|
||||||
url = reverse("admin:common_user_changelist")
|
url = reverse("admin:common_user_changelist")
|
||||||
response = admin_client.get(url, data={"q": "test"})
|
response = admin_client.get(url, data={"q": "test"})
|
||||||
assert response.status_code == 200
|
assert response.status_code == HTTPStatus.OK
|
||||||
|
|
||||||
def test_add(self, admin_client):
|
def test_add(self, admin_client):
|
||||||
url = reverse("admin:common_user_add")
|
url = reverse("admin:common_user_add")
|
||||||
response = admin_client.get(url)
|
response = admin_client.get(url)
|
||||||
assert response.status_code == 200
|
assert response.status_code == HTTPStatus.OK
|
||||||
|
|
||||||
response = admin_client.post(
|
response = admin_client.post(
|
||||||
url,
|
url,
|
||||||
|
@ -29,11 +31,11 @@ class TestUserAdmin:
|
||||||
"password2": "My_R@ndom-P@ssw0rd",
|
"password2": "My_R@ndom-P@ssw0rd",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
assert response.status_code == 302
|
assert response.status_code == HTTPStatus.FOUND
|
||||||
assert get_user_model().objects.filter(username="test").exists()
|
assert get_user_model().objects.filter(username="test").exists()
|
||||||
|
|
||||||
def test_view_user(self, admin_client):
|
def test_view_user(self, admin_client):
|
||||||
user = get_user_model().objects.get(username="admin")
|
user = get_user_model().objects.get(username="admin")
|
||||||
url = reverse("admin:common_user_change", kwargs={"object_id": user.pk})
|
url = reverse("admin:common_user_change", kwargs={"object_id": user.pk})
|
||||||
response = admin_client.get(url)
|
response = admin_client.get(url)
|
||||||
assert response.status_code == 200
|
assert response.status_code == HTTPStatus.OK
|
||||||
|
|
|
@ -6,7 +6,7 @@ from django.shortcuts import render
|
||||||
|
|
||||||
|
|
||||||
def hello_world(request: WSGIRequest) -> HttpResponse:
|
def hello_world(request: WSGIRequest) -> HttpResponse:
|
||||||
context = {"value": random.randint(1, 1000)} # noqa: S311
|
context = {"value": random.randint(1, 1000)}
|
||||||
if request.htmx:
|
if request.htmx:
|
||||||
return render(request, "common/hello-random.html", context)
|
return render(request, "common/hello-random.html", context)
|
||||||
return render(request, "common/base.html", context)
|
return render(request, "common/base.html", context)
|
||||||
|
|
|
@ -3,5 +3,5 @@ from django.core.management import call_command
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session", autouse=True)
|
@pytest.fixture(scope="session", autouse=True)
|
||||||
def collectstatic():
|
def _collectstatic():
|
||||||
call_command("collectstatic", "--clear", "--noinput", "--verbosity=0")
|
call_command("collectstatic", "--clear", "--noinput", "--verbosity=0")
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
def app(request):
|
def app(_):
|
||||||
return settings.APP
|
return settings.APP
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
def debug_toolbar_bypass_internal_ips(request) -> bool:
|
def debug_toolbar_bypass_internal_ips(_) -> bool:
|
||||||
"""
|
"""
|
||||||
Display debug toolbar according to the DEBUG_TOOLBAR setting only.
|
Display debug toolbar according to the DEBUG_TOOLBAR setting only.
|
||||||
|
|
||||||
|
|
|
@ -236,17 +236,17 @@ APP = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try:
|
try:
|
||||||
with open("/app/git/build-date") as f:
|
with Path("/app/git/build-date").open() as f:
|
||||||
APP["build"]["date"] = f.read().strip()
|
APP["build"]["date"] = f.read().strip()
|
||||||
except Exception: # noqa: S110
|
except Exception:
|
||||||
pass
|
pass # noqa: S110
|
||||||
try:
|
try:
|
||||||
with open("/app/git/git-commit") as f:
|
with Path("/app/git/git-commit").open() as f:
|
||||||
APP["build"]["commit"] = f.read().strip()
|
APP["build"]["commit"] = f.read().strip()
|
||||||
except Exception: # noqa: S110
|
except Exception:
|
||||||
pass
|
pass # noqa: S110
|
||||||
try:
|
try:
|
||||||
with open("/app/git/git-describe") as f:
|
with Path("/app/git/git-describe").open() as f:
|
||||||
APP["build"]["describe"] = f.read().strip()
|
APP["build"]["describe"] = f.read().strip()
|
||||||
except Exception: # noqa: S110
|
except Exception:
|
||||||
pass
|
pass # noqa: S110
|
||||||
|
|
|
@ -10,7 +10,7 @@ TEST_ENV = {"ENV_FILE": BASE_DIR / "envs" / "test-envs.env"}
|
||||||
|
|
||||||
|
|
||||||
@task
|
@task
|
||||||
def sync_dependencies(ctx: Context, update: bool = False):
|
def sync_dependencies(ctx: Context, *, update: bool = False):
|
||||||
common_args = "-q --allow-unsafe --resolver=backtracking"
|
common_args = "-q --allow-unsafe --resolver=backtracking"
|
||||||
if update:
|
if update:
|
||||||
common_args += " --upgrade"
|
common_args += " --upgrade"
|
||||||
|
|
Loading…
Reference in a new issue