From 2b2f8b77f75fe920663d7f4768d8bc67c7625494 Mon Sep 17 00:00:00 2001 From: Henri Hulski Date: Wed, 7 Oct 2026 20:42:49 +0200 Subject: [PATCH 1/9] docs: update developing.rst and add readthedocs integration --- .readthedocs.yaml | 34 +++++++++++++++++++++ doc/developing.rst | 75 ++++++++++++++++++++++++++++------------------ 2 files changed, 80 insertions(+), 29 deletions(-) create mode 100644 .readthedocs.yaml diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..a5fcb53 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,34 @@ +# Read the Docs configuration file for Sphinx projects +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Set the OS, Python version and other tools you might need +build: + os: ubuntu-lts-latest + tools: + python: "3.14" + +# Build documentation in the "doc/" directory with Sphinx +sphinx: + configuration: doc/conf.py + # You can configure Sphinx to use a different builder, for instance use the dirhtml builder for simpler URLs + # builder: "dirhtml" + # Fail on all warnings to avoid broken references + # fail_on_warning: true + +# Optionally build your docs in additional formats such as PDF and ePub +# formats: +# - pdf +# - epub + +# Optional but recommended, declare the Python requirements required +# to build your documentation +# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html +python: + install: + - method: pip + path: . + extra_requirements: + - docs \ No newline at end of file diff --git a/doc/developing.rst b/doc/developing.rst index e85787d..3f5c78f 100644 --- a/doc/developing.rst +++ b/doc/developing.rst @@ -17,9 +17,7 @@ Then go to the reg directory:: $ cd reg -Make sure you have virtualenv_ installed. - -Create a new virtualenv for Python 3 inside the reg directory:: +Create a new virtualenv inside the reg directory:: $ python -m venv --upgrade-deps .venv @@ -30,7 +28,7 @@ Activate the virtualenv:: Install the various dependencies and development tools from develop_requirements.txt:: - $ pip install -Ur develop_requirements.txt + (.venv) $ pip install -Ur develop_requirements.txt For upgrading the requirements just run the command again. @@ -39,7 +37,6 @@ For upgrading the requirements just run the command again. The following commands work only if you have the virtualenv activated. .. _github: https://docs.github.com/en/authentication/connecting-to-github-with-ssh -.. _virtualenv: https://pypi.org/project/virtualenv Install pre-commit hook for Black integration --------------------------------------------- @@ -47,7 +44,7 @@ Install pre-commit hook for Black integration We're using Black_ for formatting the code and it's recommended to install the `pre-commit hook`_ for Black integration before committing:: - $ pre-commit install + (.venv) $ pre-commit install .. _`pre-commit hook`: https://black.readthedocs.io/en/stable/integrations/source_version_control.html @@ -56,11 +53,11 @@ Running the tests You can run the tests using `pytest`_:: - $ pytest + (.venv) $ pytest To generate test coverage information as HTML do:: - $ pytest --cov --cov-report html + (.venv) $ pytest --cov --cov-report html You can then point your web browser to the ``htmlcov/index.html`` file in the project directory and click on modules to see detailed coverage @@ -73,13 +70,24 @@ Black To format the code with the `Black Code Formatter`_ run in the root directory:: - $ black reg + (.venv) $ black reg Black has also integration_ for the most popular editors. .. _`Black Code Formatter`: https://black.readthedocs.io .. _integration: https://black.readthedocs.io/en/stable/editor_integration.html +isort +----- + +To sort imports with isort_ from the project directory do:: + + (.venv) $ isort . + +isort uses the settings in ``pyproject.toml``. + +.. _isort: https://pycqa.github.io/isort/ + flake8 ------ @@ -88,21 +96,30 @@ mistakes using pyflakes_, checks for PEP8_ style compliance and can do `cyclomatic complexity`_ checking. To do pyflakes and pep8 checking do:: - $ flake8 reg + (.venv) $ flake8 reg To also show cyclomatic complexity, use this command:: - $ flake8 --max-complexity=10 reg - + (.venv) $ flake8 --max-complexity=10 reg .. _flake8: https://pypi.org/project/flake8 - .. _pyflakes: https://pypi.org/project/pyflakes - .. _pep8: https://peps.python.org/pep-0008 - .. _`cyclomatic complexity`: https://en.wikipedia.org/wiki/Cyclomatic_complexity +Type checking +------------- + +Reg uses mypy_ and pyright_ for type checking. Run either checker +from the project directory:: + + (.venv) $ mypy + (.venv) $ pyright + +Both checkers use the settings in ``pyproject.toml``. + +.. _mypy: https://mypy.readthedocs.io/ +.. _pyright: https://microsoft.github.io/pyright/ Running the documentation tests ------------------------------- @@ -110,32 +127,32 @@ Running the documentation tests The documentation contains code. To check these code snippets, you can run this code using this command:: - (py3) $ sphinx-build -b doctest doc doc/build/doctest + (.venv) $ sphinx-build -b doctest doc doc/_build/doctest Or alternatively if you have ``Make`` installed:: - (py3) $ cd doc - (py3) $ make doctest + (.venv) $ cd doc + (.venv) $ make doctest Or from the Reg project directory:: - (py3) $ make -C doc doctest + (.venv) $ make -C doc doctest Building the HTML documentation ------------------------------- To build the HTML documentation (output in ``doc/_build/html``), run:: - $ sphinx-build doc doc/_build/html + (.venv) $ sphinx-build doc doc/_build/html Or alternatively if you have ``Make`` installed:: - $ cd doc - $ make html + (.venv) $ cd doc + (.venv) $ make html Or from the Reg project directory:: - $ make -C doc html + (.venv) $ make -C doc html Tox --- @@ -154,20 +171,20 @@ One tool you can use to install multiple versions of Python is pyenv_. To find out which test environments are defined for Reg in tox.ini run:: - $ tox -l + (.venv) $ tox -l You can run all tox tests with:: - $ tox + (.venv) $ tox You can also specify a test environment to run e.g.:: - $ tox -e py311 - $ tox -e lint - $ tox -e docs + (.venv) $ tox -e py311 + (.venv) $ tox -e lint + (.venv) $ tox -e docs To run a simple performance test you can use:: - $ tox -e perf + (.venv) $ tox -e perf .. _pyenv: https://github.com/pyenv/pyenv From dcd398c819d007782963aa02bb5fc5902cf72518 Mon Sep 17 00:00:00 2001 From: Henri Hulski Date: Wed, 7 Oct 2026 21:40:37 +0200 Subject: [PATCH 2/9] chore: remove Python 3.10, add Python 3.15, switch to Pypy 3.12 and adjust code --- .github/workflows/main.yml | 2 +- .pre-commit-config.yaml | 2 +- pyproject.toml | 22 +++++++++++----------- reg/context.py | 7 +++++-- reg/dispatch.py | 7 +++++-- reg/tests/test_arginfo.py | 3 +-- reg/tests/test_dispatch.py | 3 +-- reg/tests/test_dispatch_method.py | 3 +-- 8 files changed, 26 insertions(+), 23 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0fcdb62..4c5e82b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -19,7 +19,7 @@ jobs: strategy: matrix: - python-version: ["3.10", "3.11", "3.12", "3.13", "3.14", "pypy3.11"] + python-version: ["3.11", "3.12", "3.13", "3.14", "3.15", "pypy-3.12"] # Steps represent a sequence of tasks that will be executed as part of the job steps: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5ea0c14..b692006 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,4 +12,4 @@ repos: rev: v3.21.2 hooks: - id: pyupgrade - args: [--py310-plus] + args: [--py311-plus] diff --git a/pyproject.toml b/pyproject.toml index be923d1..b4f728e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,17 +15,17 @@ classifiers = [ "Intended Audience :: Developers", "Topic :: Software Development :: Libraries :: Python Modules", "Programming Language :: Python", - "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", + "Programming Language :: Python :: 3.15", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Development Status :: 5 - Production/Stable", ] -requires-python = ">= 3.10" +requires-python = ">= 3.11" [project.urls] Documentation = "https://reg.readthedocs.io" @@ -34,7 +34,7 @@ Issues = "https://github.com/morepath/reg/issues" Changelog = "https://github.com/morepath/reg/blob/master/CHANGES.txt" [project.optional-dependencies] -test = ["pytest >= 8", "pytest-env", "typing_extensions"] +test = ["pytest >= 8", "pytest-env"] coverage = ["pytest-cov"] lint = [ "black", @@ -79,7 +79,7 @@ extend-select = ["TC", "TC1"] max-line-length = 88 [tool.mypy] -python_version = "3.10" +python_version = "3.11" strict = true files = ["."] warn_unreachable = true @@ -92,11 +92,11 @@ typeCheckingMode = "strict" [tool.tox] requires = ["tox>=4"] env_list = [ - "py310", "py311", "py312", "py313", "py314", + "py315", "pypy3", "coverage", "pre-commit", @@ -108,12 +108,12 @@ env_list = [ skip_missing_interpreters = true [tool.tox.gh.python] -"3.10" = ["py310"] "3.11" = ["py311"] "3.12" = ["py312"] "3.13" = ["py313"] "3.14" = ["py314", "pre-commit", "coverage", "mypy", "pyright", "perf"] -"pypy-3.11" = ["pypy3"] +"3.15" = ["py315"] +"pypy-3.12" = ["pypy3"] [tool.tox.env_run_base] package = "editable" @@ -153,26 +153,26 @@ commands = [ base_python = ["python3"] extras = ["mypy", "docs"] commands = [ - ["mypy", "--python-version", "3.10"], ["mypy", "--python-version", "3.11"], ["mypy", "--python-version", "3.12"], ["mypy", "--python-version", "3.13"], ["mypy", "--python-version", "3.14"], + ["mypy", "--python-version", "3.15"], ] [tool.tox.env.pyright] base_python = ["python3"] extras = ["pyright", "docs"] commands = [ - ["pyright", "--pythonversion", "3.10"], ["pyright", "--pythonversion", "3.11"], ["pyright", "--pythonversion", "3.12"], ["pyright", "--pythonversion", "3.13"], ["pyright", "--pythonversion", "3.14"], + ["pyright", "--pythonversion", "3.15"], ] [tool.black] -target-version = ['py310', 'py311', 'py312', 'py313', 'py314'] +target-version = ['py311', 'py312', 'py313', 'py314', 'py315'] include = '\.pyi?$' exclude = ''' ( @@ -191,7 +191,7 @@ exclude = ''' [tool.isort] profile = 'black' -py_version = 310 +py_version = 311 skip_gitignore = true extra_standard_library = ["typing_extensions"] known_first_party = ["dectate", "importscan", "reg"] diff --git a/reg/context.py b/reg/context.py index 8118667..7781024 100644 --- a/reg/context.py +++ b/reg/context.py @@ -7,9 +7,12 @@ Any, Concatenate, Generic, + Never, + ParamSpec, + TypeVar, + cast, + overload, ) -from typing import NoReturn as Never -from typing import ParamSpec, TypeVar, cast, overload from .arginfo import arginfo from .dispatch import Dispatch, dispatch, execute, format_signature, identity diff --git a/reg/dispatch.py b/reg/dispatch.py index fe1e5b5..7583c53 100644 --- a/reg/dispatch.py +++ b/reg/dispatch.py @@ -6,9 +6,12 @@ Any, Generic, NamedTuple, + Never, + ParamSpec, + TypeVar, + cast, + overload, ) -from typing import NoReturn as Never -from typing import ParamSpec, TypeVar, cast, overload from .arginfo import arginfo from .error import RegistrationError diff --git a/reg/tests/test_arginfo.py b/reg/tests/test_arginfo.py index 711ba78..c8890b0 100644 --- a/reg/tests/test_arginfo.py +++ b/reg/tests/test_arginfo.py @@ -1,7 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any -from typing_extensions import assert_type +from typing import TYPE_CHECKING, Any, assert_type import pytest diff --git a/reg/tests/test_dispatch.py b/reg/tests/test_dispatch.py index 8a70195..48c7c92 100644 --- a/reg/tests/test_dispatch.py +++ b/reg/tests/test_dispatch.py @@ -1,7 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any -from typing_extensions import assert_type +from typing import TYPE_CHECKING, Any, assert_type import pytest diff --git a/reg/tests/test_dispatch_method.py b/reg/tests/test_dispatch_method.py index 42d5b12..1aba566 100644 --- a/reg/tests/test_dispatch_method.py +++ b/reg/tests/test_dispatch_method.py @@ -1,8 +1,7 @@ from __future__ import annotations from types import FunctionType -from typing import TYPE_CHECKING, Any -from typing_extensions import assert_type +from typing import TYPE_CHECKING, Any, assert_type import pytest From f6b8f33cc8d1c51db4a72c958462b40c8ed9e391 Mon Sep 17 00:00:00 2001 From: Henri Hulski Date: Wed, 7 Oct 2026 23:28:10 +0200 Subject: [PATCH 3/9] chore: remove coveralls integration --- .github/workflows/main.yml | 11 ----------- README.rst | 3 --- 2 files changed, 14 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4c5e82b..4454106 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -32,14 +32,3 @@ jobs: run: python -m pip install --upgrade pip tox tox-gh - name: "Run tox targets for ${{ matrix.python-version }}" run: "python -m tox" - - - name: "Report to coveralls" - # coverage is only created in the py314 environment - # --service=github is a workaround for bug - # https://github.com/coveralls-clients/coveralls-python/issues/251 - if: "matrix.python-version == '3.14'" - run: | - pip install coveralls - coveralls --service=github - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/README.rst b/README.rst index 9281e0d..09596e2 100644 --- a/README.rst +++ b/README.rst @@ -2,9 +2,6 @@ :target: https://github.com/morepath/reg/actions?workflow=CI :alt: CI Status -.. image:: https://coveralls.io/repos/github/morepath/reg/badge.svg?branch=master - :target: https://coveralls.io/github/morepath/reg?branch=master - .. image:: https://img.shields.io/pypi/v/reg.svg :target: https://pypi.org/project/reg/ From 835ad0ae54e57024da7048c8ae051fcdb50d9be8 Mon Sep 17 00:00:00 2001 From: Henri Hulski Date: Thu, 8 Oct 2026 17:23:31 +0200 Subject: [PATCH 4/9] chore: replace flake8, black, isort, pyupgrade and pre-commit with ruff --- .pre-commit-config.yaml | 15 ---------- develop_requirements.txt | 1 - doc/developing.rst | 63 ++++++++++------------------------------ pyproject.toml | 60 ++++++++++---------------------------- 4 files changed, 31 insertions(+), 108 deletions(-) delete mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml deleted file mode 100644 index b692006..0000000 --- a/.pre-commit-config.yaml +++ /dev/null @@ -1,15 +0,0 @@ -repos: - - repo: https://github.com/psf/black-pre-commit-mirror - rev: 26.10.0 - hooks: - - id: black - - repo: https://github.com/PyCQA/flake8 - rev: "7.4.1" - hooks: - - id: flake8 - additional_dependencies: [flake8-pyproject, flake8-type-checking] - - repo: https://github.com/asottile/pyupgrade - rev: v3.21.2 - hooks: - - id: pyupgrade - args: [--py311-plus] diff --git a/develop_requirements.txt b/develop_requirements.txt index f08b794..c1d8e4c 100644 --- a/develop_requirements.txt +++ b/develop_requirements.txt @@ -1,6 +1,5 @@ # development -e '.[test,coverage,lint,docs,mypy,pyright]' -pre-commit tox >= 4 # releaser diff --git a/doc/developing.rst b/doc/developing.rst index 3f5c78f..3819b40 100644 --- a/doc/developing.rst +++ b/doc/developing.rst @@ -38,15 +38,24 @@ For upgrading the requirements just run the command again. .. _github: https://docs.github.com/en/authentication/connecting-to-github-with-ssh -Install pre-commit hook for Black integration ---------------------------------------------- +Code quality checks +------------------- -We're using Black_ for formatting the code and it's recommended to -install the `pre-commit hook`_ for Black integration before committing:: +Morepath uses Ruff_ for linting, import sorting, and code formatting. Run +the checks from the project directory with:: - (.venv) $ pre-commit install + (.venv) $ ruff check . + (.venv) $ ruff format --check . -.. _`pre-commit hook`: https://black.readthedocs.io/en/stable/integrations/source_version_control.html +To apply lint fixes and format the code, run:: + + (.venv) $ ruff check --fix . + (.venv) $ ruff format . + +Ruff uses the project settings in ``pyproject.toml``. The ``lint`` tox +environment runs both checks. + +.. _Ruff: https://docs.astral.sh/ruff/ Running the tests ----------------- @@ -65,48 +74,6 @@ information. .. _`pytest`: https://pytest.org -Black ------ - -To format the code with the `Black Code Formatter`_ run in the root directory:: - - (.venv) $ black reg - -Black has also integration_ for the most popular editors. - -.. _`Black Code Formatter`: https://black.readthedocs.io -.. _integration: https://black.readthedocs.io/en/stable/editor_integration.html - -isort ------ - -To sort imports with isort_ from the project directory do:: - - (.venv) $ isort . - -isort uses the settings in ``pyproject.toml``. - -.. _isort: https://pycqa.github.io/isort/ - -flake8 ------- - -flake8_ is a tool that can do various checks for common Python -mistakes using pyflakes_, checks for PEP8_ style compliance and -can do `cyclomatic complexity`_ checking. To do pyflakes and pep8 -checking do:: - - (.venv) $ flake8 reg - -To also show cyclomatic complexity, use this command:: - - (.venv) $ flake8 --max-complexity=10 reg - -.. _flake8: https://pypi.org/project/flake8 -.. _pyflakes: https://pypi.org/project/pyflakes -.. _pep8: https://peps.python.org/pep-0008 -.. _`cyclomatic complexity`: https://en.wikipedia.org/wiki/Cyclomatic_complexity - Type checking ------------- diff --git a/pyproject.toml b/pyproject.toml index b4f728e..46a59fb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,13 +36,7 @@ Changelog = "https://github.com/morepath/reg/blob/master/CHANGES.txt" [project.optional-dependencies] test = ["pytest >= 8", "pytest-env"] coverage = ["pytest-cov"] -lint = [ - "black", - "flake8", - "flake8-pyproject", - "flake8-type-checking", - "isort", -] +lint = ["ruff"] # NOTE: sphinx 9.1 introduced PEP-695 syntax into the code-base, so we # can't type check in pre-3.12 mode this way, which we need to, # as long as we still support these versions. @@ -72,11 +66,14 @@ source = ["reg"] [tool.coverage.report] show_missing = true -[tool.flake8] -show-source = true -ignore = ["E203", "E301", "E501", "E704", "E731", "W503"] -extend-select = ["TC", "TC1"] -max-line-length = 88 +[tool.ruff.lint] +select = ["E", "F", "W", "I", "TC", "UP", "RUF"] +ignore = ["E501", "E731"] +future-annotations = true + +[tool.ruff.lint.isort] +extra-standard-library = ["typing_extensions"] +known-first-party = ["dectate", "importscan", "reg"] [tool.mypy] python_version = "3.11" @@ -99,7 +96,7 @@ env_list = [ "py315", "pypy3", "coverage", - "pre-commit", + "lint", "docs", "perf", "mypy", @@ -111,7 +108,7 @@ skip_missing_interpreters = true "3.11" = ["py311"] "3.12" = ["py312"] "3.13" = ["py313"] -"3.14" = ["py314", "pre-commit", "coverage", "mypy", "pyright", "perf"] +"3.14" = ["py314", "coverage", "lint", "mypy", "pyright", "perf"] "3.15" = ["py315"] "pypy-3.12" = ["pypy3"] @@ -127,12 +124,12 @@ commands = [ ["pytest", "--cov", "--cov-fail-under=100", "{posargs:reg}"], ] -[tool.tox.env.pre-commit] -base_python = ["python3"] -package = "skip" -deps = ["pre-commit"] +[tool.tox.env.lint] +base_python = ["python3.14"] +extras = ["lint"] commands = [ - ["pre-commit", "run", "--all-files"], + ["ruff", "check", "."], + ["ruff", "format", "--check", "."], ] [tool.tox.env.docs] @@ -170,28 +167,3 @@ commands = [ ["pyright", "--pythonversion", "3.14"], ["pyright", "--pythonversion", "3.15"], ] - -[tool.black] -target-version = ['py311', 'py312', 'py313', 'py314', 'py315'] -include = '\.pyi?$' -exclude = ''' -( - /( - \.git - | \.tox - | env - | venv - | .venv - | __pycache__ - | build - | dist - )/ -) -''' - -[tool.isort] -profile = 'black' -py_version = 311 -skip_gitignore = true -extra_standard_library = ["typing_extensions"] -known_first_party = ["dectate", "importscan", "reg"] From 1e53a26942db7bce761f56b76a6643ab48099114 Mon Sep 17 00:00:00 2001 From: Henri Hulski Date: Thu, 8 Oct 2026 17:31:13 +0200 Subject: [PATCH 5/9] style: format and lint the codebase with ruff --- doc/conf.py | 2 +- reg/dispatch.py | 14 ++++++-------- reg/tests/test_classdispatch.py | 12 ++++++------ reg/tests/test_dispatch.py | 18 +++++++++--------- reg/tests/test_dispatch_method.py | 11 ++++++----- reg/tests/test_docgen.py | 20 +++++++++++++++----- 6 files changed, 43 insertions(+), 34 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index bbafe93..b3c9030 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -60,7 +60,7 @@ # General information about the project. project = "Reg" -copyright = "2010 - %s, Morepath Developers" % datetime.today().year +copyright = f"2010 - {datetime.today().year}, Morepath Developers" author = "Martijn Faassen" # The version info for the project you're documenting, acts as replacement for diff --git a/reg/dispatch.py b/reg/dispatch.py index 7583c53..ce826ba 100644 --- a/reg/dispatch.py +++ b/reg/dispatch.py @@ -171,7 +171,7 @@ def call({signature}): args = arginfo(self.wrapped_func) assert args is not None signature = format_signature(args) - predicate_args = ", ".join("{0}={0}".format(x) for x in args.args) + predicate_args = ", ".join(f"{x}={x}" for x in args.args) code_source = code_template.format( signature=signature, predicate_args=predicate_args ) @@ -201,10 +201,8 @@ def call({signature}): # We now build the implementation for the predicate_key method self._predicate_key = execute( - "def predicate_key({signature}):\n" - " return _return_type(_registry_key({predicate_args}))".format( - signature=format_signature(args), predicate_args=predicate_args - ), + f"def predicate_key({format_signature(args)}):\n" + f" return _return_type(_registry_key({predicate_args}))", _registry_key=None, _return_type=None, )["predicate_key"] @@ -287,14 +285,14 @@ def validate_signature(f: Callable[..., Any], dispatch: Callable[..., Any]) -> N f_arginfo = arginfo(f) if f_arginfo is None: raise RegistrationError( - "Cannot register non-callable for dispatch " "%r: %r" % (dispatch, f) + f"Cannot register non-callable for dispatch {dispatch!r}: {f!r}" ) d_arginfo = arginfo(dispatch) assert d_arginfo is not None if not same_signature(d_arginfo, f_arginfo): raise RegistrationError( - "Signature of callable dispatched to (%r) " - "not that of dispatch (%r)" % (f, dispatch) + f"Signature of callable dispatched to ({f!r}) " + f"not that of dispatch ({dispatch!r})" ) diff --git a/reg/tests/test_classdispatch.py b/reg/tests/test_classdispatch.py index e38b783..8c98645 100644 --- a/reg/tests/test_classdispatch.py +++ b/reg/tests/test_classdispatch.py @@ -30,7 +30,7 @@ def something(cls: type[Any]) -> str: raise NotImplementedError() def something_for_object(cls: type[object]) -> str: - return "Something for %s" % cls + return f"Something for {cls}" something.register(something_for_object, cls=object) @@ -46,10 +46,10 @@ def something(cls: type[Any], other: Any) -> str: raise NotImplementedError() def something_for_object_and_object(cls: type[object], other: object) -> str: - return "Something, other is object: %s" % other + return f"Something, other is object: {other}" def something_for_object_and_foo(cls: type[object], other: Foo) -> str: - return "Something, other is Foo: %s" % other + return f"Something, other is Foo: {other}" something.register(something_for_object_and_object, cls=object, other=object) @@ -67,7 +67,7 @@ def something(cls: type[Any], extra: str) -> str: raise NotImplementedError() def something_for_object(cls: type[object], extra: str) -> str: - return "Extra: %s" % extra + return f"Extra: {extra}" something.register(something_for_object, cls=object) @@ -93,10 +93,10 @@ def something(cls: type[Any]) -> str: raise NotImplementedError() def something_for_object(cls: type[object]) -> str: - return "Something for %s" % cls + return f"Something for {cls}" def something_for_special(cls: type[SpecialClass]) -> str: - return "Special for %s" % cls + return f"Special for {cls}" something.register(something_for_object, cls=object) something.register(something_for_special, cls=SpecialClass) diff --git a/reg/tests/test_dispatch.py b/reg/tests/test_dispatch.py index 48c7c92..be98dc5 100644 --- a/reg/tests/test_dispatch.py +++ b/reg/tests/test_dispatch.py @@ -363,7 +363,7 @@ def target(obj: str) -> None: def test_call_not_found_one_source() -> None: @dispatch("obj") def target(obj: str) -> str: - return "default: %s" % obj + return f"default: {obj}" assert target("dummy") == "default: dummy" @@ -446,10 +446,10 @@ def specific(obj: Alpha) -> str: def test_extra_arg_for_call() -> None: @dispatch("obj") def target(obj: Any, extra: str) -> str: - return "General: %s" % extra + return f"General: {extra}" def specific(obj: Alpha, extra: str) -> str: - return "Specific: %s" % extra + return f"Specific: {extra}" target.register(specific, obj=Alpha) @@ -521,10 +521,10 @@ def b(obj: Beta) -> str: def test_different_defaults_in_specific_non_dispatch_arg() -> None: @dispatch("obj") def target(obj: Any, blah: str = "default") -> str: - return "fallback: %s" % blah + return f"fallback: {blah}" def a(obj: Any, blah: str = "default 2") -> str: - return "a: %s" % blah + return f"a: {blah}" target.register(a, obj=Alpha) @@ -534,10 +534,10 @@ def a(obj: Any, blah: str = "default 2") -> str: def test_different_defaults_in_specific_dispatch_arg() -> None: @dispatch(match_key("key")) def target(key: str = "default") -> str: - return "fallback: %s" % key + return f"fallback: {key}" def a(key: str = "default 2") -> str: - return "a: %s" % key + return f"a: {key}" target.register(a, key="foo") @@ -549,10 +549,10 @@ def a(key: str = "default 2") -> str: def test_different_defaults_in_specific_dispatch_arg_causes_dispatch() -> None: @dispatch(match_key("key")) def target(key: str = "foo") -> str: - return "fallback: %s" % key + return f"fallback: {key}" def a(key: str = "default 2") -> str: - return "a: %s" % key + return f"a: {key}" target.register(a, key="foo") diff --git a/reg/tests/test_dispatch_method.py b/reg/tests/test_dispatch_method.py index 1aba566..91d5586 100644 --- a/reg/tests/test_dispatch_method.py +++ b/reg/tests/test_dispatch_method.py @@ -230,10 +230,11 @@ class Beta: assert foo.bar(Alpha()) == "default" Foo.bar.register( - methodify(lambda obj: "Alpha", "app"), obj=Alpha # pyright: ignore + methodify(lambda obj: "Alpha", "app"), # pyright: ignore + obj=Alpha, ) Foo.bar.register( - methodify(lambda app, obj: "Beta %s" % app.x, "app"), # pyright: ignore + methodify(lambda app, obj: f"Beta {app.x}", "app"), # pyright: ignore obj=Beta, ) @@ -271,7 +272,7 @@ def __init__(self, x: str) -> None: @dispatch_method(match_instance("obj")) def bar(self, obj: Any) -> str: - return "default %s" % self.x + return f"default {self.x}" class Alpha: pass @@ -279,8 +280,8 @@ class Alpha: class Beta: pass - Foo.bar.register(lambda self, obj: "Alpha %s" % self.x, obj=Alpha) - Foo.bar.register(lambda self, obj: "Beta %s" % self.x, obj=Beta) + Foo.bar.register(lambda self, obj: f"Alpha {self.x}", obj=Alpha) + Foo.bar.register(lambda self, obj: f"Beta {self.x}", obj=Beta) foo = Foo("hello") diff --git a/reg/tests/test_docgen.py b/reg/tests/test_docgen.py index 9929dad..41ab0d4 100644 --- a/reg/tests/test_docgen.py +++ b/reg/tests/test_docgen.py @@ -4,7 +4,6 @@ import sys from typing import TYPE_CHECKING -import pytest from sphinx.application import Sphinx from .fixtures.module import Foo, foo # pyright: ignore[reportUnknownVariableType] @@ -12,6 +11,8 @@ if TYPE_CHECKING: from pathlib import Path + import pytest + def rstrip_lines(s: str) -> str: "Delete trailing spaces from each line in s." @@ -57,37 +58,45 @@ def test_dispatch_method_help(capsys: pytest.CaptureFixture[str]) -> None: Foo.bar # pyright: ignore[reportUnknownMemberType, reportUnknownArgumentType] ) out, _err = capsys.readouterr() - assert rstrip_lines(out) == """\ + assert ( + rstrip_lines(out) + == """\ Help on function bar in module reg.tests.fixtures.module: bar(self, obj) Return the bar of an object. """ + ) def test_dispatch_help(capsys: pytest.CaptureFixture[str]) -> None: pydoc.help(foo) # pyright: ignore[reportUnknownArgumentType] out, _err = capsys.readouterr() - assert rstrip_lines(out) == """\ + assert ( + rstrip_lines(out) + == """\ Help on function foo in module reg.tests.fixtures.module: foo(obj) return the foo of an object. """ + ) def test_autodoc(tmp_path: Path) -> None: root = str(tmp_path) (tmp_path / "conf.py").write_text("extensions = ['sphinx.ext.autodoc']\n") (tmp_path / "contents.rst").write_text( - ".. automodule:: reg.tests.fixtures.module\n" " :members:\n" + ".. automodule:: reg.tests.fixtures.module\n :members:\n" ) # status=None makes Sphinx completely quiet, in case you run # py.test with the -s switch. For debugging you might want to # remove it. app = Sphinx(root, root, root + "/build", root, "text", status=None) app.build() - assert (tmp_path / "build/contents.txt").read_text() == """\ + assert ( + (tmp_path / "build/contents.txt").read_text() + == """\ Sample module for testing autodoc. class reg.tests.fixtures.module.Foo @@ -106,3 +115,4 @@ class reg.tests.fixtures.module.Foo return the foo of an object. """ + ) From 940693496f7330d36d393dcc8cb8e769f24ee507 Mon Sep 17 00:00:00 2001 From: Henri Hulski Date: Thu, 8 Oct 2026 20:46:44 +0200 Subject: [PATCH 6/9] deploy: adjust deployment strategy and publish to pypi using gh-actions --- .github/workflows/main.yml | 7 +++++-- .github/workflows/release.yml | 39 +++++++++++++++++++++++++++++++++++ pyproject.toml | 8 ++++++- 3 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4454106..6df12bd 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -18,16 +18,19 @@ jobs: runs-on: "ubuntu-latest" strategy: + fail-fast: false matrix: python-version: ["3.11", "3.12", "3.13", "3.14", "3.15", "pypy-3.12"] # Steps represent a sequence of tasks that will be executed as part of the job steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: "actions/checkout@v6" - - uses: "actions/setup-python@v6" + - uses: "actions/checkout@v7" + - uses: "actions/setup-python@v7" with: python-version: "${{ matrix.python-version }}" + allow-prereleases: true + check-latest: true - name: "Install dependencies" run: python -m pip install --upgrade pip tox tox-gh - name: "Run tox targets for ${{ matrix.python-version }}" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..aad6a8a --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,39 @@ +name: Release + +on: + push: + tags: ["v[0-9]*"] + +permissions: {} + +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + - uses: actions/setup-python@v7 + with: + python-version: "3.14" + - run: python -m pip install build + - run: python -m build + - uses: actions/upload-artifact@v7 + with: + name: dist + path: dist/ + + publish: + needs: build + runs-on: ubuntu-latest + environment: pypi + permissions: + id-token: write + steps: + - uses: actions/download-artifact@v8 + with: + name: dist + path: dist/ + - uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/pyproject.toml b/pyproject.toml index 46a59fb..9d162f3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -136,7 +136,8 @@ commands = [ base_python = ["python3"] extras = ["docs"] commands = [ - ["sphinx-build", "-b", "doctest", "doc", "{env_tmp_dir}"], + ["sphinx-build", "-W", "-b", "doctest", "doc", "{env_tmp_dir}"], + ["sphinx-build", "-W", "-b", "html", "doc", "{env_tmp_dir}"], ] [tool.tox.env.perf] @@ -167,3 +168,8 @@ commands = [ ["pyright", "--pythonversion", "3.14"], ["pyright", "--pythonversion", "3.15"], ] + +[tool.zest-releaser] +release = false +upload-pypi = false +tag-format = "v{version}" From aad9e223b86c044f67ffceb1152e1cfc2fb1eb0c Mon Sep 17 00:00:00 2001 From: Henri Hulski Date: Thu, 8 Oct 2026 22:13:51 +0200 Subject: [PATCH 7/9] docs: update CHANGES --- CHANGES.txt | 47 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 99637cf..d54b093 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,8 +1,15 @@ CHANGES ******* -0.13 (unreleased) -================= +1.0.0 (unreleased) +================== + +- Add type annotations and integrate ``mypy`` and ``pyright``. + +- Switch to ``pyproject.toml`` based project setup. + +- Replace ``repoze.lru.lru_cache`` with ``functools.lru_cache`` + to get rid of the dependency and gain typing support. - ``reg.arginfo`` is now implemented manually using ``inspect.signature``, instead of relying on the deprecated @@ -14,11 +21,41 @@ CHANGES is now very limited, compared to ``inspect.signature``, since that already properly handles ``self`` parameters. -- Added support for Python 3.10, 3.11, 3.12, 3.13 and 3.14 +- Add support for Python 3.11, 3.12, 3.13, 3.14 and 3.15. + +- Drop support for Python below 3.11. + +- Support Pypy 3.12. + +- Use GitHub Actions for CI. + +- Fix docs, docstrings and Sphinx config. + +- Update developing documentation. + +- Make an overall cleanup. + +- Fix readthedocs integration. + +- Replace flake8, black, isort, pyupgrade and pre-commit with Ruff_. + + .. _Ruff: https://docs.astral.sh/ruff + +- Remove coveralls integration. + +- Adjust the deployment strategy: + + * Move the Pypi upload from ``zest.release`` to ``gh-actions``. -- Drop support for Python below 3.10 + * Use ``v`` + version as release tags like ``v1.0.0``. -- Use GitHub Actions for CI + * So we still use ``zest.release`` but without publishing to Pypi. + The Pypi upload is triggered by pushing the new release tag to GitHub. + This happens in the Release workflow in ``.github/workflows/release.yml``. + With this workflow we get Trusted Publishing with short-lived OIDC tokens + that PyPI issues to one specific repository, workflow file and environment (``pypi``). + We also get PEP 740 provenance and can set required reviewers for the environment + and a ruleset who can create release tags and trigger releases. 0.12 (2020-01-29) From 59863f654117306988eec3e4fa754f5d3f2c5563 Mon Sep 17 00:00:00 2001 From: Henri Hulski Date: Fri, 9 Oct 2026 15:10:14 +0200 Subject: [PATCH 8/9] chore: add tracked Git pre-commit hook for Ruff checks --- .gitattributes | 2 + .githooks/pre-commit | 93 ++++++++++++++++++++++++++++++++++++++++++++ CHANGES.txt | 2 + MANIFEST.in | 1 + doc/developing.rst | 17 ++++++++ 5 files changed, 115 insertions(+) create mode 100644 .gitattributes create mode 100755 .githooks/pre-commit diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..e5c6b4a --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +* text=auto +.githooks/* text eol=lf diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 0000000..2b3ce42 --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,93 @@ +#!/usr/bin/env bash + +abort() { + printf '%s\n' "$@" >&2 + exit 1 +} + +tmpdir=$(mktemp -d "${TMPDIR:-/tmp}/morepath-ruff.XXXXXX") || + abort 'Ruff pre-commit check: could not create a temporary directory.' +trap 'rm -rf "$tmpdir"' EXIT + +list_file=$tmpdir/staged-files +staged_file=$tmpdir/staged +formatted_file=$tmpdir/formatted + +# Write the list to a file first so a git failure is not silently ignored. +git diff --cached --name-only --diff-filter=ACMR -z -- '*.py' '*.pyi' \ + >"$list_file" || + abort 'Ruff pre-commit check: could not list the staged files.' + +python_files=() +while IFS= read -r -d '' path; do + python_files+=("$path") +done <"$list_file" + +if (( ${#python_files[@]} == 0 )); then + exit 0 +fi + +if [[ -x .venv/bin/ruff ]]; then + ruff_cmd=.venv/bin/ruff +elif [[ -f .venv/Scripts/ruff.exe ]]; then + ruff_cmd=.venv/Scripts/ruff.exe +elif command -v ruff >/dev/null 2>&1; then + ruff_cmd=$(command -v ruff) +elif command -v ruff.exe >/dev/null 2>&1; then + ruff_cmd=$(command -v ruff.exe) +else + abort 'Ruff not found in .venv or PATH.' \ + "Install it with pip install -e '.[lint]'" +fi + +# report