From 1242bdeeef992990b18be43b79769db386458c73 Mon Sep 17 00:00:00 2001 From: Patrick Gerken Date: Wed, 5 Mar 2025 14:58:52 +0100 Subject: [PATCH 1/3] chore(repo): Update lint tools to what lektor uses Still work in progress. Commits will be rebased! --- .pre-commit-config.yaml | 31 +++++++++++++++++++++++------ Makefile | 19 ++++++++++++++++-- lektor_atom.py | 10 ++++++---- pyproject.toml | 42 +++++++++++++++++++++++++++++++++++++++ setup.cfg | 3 +++ tests/conftest.py | 26 ++++++++++-------------- tests/test_lektor_atom.py | 34 +++++++++++++++---------------- tox.ini | 26 ++++++++++++++++++++++-- 8 files changed, 144 insertions(+), 47 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f58166c..654c071 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,9 +1,28 @@ repos: -- repo: https://github.com/ambv/black - rev: 21.12b0 + - repo: https://github.com/ambv/black + # black >= 24 is incompatible with reorder-python-imports + # See https://github.com/psf/black/issues/4175 + rev: "23.12.1" hooks: - - id: black -- repo: https://github.com/asottile/reorder_python_imports - rev: v2.6.0 + - id: black + - repo: https://github.com/asottile/reorder_python_imports + rev: "v3.14.0" hooks: - - id: reorder-python-imports + - id: reorder-python-imports + args: ["--py39-plus"] + - repo: https://github.com/asottile/pyupgrade + rev: "v3.19.0" + hooks: + - id: pyupgrade + args: ["--py39-plus"] + - repo: https://github.com/pycqa/flake8 + rev: "7.1.1" + hooks: + - id: flake8 + language_version: python3 + additional_dependencies: + # NB: autoupdate does not pick up flake8-bugbear since it is a + # transitive dependency. Make sure to update flake8-bugbear + # manually on a regular basis. + - flake8-bugbear==24.10.31 + diff --git a/Makefile b/Makefile index b3a5af4..c288d0f 100644 --- a/Makefile +++ b/Makefile @@ -1,2 +1,17 @@ -test: - tox +.DEFAULT_GOAL := help + +test-python: ## Run tests on Python files. + @echo "---> running python tests" + tox -e py + +.PHONY: lint +lint: ## Lint code. + pre-commit run -a + tox -e lint + +.PHONY: test +test: lint test-python + +.PHONY: help +help: + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' diff --git a/lektor_atom.py b/lektor_atom.py index 3c45df5..69ee0d3 100644 --- a/lektor_atom.py +++ b/lektor_atom.py @@ -67,7 +67,7 @@ def get_item_title(item, field): def get_item_body(item, field): if field not in item: - raise RuntimeError("Body field %r not found in %r" % (field, item)) + raise RuntimeError(f"Body field {field!r} not found in {item!r}") with get_ctx().changed_base_url(item.url_path): return str(escape(item[field])) @@ -132,14 +132,16 @@ def build_artifact(self, artifact): content=get_item_body(item, feed_source.item_body_field), link=url_to(item, external=True), unique_id=get_id( - "%s/%s" % (ctx.env.project.id, item["_path"].encode("utf-8")) + "{}/{}".format( + ctx.env.project.id, item["_path"].encode("utf-8") + ) ), author_name=item_author, updateddate=get_item_updated(item, feed_source.item_date_field), ) except Exception as exc: - msg = "%s: %s" % (item["_id"], exc) + msg = "{}: {}".format(item["_id"], exc) click.echo(click.style("E", fg="red") + " " + msg) with artifact.open("wb") as f: @@ -168,7 +170,7 @@ class AtomPlugin(Plugin): def get_atom_config(self, feed_id, key): default_value = self.defaults[key] - return self.get_config().get("%s.%s" % (feed_id, key), default_value) + return self.get_config().get(f"{feed_id}.{key}", default_value) def on_setup_env(self, **extra): self.env.add_build_program(AtomFeedSource, AtomFeedBuilderProgram) diff --git a/pyproject.toml b/pyproject.toml index 0846fc7..83aaf1f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,3 +3,45 @@ requires = ["setuptools>=45", "wheel", "setuptools_scm[toml]>=6"] build-backend = "setuptools.build_meta" [tool.setuptools_scm] + +################################################################ +# +# pylint +# +[tool.pylint.main] +ignore = [".git"] +load-plugins = "pylint.extensions.no_self_use" +extension-pkg-allow-list = "lxml" + +[tool.pylint.format] +max-line-length = 91 +max-module-lines = 2000 + +[tool.pylint.design] +#max-attributes = 20 +#max-positional-arguments = 7 # default is 5 +#max-locals = 30 +#max-branches = 20 +#max-nested-blocks = 8 +#max-returns = 8 + + +[tool.pylint."messages control"] +disable = [ +# "redundant-u-string-prefix", +# "consider-using-f-string", + "missing-docstring", +# "unused-argument", +# "redefined-outer-name", +# "invalid-name", +# "protected-access", +# "fixme", +# "broad-except", +# "redefined-builtin", +# "too-many-arguments", +# "too-few-public-methods", +# "too-many-public-methods", +# "duplicate-code", +# "unnecessary-dunder-call", +] + diff --git a/setup.cfg b/setup.cfg index 6d73107..ad77a65 100644 --- a/setup.cfg +++ b/setup.cfg @@ -27,3 +27,6 @@ setup_requires = [options.entry_points] lektor.plugins = atom = lektor_atom:AtomPlugin + +[options.extras_require] +test=pytest diff --git a/tests/conftest.py b/tests/conftest.py index 9e7ed01..f3ba142 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,9 +1,15 @@ +# pylint: disable=W0613,W0621 import os import shutil import tempfile from datetime import datetime import pytest +from lektor import db +from lektor.builder import Builder +from lektor.environment import Environment +from lektor.project import Project +from lektor.reporter import BufferReporter from lektor.types import Type @@ -14,15 +20,11 @@ def value_from_raw(self, raw): @pytest.fixture(scope="function") def project(request): - from lektor.project import Project - return Project.from_path(os.path.join(os.path.dirname(__file__), "demo-project")) @pytest.fixture(scope="function") def env(request, project): - from lektor.environment import Environment - e = Environment(project) e.types["datetime"] = DatetimeType # As if we had a datetime plugin. return e @@ -30,21 +32,17 @@ def env(request, project): @pytest.fixture(scope="function") def pad(request, env): - from lektor.db import Database - - return Database(env).new_pad() + return db.Database(env).new_pad() def make_builder(request, pad): - from lektor.builder import Builder - out = tempfile.mkdtemp() b = Builder(pad, out) def cleanup(): try: shutil.rmtree(out) - except (OSError, IOError): + except OSError: pass request.addfinalizer(cleanup) @@ -57,16 +55,12 @@ def builder(request, pad): @pytest.fixture(scope="function") -def F(): - from lektor.db import F - - return F +def F(): # pylint: disable=invalid-name + return db.F @pytest.fixture(scope="function") def reporter(request, env): - from lektor.reporter import BufferReporter - r = BufferReporter(env) r.push() request.addfinalizer(r.pop) diff --git a/tests/test_lektor_atom.py b/tests/test_lektor_atom.py index 9eed303..a9d0017 100644 --- a/tests/test_lektor_atom.py +++ b/tests/test_lektor_atom.py @@ -9,11 +9,12 @@ from urlparse import urljoin -def test_typical_feed(pad, builder): +def test_typical_feed(builder): failures = builder.build_all() assert not failures feed_path = os.path.join(builder.destination_path, "typical-blog/feed.xml") - feed = objectify.parse(open(feed_path)).getroot() + with open(feed_path, encoding="utf-8") as feed_stream: + feed = objectify.parse(feed_stream).getroot() assert "Feed One" == feed.title assert "My Summary" == feed.subtitle @@ -51,11 +52,12 @@ def test_typical_feed(pad, builder): assert "A. Jesse Jiryu Davis" == post1.author.name -def test_custom_feed(pad, builder): +def test_custom_feed(builder): failures = builder.build_all() assert not failures feed_path = os.path.join(builder.destination_path, "custom-blog/atom.xml") - feed = objectify.parse(open(feed_path)).getroot() + with open(feed_path, encoding="utf-8") as feed_stream: + feed = objectify.parse(feed_stream).getroot() assert "Feed Three" == feed.title assert "

