From 593aece4e8fa74051a717f15856424bb56b2244a Mon Sep 17 00:00:00 2001 From: Henri Hulski Date: Sat, 26 Sep 2026 20:28:41 +0200 Subject: [PATCH 1/5] chore: update .gitignore and devepop_requirements --- .gitignore | 14 ++++++++++---- develop_requirements.txt | 3 +-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index dbfe250..c77977c 100644 --- a/.gitignore +++ b/.gitignore @@ -9,14 +9,20 @@ __pycache__/ pip-wheel-metadata # Virtualenv +.venv/ /env/ /src/ # Unit test / coverage reports -.cache/ -/.coverage -/htmlcov -/.tox +.cache +.pytest_cache +.coverage +htmlcov +.tox # Sphinx documentation /doc/build/ +/doc/_build/ + +# Type checkers +.mypy_cache/ diff --git a/develop_requirements.txt b/develop_requirements.txt index 780e977..f08b794 100644 --- a/develop_requirements.txt +++ b/develop_requirements.txt @@ -1,8 +1,7 @@ # development --e '.[test,coverage,lint,docs]' +-e '.[test,coverage,lint,docs,mypy,pyright]' pre-commit tox >= 4 -radon # releaser zest.releaser[recommended] From efe6a9ad60d880fcb4732ea52069ba5cbe900087 Mon Sep 17 00:00:00 2001 From: Henri Hulski Date: Sat, 26 Sep 2026 20:35:03 +0200 Subject: [PATCH 2/5] chore: adjust setup and add flake8-type-checking --- .github/workflows/main.yml | 2 +- .pre-commit-config.yaml | 2 +- pyproject.toml | 87 ++++++++++++++++++++++++++------------ 3 files changed, 63 insertions(+), 28 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0a43517..0fcdb62 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -29,7 +29,7 @@ jobs: with: python-version: "${{ matrix.python-version }}" - name: "Install dependencies" - run: python -m pip install --upgrade pip tox tox-gh + run: python -m pip install --upgrade pip tox tox-gh - name: "Run tox targets for ${{ matrix.python-version }}" run: "python -m tox" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f64684f..2c60082 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,7 +7,7 @@ repos: rev: "7.3.0" hooks: - id: flake8 - additional_dependencies: [flake8-pyproject] + additional_dependencies: [flake8-pyproject, flake8-type-checking] - repo: https://github.com/asottile/pyupgrade rev: v3.21.2 hooks: diff --git a/pyproject.toml b/pyproject.toml index 6c25fa3..84300d3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "reg" -version = "0.13.dev0" +version = "1.0.0.dev0" dynamic = ["readme"] description = "Clever dispatch" license = "BSD-3-Clause" @@ -14,12 +14,14 @@ maintainers = [{name = "Henri Hulski", email = "henri@sancode.it"}] 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 :: Only", + "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Development Status :: 5 - Production/Stable", ] @@ -33,15 +35,21 @@ 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", "sphinx", "typing_extensions"] -docs = ["sphinx"] +test = ["pytest >= 8", "pytest-env", "typing_extensions"] coverage = ["pytest-cov"] -lint = ["black", "flake8", "flake8-pyproject"] +lint = [ + "black", + "flake8", + "flake8-pyproject", + "flake8-type-checking", + "isort", +] # 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. -mypy = ["mypy", "pytest", "sphinx < 9.1.0"] -pyright = ["pyright", "pytest", "sphinx < 9.1.0"] +docs = ["sphinx < 9.1"] +mypy = ["mypy", "pytest"] +pyright = ["pyright", "pytest"] [tool.setuptools.packages] find = {} @@ -68,16 +76,18 @@ show_missing = true [tool.flake8] show-source = true ignore = ["E203", "E301", "E501", "E704", "E731", "W503"] +extend-select = ["TC", "TC1"] max-line-length = 88 [tool.mypy] python_version = "3.10" strict = true +files = ["."] warn_unreachable = true +warn_unused_ignores = true -[[tool.mypy.overrides]] -module = "reg.tests.fixtures.*" -disallow_untyped_defs = false +[tool.pyright] +typeCheckingMode = "strict" [tool.tox] requires = ["tox>=4"] @@ -93,26 +103,26 @@ env_list = [ "docs", "perf", "mypy", - "pyright" + "pyright", ] skip_missing_interpreters = true [tool.tox.gh.python] -"3.10" = ["py310", "mypy", "pyright", "perf"] +"3.10" = ["py310"] "3.11" = ["py311"] "3.12" = ["py312"] "3.13" = ["py313"] -"3.14" = ["py314", "pre-commit", "coverage"] +"3.14" = ["py314", "pre-commit", "coverage", "mypy", "pyright", "perf"] "pypy-3.11" = ["pypy3"] [tool.tox.env_run_base] package = "editable" -extras = ["test"] +extras = ["test", "docs"] commands = [["pytest", "{posargs:reg}"]] [tool.tox.env.coverage] base_python = ["python3"] -extras = ["test", "coverage"] +extras = ["test", "coverage", "docs"] commands = [ ["pytest", "--cov", "--cov-fail-under=100", "{posargs:reg}"], ] @@ -141,22 +151,47 @@ commands = [ [tool.tox.env.mypy] base_python = ["python3"] -extras = ["mypy"] +extras = ["mypy", "docs"] commands = [ - ["mypy", "-p", "reg", "--python-version", "3.10"], - ["mypy", "-p", "reg", "--python-version", "3.11"], - ["mypy", "-p", "reg", "--python-version", "3.12"], - ["mypy", "-p", "reg", "--python-version", "3.13"], - ["mypy", "-p", "reg", "--python-version", "3.14"], + ["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"], ] [tool.tox.env.pyright] base_python = ["python3"] -extras = ["pyright"] +extras = ["pyright", "docs"] commands = [ - ["pyright", "reg", "--pythonversion", "3.10"], - ["pyright", "reg", "--pythonversion", "3.11"], - ["pyright", "reg", "--pythonversion", "3.12"], - ["pyright", "reg", "--pythonversion", "3.13"], - ["pyright", "reg", "--pythonversion", "3.14"], + ["pyright", "--pythonversion", "3.10"], + ["pyright", "--pythonversion", "3.11"], + ["pyright", "--pythonversion", "3.12"], + ["pyright", "--pythonversion", "3.13"], + ["pyright", "--pythonversion", "3.14"], ] + +[tool.black] +target-version = ['py310', 'py311', 'py312', 'py313', 'py314'] +include = '\.pyi?$' +exclude = ''' +( + /( + \.git + | \.tox + | env + | venv + | .venv + | __pycache__ + | build + | dist + )/ +) +''' + +[tool.isort] +profile = 'black' +py_version = 310 +skip_gitignore = true +extra_standard_library = ["typing_extensions"] +known_first_party = ["dectate", "importscan", "reg"] From 77f2c0ebd17a74487a63a6458cb5f2293d62989f Mon Sep 17 00:00:00 2001 From: Henri Hulski Date: Sat, 26 Sep 2026 20:55:54 +0200 Subject: [PATCH 3/5] typing: fix typing errors --- perf.py | 45 +++++++++++++++++-------------- profdispatch.py | 36 ++++++++++++++----------- reg/arginfo.py | 42 +++++++++++++++-------------- reg/cache.py | 4 ++- reg/predicate.py | 1 + reg/tests/fixtures/module.py | 6 ++--- reg/tests/test_dispatch.py | 14 +++++----- reg/tests/test_dispatch_method.py | 13 ++++++--- reg/tests/test_docgen.py | 12 +++++---- reg/tests/test_registry.py | 2 +- tox_perf.py | 45 +++++++++++++++++-------------- 11 files changed, 124 insertions(+), 96 deletions(-) diff --git a/perf.py b/perf.py index f7cef18..445931a 100644 --- a/perf.py +++ b/perf.py @@ -1,35 +1,40 @@ +from __future__ import annotations + import timeit +from typing import TYPE_CHECKING, Any + +from reg import DictCachingKeyLookup, dispatch -from reg import dispatch -from reg import DictCachingKeyLookup +if TYPE_CHECKING: + from reg.predicate import PredicateRegistry -def get_key_lookup(r): +def get_key_lookup(r: PredicateRegistry[Any]) -> DictCachingKeyLookup[Any]: return DictCachingKeyLookup(r) @dispatch(get_key_lookup=get_key_lookup) -def args0(): +def args0() -> str: raise NotImplementedError() @dispatch("a", get_key_lookup=get_key_lookup) -def args1(a): +def args1(a: Foo) -> str: raise NotImplementedError() @dispatch("a", "b", get_key_lookup=get_key_lookup) -def args2(a, b): +def args2(a: Foo, b: Foo) -> str: raise NotImplementedError() @dispatch("a", "b", "c", get_key_lookup=get_key_lookup) -def args3(a, b, c): +def args3(a: Foo, b: Foo, c: Foo) -> str: raise NotImplementedError() @dispatch("a", "b", "c", "d", get_key_lookup=get_key_lookup) -def args4(a, b, c, d): +def args4(a: Foo, b: Foo, c: Foo, d: Foo) -> str: raise NotImplementedError() @@ -37,23 +42,23 @@ class Foo: pass -def myargs0(): +def myargs0() -> str: return "args0" -def myargs1(a): +def myargs1(a: Foo) -> str: return "args1" -def myargs2(a, b): +def myargs2(a: Foo, b: Foo) -> str: return "args2" -def myargs3(a, b, c): +def myargs3(a: Foo, b: Foo, c: Foo) -> str: return "args3" -def myargs4(a, b, c, d): +def myargs4(a: Foo, b: Foo, c: Foo, d: Foo) -> str: return "args4" @@ -64,31 +69,31 @@ def myargs4(a, b, c, d): args4.register(myargs4, a=Foo, b=Foo, c=Foo, d=Foo) -def docall0(): +def docall0() -> None: args0() -def docall1(): +def docall1() -> None: args1(Foo()) -def docall2(): +def docall2() -> None: args2(Foo(), Foo()) -def docall3(): +def docall3() -> None: args3(Foo(), Foo(), Foo()) -def docall4(): +def docall4() -> None: args4(Foo(), Foo(), Foo(), Foo()) -def plain_docall0(): +def plain_docall0() -> None: myargs0() -def plain_docall4(): +def plain_docall4() -> None: myargs4(Foo(), Foo(), Foo(), Foo()) diff --git a/profdispatch.py b/profdispatch.py index b718ae1..1134e7e 100644 --- a/profdispatch.py +++ b/profdispatch.py @@ -1,9 +1,15 @@ +from __future__ import annotations + from cProfile import run -from reg import dispatch -from reg import LruCachingKeyLookup +from typing import TYPE_CHECKING, Any + +from reg import LruCachingKeyLookup, dispatch + +if TYPE_CHECKING: + from reg.predicate import PredicateRegistry -def get_key_lookup(r): +def get_key_lookup(r: PredicateRegistry[Any]) -> LruCachingKeyLookup[Any]: return LruCachingKeyLookup( r, component_cache_size=5000, @@ -13,27 +19,27 @@ def get_key_lookup(r): @dispatch(get_key_lookup=get_key_lookup) -def args0(): +def args0() -> str: raise NotImplementedError() @dispatch("a", get_key_lookup=get_key_lookup) -def args1(a): +def args1(a: Foo) -> str: raise NotImplementedError() @dispatch("a", "b", get_key_lookup=get_key_lookup) -def args2(a, b): +def args2(a: Foo, b: Foo) -> str: raise NotImplementedError() @dispatch("a", "b", "c", get_key_lookup=get_key_lookup) -def args3(a, b, c): +def args3(a: Foo, b: Foo, c: Foo) -> str: raise NotImplementedError() @dispatch("a", "b", "c", "d", get_key_lookup=get_key_lookup) -def args4(a, b, c, d): +def args4(a: Foo, b: Foo, c: Foo, d: Foo) -> str: raise NotImplementedError() @@ -41,23 +47,23 @@ class Foo: pass -def myargs0(): +def myargs0() -> str: return "args0" -def myargs1(a): +def myargs1(a: Foo) -> str: return "args1" -def myargs2(a, b): +def myargs2(a: Foo, b: Foo) -> str: return "args2" -def myargs3(a, b, c): +def myargs3(a: Foo, b: Foo, c: Foo) -> str: return "args3" -def myargs4(a, b, c, d): +def myargs4(a: Foo, b: Foo, c: Foo, d: Foo) -> str: return "args4" @@ -68,8 +74,8 @@ def myargs4(a, b, c, d): args4.register(myargs4, a=Foo, b=Foo, c=Foo, d=Foo) -def repeat_args4(): - for i in range(10000): +def repeat_args4() -> None: + for _ in range(10000): args4(Foo(), Foo(), Foo(), Foo()) diff --git a/reg/arginfo.py b/reg/arginfo.py index 6f90265..13da286 100644 --- a/reg/arginfo.py +++ b/reg/arginfo.py @@ -2,11 +2,11 @@ import inspect import sys - from typing import TYPE_CHECKING, Any, cast if TYPE_CHECKING: from collections.abc import Callable + from .types import ArgInfo if sys.version_info < (3, 14): @@ -60,11 +60,11 @@ def arginfo(callable: Callable[..., Any]) -> inspect.FullArgSpec | None: also show other getarginfo() information, and for readability. """ try: - return arginfo._cache[callable] + return arginfo._cache[callable] # pyright: ignore[reportPrivateUsage] except KeyError: # Try to get __call__ function from the cache. try: - return arginfo._cache[callable.__call__] # type: ignore + return arginfo._cache[callable.__call__] # type: ignore[operator] except (AttributeError, KeyError): pass @@ -90,13 +90,13 @@ def arginfo(callable: Callable[..., Any]) -> inspect.FullArgSpec | None: return None signature = get_signature(callable) - args = [] - varargs = None - varkw = None - defaults = [] - kwonlyargs = [] - kwonlydefaults = {} - annotations = {} + args: list[str] = [] + varargs: str | None = None + varkw: str | None = None + defaults: list[Any] = [] + kwonlyargs: list[str] = [] + kwonlydefaults: dict[str, Any] = {} + annotations: dict[str, Any] = {} if signature.return_annotation is not signature.empty: annotations["return"] = signature.return_annotation @@ -121,25 +121,27 @@ def arginfo(callable: Callable[..., Any]) -> inspect.FullArgSpec | None: if parameter.annotation is not parameter.empty: annotations[parameter.name] = parameter.annotation - result = arginfo._cache[cache_key] = inspect.FullArgSpec( - args, - varargs, - varkw, - tuple(defaults) if defaults else None, - kwonlyargs, - kwonlydefaults if kwonlydefaults else None, - annotations, + result = arginfo._cache[cache_key] = ( # pyright: ignore[reportPrivateUsage] + inspect.FullArgSpec( + args, + varargs, + varkw, + tuple(defaults) if defaults else None, + kwonlyargs, + kwonlydefaults if kwonlydefaults else None, + annotations, + ) ) return result def is_cached(callable: Callable[..., Any]) -> bool: - if callable in arginfo._cache: + if callable in arginfo._cache: # pyright: ignore[reportPrivateUsage] return True return callable.__call__ in arginfo._cache # type: ignore -arginfo._cache = {} +arginfo._cache = {} # pyright: ignore[reportPrivateUsage] arginfo.is_cached = is_cached diff --git a/reg/cache.py b/reg/cache.py index fc1a224..d826c04 100644 --- a/reg/cache.py +++ b/reg/cache.py @@ -1,11 +1,13 @@ from __future__ import annotations -from repoze.lru import lru_cache # type: ignore from typing import TYPE_CHECKING, Any, Generic +from repoze.lru import lru_cache # type: ignore[import-untyped] + if TYPE_CHECKING: from collections.abc import Callable, Sequence from typing_extensions import TypeVar + from .types import KeyLookup _ValueT = TypeVar("_ValueT", default=Callable[..., Any]) diff --git a/reg/predicate.py b/reg/predicate.py index 1b25edd..4b4c919 100644 --- a/reg/predicate.py +++ b/reg/predicate.py @@ -109,6 +109,7 @@ def match_instance( :returns: a :class:`Predicate`. """ + get_key: Callable[[dict[str, Any]], type[Any]] if func is None: get_key = lambda d: d[name].__class__ else: diff --git a/reg/tests/fixtures/module.py b/reg/tests/fixtures/module.py index 53c2957..5151a26 100644 --- a/reg/tests/fixtures/module.py +++ b/reg/tests/fixtures/module.py @@ -7,15 +7,15 @@ class Foo: "Class for foo objects." @dispatch_method("obj") - def bar(self, obj): + def bar(self, obj): # type: ignore[no-untyped-def] "Return the bar of an object." return "default" - def baz(self, obj): + def baz(self, obj): # type: ignore[no-untyped-def] "Return the baz of an object." @dispatch("obj") -def foo(obj): +def foo(obj): # type: ignore[no-untyped-def] "return the foo of an object." return "default" diff --git a/reg/tests/test_dispatch.py b/reg/tests/test_dispatch.py index 0c42786..ce4d0aa 100644 --- a/reg/tests/test_dispatch.py +++ b/reg/tests/test_dispatch.py @@ -802,7 +802,7 @@ def test_register_dispatch_key_dict() -> None: class Foo: pass - class FooSub(Foo): + class FooSub(Foo): # pyright: ignore[reportUnusedClass] pass @dispatch() @@ -1097,10 +1097,10 @@ def test_predicate_key_too_few_arguments_gives_typeerror() -> None: def foo(obj: Any) -> Any: pass - def for_bar(obj: Any) -> Any: + def for_bar(obj: Any) -> Any: # pyright: ignore[reportUnusedFunction] return obj.method() - def for_qux(obj: Any) -> Any: + def for_qux(obj: Any) -> Any: # pyright: ignore[reportUnusedFunction] return obj.method() with pytest.raises(TypeError): @@ -1112,10 +1112,10 @@ def test_predicate_key_too_many_arguments_gives_typeerror() -> None: def foo(obj: Any) -> Any: pass - def for_bar(obj: Any) -> Any: + def for_bar(obj: Any) -> Any: # pyright: ignore[reportUnusedFunction] return obj.method() - def for_qux(obj: Any) -> Any: + def for_qux(obj: Any) -> Any: # pyright: ignore[reportUnusedFunction] return obj.method() with pytest.raises(TypeError): @@ -1127,10 +1127,10 @@ def test_predicate_key_wrong_keyword_argument_gives_typeerror() -> None: def foo(obj: Any) -> Any: pass - def for_bar(obj: Any) -> Any: + def for_bar(obj: Any) -> Any: # pyright: ignore[reportUnusedFunction] return obj.method() - def for_qux(obj: Any) -> Any: + def for_qux(obj: Any) -> Any: # pyright: ignore[reportUnusedFunction] return obj.method() with pytest.raises(TypeError): diff --git a/reg/tests/test_dispatch_method.py b/reg/tests/test_dispatch_method.py index 136cadb..d8b77c3 100644 --- a/reg/tests/test_dispatch_method.py +++ b/reg/tests/test_dispatch_method.py @@ -201,12 +201,12 @@ class Alpha: pass with pytest.raises(RegistrationError): - Foo.bar.register("cannot call this", obj=Alpha) # type: ignore + Foo.bar.register("cannot call this", obj=Alpha) # type: ignore[call-overload] def test_dispatch_method_methodify_non_callable() -> None: with pytest.raises(TypeError): - methodify("cannot call this") # type: ignore + methodify("cannot call this") # type: ignore[call-overload] def test_dispatch_method_register_auto() -> None: @@ -227,8 +227,13 @@ class Beta: assert foo.bar(Alpha()) == "default" - Foo.bar.register(methodify(lambda obj: "Alpha", "app"), obj=Alpha) - Foo.bar.register(methodify(lambda app, obj: "Beta %s" % app.x, "app"), obj=Beta) + Foo.bar.register( + methodify(lambda obj: "Alpha", "app"), obj=Alpha # pyright: ignore + ) + Foo.bar.register( + methodify(lambda app, obj: "Beta %s" % app.x, "app"), # pyright: ignore + obj=Beta, + ) assert foo.bar(Alpha()) == "Alpha" assert foo.bar(Beta()) == "Beta X" diff --git a/reg/tests/test_docgen.py b/reg/tests/test_docgen.py index b899c06..4d3044b 100644 --- a/reg/tests/test_docgen.py +++ b/reg/tests/test_docgen.py @@ -5,7 +5,7 @@ import sys from pathlib import Path from sphinx.application import Sphinx -from .fixtures.module import Foo, foo +from .fixtures.module import Foo, foo # pyright: ignore[reportUnknownVariableType] def rstrip_lines(s: str) -> str: @@ -48,8 +48,10 @@ class Foo({builtins}.object) def test_dispatch_method_help(capsys: pytest.CaptureFixture[str]) -> None: - pydoc.help(Foo.bar) - out, err = capsys.readouterr() + pydoc.help( + Foo.bar # pyright: ignore[reportUnknownMemberType, reportUnknownArgumentType] + ) + out, _err = capsys.readouterr() assert rstrip_lines(out) == """\ Help on function bar in module reg.tests.fixtures.module: @@ -59,8 +61,8 @@ def test_dispatch_method_help(capsys: pytest.CaptureFixture[str]) -> None: def test_dispatch_help(capsys: pytest.CaptureFixture[str]) -> None: - pydoc.help(foo) - out, err = capsys.readouterr() + pydoc.help(foo) # pyright: ignore[reportUnknownArgumentType] + out, _err = capsys.readouterr() assert rstrip_lines(out) == """\ Help on function foo in module reg.tests.fixtures.module: diff --git a/reg/tests/test_registry.py b/reg/tests/test_registry.py index 4150f08..e784c83 100644 --- a/reg/tests/test_registry.py +++ b/reg/tests/test_registry.py @@ -149,7 +149,7 @@ class SpecialDocument(Document): def test_registry_no_sources() -> None: reg = PredicateRegistry[str]() - class Animal: + class Animal: # pyright: ignore[reportUnusedClass] pass reg.register((), "elephant") diff --git a/tox_perf.py b/tox_perf.py index 0c7bfbc..0e2f9d8 100644 --- a/tox_perf.py +++ b/tox_perf.py @@ -1,35 +1,40 @@ +from __future__ import annotations + import timeit +from typing import TYPE_CHECKING, Any + +from reg import DictCachingKeyLookup, dispatch -from reg import dispatch -from reg import DictCachingKeyLookup +if TYPE_CHECKING: + from reg.predicate import PredicateRegistry -def get_key_lookup(r): +def get_key_lookup(r: PredicateRegistry[Any]) -> DictCachingKeyLookup[Any]: return DictCachingKeyLookup(r) @dispatch(get_key_lookup=get_key_lookup) -def args0(): +def args0() -> str: raise NotImplementedError() @dispatch("a", get_key_lookup=get_key_lookup) -def args1(a): +def args1(a: Foo) -> str: raise NotImplementedError() @dispatch("a", "b", get_key_lookup=get_key_lookup) -def args2(a, b): +def args2(a: Foo, b: Foo) -> str: raise NotImplementedError() @dispatch("a", "b", "c", get_key_lookup=get_key_lookup) -def args3(a, b, c): +def args3(a: Foo, b: Foo, c: Foo) -> str: raise NotImplementedError() @dispatch("a", "b", "c", "d", get_key_lookup=get_key_lookup) -def args4(a, b, c, d): +def args4(a: Foo, b: Foo, c: Foo, d: Foo) -> str: raise NotImplementedError() @@ -37,23 +42,23 @@ class Foo: pass -def myargs0(): +def myargs0() -> str: return "args0" -def myargs1(a): +def myargs1(a: Foo) -> str: return "args1" -def myargs2(a, b): +def myargs2(a: Foo, b: Foo) -> str: return "args2" -def myargs3(a, b, c): +def myargs3(a: Foo, b: Foo, c: Foo) -> str: return "args3" -def myargs4(a, b, c, d): +def myargs4(a: Foo, b: Foo, c: Foo, d: Foo) -> str: return "args4" @@ -64,31 +69,31 @@ def myargs4(a, b, c, d): args4.register(myargs4, a=Foo, b=Foo, c=Foo, d=Foo) -def docall0(): +def docall0() -> None: args0() -def docall1(): +def docall1() -> None: args1(Foo()) -def docall2(): +def docall2() -> None: args2(Foo(), Foo()) -def docall3(): +def docall3() -> None: args3(Foo(), Foo(), Foo()) -def docall4(): +def docall4() -> None: args4(Foo(), Foo(), Foo(), Foo()) -def plain_docall0(): +def plain_docall0() -> None: myargs0() -def plain_docall4(): +def plain_docall4() -> None: myargs4(Foo(), Foo(), Foo(), Foo()) From a1a8aec707d7a291866761bddbe85a7e0b68ea88 Mon Sep 17 00:00:00 2001 From: Henri Hulski Date: Sat, 26 Sep 2026 21:08:00 +0200 Subject: [PATCH 4/5] style: fix linting including isort errors --- reg/__init__.py | 18 +++++++++--------- reg/context.py | 15 ++++++++------- reg/dispatch.py | 12 +++++------- reg/predicate.py | 2 +- reg/tests/fixtures/module.py | 2 +- reg/tests/test_arginfo.py | 5 ++++- reg/tests/test_classdispatch.py | 1 + reg/tests/test_dispatch.py | 7 ++++--- reg/tests/test_dispatch_method.py | 9 ++++++--- reg/tests/test_docgen.py | 11 ++++++++--- reg/tests/test_predicate.py | 7 ++++--- reg/tests/test_registry.py | 18 +++++++++++++----- reg/types.py | 7 +++++-- 13 files changed, 69 insertions(+), 45 deletions(-) diff --git a/reg/__init__.py b/reg/__init__.py index 30f4205..de76125 100644 --- a/reg/__init__.py +++ b/reg/__init__.py @@ -1,21 +1,21 @@ -from .dispatch import dispatch, Dispatch, LookupEntry +from .arginfo import arginfo +from .cache import DictCachingKeyLookup, LruCachingKeyLookup from .context import ( - dispatch_method, DispatchMethod, - methodify, clean_dispatch_methods, + dispatch_method, + methodify, ) -from .arginfo import arginfo +from .dispatch import Dispatch, LookupEntry, dispatch from .error import RegistrationError from .predicate import ( - Predicate, - KeyIndex, ClassIndex, - match_key, - match_instance, + KeyIndex, + Predicate, match_class, + match_instance, + match_key, ) -from .cache import DictCachingKeyLookup, LruCachingKeyLookup __all__ = ( "ClassIndex", diff --git a/reg/context.py b/reg/context.py index 88bda77..867b327 100644 --- a/reg/context.py +++ b/reg/context.py @@ -7,17 +7,16 @@ Any, Concatenate, Generic, - NoReturn as Never, - ParamSpec, - TypeVar, - cast, - overload, ) -from .dispatch import dispatch, Dispatch, format_signature, execute, identity +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 if TYPE_CHECKING: from collections.abc import Callable + from .dispatch import LookupEntry from .predicate import Predicate, PredicateRegistry from .types import BoundDispatchMethodCall, DispatchMethodCall, KeyLookup @@ -134,7 +133,9 @@ class DispatchMethod(Dispatch[Concatenate[_T, _P], _R], Generic[_P, _T, _R]): _P, _T, _R ] - def by_args(self, *args: _P.args, **kw: _P.kwargs) -> LookupEntry[Callable[Concatenate[_T, _P], _R]]: # type: ignore[override] + def by_args( # type: ignore[override] + self, *args: _P.args, **kw: _P.kwargs + ) -> LookupEntry[Callable[Concatenate[_T, _P], _R]]: """Lookup an implementation by invocation arguments. :param args: positional arguments used in invocation. diff --git a/reg/dispatch.py b/reg/dispatch.py index 7497002..1c75af9 100644 --- a/reg/dispatch.py +++ b/reg/dispatch.py @@ -6,20 +6,18 @@ Any, Generic, NamedTuple, - NoReturn as Never, - ParamSpec, - TypeVar, - cast, - overload, ) -from .predicate import match_instance -from .predicate import PredicateRegistry +from typing import NoReturn as Never +from typing import ParamSpec, TypeVar, cast, overload + from .arginfo import arginfo from .error import RegistrationError +from .predicate import PredicateRegistry, match_instance if TYPE_CHECKING: from collections.abc import Callable, Iterable from inspect import FullArgSpec + from .predicate import Predicate from .types import DispatchCall, GetKeyLookup, KeyLookup diff --git a/reg/predicate.py b/reg/predicate.py index 4b4c919..84af8bc 100644 --- a/reg/predicate.py +++ b/reg/predicate.py @@ -1,8 +1,8 @@ from __future__ import annotations import inspect -from operator import itemgetter from itertools import product +from operator import itemgetter from typing import TYPE_CHECKING, Any, Generic from .error import RegistrationError diff --git a/reg/tests/fixtures/module.py b/reg/tests/fixtures/module.py index 5151a26..131ad8d 100644 --- a/reg/tests/fixtures/module.py +++ b/reg/tests/fixtures/module.py @@ -1,6 +1,6 @@ "Sample module for testing autodoc." -from reg import dispatch_method, dispatch +from reg import dispatch, dispatch_method class Foo: diff --git a/reg/tests/test_arginfo.py b/reg/tests/test_arginfo.py index 97eb191..711ba78 100644 --- a/reg/tests/test_arginfo.py +++ b/reg/tests/test_arginfo.py @@ -1,13 +1,16 @@ from __future__ import annotations -import pytest from typing import TYPE_CHECKING, Any from typing_extensions import assert_type + +import pytest + from ..arginfo import arginfo if TYPE_CHECKING: from collections.abc import Callable from inspect import FullArgSpec # noqa: F401 + from ..types import ArgInfo # noqa: F401 diff --git a/reg/tests/test_classdispatch.py b/reg/tests/test_classdispatch.py index 8acec87..e38b783 100644 --- a/reg/tests/test_classdispatch.py +++ b/reg/tests/test_classdispatch.py @@ -1,6 +1,7 @@ from __future__ import annotations from typing import Any + from ..dispatch import dispatch from ..predicate import match_class diff --git a/reg/tests/test_dispatch.py b/reg/tests/test_dispatch.py index ce4d0aa..8a70195 100644 --- a/reg/tests/test_dispatch.py +++ b/reg/tests/test_dispatch.py @@ -1,12 +1,13 @@ from __future__ import annotations -import pytest - from typing import TYPE_CHECKING, Any from typing_extensions import assert_type -from ..predicate import match_instance, match_key, match_class + +import pytest + from ..dispatch import dispatch from ..error import RegistrationError +from ..predicate import match_class, match_instance, match_key if TYPE_CHECKING: from ..types import DispatchCall # noqa: F401 diff --git a/reg/tests/test_dispatch_method.py b/reg/tests/test_dispatch_method.py index d8b77c3..42d5b12 100644 --- a/reg/tests/test_dispatch_method.py +++ b/reg/tests/test_dispatch_method.py @@ -3,18 +3,21 @@ from types import FunctionType from typing import TYPE_CHECKING, Any from typing_extensions import assert_type + import pytest -from ..dispatch import dispatch + from ..context import ( + clean_dispatch_methods, dispatch_method, methodify, - clean_dispatch_methods, ) -from ..predicate import match_instance +from ..dispatch import dispatch from ..error import RegistrationError +from ..predicate import match_instance if TYPE_CHECKING: from collections.abc import Callable + from ..types import BoundDispatchMethodCall, DispatchMethodCall # noqa: F401 diff --git a/reg/tests/test_docgen.py b/reg/tests/test_docgen.py index 4d3044b..9929dad 100644 --- a/reg/tests/test_docgen.py +++ b/reg/tests/test_docgen.py @@ -1,12 +1,17 @@ from __future__ import annotations import pydoc -import pytest import sys -from pathlib import Path +from typing import TYPE_CHECKING + +import pytest from sphinx.application import Sphinx + from .fixtures.module import Foo, foo # pyright: ignore[reportUnknownVariableType] +if TYPE_CHECKING: + from pathlib import Path + def rstrip_lines(s: str) -> str: "Delete trailing spaces from each line in s." @@ -15,7 +20,7 @@ def rstrip_lines(s: str) -> str: def test_dispatch_method_class_help(capsys: pytest.CaptureFixture[str]) -> None: pydoc.help(Foo) - out, err = capsys.readouterr() + out, _err = capsys.readouterr() assert ( rstrip_lines(out) == """\ diff --git a/reg/tests/test_predicate.py b/reg/tests/test_predicate.py index 0de7a4a..22bea2e 100644 --- a/reg/tests/test_predicate.py +++ b/reg/tests/test_predicate.py @@ -1,12 +1,13 @@ +import pytest + +from ..error import RegistrationError from ..predicate import ( - KeyIndex, ClassIndex, + KeyIndex, PredicateRegistry, match_instance, match_key, ) -from ..error import RegistrationError -import pytest def test_key_index_permutations() -> None: diff --git a/reg/tests/test_registry.py b/reg/tests/test_registry.py index e784c83..1e0e2ef 100644 --- a/reg/tests/test_registry.py +++ b/reg/tests/test_registry.py @@ -1,14 +1,17 @@ from __future__ import annotations +from typing import TYPE_CHECKING, Any, ParamSpec, TypeVar + import pytest -from typing import Any, ParamSpec, TypeVar, TYPE_CHECKING -from ..predicate import PredicateRegistry, match_instance, match_key + from ..cache import DictCachingKeyLookup, LruCachingKeyLookup -from ..error import RegistrationError from ..dispatch import dispatch +from ..error import RegistrationError +from ..predicate import PredicateRegistry, match_instance, match_key if TYPE_CHECKING: from collections.abc import Callable + from ..types import DispatchCall _T = TypeVar("_T") @@ -241,8 +244,13 @@ def foo_edit(self: Foo, request: Request) -> str: # use a bit of inside knowledge to check the cache is filled assert view.key_lookup.component.__self__.get((Foo, "", "GET")) is not None # type: ignore[attr-defined] - assert view.key_lookup.component.__self__.get((FooSub, "", "GET")) is not None # type: ignore[attr-defined] - assert view.key_lookup.component.__self__.get((FooSub, "edit", "POST")) is not None # type: ignore[attr-defined] + assert ( + view.key_lookup.component.__self__.get((FooSub, "", "GET")) is not None # type: ignore[attr-defined] + ) + assert ( + view.key_lookup.component.__self__.get((FooSub, "edit", "POST")) # type: ignore[attr-defined] + is not None + ) # now let's do this again. this time things come from the component cache assert view(Foo(), Request("", "GET")) == "foo default" diff --git a/reg/types.py b/reg/types.py index 3f1efae..7c8d44d 100644 --- a/reg/types.py +++ b/reg/types.py @@ -14,6 +14,7 @@ from collections.abc import Callable, Iterable, Sequence from inspect import FullArgSpec from typing_extensions import TypeVar + from .dispatch import LookupEntry from .predicate import Predicate, PredicateRegistry @@ -114,7 +115,9 @@ def __call__(self, *args: _P.args, **kwargs: _P.kwargs) -> _T: class DispatchMethodCall(DispatchCall[Concatenate[_T, _P], _R], Protocol[_P, _T, _R]): - def by_args(self, *args: _P.args, **kw: _P.kwargs) -> LookupEntry[Callable[Concatenate[_T, _P], _R]]: # type: ignore[override] + def by_args( # type: ignore[override] + self, *args: _P.args, **kw: _P.kwargs + ) -> LookupEntry[Callable[Concatenate[_T, _P], _R]]: """Lookup an implementation by invocation arguments. :param args: positional arguments used in invocation. @@ -183,4 +186,4 @@ def all(self, key: Sequence[Any], /) -> Iterable[_ValueT]: raise NotImplementedError -GetKeyLookup: TypeAlias = Callable[[PredicateRegistry[_ValueT]], KeyLookup[_ValueT]] +GetKeyLookup: TypeAlias = "Callable[[PredicateRegistry[_ValueT]], KeyLookup[_ValueT]]" From fa75f8aefd301fcc3f54957fd7b684a7ba4b4faf Mon Sep 17 00:00:00 2001 From: Henri Hulski Date: Sat, 26 Sep 2026 21:14:04 +0200 Subject: [PATCH 5/5] docs: fix docs, docstrings and sphinx config --- CREDITS.txt | 2 +- README.rst | 2 +- doc/conf.py | 56 +++++++++++++++++++++++++++++++++++++--------- doc/developing.rst | 31 +++++++++++-------------- doc/history.rst | 12 +++++----- doc/patterns.rst | 6 ++--- doc/usage.rst | 4 ++-- reg/cache.py | 18 +++++++-------- reg/context.py | 10 ++++----- reg/dispatch.py | 12 +++++----- 10 files changed, 91 insertions(+), 62 deletions(-) diff --git a/CREDITS.txt b/CREDITS.txt index 897a886..690e4fc 100644 --- a/CREDITS.txt +++ b/CREDITS.txt @@ -18,7 +18,7 @@ With contributions by Special contributions --------------------- -* Special thanks to CONTACT software (http://contact.de) +* Special thanks to CONTACT software (https://www.contact-software.com/) Special inspiration ------------------- diff --git a/README.rst b/README.rst index 831d02c..9281e0d 100644 --- a/README.rst +++ b/README.rst @@ -21,4 +21,4 @@ for your application, library or framework. Documentation_. -.. _Documentation: http://reg.readthedocs.org +.. _Documentation: https://reg.readthedocs.io diff --git a/doc/conf.py b/doc/conf.py index cdaf095..bbafe93 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -11,8 +11,8 @@ # serve to show the default. import os -import pkg_resources from datetime import datetime +from importlib import metadata # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the @@ -32,6 +32,20 @@ "sphinx.ext.intersphinx", ] +# These are internal typing-only names (TypeVars, ParamSpecs, and type +# aliases/protocols that only exist for static type checkers) that show up +# in autodoc-rendered signatures but are never documented as objects of +# their own, so they can never resolve to a cross-reference target. +nitpick_ignore_regex = [ + ("py:class", r"^(.*\.)?_(P|T|R|F|ValueT)(\.(args|kwargs))?$"), + ("py:class", r"^(.*\.)?(Get)?KeyLookup$"), +] +# inspect.FullArgSpec is a real class, but the CPython docs don't expose +# it as a linkable object (only inspect.getfullargspec is documented). +nitpick_ignore = [ + ("py:class", "inspect.FullArgSpec"), +] + # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] @@ -47,17 +61,31 @@ # General information about the project. project = "Reg" copyright = "2010 - %s, Morepath Developers" % datetime.today().year +author = "Martijn Faassen" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = pkg_resources.get_distribution("reg").version +try: + version = metadata.version("reg") +except metadata.PackageNotFoundError: + # Fallback for ReadTheDocs and other environments where the package isn't installed + # Try to get version from pyproject.toml + import re + + try: + pyproject_path = os.path.join(os.path.dirname(__file__), "..", "pyproject.toml") + with open(pyproject_path) as f: + content = f.read() + # Simple regex to extract version + version_match = re.search(r'version\s*=\s*["\']([^"\']+)["\']', content) + version = version_match.group(1) if version_match else "0.0.0" + except (FileNotFoundError, Exception): + version = "0.0.0" # The full version, including alpha/beta/rc tags. release = version -if release.endswith("dev"): - release = "%s (unreleased)" % release # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -71,7 +99,7 @@ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = ["build"] +exclude_patterns = ["_build"] # The reST default role (used for this markup: `text`) to use for all documents. # default_role = None @@ -127,7 +155,7 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = [] +html_static_path: list[str] = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. @@ -176,7 +204,7 @@ # -- Options for LaTeX output -------------------------------------------------- -latex_elements = { +latex_elements: dict[str, str] = { # The paper size ('letterpaper' or 'a4paper'). # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). @@ -188,7 +216,13 @@ # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ("index", "Reg.tex", "Reg Documentation", "Martijn Faassen", "manual"), + ( + "index", + "Reg.tex", + "Reg Documentation", + author, + "manual", + ), ] # The name of an image file (relative to this directory) to place at the top of @@ -216,7 +250,7 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [("index", "reg", "Reg Documentation", ["Martijn Faassen"], 1)] +man_pages = [(master_doc, "reg", "Reg Documentation", [author], 1)] # If true, show URL addresses after external links. # man_show_urls = False @@ -232,7 +266,7 @@ "index", "Reg", "Reg Documentation", - "Martijn Faassen", + author, "Reg", "One line description of project.", "Miscellaneous", @@ -250,4 +284,4 @@ doctest_path = [os.path.abspath("..")] -intersphinx_mapping = {"python": ("http://docs.python.org/3.5", None)} +intersphinx_mapping = {"python": ("https://docs.python.org/3/", None)} diff --git a/doc/developing.rst b/doc/developing.rst index 3a79b6f..2ee3558 100644 --- a/doc/developing.rst +++ b/doc/developing.rst @@ -21,15 +21,11 @@ Make sure you have virtualenv_ installed. Create a new virtualenv for Python 3 inside the reg directory:: - $ virtualenv -p python3 env/py3 + $ python -m venv --upgrade-deps .venv Activate the virtualenv:: - $ source env/py3/bin/activate - -Make sure you have recent setuptools and pip installed:: - - $ pip install -U setuptools pip + $ source .venv/bin/activate Install the various dependencies and development tools from develop_requirements.txt:: @@ -42,9 +38,8 @@ For upgrading the requirements just run the command again. The following commands work only if you have the virtualenv activated. -.. _github: https://help.github.com/articles/generating-an-ssh-key - -.. _virtualenv: https://pypi.python.org/pypi/virtualenv +.. _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 --------------------------------------------- @@ -54,7 +49,7 @@ install the `pre-commit hook`_ for Black integration before committing:: $ pre-commit install -.. _`pre-commit hook`: https://black.readthedocs.io/en/stable/version_control_integration.html +.. _`pre-commit hook`: https://black.readthedocs.io/en/stable/integrations/source_version_control.html Running the tests ----------------- @@ -71,14 +66,14 @@ 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 information. -.. _`py.test`: http://pytest.org/latest/ +.. _`py.test`: https://pytest.org/latest/ Black ----- To format the code with the `Black Code Formatter`_ run in the root directory:: - $ black morepath + $ black reg Black has also integration_ for the most popular editors. @@ -105,9 +100,9 @@ Or from the Reg project directory:: Building the HTML documentation ------------------------------- -To build the HTML documentation (output in ``doc/build/html``), run:: +To build the HTML documentation (output in ``doc/_build/html``), run:: - $ sphinx-build doc doc/build/html + $ sphinx-build doc doc/_build/html Or alternatively if you have ``Make`` installed:: @@ -132,11 +127,11 @@ To also show cyclomatic complexity, use this command:: $ flake8 --max-complexity=10 reg -.. _flake8: https://pypi.python.org/pypi/flake8 +.. _flake8: https://pypi.org/project/flake8 -.. _pyflakes: https://pypi.python.org/pypi/pyflakes +.. _pyflakes: https://pypi.org/project/pyflakes -.. _pep8: http://www.python.org/dev/peps/pep-0008/ +.. _pep8: https://peps.python.org/pep-0008 .. _`cyclomatic complexity`: https://en.wikipedia.org/wiki/Cyclomatic_complexity @@ -173,4 +168,4 @@ To run a simple performance test you can use:: $ tox -e perf -.. _pyenv: https://github.com/yyuu/pyenv +.. _pyenv: https://github.com/pyenv/pyenv diff --git a/doc/history.rst b/doc/history.rst index 933ee71..108a27b 100644 --- a/doc/history.rst +++ b/doc/history.rst @@ -34,7 +34,7 @@ The core registry (mapping) code was conceived by Thomas Lotze and Martijn Faassen as a speculative sandbox project in January of 2010. It was called ``iface`` then: -http://svn.zope.org/Sandbox/faassen/iface/ +https://github.com/faassen/iface This registry was instrumental in getting Reg started, but was subsequently removed in a later refactoring. @@ -103,9 +103,9 @@ refactoring_ to use generic functions throughout and no interfaces for lookup. Martijn then used this version of Reg in Morepath for about a year. -.. _announcement: http://blog.startifact.com/posts/reg-component-architecture-reimagined.html +.. _announcement: https://blog.startifact.com/posts/reg-component-architecture-reimagined/ -.. _refactoring: http://blog.startifact.com/posts/reg-now-with-more-generic.html +.. _refactoring: https://blog.startifact.com/posts/reg-now-with-more-generic/ Predicate dispatch ~~~~~~~~~~~~~~~~~~ @@ -179,13 +179,13 @@ significant evolution since then. ``zope.interface`` has a long history, going all the way back to December 1998, when a scarecrow interface package was released for discussion: -http://old.zope.org/Members/jim/PythonInterfaces/Summary/ +https://old.zope.org/Members/jim/PythonInterfaces/Summary/ -http://old.zope.org/Members/jim/PythonInterfaces/Interface/ +https://old.zope.org/Members/jim/PythonInterfaces/Interface/ A later version of this codebase found itself in Zope, as ``Interface``: -http://svn.zope.org/Zope/tags/2-8-6/lib/python/Interface/ +https://github.com/zopefoundation/Zope/tree/2-8-6/lib/python/Interface A new version called zope.interface was developed for the Zope 3 project, somewhere around the year 2001 or 2002 (code historians, diff --git a/doc/patterns.rst b/doc/patterns.rst index 29a9750..c8eff25 100644 --- a/doc/patterns.rst +++ b/doc/patterns.rst @@ -35,7 +35,7 @@ small icon and a large icon. We want this API: implement the API in a base class either. We just do it in this example to be explicit. - .. _`abc module`: http://docs.python.org/2/library/abc.html + .. _`abc module`: https://docs.python.org/3/library/abc.html We define ``Document`` and ``Image`` content classes: @@ -207,7 +207,7 @@ follow the open/closed principle and add functionality to a class without modifying it. This works for instance methods, but what about ``classmethod``? This takes the *class* as the first argument, not an instance. You can configure ``@reg.dispatch`` decorator with a special -:class:`Predicate` instance that lets you dispatch on a class argument +:class:`reg.Predicate` instance that lets you dispatch on a class argument instead of an instance argument. Here's what it looks like: @@ -218,7 +218,7 @@ Here's what it looks like: def something(cls): raise NotImplementedError() -Note the call to :func:`match_class` here. This lets us specify that +Note the call to :func:`reg.match_class` here. This lets us specify that we want to dispatch on the class, in this case we simply want the ``cls`` argument. diff --git a/doc/usage.rst b/doc/usage.rst index 2d44eb4..b8199a2 100644 --- a/doc/usage.rst +++ b/doc/usage.rst @@ -34,7 +34,7 @@ Predicate dispatch .. _`dynamic dispatch`: https://en.wikipedia.org/wiki/Dynamic_dispatch - .. _`multiple dispatch`: http://en.wikipedia.org/wiki/Multiple_dispatch + .. _`multiple dispatch`: https://en.wikipedia.org/wiki/Multiple_dispatch .. _`predicate dispatch`: https://en.wikipedia.org/wiki/Predicate_dispatch @@ -56,7 +56,7 @@ Multiple registries Reg is designed with a caching layer that allows it to support these features efficiently. -.. _`Morepath`: http://morepath.readthedocs.io +.. _`Morepath`: https://morepath.readthedocs.io Example ------- diff --git a/reg/cache.py b/reg/cache.py index d826c04..a89c108 100644 --- a/reg/cache.py +++ b/reg/cache.py @@ -34,8 +34,8 @@ def __missing__(self, key: _KT) -> _VT: class DictCachingKeyLookup(Generic[_ValueT]): """A key lookup that caches. - Implements the read-only API of :class:`reg.PredicateRegistry` using - a cache to speed up access. + Implements the read-only API of :class:`reg.predicate.PredicateRegistry` + using a cache to speed up access. This cache is backed by a simple dictionary so could potentially grow large if the dispatch in question can be called with a large @@ -43,7 +43,7 @@ class DictCachingKeyLookup(Generic[_ValueT]): predicate keys. If so, you can use :class:`reg.LruCachingKeyLookup` instead. - :param: key_lookup - the :class:`PredicateRegistry` to cache. + :param: key_lookup - the :class:`reg.predicate.PredicateRegistry` to cache. """ @@ -75,21 +75,21 @@ def all(self, key: Sequence[Any], /) -> list[_ValueT]: class LruCachingKeyLookup(Generic[_ValueT]): """A key lookup that caches. - Implements the read-only API of :class:`reg.PredicateRegistry`, using - a cache to speed up access. + Implements the read-only API of :class:`reg.predicate.PredicateRegistry`, + using a cache to speed up access. The cache is LRU so won't grow beyond a certain limit, preserving memory. This is only useful if you except the access pattern to your function to involve a huge range of different predicate keys. - :param: key_lookup - the :class:`PredicateRegistry` to cache. + :param: key_lookup - the :class:`reg.predicate.PredicateRegistry` to cache. :param component_cache_size: how many cache entries to store for - the :meth:`component` method. This is also used by dispatch + the ``component`` method. This is also used by dispatch calls. :param all_cache_size: how many cache entries to store for the - the :meth:`all` method. + the ``all`` method. :param fallback_cache_size: how many cache entries to store for - the :meth:`fallback` method. + the ``fallback`` method. """ def __init__( diff --git a/reg/context.py b/reg/context.py index 867b327..8118667 100644 --- a/reg/context.py +++ b/reg/context.py @@ -34,7 +34,7 @@ class dispatch_method(dispatch, Generic[_P, _T, _R]): This takes the predicates to dispatch on as zero or more parameters. - :param predicates: sequence of :class:`Predicate` instances to do + :param predicates: sequence of :class:`reg.Predicate` instances to do the dispatch on. You create predicates using :func:`reg.match_instance`, :func:`reg.match_key`, :func:`reg.match_class`, or with a custom predicate class. @@ -42,9 +42,9 @@ class dispatch_method(dispatch, Generic[_P, _T, _R]): You can also pass in plain string argument, which is turned into a :func:`reg.match_instance` predicate. :param get_key_lookup: a function that gets a - :class:`PredicateRegistry` instance and returns a key lookup. A - :class:`PredicateRegistry` instance is itself a key lookup, but - you can return a caching key lookup (such as + :class:`reg.predicate.PredicateRegistry` instance and returns a key + lookup. A :class:`reg.predicate.PredicateRegistry` instance is itself + a key lookup, but you can return a caching key lookup (such as :class:`reg.DictCachingKeyLookup` or :class:`reg.LruCachingKeyLookup`) to make it more efficient. :param first_invocation_hook: a callable that accepts an instance of the @@ -174,7 +174,7 @@ def methodify( If it is, the signature of ``func`` needn't be amended, but wrapping might still be necessary. - In all cases, :func:`inspect_methodified` lets you retrieve the wrapped + In all cases, ``inspect_methodified`` lets you retrieve the wrapped function. :param func: the function to turn into method. diff --git a/reg/dispatch.py b/reg/dispatch.py index 1c75af9..fe1e5b5 100644 --- a/reg/dispatch.py +++ b/reg/dispatch.py @@ -43,9 +43,9 @@ class dispatch: can also pass in plain string argument, which is turned into a :func:`reg.match_instance` predicate. :param get_key_lookup: a function that gets a - :class:`PredicateRegistry` instance and returns a key lookup. A - :class:`PredicateRegistry` instance is itself a key lookup, but - you can return a caching key lookup (such as + :class:`reg.predicate.PredicateRegistry` instance and returns a key + lookup. A :class:`reg.predicate.PredicateRegistry` instance is itself + a key lookup, but you can return a caching key lookup (such as :class:`reg.DictCachingKeyLookup` or :class:`reg.LruCachingKeyLookup`) to make it more efficient. :returns: a function that you can use as if it were a @@ -115,9 +115,9 @@ class Dispatch(Generic[_P, _T]): match that of this function. This function is used as a fallback implementation that is called if no specific implementations match. :param get_key_lookup: a function that gets a - :class:`PredicateRegistry` instance and returns a key lookup. A - :class:`PredicateRegistry` instance is itself a key lookup, but - you can return a caching key lookup (such as + :class:`reg.predicate.PredicateRegistry` instance and returns a key + lookup. A :class:`reg.predicate.PredicateRegistry` instance is itself + a key lookup, but you can return a caching key lookup (such as :class:`reg.DictCachingKeyLookup` or :class:`reg.LruCachingKeyLookup`) to make it more efficient. """