Switch to ruff and fix errors

This commit is contained in:
Gabriel Augendre 2023-01-30 20:58:18 +01:00
parent cf5015e0d7
commit 590970d352
22 changed files with 206 additions and 90 deletions

View file

@ -22,6 +22,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/

View file

@ -33,11 +33,6 @@ repos:
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:
@ -47,23 +42,11 @@ repos:
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
- flake8-pytest-style
- flake8-pyi
- wemake-python-styleguide
- 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:
@ -76,7 +59,7 @@ repos:
args: [--fix] args: [--fix]
types_or: [javascript, css] types_or: [javascript, css]
additional_dependencies: additional_dependencies:
- eslint@8.29.0 - eslint@8.32.0
- eslint-config-prettier@8.5.0 - eslint-config-prettier@8.5.0
- repo: https://github.com/jazzband/pip-tools - repo: https://github.com/jazzband/pip-tools
rev: 6.12.1 rev: 6.12.1
@ -90,7 +73,7 @@ repos:
args: [-q, --allow-unsafe, --resolver=backtracking, --strip-extras, --output-file=constraints.txt, requirements.in] args: [-q, --allow-unsafe, --resolver=backtracking, --strip-extras, --output-file=constraints.txt, requirements.in]
files: ^requirements\.in|constraints\.txt$ files: ^requirements\.in|constraints\.txt$
- id: pip-compile - id: pip-compile
name: pip-compile requirements-dev.in name: pip-compile requirements-dev.txt
args: [-q, --allow-unsafe, --resolver=backtracking, --generate-hashes, requirements-dev.in] args: [-q, --allow-unsafe, --resolver=backtracking, --generate-hashes, requirements-dev.in]
files: ^requirements-dev\.(in|txt)$ files: ^requirements-dev\.(in|txt)$
- repo: local - repo: local

View file

@ -30,32 +30,85 @@ module = [
] ]
ignore_missing_imports = true ignore_missing_imports = true
[tool.flakeheaven] ###############################################################################
max_complexity = 10 # ruff
format = "grouped" ###############################################################################
[tool.ruff]
[tool.flakeheaven.plugins] src = ["src"]
"flake8-*" = [ target-version = "py311"
"+*", select = [
# long lines "F", # pyflakes
"-E501", "E", "W", # pycodestyle
# conflict with black on PEP8 interpretation "C90", # mccabe
"-E203", "I", # isort
# deprecated rule: https://www.flake8rules.com/rules/W503.html "N", # pep8-naming
"-W503", "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
] ]
flake8-builtins = ["-A003"] # class attribute is shadowing a python builtin unfixable = ["T20", "RUF001", "RUF002", "RUF003"]
flake8-quotes = ["-Q000"] # found double quotes, conflict with black
flake8-commas = ["-C812"] # missing trailing comma, conflict with black
flake8-docstrings = ["-D1??"] # missing docstring
flake8-rst-docstrings = ["-*"]
flake8-isort = ["-*"]
[tool.flakeheaven.exceptions."**/migrations/*"] ignore = [
"UP", # pyupgrade
"YTT", # flake8-2020
"ANN", # flake8-annotations
"BLE", # flake8-blind-except
"COM", # flake8-commas
"EM", # flake8-errmsg
"Q", # flake8-quotes
"TCH", # flake8-type-checking / TODO: revisit later ?
[tool.flakeheaven.exceptions."**/tests/*"] "E501", # long lines
flake8-bandit = ["-S101"] # Use of assert detected. "D1", # missing docstring
"TRY003", # Avoid specifying long messages outside the exception class
]
[build-system] [tool.ruff.per-file-ignores]
requires = ["poetry-core>=1.0.0"] "**/tests/*" = [
build-backend = "poetry.core.masonry.api" "S101", # Use of assert detected.
"S106", # Possible hardcoded password.
"B011", # Do not call assert False since python -O removes these calls.
"ARG001", # Unused function argument (mostly fixtures)
"PLR2004", # Magic value used in comparison, consider replacing {value} with a constant variable
]
# File {name} is part of an implicit namespace package. Add an `__init__.py`.
"tasks.py" = ["INP001"]
"src/conftest.py" = ["INP001"]
"src/manage.py" = ["INP001"]
"**/migrations/*" = [
"ARG001", # Unused function argument
"N806", # Variable in function should be lowercase
]
"**/*.pyi" = ["ALL"]
[tool.ruff.pydocstyle]
convention = "pep257"
[tool.ruff.mccabe]
max-complexity = 10

