diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index bf790b9..775de98 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -22,6 +22,8 @@ jobs: run: | pip install pip-tools pip-sync requirements.txt requirements-dev.txt + - name: Ruff + run: ruff --format=github . - name: Test run: pytest --cov=. --cov-branch --cov-report term-missing:skip-covered working-directory: ./src/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6e89c98..d9a067f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -33,11 +33,6 @@ repos: hooks: - id: django-upgrade 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 rev: 22.12.0 hooks: @@ -47,23 +42,11 @@ repos: rev: v1.5.2 hooks: - id: djhtml - - repo: https://github.com/flakeheaven/flakeheaven - rev: 3.2.1 + - repo: https://github.com/charliermarsh/ruff-pre-commit + rev: 'v0.0.237' hooks: - - id: flakeheaven - additional_dependencies: - - 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 + - id: ruff + args: [--fix] - repo: https://github.com/pre-commit/mirrors-prettier rev: v3.0.0-alpha.4 hooks: @@ -76,7 +59,7 @@ repos: args: [--fix] types_or: [javascript, css] additional_dependencies: - - eslint@8.29.0 + - eslint@8.32.0 - eslint-config-prettier@8.5.0 - repo: https://github.com/jazzband/pip-tools rev: 6.12.1 @@ -90,7 +73,7 @@ repos: args: [-q, --allow-unsafe, --resolver=backtracking, --strip-extras, --output-file=constraints.txt, requirements.in] files: ^requirements\.in|constraints\.txt$ - 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] files: ^requirements-dev\.(in|txt)$ - repo: local diff --git a/pyproject.toml b/pyproject.toml index 12c8856..0d21d6e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,32 +30,85 @@ module = [ ] ignore_missing_imports = true -[tool.flakeheaven] -max_complexity = 10 -format = "grouped" - -[tool.flakeheaven.plugins] -"flake8-*" = [ - "+*", - # long lines - "-E501", - # conflict with black on PEP8 interpretation - "-E203", - # deprecated rule: https://www.flake8rules.com/rules/W503.html - "-W503", +############################################################################### +# ruff +############################################################################### +[tool.ruff] +src = ["src"] +target-version = "py311" +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 ] -flake8-builtins = ["-A003"] # class attribute is shadowing a python builtin -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 = ["-*"] +unfixable = ["T20", "RUF001", "RUF002", "RUF003"] -[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/*"] -flake8-bandit = ["-S101"] # Use of assert detected. + "E501", # long lines + "D1", # missing docstring + "TRY003", # Avoid specifying long messages outside the exception class +] -[build-system] -requires = ["poetry-core>=1.0.0"] -build-backend = "poetry.core.masonry.api" +[tool.ruff.per-file-ignores] +"**/tests/*" = [ + "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 diff --git a/requirements-dev.in b/requirements-dev.in index 6c2a610..7d842cb 100644 --- a/requirements-dev.in +++ b/requirements-dev.in @@ -17,3 +17,6 @@ types-Pillow>=9.2 lxml-stubs>=0.4.0 django-debug-toolbar>=3.2 bpython>=0.22.1 +black>=22.12.0 +pip-tools>=6.0 +ruff>=0.0.237 diff --git a/requirements-dev.txt b/requirements-dev.txt index 526162c..fc0e027 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -16,6 +16,20 @@ attrs==22.1.0 \ # via # pytest # 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 \ --hash=sha256:63b8554ae2e0e7f43749b6715c734cc8f3883010a809bf16790102563e6cf25b \ --hash=sha256:9a0d099695bf621d4680dd6c73f6ad547f6a3442fbdbe80c4b1daa1edbc492fc @@ -24,6 +38,10 @@ bpython==0.24 \ --hash=sha256:0d196ae3d1ce3dcd559a3fb89ed2c468dfbd1504af0d680b906dd65a9c7a32eb \ --hash=sha256:98736ffd7a8c48fd2bfb53d898a475f4241bde0b672125706af04d9d08fd3dbd # via -r requirements-dev.in +build==0.10.0 \ + --hash=sha256:af266720050a66c893a6096a2f410989eeac74ff9a68ba194b3f6473e8e26171 \ + --hash=sha256:d5b71264afdb5951d6704482aac78de887c80691c52b88a9ad195983ca2c9269 + # via pip-tools certifi==2022.12.7 \ --hash=sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3 \ --hash=sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18 @@ -40,6 +58,12 @@ charset-normalizer==2.1.1 \ # via # -c constraints.txt # requests +click==8.1.3 \ + --hash=sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e \ + --hash=sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48 + # via + # black + # pip-tools coverage[toml]==7.0.0 \ --hash=sha256:0a8b0e86bede874bf5da566b02194fbb12dd14ce3585cabd58452007f272ba81 \ --hash=sha256:100546219af59d2ad82d4575de03a303eb27b75ea36ffbd1677371924d50bcbc \ @@ -355,7 +379,9 @@ mypy==0.991 \ mypy-extensions==0.4.3 \ --hash=sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d \ --hash=sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8 - # via mypy + # via + # black + # mypy nodeenv==1.7.0 \ --hash=sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e \ --hash=sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b @@ -364,12 +390,27 @@ packaging==22.0 \ --hash=sha256:2198ec20bd4c017b8f9717e00f0c8714076fc2fd93816750ab48e2c41de2cfd3 \ --hash=sha256:957e2148ba0e1a3b282772e791ef1d8083648bc131c8ab0c1feba110ce1146c3 # via + # build # pytest # 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 \ --hash=sha256:1a89a12377800c81983db6be069ec068eee989748799b946cce2a6e80dcc54ca \ --hash=sha256:b46ffafa316e6b83b47489d240ce17173f123a9b9c83282141c3daf26ad9ac2e - # via virtualenv + # via + # black + # virtualenv pluggy==1.0.0 \ --hash=sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159 \ --hash=sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3 @@ -384,6 +425,10 @@ pygments==2.13.0 \ # via # -c constraints.txt # bpython +pyproject-hooks==1.0.0 \ + --hash=sha256:283c11acd6b928d2f6a7c73fa0d01cb2bdc5f07c57a2eeb6e83d5e56b97976f8 \ + --hash=sha256:f271b298b97f5955d53fb12b72c1fb1948c22c1a6b70b315c54cedaca0264ef5 + # via build pytest==7.2.1 \ --hash=sha256:c7c6ca206e93355074ae32f7403e8ea12163b1163c976fee7d4d84027c162be5 \ --hash=sha256:d45e0952f3727241918b8fd0f376f5ff6b301cc0777c6f9a556935c92d8a7d42 @@ -468,6 +513,31 @@ requests==2.28.1 \ # via # -c constraints.txt # 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 \ --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \ --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 @@ -531,6 +601,10 @@ wcwidth==0.2.5 \ --hash=sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784 \ --hash=sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83 # via blessed +wheel==0.38.4 \ + --hash=sha256:965f5259b566725405b05e7cf774052044b1ed30119b5d586b2703aafe8719ac \ + --hash=sha256:b60533f3f5d530e971d6737ca6d58681ee434818fab630c83a734bb10c083ce8 + # via pip-tools wrapt==1.14.1 \ --hash=sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3 \ --hash=sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b \ diff --git a/src/articles/admin.py b/src/articles/admin.py index 787f6fa..3407693 100644 --- a/src/articles/admin.py +++ b/src/articles/admin.py @@ -80,8 +80,7 @@ class ArticleAdmin(admin.ModelAdmin): def get_queryset(self, request: WSGIRequest) -> QuerySet: queryset = super().get_queryset(request) - queryset = queryset.prefetch_related("tags") - return queryset + return queryset.prefetch_related("tags") @admin.action(description="Publish selected articles") def publish(self, request: WSGIRequest, queryset: QuerySet) -> None: diff --git a/src/articles/context_processors.py b/src/articles/context_processors.py index 07d4840..17546b0 100644 --- a/src/articles/context_processors.py +++ b/src/articles/context_processors.py @@ -1,4 +1,5 @@ import copy +from pathlib import Path from typing import Any from django.conf import settings @@ -30,7 +31,7 @@ def git_version(request: WSGIRequest) -> dict[str, Any]: if request.path in IGNORED_PATHS: return {} try: - with open("/app/git/git-commit") as f: + with Path("/app/git/git-commit").open() as f: version = f.read().strip() url = settings.BLOG["repo"]["commit_url"].format(commit_sha=version) version = version[:8] @@ -40,7 +41,7 @@ def git_version(request: WSGIRequest) -> dict[str, Any]: return {"git_version": version, "git_version_url": url} -def analytics(request: WSGIRequest) -> dict[str, Any]: +def analytics(_: WSGIRequest) -> dict[str, Any]: return { "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} -def blog_metadata(request: WSGIRequest) -> dict[str, Any]: +def blog_metadata(_request: WSGIRequest) -> dict[str, Any]: blog_settings = copy.deepcopy(settings.BLOG) return { "blog": blog_settings, diff --git a/src/articles/markdown.py b/src/articles/markdown.py index 1c44e59..6ef33ff 100644 --- a/src/articles/markdown.py +++ b/src/articles/markdown.py @@ -9,15 +9,15 @@ from markdown.inlinepatterns import ( 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) if el is not None: el.set("loading", "lazy") - return el, match_start, index # type: ignore + return el, match_start, index # type: ignore[no-untyped-def] 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) if el is not None: el.set("loading", "lazy") @@ -25,7 +25,7 @@ class LazyImageReferenceInlineProcessor(ImageReferenceInlineProcessor): class LazyLoadingImageExtension(Extension): - def extendMarkdown(self, md: Markdown) -> None: + def extendMarkdown(self, md: Markdown) -> None: # noqa: N802 md.inlinePatterns.register( LazyImageInlineProcessor(IMAGE_LINK_RE, md), "image_link", 150 ) diff --git a/src/articles/models.py b/src/articles/models.py index 20bb221..23ac6d8 100644 --- a/src/articles/models.py +++ b/src/articles/models.py @@ -16,7 +16,6 @@ from django.db.models import F, Prefetch from django.template.defaultfilters import slugify from django.urls import reverse from django.utils import timezone -from lxml.etree import ParseError # noqa: S410 from articles.utils import ( build_full_absolute_url, @@ -146,7 +145,7 @@ class Article(models.Model): for tag in self.tags.all().prefetch_related( 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]) return random.sample(list(related_articles), sample_size) diff --git a/src/articles/utils.py b/src/articles/utils.py index ee00254..711ad27 100644 --- a/src/articles/utils.py +++ b/src/articles/utils.py @@ -13,8 +13,7 @@ from articles.markdown import LazyLoadingImageExtension def build_full_absolute_url(request: WSGIRequest | None, url: str) -> str: if request: 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] def format_article_content(content: str) -> str: diff --git a/src/articles/views/api.py b/src/articles/views/api.py index 1b6f3f1..adf7634 100644 --- a/src/articles/views/api.py +++ b/src/articles/views/api.py @@ -12,7 +12,6 @@ from articles.models import Article, Tag @login_required @require_POST def render_article(request: WSGIRequest, article_pk: int) -> HttpResponse: - print(f"{type(request)=}") template = "articles/article_detail.html" article = Article.objects.get(pk=article_pk) article.content = request.POST.get("content", article.content) diff --git a/src/articles/views/feeds.py b/src/articles/views/feeds.py index a52ba26..8e6db38 100644 --- a/src/articles/views/feeds.py +++ b/src/articles/views/feeds.py @@ -35,7 +35,7 @@ class CompleteFeed(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")) def title(self, tag: Tag) -> str: diff --git a/src/attachments/management/__init__.py b/src/attachments/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/attachments/management/commands/__init__.py b/src/attachments/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/attachments/management/commands/reprocess_all_attachments.py b/src/attachments/management/commands/reprocess_all_attachments.py index 73d4aff..3e3ba9e 100644 --- a/src/attachments/management/commands/reprocess_all_attachments.py +++ b/src/attachments/management/commands/reprocess_all_attachments.py @@ -6,9 +6,9 @@ from attachments.models import Attachment 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(): self.stdout.write(f"Processing {attachment}...") attachment.reprocess() diff --git a/src/attachments/models.py b/src/attachments/models.py index 3d31a40..6583cdf 100644 --- a/src/attachments/models.py +++ b/src/attachments/models.py @@ -46,7 +46,7 @@ class Attachment(models.Model): return f"{self.description} ({self.original_file.name})" def reprocess(self) -> None: - self.processed_file = None # type: ignore + self.processed_file = None # type: ignore[assignment] self.save() @property @@ -63,12 +63,12 @@ class Attachment(models.Model): super().save(*args, **kwargs) if self.processed_file: - return + return None try: Image.open(self.original_file.path) except OSError: - return + return None # Submit job to shortpixel base_data = { @@ -88,9 +88,12 @@ class Attachment(models.Model): } data = {**base_data, **post_data} 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( - 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_data = res[0] @@ -104,20 +107,20 @@ class Attachment(models.Model): } check_data = {**base_data, **post_data} 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] # Download image current_path = Path(self.original_file.path) temp_dir = Path(tempfile.mkdtemp()) temp_path = temp_dir / (current_path.stem + "-processed" + current_path.suffix) - img = requests.get(res_data["LossyURL"], stream=True) - with open(temp_path, "wb") as temp_file: + img = requests.get(res_data["LossyURL"], stream=True, timeout=10) + with Path(temp_path).open("wb") as temp_file: for chunk in img: temp_file.write(chunk) # 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) self.processed_file.save(temp_path.name, f, save=False) diff --git a/src/attachments/tests/test_models.py b/src/attachments/tests/test_models.py index c4fb608..0a2d7e5 100644 --- a/src/attachments/tests/test_models.py +++ b/src/attachments/tests/test_models.py @@ -14,7 +14,7 @@ def test_attachment_is_processed_by_shortpixel() -> None: # or from upper in the hierarchy (e.g.: settings.BASE_DIR) img_path = Path(__file__).parent / "resources" / "image.png" 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) attachment = Attachment(description="test attachment", original_file=img_file) attachment.save() diff --git a/src/attachments/views.py b/src/attachments/views.py index fc452ae..349301e 100644 --- a/src/attachments/views.py +++ b/src/attachments/views.py @@ -5,11 +5,11 @@ from django.shortcuts import get_object_or_404 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) 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) return HttpResponseRedirect(attachment.processed_file.url) diff --git a/src/blog/settings.py b/src/blog/settings.py index 6fb3aa7..69461a8 100644 --- a/src/blog/settings.py +++ b/src/blog/settings.py @@ -206,7 +206,7 @@ MEDIA_ROOT = BASE_DIR / "media" AUTH_USER_MODEL = "articles.User" BLOG = { - "title": "Gab’s Notes", + "title": "Gab’s Notes", # noqa: RUF001 "author": "Gabriel Augendre", "email": "ga-notes@augendre.info", "description": "My take on tech-related subjects (but not only).", diff --git a/src/blog/urls.py b/src/blog/urls.py index c0c95f7..5555c07 100644 --- a/src/blog/urls.py +++ b/src/blog/urls.py @@ -1,4 +1,4 @@ -"""blog URL Configuration +"""blog URL Configuration. The `urlpatterns` list routes URLs to views. For more information please see: 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.urls import include, path 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 @@ -35,7 +35,7 @@ urlpatterns = [ ] 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 += [path("__debug__/", include(debug_toolbar.urls))] diff --git a/src/manage.py b/src/manage.py index b532cc3..6ce3ee2 100755 --- a/src/manage.py +++ b/src/manage.py @@ -5,7 +5,7 @@ import sys def main() -> None: - """Run administrative tasks.""" # noqa: DAR401 + """Run administrative tasks.""" os.environ.setdefault("DJANGO_SETTINGS_MODULE", "blog.settings") try: from django.core.management import execute_from_command_line diff --git a/tasks.py b/tasks.py index 0aac376..ba4527e 100644 --- a/tasks.py +++ b/tasks.py @@ -87,7 +87,7 @@ def mypy(ctx: Context) -> None: @task(pre=[pre_commit, test_cov]) -def check(ctx: Context) -> None: +def check(_ctx: Context) -> None: pass @@ -113,24 +113,25 @@ def deploy(ctx: Context) -> None: @task -def check_alive(ctx: Context) -> None: +def check_alive(_ctx: Context) -> None: import requests exception = None for _ in range(5): try: - res = requests.get("https://gabnotes.org") + res = requests.get("https://gabnotes.org", timeout=5) res.raise_for_status() - print("Server is up & running") - return except requests.exceptions.HTTPError as e: time.sleep(2) exception = e + else: + print("Server is up & running") # noqa: T201 + return raise RuntimeError("Failed to reach the server") from exception @task(pre=[check, build, publish, deploy], post=[check_alive]) -def beam(ctx: Context) -> None: +def beam(_ctx: Context) -> None: pass