From 1e4d3ec3813b2e80f8d77eb620e6dd54eb8a7dce Mon Sep 17 00:00:00 2001 From: Jacob Hayes Date: Sat, 17 Apr 2021 20:52:26 -0400 Subject: [PATCH 1/7] Add minimal Dispatcher type hinting --- multipledispatch/dispatcher.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/multipledispatch/dispatcher.py b/multipledispatch/dispatcher.py index 7568595..2874a9d 100644 --- a/multipledispatch/dispatcher.py +++ b/multipledispatch/dispatcher.py @@ -1,3 +1,5 @@ +from collections.abc import Callable +from typing import Any, Generic, List, Optional, Tuple, TypeVar from warnings import warn import inspect from .conflict import ordering, ambiguities, super_signature, AmbiguityWarning @@ -95,7 +97,10 @@ def variadic_signature_matches(types, full_signature): return all(variadic_signature_matches_iter(types, full_signature)) -class Dispatcher(object): +DISPATCHED_RETURN = TypeVar("DISPATCHED_RETURN") + + +class Dispatcher(Generic[DISPATCHED_RETURN]): """ Dispatch methods based on type signature Use ``dispatch`` to add implementations @@ -119,14 +124,16 @@ class Dispatcher(object): """ __slots__ = '__name__', 'name', 'funcs', '_ordering', '_cache', 'doc' - def __init__(self, name, doc=None): + def __init__(self, name: str, doc: Optional[None] = None) -> None: self.name = self.__name__ = name self.funcs = {} self.doc = doc self._cache = {} - def register(self, *types, **kwargs): + def register( + self, *types: List[type], **kwargs: Any + ) -> Callable[[Callable[..., DISPATCHED_RETURN]], Callable[..., DISPATCHED_RETURN]]: """ register dispatcher with new implementation >>> f = Dispatcher('f') @@ -183,7 +190,7 @@ def get_func_annotations(cls, func): if all(ann is not Parameter.empty for ann in annotations): return annotations - def add(self, signature, func): + def add(self, signature: Tuple[type, ...], func: Callable[..., DISPATCHED_RETURN]) -> None: """ Add new types/method pair to dispatcher >>> D = Dispatcher('add') @@ -263,7 +270,7 @@ def reorder(self, on_ambiguity=ambiguity_warn): on_ambiguity(self, amb) return od - def __call__(self, *args, **kwargs): + def __call__(self, *args: Any, **kwargs: Any) -> DISPATCHED_RETURN: types = tuple([type(arg) for arg in args]) try: func = self._cache[types] From 70448488f2fea49e9ceeaffa6f3146e51a1dfc07 Mon Sep 17 00:00:00 2001 From: Jacob Hayes Date: Sat, 17 Apr 2021 20:54:03 -0400 Subject: [PATCH 2/7] Isolate dispatch tests to avoid AmbiguityWarning --- multipledispatch/tests/test_core.py | 33 +++++++++++++++-------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/multipledispatch/tests/test_core.py b/multipledispatch/tests/test_core.py index d3f6eec..9483559 100644 --- a/multipledispatch/tests/test_core.py +++ b/multipledispatch/tests/test_core.py @@ -1,14 +1,15 @@ -from multipledispatch import dispatch +from multipledispatch import dispatch as orig_dispatch from multipledispatch.utils import raises from functools import partial +import pytest -test_namespace = dict() -orig_dispatch = dispatch -dispatch = partial(dispatch, namespace=test_namespace) +@pytest.fixture(name="dispatch") +def fixture_dispatch(): + return partial(orig_dispatch, namespace=dict()) -def test_singledispatch(): +def test_singledispatch(dispatch): @dispatch(int) def f(x): return x + 1 @@ -28,7 +29,7 @@ def f(x): assert raises(NotImplementedError, lambda: f('hello')) -def test_multipledispatch(benchmark): +def test_multipledispatch(dispatch): @dispatch(int, int) def f(x, y): return x + y @@ -48,7 +49,7 @@ class D(C): pass class E(C): pass -def test_inheritance(): +def test_inheritance(dispatch): @dispatch(A) def f(x): return 'a' @@ -62,7 +63,7 @@ def f(x): assert f(C()) == 'a' -def test_inheritance_and_multiple_dispatch(): +def test_inheritance_and_multiple_dispatch(dispatch): @dispatch(A, A) def f(x, y): return type(x), type(y) @@ -78,7 +79,7 @@ def f(x, y): assert raises(NotImplementedError, lambda: f(B(), B())) -def test_competing_solutions(): +def test_competing_solutions(dispatch): @dispatch(A) def h(x): return 1 @@ -90,7 +91,7 @@ def h(x): assert h(D()) == 2 -def test_competing_multiple(): +def test_competing_multiple(dispatch): @dispatch(A, B) def h(x, y): return 1 @@ -102,7 +103,7 @@ def h(x, y): assert h(D(), B()) == 2 -def test_competing_ambiguous(): +def test_competing_ambiguous(dispatch): @dispatch(A, C) def f(x, y): return 2 @@ -115,7 +116,7 @@ def f(x, y): # assert raises(Warning, lambda : f(C(), C())) -def test_caching_correct_behavior(): +def test_caching_correct_behavior(dispatch): @dispatch(A) def f(x): return 1 @@ -129,7 +130,7 @@ def f(x): assert f(C()) == 2 -def test_union_types(): +def test_union_types(dispatch): @dispatch((A, C)) def f(x): return 1 @@ -156,7 +157,7 @@ def foo(x): """ Fails -def test_dispatch_on_dispatch(): +def test_dispatch_on_dispatch(dispatch): @dispatch(A) @dispatch(C) def q(x): @@ -167,7 +168,7 @@ def q(x): """ -def test_methods(): +def test_methods(dispatch): class Foo(object): @dispatch(float) def f(self, x): @@ -188,7 +189,7 @@ def g(self, x): assert foo.g(1) == 4 -def test_methods_multiple_dispatch(): +def test_methods_multiple_dispatch(dispatch): class Foo(object): @dispatch(A, A) def f(x, y): From 2413732d4a288fd0ca6fbadcc5df47ddde34dce1 Mon Sep 17 00:00:00 2001 From: Jacob Hayes Date: Sat, 17 Apr 2021 21:24:17 -0400 Subject: [PATCH 3/7] Add string annotation test --- multipledispatch/tests/test_dispatcher_3only.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/multipledispatch/tests/test_dispatcher_3only.py b/multipledispatch/tests/test_dispatcher_3only.py index b041450..f23bc21 100644 --- a/multipledispatch/tests/test_dispatcher_3only.py +++ b/multipledispatch/tests/test_dispatcher_3only.py @@ -17,6 +17,10 @@ def inc(x: int): def inc(x: float): return x - 1 + @f.register() + def inc(x: 'float'): + return x - 1 + assert f(1) == 2 assert f(1.0) == 0.0 From 787384c15ee4627fae753dbc3f658544ef3e6fb8 Mon Sep 17 00:00:00 2001 From: Jacob Hayes Date: Sat, 17 Apr 2021 21:25:09 -0400 Subject: [PATCH 4/7] Use get_type_hints to parse annotations --- multipledispatch/dispatcher.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/multipledispatch/dispatcher.py b/multipledispatch/dispatcher.py index 2874a9d..8af0f32 100644 --- a/multipledispatch/dispatcher.py +++ b/multipledispatch/dispatcher.py @@ -1,5 +1,5 @@ from collections.abc import Callable -from typing import Any, Generic, List, Optional, Tuple, TypeVar +from typing import Any, Generic, List, Optional, Tuple, TypeVar, get_type_hints from warnings import warn import inspect from .conflict import ordering, ambiguities, super_signature, AmbiguityWarning @@ -178,14 +178,14 @@ def get_func_annotations(cls, func): if params: Parameter = inspect.Parameter - params = (param for param in params - if param.kind in - (Parameter.POSITIONAL_ONLY, - Parameter.POSITIONAL_OR_KEYWORD)) - + hints = get_type_hints(func) annotations = tuple( - param.annotation - for param in params) + hints.get(param.name, Parameter.empty) + for param in params + if param.kind in ( + Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD + ) + ) if all(ann is not Parameter.empty for ann in annotations): return annotations From 29eba322c4956de88b1ff92b63ae3ace07f16327 Mon Sep 17 00:00:00 2001 From: Jacob Hayes Date: Sat, 17 Apr 2021 21:41:29 -0400 Subject: [PATCH 5/7] Fix non-generic collections.abc.Callable in py<3.9 --- multipledispatch/dispatcher.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/multipledispatch/dispatcher.py b/multipledispatch/dispatcher.py index 8af0f32..32d2db3 100644 --- a/multipledispatch/dispatcher.py +++ b/multipledispatch/dispatcher.py @@ -1,5 +1,4 @@ -from collections.abc import Callable -from typing import Any, Generic, List, Optional, Tuple, TypeVar, get_type_hints +from typing import Any, Callable, Generic, List, Optional, Tuple, TypeVar, get_type_hints from warnings import warn import inspect from .conflict import ordering, ambiguities, super_signature, AmbiguityWarning From 5e37a42b92377827f69556d2360453993acd9f33 Mon Sep 17 00:00:00 2001 From: Jacob Hayes Date: Sat, 17 Apr 2021 22:01:13 -0400 Subject: [PATCH 6/7] Remove end-of-life python versions from CI (pypy2, 2.7, 3.4, 3.5) and add 3.9 --- .travis.yml | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/.travis.yml b/.travis.yml index b5c7633..7530e89 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,18 +1,14 @@ language: python python: - - "pypy" - "pypy3" matrix: include: - - { python: '2.7', env: } - - { arch: arm64, python: '2.7' } - - { python: '3.4', env: } - - { python: '3.5', env: } - { python: '3.6', env: } - { python: '3.7', env: } - { python: '3.8', env: } - - { arch: arm64, python: '3.8' } + - { python: '3.9', env: } + - { arch: arm64, python: '3.9' } install: - pip install --upgrade pip - pip install coverage @@ -20,11 +16,7 @@ install: script: - | - if [[ $(bc <<< "$TRAVIS_PYTHON_VERSION >= 3.3") -eq 1 ]]; then - py.test --doctest-modules multipledispatch - else - py.test --doctest-modules --ignore=multipledispatch/tests/test_dispatcher_3only.py multipledispatch - fi + py.test --doctest-modules multipledispatch after_success: - | From 25968b7ddd095ebba9e14d3662e2ef5915f8dca0 Mon Sep 17 00:00:00 2001 From: Jacob Hayes Date: Sat, 17 Apr 2021 22:46:01 -0400 Subject: [PATCH 7/7] Fix * type hint --- multipledispatch/dispatcher.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/multipledispatch/dispatcher.py b/multipledispatch/dispatcher.py index 32d2db3..8409205 100644 --- a/multipledispatch/dispatcher.py +++ b/multipledispatch/dispatcher.py @@ -1,4 +1,4 @@ -from typing import Any, Callable, Generic, List, Optional, Tuple, TypeVar, get_type_hints +from typing import Any, Callable, Generic, Optional, Tuple, TypeVar, get_type_hints from warnings import warn import inspect from .conflict import ordering, ambiguities, super_signature, AmbiguityWarning @@ -131,7 +131,7 @@ def __init__(self, name: str, doc: Optional[None] = None) -> None: self._cache = {} def register( - self, *types: List[type], **kwargs: Any + self, *types: type, **kwargs: Any ) -> Callable[[Callable[..., DISPATCHED_RETURN]], Callable[..., DISPATCHED_RETURN]]: """ register dispatcher with new implementation