From 1a310300402bd9f402504c25cec6866d39f4abe3 Mon Sep 17 00:00:00 2001 From: GBeauregard Date: Wed, 26 Jan 2022 11:52:20 -0800 Subject: [PATCH 1/6] Pass status of special forms to forward references Previously this didn't matter because there weren't any valid code paths that could trigger a type check with a special form, but after the bug fix for `Annotated` wrapping special forms it's now possible to annotate something like `Annotated['ClassVar[int]', (3, 4)]`. This change would also be needed for proposed future changes, such as allowing `ClassVar` and `Final` to nest each other in dataclasses. --- Lib/test/test_typing.py | 7 +++++++ Lib/typing.py | 18 +++++++++--------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index b5767d02691d8d..fe012dbe7f4add 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -2870,6 +2870,13 @@ def foo(a: 'Callable[..., T]'): self.assertEqual(get_type_hints(foo, globals(), locals()), {'a': Callable[..., T]}) + def test_special_forms_forward(self): + + class C: + a: Annotated['ClassVar[int]', (3, 5)] = 4 + + self.assertEqual(get_type_hints(C, globals())['a'], ClassVar[int]) + def test_syntax_error(self): with self.assertRaises(SyntaxError): diff --git a/Lib/typing.py b/Lib/typing.py index e3e098b1fcc8f3..98f34161ee0720 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -142,12 +142,12 @@ def _idfunc(_, x): # legitimate imports of those modules. -def _type_convert(arg, module=None): +def _type_convert(arg, module=None, *, allow_special_forms=False): """For converting None to type(None), and strings to ForwardRef.""" if arg is None: return type(None) if isinstance(arg, str): - return ForwardRef(arg, module=module) + return ForwardRef(arg, module=module, allow_special_forms=allow_special_forms) return arg @@ -169,7 +169,7 @@ def _type_check(arg, msg, is_argument=True, module=None, *, allow_special_forms= if is_argument: invalid_generic_forms += (Final,) - arg = _type_convert(arg, module=module) + arg = _type_convert(arg, module=module, allow_special_forms=allow_special_forms) if (isinstance(arg, _GenericAlias) and arg.__origin__ in invalid_generic_forms): raise TypeError(f"{arg} is not valid as type argument") @@ -661,10 +661,10 @@ class ForwardRef(_Final, _root=True): __slots__ = ('__forward_arg__', '__forward_code__', '__forward_evaluated__', '__forward_value__', - '__forward_is_argument__', '__forward_is_class__', + '__forward_is_argument__', '__forward_allow_special_forms__', '__forward_module__') - def __init__(self, arg, is_argument=True, module=None, *, is_class=False): + def __init__(self, arg, is_argument=True, module=None, *, allow_special_forms=False): if not isinstance(arg, str): raise TypeError(f"Forward reference must be a string -- got {arg!r}") try: @@ -676,7 +676,7 @@ def __init__(self, arg, is_argument=True, module=None, *, is_class=False): self.__forward_evaluated__ = False self.__forward_value__ = None self.__forward_is_argument__ = is_argument - self.__forward_is_class__ = is_class + self.__forward_allow_special_forms__ = allow_special_forms self.__forward_module__ = module def _evaluate(self, globalns, localns, recursive_guard): @@ -697,7 +697,7 @@ def _evaluate(self, globalns, localns, recursive_guard): eval(self.__forward_code__, globalns, localns), "Forward references must evaluate to types.", is_argument=self.__forward_is_argument__, - allow_special_forms=self.__forward_is_class__, + allow_special_forms=self.__forward_allow_special_forms__, ) self.__forward_value__ = _eval_type( type_, globalns, localns, recursive_guard | {self.__forward_arg__} @@ -1804,7 +1804,7 @@ def get_type_hints(obj, globalns=None, localns=None, include_extras=False): if value is None: value = type(None) if isinstance(value, str): - value = ForwardRef(value, is_argument=False, is_class=True) + value = ForwardRef(value, is_argument=False, allow_special_forms=True) value = _eval_type(value, base_globals, base_locals) hints[name] = value return hints if include_extras else {k: _strip_annotations(t) for k, t in hints.items()} @@ -1841,7 +1841,7 @@ def get_type_hints(obj, globalns=None, localns=None, include_extras=False): value = ForwardRef( value, is_argument=not isinstance(obj, types.ModuleType), - is_class=False, + allow_special_forms=False, ) value = _eval_type(value, globalns, localns) if name in defaults and defaults[name] is None: From 1b4c262cddeb3c21fa7585169f7a8c4493ebea74 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Wed, 26 Jan 2022 20:36:31 +0000 Subject: [PATCH 2/6] =?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 --- .../NEWS.d/next/Library/2022-01-26-20-36-30.bpo-46539.23iW1d.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2022-01-26-20-36-30.bpo-46539.23iW1d.rst diff --git a/Misc/NEWS.d/next/Library/2022-01-26-20-36-30.bpo-46539.23iW1d.rst b/Misc/NEWS.d/next/Library/2022-01-26-20-36-30.bpo-46539.23iW1d.rst new file mode 100644 index 00000000000000..e58531faad8b3f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-01-26-20-36-30.bpo-46539.23iW1d.rst @@ -0,0 +1 @@ +typing: Pass on status of special forms to forward references. Patch by Gregory Beauregard. \ No newline at end of file From 535a124945fc871168573d0314c92796d4fe69a3 Mon Sep 17 00:00:00 2001 From: GBeauregard Date: Wed, 26 Jan 2022 13:41:23 -0800 Subject: [PATCH 3/6] restore ForwardRef public api arg name --- Lib/typing.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Lib/typing.py b/Lib/typing.py index 98f34161ee0720..5c1d42038995b8 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -147,7 +147,7 @@ def _type_convert(arg, module=None, *, allow_special_forms=False): if arg is None: return type(None) if isinstance(arg, str): - return ForwardRef(arg, module=module, allow_special_forms=allow_special_forms) + return ForwardRef(arg, module=module, is_class=allow_special_forms) return arg @@ -664,7 +664,7 @@ class ForwardRef(_Final, _root=True): '__forward_is_argument__', '__forward_allow_special_forms__', '__forward_module__') - def __init__(self, arg, is_argument=True, module=None, *, allow_special_forms=False): + def __init__(self, arg, is_argument=True, module=None, *, is_class=False): if not isinstance(arg, str): raise TypeError(f"Forward reference must be a string -- got {arg!r}") try: @@ -676,7 +676,7 @@ def __init__(self, arg, is_argument=True, module=None, *, allow_special_forms=Fa self.__forward_evaluated__ = False self.__forward_value__ = None self.__forward_is_argument__ = is_argument - self.__forward_allow_special_forms__ = allow_special_forms + self.__forward_allow_special_forms__ = is_class self.__forward_module__ = module def _evaluate(self, globalns, localns, recursive_guard): @@ -1804,7 +1804,7 @@ def get_type_hints(obj, globalns=None, localns=None, include_extras=False): if value is None: value = type(None) if isinstance(value, str): - value = ForwardRef(value, is_argument=False, allow_special_forms=True) + value = ForwardRef(value, is_argument=False, is_class=True) value = _eval_type(value, base_globals, base_locals) hints[name] = value return hints if include_extras else {k: _strip_annotations(t) for k, t in hints.items()} @@ -1841,7 +1841,7 @@ def get_type_hints(obj, globalns=None, localns=None, include_extras=False): value = ForwardRef( value, is_argument=not isinstance(obj, types.ModuleType), - allow_special_forms=False, + is_class=False, ) value = _eval_type(value, globalns, localns) if name in defaults and defaults[name] is None: From 32deeb8c2983311dc010b25ea95133c7891bd222 Mon Sep 17 00:00:00 2001 From: GBeauregard Date: Wed, 26 Jan 2022 17:23:28 -0800 Subject: [PATCH 4/6] update NEWS and change dunder method back --- Lib/typing.py | 6 +++--- .../next/Library/2022-01-26-20-36-30.bpo-46539.23iW1d.rst | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Lib/typing.py b/Lib/typing.py index 5c1d42038995b8..450cd7b51184ef 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -661,7 +661,7 @@ class ForwardRef(_Final, _root=True): __slots__ = ('__forward_arg__', '__forward_code__', '__forward_evaluated__', '__forward_value__', - '__forward_is_argument__', '__forward_allow_special_forms__', + '__forward_is_argument__', '__forward_is_class__', '__forward_module__') def __init__(self, arg, is_argument=True, module=None, *, is_class=False): @@ -676,7 +676,7 @@ def __init__(self, arg, is_argument=True, module=None, *, is_class=False): self.__forward_evaluated__ = False self.__forward_value__ = None self.__forward_is_argument__ = is_argument - self.__forward_allow_special_forms__ = is_class + self.__forward_is_class__ = is_class self.__forward_module__ = module def _evaluate(self, globalns, localns, recursive_guard): @@ -697,7 +697,7 @@ def _evaluate(self, globalns, localns, recursive_guard): eval(self.__forward_code__, globalns, localns), "Forward references must evaluate to types.", is_argument=self.__forward_is_argument__, - allow_special_forms=self.__forward_allow_special_forms__, + allow_special_forms=self.__forward_is_class__, ) self.__forward_value__ = _eval_type( type_, globalns, localns, recursive_guard | {self.__forward_arg__} diff --git a/Misc/NEWS.d/next/Library/2022-01-26-20-36-30.bpo-46539.23iW1d.rst b/Misc/NEWS.d/next/Library/2022-01-26-20-36-30.bpo-46539.23iW1d.rst index e58531faad8b3f..2dc2808ac83ebf 100644 --- a/Misc/NEWS.d/next/Library/2022-01-26-20-36-30.bpo-46539.23iW1d.rst +++ b/Misc/NEWS.d/next/Library/2022-01-26-20-36-30.bpo-46539.23iW1d.rst @@ -1 +1 @@ -typing: Pass on status of special forms to forward references. Patch by Gregory Beauregard. \ No newline at end of file +In :func:`typing.get_type_hints`, support evaluating stringified `ClassVar` and `Final` annotations inside `Annotated`. Patch by Gregory Beauregard. From 41aafb4e35bc8c60336f211b7f4b4ef6a9e69301 Mon Sep 17 00:00:00 2001 From: GBeauregard Date: Wed, 26 Jan 2022 17:30:16 -0800 Subject: [PATCH 5/6] add more tests to further cover behavior --- Lib/test/test_typing.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index fe012dbe7f4add..4b260d49bdfe46 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -2874,8 +2874,15 @@ def test_special_forms_forward(self): class C: a: Annotated['ClassVar[int]', (3, 5)] = 4 + b: Annotated['Final[int]', "const"] = 4 + + class CF: + b: List['Final[int]'] = 4 self.assertEqual(get_type_hints(C, globals())['a'], ClassVar[int]) + self.assertEqual(get_type_hints(C, globals())['b'], Final[int]) + with self.assertRaises(TypeError): + get_type_hints(CF, globals()), def test_syntax_error(self): From 8c148e246482e9cb87407f8a52d32f41a97e070f Mon Sep 17 00:00:00 2001 From: GBeauregard Date: Wed, 26 Jan 2022 17:36:36 -0800 Subject: [PATCH 6/6] use double backticks for RST --- .../next/Library/2022-01-26-20-36-30.bpo-46539.23iW1d.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2022-01-26-20-36-30.bpo-46539.23iW1d.rst b/Misc/NEWS.d/next/Library/2022-01-26-20-36-30.bpo-46539.23iW1d.rst index 2dc2808ac83ebf..2bdde21b6e58e5 100644 --- a/Misc/NEWS.d/next/Library/2022-01-26-20-36-30.bpo-46539.23iW1d.rst +++ b/Misc/NEWS.d/next/Library/2022-01-26-20-36-30.bpo-46539.23iW1d.rst @@ -1 +1 @@ -In :func:`typing.get_type_hints`, support evaluating stringified `ClassVar` and `Final` annotations inside `Annotated`. Patch by Gregory Beauregard. +In :func:`typing.get_type_hints`, support evaluating stringified ``ClassVar`` and ``Final`` annotations inside ``Annotated``. Patch by Gregory Beauregard. 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