diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0bc8ab4..b34c8ed 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,11 +1,11 @@ default_install_hook_types: - - pre-commit - - pre-push - - commit-msg +- pre-commit +- pre-push +- commit-msg repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.6.0 hooks: # - id: check-yaml - id: end-of-file-fixer @@ -15,24 +15,12 @@ repos: - id: debug-statements stages: [pre-push] -## Isort -- repo: https://github.com/pycqa/isort - rev: 5.12.0 - hooks: - - id: isort - name: isort - stages: [pre-commit] - -## Black -- repo: https://github.com/psf/black - rev: 23.3.0 - hooks: - - id: black - stages: [pre-commit] - ## Ruff - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.0.260 + rev: v0.5.0 hooks: - id: ruff + args: [--fix] + stages: [pre-commit] + - id: ruff-format stages: [pre-commit] diff --git a/ckanext/__init__.py b/ckanext/__init__.py index 35ee891..6c0b86d 100644 --- a/ckanext/__init__.py +++ b/ckanext/__init__.py @@ -1,4 +1,3 @@ -# encoding: utf-8 # this is a namespace package try: diff --git a/ckanext/check_link/helpers.py b/ckanext/check_link/helpers.py index 61090ba..a6c1e9f 100644 --- a/ckanext/check_link/helpers.py +++ b/ckanext/check_link/helpers.py @@ -2,14 +2,9 @@ import ckan.plugins.toolkit as tk -from ckanext.toolbelt.decorators import Collector - CONFIG_HEADER_LINK = "ckanext.check_link.show_header_link" DEFAULT_HEADER_LINK = False -helper, get_helpers = Collector("check_link").split() - -@helper -def show_header_link() -> bool: +def check_link_show_header_link() -> bool: return tk.asbool(tk.config.get(CONFIG_HEADER_LINK, DEFAULT_HEADER_LINK)) diff --git a/ckanext/check_link/logic/action/check.py b/ckanext/check_link/logic/action/check.py index 7763aec..353b119 100644 --- a/ckanext/check_link/logic/action/check.py +++ b/ckanext/check_link/logic/action/check.py @@ -1,5 +1,6 @@ from __future__ import annotations +import contextlib import logging from itertools import islice from typing import Any, Iterable @@ -12,7 +13,7 @@ from ckanext.toolbelt.decorators import Collector -from .. import schema +from ckanext.check_link.logic import schema CONFIG_TIMEOUT = "ckanext.check_link.check.timeout" DEFAULT_TIMEOUT = 10 @@ -188,9 +189,7 @@ def _save_reports(context, reports: Iterable[dict[str, Any]], clear: bool): for report in reports: if clear and report["state"] == "available": - try: + with contextlib.suppress(tk.ObjectNotFound): delete(context.copy(), report) - except tk.ObjectNotFound: - pass else: save(context.copy(), report) diff --git a/ckanext/check_link/logic/action/report.py b/ckanext/check_link/logic/action/report.py index f29219f..d20b705 100644 --- a/ckanext/check_link/logic/action/report.py +++ b/ckanext/check_link/logic/action/report.py @@ -3,10 +3,10 @@ import ckan.plugins.toolkit as tk from ckan.logic import validate -from ckanext.check_link.model import Report from ckanext.toolbelt.decorators import Collector -from .. import schema +from ckanext.check_link.logic import schema +from ckanext.check_link.model import Report action, get_actions = Collector("check_link").split() diff --git a/ckanext/check_link/migration/check_link/env.py b/ckanext/check_link/migration/check_link/env.py index 99106b8..1ae37e4 100644 --- a/ckanext/check_link/migration/check_link/env.py +++ b/ckanext/check_link/migration/check_link/env.py @@ -1,6 +1,4 @@ -# -*- coding: utf-8 -*- -from __future__ import with_statement import os from logging.config import fileConfig @@ -48,7 +46,7 @@ def run_migrations_offline(): url=url, target_metadata=target_metadata, literal_binds=True, - version_table="{}_alembic_version".format(name), + version_table=f"{name}_alembic_version", ) with context.begin_transaction(): @@ -72,7 +70,7 @@ def run_migrations_online(): context.configure( connection=connection, target_metadata=target_metadata, - version_table="{}_alembic_version".format(name), + version_table=f"{name}_alembic_version", ) with context.begin_transaction(): diff --git a/ckanext/check_link/model/report.py b/ckanext/check_link/model/report.py index 14874e5..71ac01e 100644 --- a/ckanext/check_link/model/report.py +++ b/ckanext/check_link/model/report.py @@ -1,7 +1,7 @@ from __future__ import annotations from datetime import datetime -from typing import Any, Optional +from typing import Any from sqlalchemy import ( Column, @@ -66,14 +66,14 @@ def dictize(self, context: dict[str, Any]) -> dict[str, Any]: return result @classmethod - def by_resource_id(cls, id_: str) -> Optional[Self]: + def by_resource_id(cls, id_: str) -> Self | None: if not id_: - return + return None return model.Session.query(cls).filter(cls.resource_id == id_).one_or_none() @classmethod - def by_url(cls, url: str) -> Optional[Self]: + def by_url(cls, url: str) -> Self | None: return ( model.Session.query(cls) .filter(cls.resource_id.is_(None), cls.url == url) diff --git a/ckanext/check_link/plugin.py b/ckanext/check_link/plugin.py index 7d26254..4173390 100644 --- a/ckanext/check_link/plugin.py +++ b/ckanext/check_link/plugin.py @@ -2,41 +2,39 @@ from typing import Any -import ckan.plugins as plugins -import ckan.plugins.toolkit as toolkit +import ckan.plugins as p +import ckan.plugins.toolkit as tk from ckan import model -from . import cli, helpers, views +from . import cli, views from .logic import action, auth from .model import Report CONFIG_CASCADE_DELETE = "ckanext.check_link.remove_reports_when_resource_deleted" -class CheckLinkPlugin(plugins.SingletonPlugin): - plugins.implements(plugins.IConfigurer) - plugins.implements(plugins.IActions) - plugins.implements(plugins.IAuthFunctions) - plugins.implements(plugins.IBlueprint) - plugins.implements(plugins.IClick) - plugins.implements(plugins.ITemplateHelpers) - plugins.implements(plugins.IDomainObjectModification, inherit=True) +@tk.blanket.helpers +class CheckLinkPlugin(p.SingletonPlugin): + p.implements(p.IConfigurer) + p.implements(p.IActions) + p.implements(p.IAuthFunctions) + p.implements(p.IBlueprint) + p.implements(p.IClick) + p.implements(p.IDomainObjectModification, inherit=True) def notify(self, entity: Any, operation: str) -> None: - if isinstance(entity, model.Resource) and entity.state == "deleted": - if toolkit.asbool(toolkit.config.get(CONFIG_CASCADE_DELETE)): - _remove_resource_report(entity.id) - - # ITemplateHelpers - - def get_helpers(self): - return helpers.get_helpers() + if ( + isinstance(entity, model.Resource) + and entity.state == "deleted" + and tk.asbool(tk.config.get(CONFIG_CASCADE_DELETE)) + ): + _remove_resource_report(entity.id) # IConfigurer def update_config(self, config_): - toolkit.add_template_directory(config_, "templates") - toolkit.add_public_directory(config_, "public") - toolkit.add_resource("assets", "check_link") + tk.add_template_directory(config_, "templates") + tk.add_public_directory(config_, "public") + tk.add_resource("assets", "check_link") # IActions def get_actions(self): diff --git a/pyproject.toml b/pyproject.toml index bedc946..2cd277a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "ckanext-check-link" -version = "0.1.2.post1" +version = "0.2.0" description = "Resource URL checker" classifiers = [ "Development Status :: 4 - Beta", "License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11",] keywords = [ "CKAN",] @@ -28,6 +28,13 @@ text = "AGPL" Homepage = "https://github.com/DataShades/ckanext-check-link" [project.optional-dependencies] +dev = [ + "pytest-ckan", + "pytest-factoryboy", + "responses", + "pytest-httpx", + "pytest-asyncio", +] [project.entry-points."ckan.plugins"] check_link = "ckanext.check_link.plugin:CheckLinkPlugin" @@ -38,55 +45,78 @@ ckan = "ckan.lib.extract:extract_ckan" [tool.setuptools.packages] find = {} -[tool.black] -# line-length = 88 -# preview = true - -[tool.ruff] -target-version = "py38" +[tool.ruff.lint] select = [ - # "B", # likely bugs and design problems + # "ANN0", # type annotations for function arguments + "B", # likely bugs and design problems # "BLE", # do not catch blind exception - # "C40", # better list/set/dict comprehensions + "C4", # better list/set/dict comprehensions # "C90", # check McCabe complexity - # "COM", # trailing commas + # "DTZ", # enforce timezone in date objects "E", # pycodestyle error # "W", # pycodestyle warning "F", # pyflakes - # "G", # format strings for logging statements - # "N", # naming conventions + # "FA", # verify annotations from future + "G", # format strings for logging statements + "N", # naming conventions + "I", # isort + "ICN", # import conventions + # # "D1", # require doc + # "D2", # doc formatting + # "D4", # doc convention # "PL", # pylint + # "PERF", # performance anti-patterns # "PT", # pytest style - # "PIE", # misc lints - # "Q", # preferred quoting style - # "RET", # improvements for return statements - # "RSE", # improvements for rise statements + "PIE", # misc lints + "RET", # improvements for return statements + "RSE", # improvements for rise statements # "S", # security testing - # "SIM", # simplify code - # "T10", # debugging statements - # "T20", # print statements - # "TID", # tidier imports + "SIM", # simplify code + "T10", # debugging statements + "T20", # print statements + "TID", # tidier imports # "TRY", # better exceptions - # "UP", # upgrade syntax for newer versions of the language + "UP", # upgrade syntax for newer versions of the language ] + ignore = [ - # "PT004", # fixture does not return anything, add leading underscore: violated by clean_db - # "PLC1901", # simplify comparison to empty string: violated by SQLAlchemy filters - "E712", # SQLAlchemy comparision to False + "RET503", # don't enforce return-None + "E712", # comparison to bool: violated by SQLAlchemy filters + "PT004", # fixture does not return anything, add leading underscore: violated by clean_db + "PLC1901", # simplify comparison to empty string: violated by SQLAlchemy filters ] -[tool.ruff.per-file-ignores] -"ckanext/check-link/tests*" = [ - "S", # security testing - "PLR2004" # magic value used in comparison +[tool.ruff.lint.per-file-ignores] +"ckanext/check_link/tests*" = ["S", "PL", "ANN"] +"ckanext/check_link/logic/*" = [ + "D417", # actions don't describe context and data_dict ] -[tool.isort] -known_ckan = "ckan" -known_ckanext = "ckanext" -known_self = "ckanext.check-link" -sections = "FUTURE,STDLIB,FIRSTPARTY,THIRDPARTY,CKAN,CKANEXT,SELF,LOCALFOLDER" -profile = "black" +[tool.ruff.lint.flake8-import-conventions.aliases] +"ckan.plugins" = "p" +"ckan.plugins.toolkit" = "tk" +sqlalchemy = "sa" + +[tool.ruff.lint.pydocstyle] +convention = "google" + +[tool.ruff.lint.isort] +section-order = [ + "future", + "standard-library", + "first-party", + "third-party", + "ckan", + "ckanext", + "self", + "local-folder", +] + +[tool.ruff.lint.isort.sections] +ckan = ["ckan"] +ckanext = ["ckanext"] +self = ["ckanext.check_link"] + [tool.pytest.ini_options] asyncio_mode = "auto" diff --git a/setup.py b/setup.py index 376813c..288c82d 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from setuptools import setup setup(