View file

@ -17,3 +17,6 @@ types-Pillow>=9.2
lxml-stubs>=0.4.0 lxml-stubs>=0.4.0
django-debug-toolbar>=3.2 django-debug-toolbar>=3.2
bpython>=0.22.1 bpython>=0.22.1
black>=22.12.0
pip-tools>=6.0
ruff>=0.0.237

View file

@ -16,6 +16,20 @@ attrs==22.1.0 \
# via # via
# pytest # pytest
# pytest-recording # pytest-recording
black==22.12.0 \
--hash=sha256:101c69b23df9b44247bd88e1d7e90154336ac4992502d4197bdac35dd7ee3320 \
--hash=sha256:159a46a4947f73387b4d83e87ea006dbb2337eab6c879620a3ba52699b1f4351 \
--hash=sha256:1f58cbe16dfe8c12b7434e50ff889fa479072096d79f0a7f25e4ab8e94cd8350 \
--hash=sha256:229351e5a18ca30f447bf724d007f890f97e13af070bb6ad4c0a441cd7596a2f \
--hash=sha256:436cc9167dd28040ad90d3b404aec22cedf24a6e4d7de221bec2730ec0c97bcf \
--hash=sha256:559c7a1ba9a006226f09e4916060982fd27334ae1998e7a38b3f33a37f7a2148 \
--hash=sha256:7412e75863aa5c5411886804678b7d083c7c28421210180d67dfd8cf1221e1f4 \
--hash=sha256:77d86c9f3db9b1bf6761244bc0b3572a546f5fe37917a044e02f3166d5aafa7d \
--hash=sha256:82d9fe8fee3401e02e79767016b4907820a7dc28d70d137eb397b92ef3cc5bfc \
--hash=sha256:9eedd20838bd5d75b80c9f5487dbcb06836a43833a37846cf1d8c1cc01cef59d \
--hash=sha256:c116eed0efb9ff870ded8b62fe9f28dd61ef6e9ddd28d83d7d264a38417dcee2 \
--hash=sha256:d30b212bffeb1e252b31dd269dfae69dd17e06d92b87ad26e23890f3efea366f
# via -r requirements-dev.in
blessed==1.19.1 \ blessed==1.19.1 \
--hash=sha256:63b8554ae2e0e7f43749b6715c734cc8f3883010a809bf16790102563e6cf25b \ --hash=sha256:63b8554ae2e0e7f43749b6715c734cc8f3883010a809bf16790102563e6cf25b \
--hash=sha256:9a0d099695bf621d4680dd6c73f6ad547f6a3442fbdbe80c4b1daa1edbc492fc --hash=sha256:9a0d099695bf621d4680dd6c73f6ad547f6a3442fbdbe80c4b1daa1edbc492fc
@ -24,6 +38,10 @@ bpython==0.24 \
--hash=sha256:0d196ae3d1ce3dcd559a3fb89ed2c468dfbd1504af0d680b906dd65a9c7a32eb \ --hash=sha256:0d196ae3d1ce3dcd559a3fb89ed2c468dfbd1504af0d680b906dd65a9c7a32eb \
--hash=sha256:98736ffd7a8c48fd2bfb53d898a475f4241bde0b672125706af04d9d08fd3dbd --hash=sha256:98736ffd7a8c48fd2bfb53d898a475f4241bde0b672125706af04d9d08fd3dbd
# via -r requirements-dev.in # via -r requirements-dev.in
build==0.10.0 \
--hash=sha256:af266720050a66c893a6096a2f410989eeac74ff9a68ba194b3f6473e8e26171 \
--hash=sha256:d5b71264afdb5951d6704482aac78de887c80691c52b88a9ad195983ca2c9269
# via pip-tools
certifi==2022.12.7 \ certifi==2022.12.7 \
--hash=sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3 \ --hash=sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3 \
--hash=sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18 --hash=sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18
@ -40,6 +58,12 @@ charset-normalizer==2.1.1 \
# via # via
# -c constraints.txt # -c constraints.txt
# requests # requests
click==8.1.3 \
--hash=sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e \
--hash=sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48
# via
# black
# pip-tools
coverage[toml]==7.0.0 \ coverage[toml]==7.0.0 \
--hash=sha256:0a8b0e86bede874bf5da566b02194fbb12dd14ce3585cabd58452007f272ba81 \ --hash=sha256:0a8b0e86bede874bf5da566b02194fbb12dd14ce3585cabd58452007f272ba81 \
--hash=sha256:100546219af59d2ad82d4575de03a303eb27b75ea36ffbd1677371924d50bcbc \ --hash=sha256:100546219af59d2ad82d4575de03a303eb27b75ea36ffbd1677371924d50bcbc \
@ -355,7 +379,9 @@ mypy==0.991 \
mypy-extensions==0.4.3 \ mypy-extensions==0.4.3 \
--hash=sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d \ --hash=sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d \
--hash=sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8 --hash=sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8
# via mypy # via
# black
# mypy
nodeenv==1.7.0 \ nodeenv==1.7.0 \
--hash=sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e \ --hash=sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e \
--hash=sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b --hash=sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b
@ -364,12 +390,27 @@ packaging==22.0 \
--hash=sha256:2198ec20bd4c017b8f9717e00f0c8714076fc2fd93816750ab48e2c41de2cfd3 \ --hash=sha256:2198ec20bd4c017b8f9717e00f0c8714076fc2fd93816750ab48e2c41de2cfd3 \
--hash=sha256:957e2148ba0e1a3b282772e791ef1d8083648bc131c8ab0c1feba110ce1146c3 --hash=sha256:957e2148ba0e1a3b282772e791ef1d8083648bc131c8ab0c1feba110ce1146c3
# via # via
# build
# pytest # pytest
# pytest-rerunfailures # pytest-rerunfailures
pathspec==0.11.0 \
--hash=sha256:3a66eb970cbac598f9e5ccb5b2cf58930cd8e3ed86d393d541eaf2d8b1705229 \
--hash=sha256:64d338d4e0914e91c1792321e6907b5a593f1ab1851de7fc269557a21b30ebbc
# via black
pip==22.3.1 \
--hash=sha256:65fd48317359f3af8e593943e6ae1506b66325085ea64b706a998c6e83eeaf38 \
--hash=sha256:908c78e6bc29b676ede1c4d57981d490cb892eb45cd8c214ab6298125119e077
# via pip-tools
pip-tools==6.12.1 \
--hash=sha256:88efb7b29a923ffeac0713e6f23ef8529cc6175527d42b93f73756cc94387293 \
--hash=sha256:f0c0c0ec57b58250afce458e2e6058b1f30a4263db895b7d72fd6311bf1dc6f7
# via -r requirements-dev.in
platformdirs==2.6.0 \ platformdirs==2.6.0 \
--hash=sha256:1a89a12377800c81983db6be069ec068eee989748799b946cce2a6e80dcc54ca \ --hash=sha256:1a89a12377800c81983db6be069ec068eee989748799b946cce2a6e80dcc54ca \
--hash=sha256:b46ffafa316e6b83b47489d240ce17173f123a9b9c83282141c3daf26ad9ac2e --hash=sha256:b46ffafa316e6b83b47489d240ce17173f123a9b9c83282141c3daf26ad9ac2e
# via virtualenv # via
# black
# virtualenv
pluggy==1.0.0 \ pluggy==1.0.0 \
--hash=sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159 \ --hash=sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159 \
--hash=sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3 --hash=sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3
@ -384,6 +425,10 @@ pygments==2.13.0 \
# via # via
# -c constraints.txt # -c constraints.txt
# bpython # bpython
pyproject-hooks==1.0.0 \
--hash=sha256:283c11acd6b928d2f6a7c73fa0d01cb2bdc5f07c57a2eeb6e83d5e56b97976f8 \
--hash=sha256:f271b298b97f5955d53fb12b72c1fb1948c22c1a6b70b315c54cedaca0264ef5
# via build
pytest==7.2.1 \ pytest==7.2.1 \
--hash=sha256:c7c6ca206e93355074ae32f7403e8ea12163b1163c976fee7d4d84027c162be5 \ --hash=sha256:c7c6ca206e93355074ae32f7403e8ea12163b1163c976fee7d4d84027c162be5 \
--hash=sha256:d45e0952f3727241918b8fd0f376f5ff6b301cc0777c6f9a556935c92d8a7d42 --hash=sha256:d45e0952f3727241918b8fd0f376f5ff6b301cc0777c6f9a556935c92d8a7d42
@ -468,6 +513,31 @@ requests==2.28.1 \
# via # via
# -c constraints.txt # -c constraints.txt
# bpython # bpython
ruff==0.0.237 \
--hash=sha256:0cc6cb7c1efcc260df5a939435649610a28f9f438b8b313384c8985ac6574f9f \
--hash=sha256:0d122433a21ce4a21fbba34b73fc3add0ccddd1643b3ff5abb8d2767952f872e \
--hash=sha256:2ea04d826ffca58a7ae926115a801960c757d53c9027f2ca9acbe84c9f2b2f04 \
--hash=sha256:3d6ed86d0d4d742360a262d52191581f12b669a68e59ae3b52e80d7483b3d7b3 \
--hash=sha256:46c5977b643aaf2b6f84641265f835b6c7f67fcca38dbae08c4f15602e084ca0 \
--hash=sha256:525e5ec81cee29b993f77976026a6bf44528a14aa6edb1ef47bd8079147395ae \
--hash=sha256:630c575f543733adf6c19a11d9a02ca9ecc364bd7140af8a4c854d4728be6b56 \
--hash=sha256:7eef0c7a1e45a4e30328ae101613575944cbf47a3a11494bf9827722da6c66b3 \
--hash=sha256:80ce10718abbf502818c0d650ebab99fdcef5e937a1ded3884493ddff804373c \
--hash=sha256:8d6a1d21ae15da2b1dcffeee2606e90de0e6717e72957da7d16ab6ae18dd0058 \
--hash=sha256:8ed113937fab9f73f8c1a6c0350bb4fe03e951370139c6e0adb81f48a8dcf4c6 \
--hash=sha256:b76311335adda4de3c1d471e64e89a49abfeebf02647e3db064e7740e7f36ed6 \
--hash=sha256:bb96796be5919871fa9ae7e88968ba9e14306d9a3f217ca6c204f68a5abeccdd \
--hash=sha256:e9bcb71a3efb5fe886eb48d739cfae5df4a15617e7b5a7668aa45ebf74c0d3fa \
--hash=sha256:ea239cfedf67b74ea4952e1074bb99a4281c2145441d70bc7e2f058d5c49f1c9 \
--hash=sha256:fedfb60f986c26cdb1809db02866e68508db99910c587d2c4066a5c07aa85593
# via -r requirements-dev.in
setuptools==65.6.3 \
--hash=sha256:57f6f22bde4e042978bcd50176fdb381d7c21a9efa4041202288d3737a0c6a54 \
--hash=sha256:a7620757bf984b58deaf32fc8a4577a9bbc0850cf92c20e1ce41c38c19e5fb75
# via
# -c constraints.txt
# nodeenv
# pip-tools
six==1.16.0 \ six==1.16.0 \
--hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \ --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
--hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
@ -531,6 +601,10 @@ wcwidth==0.2.5 \
--hash=sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784 \ --hash=sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784 \
--hash=sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83 --hash=sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83
# via blessed # via blessed
wheel==0.38.4 \
--hash=sha256:965f5259b566725405b05e7cf774052044b1ed30119b5d586b2703aafe8719ac \
--hash=sha256:b60533f3f5d530e971d6737ca6d58681ee434818fab630c83a734bb10c083ce8
# via pip-tools
wrapt==1.14.1 \ wrapt==1.14.1 \
--hash=sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3 \ --hash=sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3 \
--hash=sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b \ --hash=sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b \

View file

@ -80,8 +80,7 @@ class ArticleAdmin(admin.ModelAdmin):
def get_queryset(self, request: WSGIRequest) -> QuerySet: def get_queryset(self, request: WSGIRequest) -> QuerySet:
queryset = super().get_queryset(request) queryset = super().get_queryset(request)
queryset = queryset.prefetch_related("tags") return queryset.prefetch_related("tags")
return queryset
@admin.action(description="Publish selected articles") @admin.action(description="Publish selected articles")
def publish(self, request: WSGIRequest, queryset: QuerySet) -> None: def publish(self, request: WSGIRequest, queryset: QuerySet) -> None:

View file

@ -1,4 +1,5 @@
import copy import copy
from pathlib import Path
from typing import Any from typing import Any
from django.conf import settings from django.conf import settings
@ -30,7 +31,7 @@ def git_version(request: WSGIRequest) -> dict[str, Any]:
if request.path in IGNORED_PATHS: if request.path in IGNORED_PATHS:
return {} return {}
try: try:
with open("/app/git/git-commit") as f: with Path("/app/git/git-commit").open() as f:
version = f.read().strip() version = f.read().strip()
url = settings.BLOG["repo"]["commit_url"].format(commit_sha=version) url = settings.BLOG["repo"]["commit_url"].format(commit_sha=version)
version = version[:8] version = version[:8]
@ -40,7 +41,7 @@ def git_version(request: WSGIRequest) -> dict[str, Any]:
return {"git_version": version, "git_version_url": url} return {"git_version": version, "git_version_url": url}
def analytics(request: WSGIRequest) -> dict[str, Any]: def analytics(_: WSGIRequest) -> dict[str, Any]:
return { return {
"goatcounter_domain": settings.GOATCOUNTER_DOMAIN, "goatcounter_domain": settings.GOATCOUNTER_DOMAIN,
} }
@ -56,7 +57,7 @@ def open_graph_image_url(request: WSGIRequest) -> dict[str, Any]:
return {"open_graph_image_url": url} return {"open_graph_image_url": url}
def blog_metadata(request: WSGIRequest) -> dict[str, Any]: def blog_metadata(_request: WSGIRequest) -> dict[str, Any]:
blog_settings = copy.deepcopy(settings.BLOG) blog_settings = copy.deepcopy(settings.BLOG)
return { return {
"blog": blog_settings, "blog": blog_settings,

View file

@ -9,15 +9,15 @@ from markdown.inlinepatterns import (
class LazyImageInlineProcessor(ImageInlineProcessor): class LazyImageInlineProcessor(ImageInlineProcessor):
def handleMatch(self, m, data): # type: ignore def handleMatch(self, m, data): # type: ignore[no-untyped-def] # noqa: N802
el, match_start, index = super().handleMatch(m, data) el, match_start, index = super().handleMatch(m, data)
if el is not None: if el is not None:
el.set("loading", "lazy") el.set("loading", "lazy")
return el, match_start, index # type: ignore return el, match_start, index # type: ignore[no-untyped-def]
class LazyImageReferenceInlineProcessor(ImageReferenceInlineProcessor): class LazyImageReferenceInlineProcessor(ImageReferenceInlineProcessor):
def makeTag(self, href, title, text): # type: ignore def makeTag(self, href, title, text): # type: ignore[no-untyped-def] # noqa: N802
el = super().makeTag(href, title, text) el = super().makeTag(href, title, text)
if el is not None: if el is not None:
el.set("loading", "lazy") el.set("loading", "lazy")
@ -25,7 +25,7 @@ class LazyImageReferenceInlineProcessor(ImageReferenceInlineProcessor):
class LazyLoadingImageExtension(Extension): class LazyLoadingImageExtension(Extension):
def extendMarkdown(self, md: Markdown) -> None: def extendMarkdown(self, md: Markdown) -> None: # noqa: N802
md.inlinePatterns.register( md.inlinePatterns.register(
LazyImageInlineProcessor(IMAGE_LINK_RE, md), "image_link", 150 LazyImageInlineProcessor(IMAGE_LINK_RE, md), "image_link", 150
) )

View file

@ -16,7 +16,6 @@ from django.db.models import F, Prefetch
from django.template.defaultfilters import slugify from django.template.defaultfilters import slugify
from django.urls import reverse from django.urls import reverse
from django.utils import timezone from django.utils import timezone
from lxml.etree import ParseError # noqa: S410
from articles.utils import ( from articles.utils import (
build_full_absolute_url, build_full_absolute_url,
@ -146,7 +145,7 @@ class Article(models.Model):
for tag in self.tags.all().prefetch_related( for tag in self.tags.all().prefetch_related(
Prefetch("articles", published_articles, to_attr="published_articles") Prefetch("articles", published_articles, to_attr="published_articles")
): ):
related_articles.update(tag.published_articles) # type: ignore related_articles.update(tag.published_articles)
sample_size = min([len(related_articles), 3]) sample_size = min([len(related_articles), 3])
return random.sample(list(related_articles), sample_size) return random.sample(list(related_articles), sample_size)

View file

@ -13,7 +13,6 @@ from articles.markdown import LazyLoadingImageExtension
def build_full_absolute_url(request: WSGIRequest | None, url: str) -> str: def build_full_absolute_url(request: WSGIRequest | None, url: str) -> str:
if request: if request:
return request.build_absolute_uri(url) return request.build_absolute_uri(url)
else:
return (settings.BLOG["base_url"] + url)[::-1].replace("//", "/", 1)[::-1] return (settings.BLOG["base_url"] + url)[::-1].replace("//", "/", 1)[::-1]

View file

@ -12,7 +12,6 @@ from articles.models import Article, Tag
@login_required @login_required
@require_POST @require_POST
def render_article(request: WSGIRequest, article_pk: int) -> HttpResponse: def render_article(request: WSGIRequest, article_pk: int) -> HttpResponse:
print(f"{type(request)=}")
template = "articles/article_detail.html" template = "articles/article_detail.html"
article = Article.objects.get(pk=article_pk) article = Article.objects.get(pk=article_pk)
article.content = request.POST.get("content", article.content) article.content = request.POST.get("content", article.content)

View file

@ -35,7 +35,7 @@ class CompleteFeed(BaseFeed):
class TagFeed(BaseFeed): class TagFeed(BaseFeed):
def get_object(self, request: WSGIRequest, *args: Any, **kwargs: Any) -> Tag: def get_object(self, _request: WSGIRequest, *_args: Any, **kwargs: Any) -> Tag:
return Tag.objects.get(slug=kwargs.get("slug")) return Tag.objects.get(slug=kwargs.get("slug"))
def title(self, tag: Tag) -> str: def title(self, tag: Tag) -> str:

View file

View file

@ -6,9 +6,9 @@ from attachments.models import Attachment
class Command(BaseCommand): class Command(BaseCommand):
help = "Reprocess all attachments" help = "Reprocess all attachments" # noqa: A003
def handle(self, *args: Any, **options: Any) -> None: def handle(self, *_args: Any, **_options: Any) -> None:
for attachment in Attachment.objects.all(): for attachment in Attachment.objects.all():
self.stdout.write(f"Processing {attachment}...") self.stdout.write(f"Processing {attachment}...")
attachment.reprocess() attachment.reprocess()

View file

@ -46,7 +46,7 @@ class Attachment(models.Model):
return f"{self.description} ({self.original_file.name})" return f"{self.description} ({self.original_file.name})"
def reprocess(self) -> None: def reprocess(self) -> None:
self.processed_file = None # type: ignore self.processed_file = None # type: ignore[assignment]
self.save() self.save()
@property @property
@ -63,12 +63,12 @@ class Attachment(models.Model):
super().save(*args, **kwargs) super().save(*args, **kwargs)
if self.processed_file: if self.processed_file:
return return None
try: try:
Image.open(self.original_file.path) Image.open(self.original_file.path)
except OSError: except OSError:
return return None
# Submit job to shortpixel # Submit job to shortpixel
base_data = { base_data = {
@ -88,9 +88,12 @@ class Attachment(models.Model):
} }
data = {**base_data, **post_data} data = {**base_data, **post_data}
url = "https://api.shortpixel.com/v2/post-reducer.php" url = "https://api.shortpixel.com/v2/post-reducer.php"
with open(self.original_file.path, "rb") as original_file: with Path(self.original_file.path).open("rb") as original_file:
response = requests.post( response = requests.post(
url=url, data=data, files={self.original_file.name: original_file} url=url,
data=data,
files={self.original_file.name: original_file},
timeout=10,
) )
res = response.json() res = response.json()
res_data = res[0] res_data = res[0]
@ -104,20 +107,20 @@ class Attachment(models.Model):
} }
check_data = {**base_data, **post_data} check_data = {**base_data, **post_data}
while res_data["Status"]["Code"] == "1": while res_data["Status"]["Code"] == "1":
response = requests.post(url=url, data=check_data) response = requests.post(url=url, data=check_data, timeout=10)
res_data = response.json()[0] res_data = response.json()[0]
# Download image # Download image
current_path = Path(self.original_file.path) current_path = Path(self.original_file.path)
temp_dir = Path(tempfile.mkdtemp()) temp_dir = Path(tempfile.mkdtemp())
temp_path = temp_dir / (current_path.stem + "-processed" + current_path.suffix) temp_path = temp_dir / (current_path.stem + "-processed" + current_path.suffix)
img = requests.get(res_data["LossyURL"], stream=True) img = requests.get(res_data["LossyURL"], stream=True, timeout=10)
with open(temp_path, "wb") as temp_file: with Path(temp_path).open("wb") as temp_file:
for chunk in img: for chunk in img:
temp_file.write(chunk) temp_file.write(chunk)
# Link it to our model # Link it to our model
with open(temp_path, "rb") as output_file: with Path(temp_path).open("rb") as output_file:
f = File(output_file) f = File(output_file)
self.processed_file.save(temp_path.name, f, save=False) self.processed_file.save(temp_path.name, f, save=False)

View file

@ -14,7 +14,7 @@ def test_attachment_is_processed_by_shortpixel() -> None:
# or from upper in the hierarchy (e.g.: settings.BASE_DIR) # or from upper in the hierarchy (e.g.: settings.BASE_DIR)
img_path = Path(__file__).parent / "resources" / "image.png" img_path = Path(__file__).parent / "resources" / "image.png"
img_path = img_path.relative_to(Path.cwd()) img_path = img_path.relative_to(Path.cwd())
with open(img_path, "rb") as f: with Path(img_path).open("rb") as f:
img_file = File(f) img_file = File(f)
attachment = Attachment(description="test attachment", original_file=img_file) attachment = Attachment(description="test attachment", original_file=img_file)
attachment.save() attachment.save()

View file

@ -5,11 +5,11 @@ from django.shortcuts import get_object_or_404
from attachments.models import Attachment from attachments.models import Attachment
def get_original(request: WSGIRequest, pk: int) -> HttpResponse: def get_original(_request: WSGIRequest, pk: int) -> HttpResponse:
attachment = get_object_or_404(Attachment, pk=pk) attachment = get_object_or_404(Attachment, pk=pk)
return HttpResponseRedirect(attachment.original_file.url) return HttpResponseRedirect(attachment.original_file.url)
def get_processed(request: WSGIRequest, pk: int) -> HttpResponse: def get_processed(_request: WSGIRequest, pk: int) -> HttpResponse:
attachment = get_object_or_404(Attachment, pk=pk) attachment = get_object_or_404(Attachment, pk=pk)
return HttpResponseRedirect(attachment.processed_file.url) return HttpResponseRedirect(attachment.processed_file.url)

View file

@ -206,7 +206,7 @@ MEDIA_ROOT = BASE_DIR / "media"
AUTH_USER_MODEL = "articles.User" AUTH_USER_MODEL = "articles.User"
BLOG = { BLOG = {
"title": "Gabs Notes", "title": "Gabs Notes", # noqa: RUF001
"author": "Gabriel Augendre", "author": "Gabriel Augendre",
"email": "ga-notes@augendre.info", "email": "ga-notes@augendre.info",
"description": "My take on tech-related subjects (but not only).", "description": "My take on tech-related subjects (but not only).",

View file

@ -1,4 +1,4 @@
"""blog URL Configuration """blog URL Configuration.
The `urlpatterns` list routes URLs to views. For more information please see: The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/3.1/topics/http/urls/ https://docs.djangoproject.com/en/3.1/topics/http/urls/
@ -17,7 +17,7 @@ from django.conf.urls.static import static
from django.contrib import admin from django.contrib import admin
from django.urls import include, path from django.urls import include, path
from django.views.generic import TemplateView from django.views.generic import TemplateView
from two_factor.urls import urlpatterns as tf_urls # type: ignore from two_factor.urls import urlpatterns as tf_urls # type: ignore[import]
from blog import settings from blog import settings
@ -35,7 +35,7 @@ urlpatterns = [
] ]
if settings.DEBUG: if settings.DEBUG:
import debug_toolbar # type: ignore import debug_toolbar # type: ignore[import]
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
urlpatterns += [path("__debug__/", include(debug_toolbar.urls))] urlpatterns += [path("__debug__/", include(debug_toolbar.urls))]

View file

@ -5,7 +5,7 @@ import sys
def main() -> None: def main() -> None:
"""Run administrative tasks.""" # noqa: DAR401 """Run administrative tasks."""
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "blog.settings") os.environ.setdefault("DJANGO_SETTINGS_MODULE", "blog.settings")
try: try:
from django.core.management import execute_from_command_line from django.core.management import execute_from_command_line

View file

@ -87,7 +87,7 @@ def mypy(ctx: Context) -> None:
@task(pre=[pre_commit, test_cov]) @task(pre=[pre_commit, test_cov])
def check(ctx: Context) -> None: def check(_ctx: Context) -> None:
pass pass
@ -113,24 +113,25 @@ def deploy(ctx: Context) -> None:
@task @task
def check_alive(ctx: Context) -> None: def check_alive(_ctx: Context) -> None:
import requests import requests
exception = None exception = None
for _ in range(5): for _ in range(5):
try: try:
res = requests.get("https://gabnotes.org") res = requests.get("https://gabnotes.org", timeout=5)
res.raise_for_status() res.raise_for_status()
print("Server is up & running")
return
except requests.exceptions.HTTPError as e: except requests.exceptions.HTTPError as e:
time.sleep(2) time.sleep(2)
exception = e exception = e
else:
print("Server is up & running") # noqa: T201
return
raise RuntimeError("Failed to reach the server") from exception raise RuntimeError("Failed to reach the server") from exception
@task(pre=[check, build, publish, deploy], post=[check_alive]) @task(pre=[check, build, publish, deploy], post=[check_alive])
def beam(ctx: Context) -> None: def beam(_ctx: Context) -> None:
pass pass