Switch to ruff and fix errors
This commit is contained in:
parent
cf5015e0d7
commit
590970d352
22 changed files with 206 additions and 90 deletions
2
.github/workflows/test.yaml
vendored
2
.github/workflows/test.yaml
vendored
|
@ -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/
|
||||||
|
|
|
@ -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
|
||||||
|
|
103
pyproject.toml
103
pyproject.toml
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 \
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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:
|
||||||
|
|
0
src/attachments/management/__init__.py
Normal file
0
src/attachments/management/__init__.py
Normal file
0
src/attachments/management/commands/__init__.py
Normal file
0
src/attachments/management/commands/__init__.py
Normal 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()
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -206,7 +206,7 @@ MEDIA_ROOT = BASE_DIR / "media"
|
||||||
AUTH_USER_MODEL = "articles.User"
|
AUTH_USER_MODEL = "articles.User"
|
||||||
|
|
||||||
BLOG = {
|
BLOG = {
|
||||||
"title": "Gab’s Notes",
|
"title": "Gab’s 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).",
|
||||||
|
|
|
@ -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))]
|
||||||
|
|
|
@ -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
|
||||||
|
|
13
tasks.py
13
tasks.py
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
Reference in a new issue