My Description

" == str(feed.subtitle).strip() @@ -92,7 +94,7 @@ def test_custom_feed(pad, builder): assert "A. Jesse Jiryu Davis" == post1.author.name -def test_virtual_resolver(pad, builder): +def test_virtual_resolver(pad): # Pass a virtual source path to url_to(). feed_path = "/typical-blog@atom/feed-one" url_path = pad.get("typical-blog/post1").url_to(feed_path) @@ -117,15 +119,13 @@ def test_dependencies(pad, builder, reporter): reporter.clear() builder.build(pad.get("typical-blog@atom/feed-one")) - assert set(reporter.get_recorded_dependencies()) == set( - [ - "Website.lektorproject", - "content/typical-blog", - "content/typical-blog/contents.lr", - "content/typical-blog/post1/contents.lr", - "content/typical-blog/post2/contents.lr", - "models/blog.ini", - "models/blog-post.ini", - "configs/atom.ini", - ] - ) + assert set(reporter.get_recorded_dependencies()) == { + "Website.lektorproject", + "content/typical-blog", + "content/typical-blog/contents.lr", + "content/typical-blog/post1/contents.lr", + "content/typical-blog/post2/contents.lr", + "models/blog.ini", + "models/blog-post.ini", + "configs/atom.ini", + } diff --git a/tox.ini b/tox.ini index 829f031..98a4bb9 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,9 @@ [tox] minversion = 3 -envlist = py,build-dist +envlist = + py + build-dist + lint isolated_build = True [testenv] @@ -8,7 +11,7 @@ commands = pytest {posargs:tests} deps = lxml lektor - pytest + pytest>=6 [testenv:build-dist] skip_install = True @@ -18,3 +21,22 @@ deps = commands = python -m build . twine check dist/* + +[testenv:lint] +base_python = py313,py312,py311,py310,py39 +use_develop = true +deps = + pylint==3.3.2 + lektor + pytest>=6 + lxml +commands = + pylint {posargs:tests} + +[flake8] +max-line-length = 91 +extend-ignore = + # E203: Whitespace before ':' + E203, + # E402: Module level import not at top of file + E402 From 055302d97515270823e690698be3c16348f78f60 Mon Sep 17 00:00:00 2001 From: Patrick Gerken Date: Wed, 5 Mar 2025 18:02:10 +0100 Subject: [PATCH 2/3] WIP pyprojectification --- pyproject.toml | 37 ++++++++++++++++++++++++++++++++++--- setup.cfg | 32 -------------------------------- 2 files changed, 34 insertions(+), 35 deletions(-) delete mode 100644 setup.cfg diff --git a/pyproject.toml b/pyproject.toml index 83aaf1f..3b2784a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,9 +1,39 @@ [build-system] -requires = ["setuptools>=45", "wheel", "setuptools_scm[toml]>=6"] -build-backend = "setuptools.build_meta" +requires = ["hatchling>=1.13.0,<2.0.0", "hatch-vcs"] + build-backend = "hatchling.build" -[tool.setuptools_scm] +[project] +name = "lektor-atom" +description = "Lektor plugin that generates Atom feeds." +keywords = ["Lektor", "plugin", "static-site", "blog", "atom", "rss"] +readme = "README.md" +authors = [ + {name = "A. Jesse Jiryu Davis", email = "jesse@emptysquare.net"}, +] +classifiers = [ + "Environment :: Plugins", + "Environment :: Web Environment", + "Framework :: Lektor", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", +] +dependencies = [ + "MarkupSafe", + "feedgenerator", +] +license = {text = "MIT"} +requires-python = ">=3.12" + +[project.urls] +Homepage = "https://github.com/lektor/lektor-atom" +[project.optional-dependencies] +test = [ + "pytest", +] + +[project.entry-points."lektor.plugins"] +atom = "lektor_atom:AtomPlugin" ################################################################ # # pylint @@ -45,3 +75,4 @@ disable = [ # "unnecessary-dunder-call", ] + diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index ad77a65..0000000 --- a/setup.cfg +++ /dev/null @@ -1,32 +0,0 @@ -[metadata] -name = lektor-atom -author = A. Jesse Jiryu Davis -author_email = jesse@emptysquare.net -description = Lektor plugin that generates Atom feeds. -long_description = file: README.md -long_description_content_type = text/markdown -url = https://github.com/lektor/lektor-atom -keywords = Lektor, plugin, static-site, blog, atom, rss -license = MIT -classifiers = - Environment :: Plugins - Environment :: Web Environment - Framework :: Lektor - Intended Audience :: Developers - License :: OSI Approved :: MIT License - -[options] -install_requires= - MarkupSafe - feedgenerator -py_modules = lektor_atom -setup_requires = - setuptools >= 45 - setuptools_scm >= 6 - -[options.entry_points] -lektor.plugins = - atom = lektor_atom:AtomPlugin - -[options.extras_require] -test=pytest From 8ce13415ed8405e2046368ec03d20d5d35fde93f Mon Sep 17 00:00:00 2001 From: Patrick Gerken Date: Thu, 6 Mar 2025 00:48:24 +0100 Subject: [PATCH 3/3] feat(repo): Use ruff I cleaned up the configs and got rid of setup.cfg altogether. To keep it simple, I use some very new features which will only be available with the NEXT pip release in april. I still consider this WIP. I want to test in a docker container and extend CONTRIBUTING.md to explain both a pip and a uv workflow. --- .pre-commit-config.yaml | 35 +++++---------------- CONTRIBUTING.md | 12 ++++++-- Makefile | 3 +- lektor_atom.py | 4 +-- pyproject.toml | 68 ++++++++++++++++++++++------------------- tests/conftest.py | 5 ++- tox.ini | 42 ------------------------- 7 files changed, 59 insertions(+), 110 deletions(-) delete mode 100644 tox.ini diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 654c071..8998a93 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,28 +1,9 @@ repos: - - repo: https://github.com/ambv/black - # black >= 24 is incompatible with reorder-python-imports - # See https://github.com/psf/black/issues/4175 - rev: "23.12.1" - hooks: - - id: black - - repo: https://github.com/asottile/reorder_python_imports - rev: "v3.14.0" - hooks: - - id: reorder-python-imports - args: ["--py39-plus"] - - repo: https://github.com/asottile/pyupgrade - rev: "v3.19.0" - hooks: - - id: pyupgrade - args: ["--py39-plus"] - - repo: https://github.com/pycqa/flake8 - rev: "7.1.1" - hooks: - - id: flake8 - language_version: python3 - additional_dependencies: - # NB: autoupdate does not pick up flake8-bugbear since it is a - # transitive dependency. Make sure to update flake8-bugbear - # manually on a regular basis. - - flake8-bugbear==24.10.31 - + - repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.9.9 + hooks: + # Run the linter. + - id: ruff + # Run the formatter. + - id: ruff-format diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index df6f7c3..ba28798 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,15 +2,21 @@ ## Development environment -Create a development environment with `Python>=3.6`. +Create a development environment with `Python>=3.8`. + +Please do not create a venv directly in the project directory. +else pytest will find too many tests. You can then install the development and test dependencies with: ```bash -python -m pip install lektor -python -m pip install --editable .[test] +python -m pip install --group dev --editable . ``` +[!WARNING] +This example only works with pip in version 25.1 or newer. +Version 25.1 has a planned release date of 2025-04-30 + ## Tests To run the test suite, we use `pytest`: diff --git a/Makefile b/Makefile index c288d0f..a6ce735 100644 --- a/Makefile +++ b/Makefile @@ -2,12 +2,11 @@ test-python: ## Run tests on Python files. @echo "---> running python tests" - tox -e py + tox p .PHONY: lint lint: ## Lint code. pre-commit run -a - tox -e lint .PHONY: test test: lint test-python diff --git a/lektor_atom.py b/lektor_atom.py index 69ee0d3..a0eeb30 100644 --- a/lektor_atom.py +++ b/lektor_atom.py @@ -1,7 +1,7 @@ -import hashlib -import uuid from datetime import date from datetime import datetime +import hashlib +import uuid import click from feedgenerator.django.utils.feedgenerator import Atom1Feed diff --git a/pyproject.toml b/pyproject.toml index 3b2784a..bf2b5c1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,15 +1,18 @@ [build-system] requires = ["hatchling>=1.13.0,<2.0.0", "hatch-vcs"] - build-backend = "hatchling.build" +build-backend = "hatchling.build" [project] name = "lektor-atom" description = "Lektor plugin that generates Atom feeds." keywords = ["Lektor", "plugin", "static-site", "blog", "atom", "rss"] +version = "0.4.0" readme = "README.md" authors = [ {name = "A. Jesse Jiryu Davis", email = "jesse@emptysquare.net"}, ] +requires-python = ">=3.8" +license-expression = "MIT" classifiers = [ "Environment :: Plugins", "Environment :: Web Environment", @@ -20,20 +23,28 @@ classifiers = [ dependencies = [ "MarkupSafe", "feedgenerator", + "tox>=4.24.1", ] -license = {text = "MIT"} -requires-python = ">=3.12" +[dependency-groups] +dev = [ + "pytest", + "ruff", + "lektor", + "lxml" + + ] [project.urls] Homepage = "https://github.com/lektor/lektor-atom" -[project.optional-dependencies] -test = [ - "pytest", -] - [project.entry-points."lektor.plugins"] atom = "lektor_atom:AtomPlugin" + +[tool.hatch.build.targets.sdist] +exclude = [ + ".github", + ] + ################################################################ # # pylint @@ -47,32 +58,27 @@ extension-pkg-allow-list = "lxml" max-line-length = 91 max-module-lines = 2000 -[tool.pylint.design] -#max-attributes = 20 -#max-positional-arguments = 7 # default is 5 -#max-locals = 30 -#max-branches = 20 -#max-nested-blocks = 8 -#max-returns = 8 - - [tool.pylint."messages control"] disable = [ -# "redundant-u-string-prefix", -# "consider-using-f-string", "missing-docstring", -# "unused-argument", -# "redefined-outer-name", -# "invalid-name", -# "protected-access", -# "fixme", -# "broad-except", -# "redefined-builtin", -# "too-many-arguments", -# "too-few-public-methods", -# "too-many-public-methods", -# "duplicate-code", -# "unnecessary-dunder-call", ] +[tool.tox] +requires = ["tox>=4.22"] +env_list = [ + "3.14", + "3.13", + "3.12", + "3.11", + "3.10", + "3.9", + "3.8", +] + +[tool.tox.env_run_base] +description = "Run test under {base_python}" +dependency_groups = [ + "dev", +] +commands = [["pytest"]] diff --git a/tests/conftest.py b/tests/conftest.py index f3ba142..a6d329e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,16 +1,15 @@ -# pylint: disable=W0613,W0621 +from datetime import datetime import os import shutil import tempfile -from datetime import datetime -import pytest from lektor import db from lektor.builder import Builder from lektor.environment import Environment from lektor.project import Project from lektor.reporter import BufferReporter from lektor.types import Type +import pytest class DatetimeType(Type): diff --git a/tox.ini b/tox.ini deleted file mode 100644 index 98a4bb9..0000000 --- a/tox.ini +++ /dev/null @@ -1,42 +0,0 @@ -[tox] -minversion = 3 -envlist = - py - build-dist - lint -isolated_build = True - -[testenv] -commands = pytest {posargs:tests} -deps = - lxml - lektor - pytest>=6 - -[testenv:build-dist] -skip_install = True -deps = - build - twine -commands = - python -m build . - twine check dist/* - -[testenv:lint] -base_python = py313,py312,py311,py310,py39 -use_develop = true -deps = - pylint==3.3.2 - lektor - pytest>=6 - lxml -commands = - pylint {posargs:tests} - -[flake8] -max-line-length = 91 -extend-ignore = - # E203: Whitespace before ':' - E203, - # E402: Module level import not at top of file - E402