From 4dfc9703d28bbec830b173a8ec9caa4e6217b41e Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Sun, 17 Apr 2022 21:26:57 -0700 Subject: [PATCH 1/4] gh-91621: Fix typing.get_type_hints for collections.abc.Callable This mirrors logic in typing.get_args. The trickiness comes from how we flatten args in collections.abc.Callable, see https://bugs.python.org/issue42195 --- Lib/test/test_typing.py | 6 ++++++ Lib/typing.py | 6 +++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index d4808474e4fcee..2277c4a3502b98 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -4408,6 +4408,12 @@ def test_get_type_hints_typeddict(self): 'a': Annotated[Required[int], "a", "b", "c"] }) + def test_get_type_hints_collections_abc_callable(self): + # https://github.com/python/cpython/issues/91621 + def f(x: collections.abc.Callable[[int], int]): ... + + self.assertEqual(get_type_hints(f), {'x': collections.abc.Callable[[int], int]}) + class GetUtilitiesTestCase(TestCase): def test_get_origin(self): diff --git a/Lib/typing.py b/Lib/typing.py index 3e0fbdb9891557..ae2f30ffcfad95 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -350,7 +350,11 @@ def _eval_type(t, globalns, localns, recursive_guard=frozenset()): ForwardRef(arg) if isinstance(arg, str) else arg for arg in t.__args__ ) - t = t.__origin__[args] + if (t.__origin__ is collections.abc.Callable + and not (len(args) == 2 and _is_param_expr(args[0]))): + t = t.__origin__[(args[:-1], args[-1])] + else: + t = t.__origin__[args] ev_args = tuple(_eval_type(a, globalns, localns, recursive_guard) for a in t.__args__) if ev_args == t.__args__: return t From e8c44016a3c5862e6de9a9946432e754f98280e1 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Mon, 18 Apr 2022 18:55:24 +0000 Subject: [PATCH 2/4] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Library/2022-04-18-18-55-21.gh-issue-91621.ACNlda.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2022-04-18-18-55-21.gh-issue-91621.ACNlda.rst diff --git a/Misc/NEWS.d/next/Library/2022-04-18-18-55-21.gh-issue-91621.ACNlda.rst b/Misc/NEWS.d/next/Library/2022-04-18-18-55-21.gh-issue-91621.ACNlda.rst new file mode 100644 index 00000000000000..b9e68d225a25bd --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-04-18-18-55-21.gh-issue-91621.ACNlda.rst @@ -0,0 +1 @@ +Fix :func:`typing.get_type_hints` for :class:`collections.abc.Callable`. Patch by Shantanu Jain. From 6fecdfa0b3d5f55bea3ac11f7e84c1eff7cf19de Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Mon, 18 Apr 2022 13:48:00 -0700 Subject: [PATCH 3/4] more tests --- Lib/test/test_typing.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 2277c4a3502b98..76eb578f9e3911 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -4410,9 +4410,14 @@ def test_get_type_hints_typeddict(self): def test_get_type_hints_collections_abc_callable(self): # https://github.com/python/cpython/issues/91621 + P = ParamSpec('P') def f(x: collections.abc.Callable[[int], int]): ... + def g(x: collections.abc.Callable[..., int]): ... + def h(x: collections.abc.Callable[P, int]): ... self.assertEqual(get_type_hints(f), {'x': collections.abc.Callable[[int], int]}) + self.assertEqual(get_type_hints(g), {'x': collections.abc.Callable[..., int]}) + self.assertEqual(get_type_hints(h), {'x': collections.abc.Callable[P, int]}) class GetUtilitiesTestCase(TestCase): From b45397fb82d88804e29f94efe4d9ad3541d7cdc5 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Mon, 2 May 2022 14:25:40 -0600 Subject: [PATCH 4/4] add helper --- Lib/typing.py | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/Lib/typing.py b/Lib/typing.py index ae2f30ffcfad95..2d68438d9fe7ba 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -202,6 +202,24 @@ def _is_param_expr(arg): (tuple, list, ParamSpec, _ConcatenateGenericAlias)) +def _should_unflatten_callable_args(typ, args): + """Internal helper for munging collections.abc.Callable's __args__. + + The canonical representation for a Callable's __args__ flattens the + argument types, see https://bugs.python.org/issue42195. For example: + + collections.abc.Callable[[int, int], str].__args__ == (int, int, str) + collections.abc.Callable[ParamSpec, str].__args__ == (ParamSpec, str) + + As a result, if we need to reconstruct the Callable from its __args__, + we need to unflatten it. + """ + return ( + typ.__origin__ is collections.abc.Callable + and not (len(args) == 2 and _is_param_expr(args[0])) + ) + + def _type_repr(obj): """Return the repr() of an object, special-casing types (internal helper). @@ -350,8 +368,7 @@ def _eval_type(t, globalns, localns, recursive_guard=frozenset()): ForwardRef(arg) if isinstance(arg, str) else arg for arg in t.__args__ ) - if (t.__origin__ is collections.abc.Callable - and not (len(args) == 2 and _is_param_expr(args[0]))): + if _should_unflatten_callable_args(t, args): t = t.__origin__[(args[:-1], args[-1])] else: t = t.__origin__[args] @@ -2350,8 +2367,7 @@ def get_args(tp): return (tp.__origin__,) + tp.__metadata__ if isinstance(tp, (_GenericAlias, GenericAlias)): res = tp.__args__ - if (tp.__origin__ is collections.abc.Callable - and not (len(res) == 2 and _is_param_expr(res[0]))): + if _should_unflatten_callable_args(tp, res): res = (list(res[:-1]), res[-1]) return res if isinstance(tp, types.UnionType): pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy