From b87995b94dd1ca6ff19963174fcbfcf6e7c15e94 Mon Sep 17 00:00:00 2001 From: Dave Shawley Date: Fri, 9 Feb 2024 07:21:18 -0500 Subject: [PATCH 1/6] gh-115165: Fix Annotated for immutable types The return value from an annotated callable can raise any exception from __setattr__ for the `__orig_class__` property. --- Lib/test/test_typing.py | 10 ++++++++++ Lib/typing.py | 2 +- .../2024-02-09-07-20-16.gh-issue-115165.yfJLXA.rst | 3 +++ 3 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2024-02-09-07-20-16.gh-issue-115165.yfJLXA.rst diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index b684af4f33ed71d..cf0fa0b1604c90b 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -8756,6 +8756,16 @@ class B(str): ... self.assertIs(type(field_c2.__metadata__[0]), float) self.assertIs(type(field_c3.__metadata__[0]), bool) + def test_annotated_callable_returning_immutable(self): + class C: + def __setattr__(self, name, value): + raise Exception('should be ignored') + + class A(str): ... + + annotated = Annotated[C, A("A")] + self.assertIsInstance(annotated(), C) + class TypeAliasTests(BaseTestCase): def test_canonical_usage_with_variable_annotation(self): diff --git a/Lib/typing.py b/Lib/typing.py index d278b4effc7ebaf..889366e151313a1 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -1127,7 +1127,7 @@ def __call__(self, *args, **kwargs): result = self.__origin__(*args, **kwargs) try: result.__orig_class__ = self - except AttributeError: + except Exception: pass return result diff --git a/Misc/NEWS.d/next/Library/2024-02-09-07-20-16.gh-issue-115165.yfJLXA.rst b/Misc/NEWS.d/next/Library/2024-02-09-07-20-16.gh-issue-115165.yfJLXA.rst new file mode 100644 index 000000000000000..67219120434fd16 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-02-09-07-20-16.gh-issue-115165.yfJLXA.rst @@ -0,0 +1,3 @@ +``typing.Annotated`` ignores exceptions when setting the ``__orig_class__`` +attribute on objects returned from a callable. Previously only ``TypeError`` +was ignored. From 1d7e6e5b1a750793d09e5f8922da573c4a1b5c53 Mon Sep 17 00:00:00 2001 From: Dave Shawley Date: Fri, 9 Feb 2024 13:00:13 -0500 Subject: [PATCH 2/6] fixup! gh-115165: Fix Annotated for immutable types Update news blurb for gh-115165 --- .../Library/2024-02-09-07-20-16.gh-issue-115165.yfJLXA.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Misc/NEWS.d/next/Library/2024-02-09-07-20-16.gh-issue-115165.yfJLXA.rst b/Misc/NEWS.d/next/Library/2024-02-09-07-20-16.gh-issue-115165.yfJLXA.rst index 67219120434fd16..9b433bffe179726 100644 --- a/Misc/NEWS.d/next/Library/2024-02-09-07-20-16.gh-issue-115165.yfJLXA.rst +++ b/Misc/NEWS.d/next/Library/2024-02-09-07-20-16.gh-issue-115165.yfJLXA.rst @@ -1,3 +1,3 @@ -``typing.Annotated`` ignores exceptions when setting the ``__orig_class__`` -attribute on objects returned from a callable. Previously only ``TypeError`` -was ignored. +``typing.Annotated`` now ignores most exceptions when attempting to set the +``__orig_class__`` attribute on objects returned when calling a generic alias. +Previously only :exc:`AttributeError`` was ignored. Patch by Dave Shawley. From ff5e88566e30ae887721e55829a8a3547617e510 Mon Sep 17 00:00:00 2001 From: Dave Shawley Date: Fri, 9 Feb 2024 13:03:04 -0500 Subject: [PATCH 3/6] fixup! gh-115165: Fix Annotated for immutable types Add comment justifying `except Exception` --- Lib/typing.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/typing.py b/Lib/typing.py index 889366e151313a1..92c00f0d28b33f9 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -1127,6 +1127,8 @@ def __call__(self, *args, **kwargs): result = self.__origin__(*args, **kwargs) try: result.__orig_class__ = self + # Some objects raise TypeError (or something even more exotic) + # if you try to set attributes on them; guarding against that here except Exception: pass return result From 7289af40d46f34c92a3159de2a357b2a68c5269b Mon Sep 17 00:00:00 2001 From: Dave Shawley Date: Fri, 9 Feb 2024 13:26:04 -0500 Subject: [PATCH 4/6] fixup! gh-115165: Fix Annotated for immutable types Renamed test_annotated_callable_returning_immutable to test_instantiate_immutable and moved it near the other instantiation tests. --- Lib/test/test_typing.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index cf0fa0b1604c90b..9a5b1aa7469faa5 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -8492,6 +8492,14 @@ def test_instantiate_generic(self): self.assertEqual(MyCount([4, 4, 5]), {4: 2, 5: 1}) self.assertEqual(MyCount[int]([4, 4, 5]), {4: 2, 5: 1}) + def test_instantiate_immutable(self): + class C: + def __setattr__(self, key, value): + raise Exception("should be ignored") + + A = Annotated[C, "a decoration"] + self.assertIsInstance(A(), C) + def test_cannot_instantiate_forward(self): A = Annotated["int", (5, 6)] with self.assertRaises(TypeError): @@ -8756,16 +8764,6 @@ class B(str): ... self.assertIs(type(field_c2.__metadata__[0]), float) self.assertIs(type(field_c3.__metadata__[0]), bool) - def test_annotated_callable_returning_immutable(self): - class C: - def __setattr__(self, name, value): - raise Exception('should be ignored') - - class A(str): ... - - annotated = Annotated[C, A("A")] - self.assertIsInstance(annotated(), C) - class TypeAliasTests(BaseTestCase): def test_canonical_usage_with_variable_annotation(self): From 7fcb3945ddb595eecad5c3fdd2be7af9a348d4a3 Mon Sep 17 00:00:00 2001 From: Dave Shawley Date: Fri, 9 Feb 2024 13:26:59 -0500 Subject: [PATCH 5/6] fixup! gh-115165: Fix Annotated for immutable types Add a test for generic instantiation that trips the same __setattr__ exception in Annotation. --- 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 9a5b1aa7469faa5..88070b34db7e929 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -4323,6 +4323,13 @@ class C(B[int]): c.bar = 'abc' self.assertEqual(c.__dict__, {'bar': 'abc'}) + def test_getattr_exceptions(self): + class Immutable[T]: + def __setattr__(self, key, value): + raise RuntimeError("immutable") + + Immutable[int]() + def test_subscripted_generics_as_proxies(self): T = TypeVar('T') class C(Generic[T]): From 702ba2ca8d699ce0cb917081f19c077371fe1bf1 Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Fri, 9 Feb 2024 21:53:32 +0000 Subject: [PATCH 6/6] Small tweaks --- Lib/test/test_typing.py | 10 ++++++++-- Lib/typing.py | 2 +- .../2024-02-09-07-20-16.gh-issue-115165.yfJLXA.rst | 5 +++-- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 88070b34db7e929..c89c42f0aabe79c 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -4323,12 +4323,15 @@ class C(B[int]): c.bar = 'abc' self.assertEqual(c.__dict__, {'bar': 'abc'}) - def test_getattr_exceptions(self): + def test_setattr_exceptions(self): class Immutable[T]: def __setattr__(self, key, value): raise RuntimeError("immutable") - Immutable[int]() + # gh-115165: This used to cause RuntimeError to be raised + # when we tried to set `__orig_class__` on the `Immutable` instance + # returned by the `Immutable[int]()` call + self.assertIsInstance(Immutable[int](), Immutable) def test_subscripted_generics_as_proxies(self): T = TypeVar('T') @@ -8505,6 +8508,9 @@ def __setattr__(self, key, value): raise Exception("should be ignored") A = Annotated[C, "a decoration"] + # gh-115165: This used to cause RuntimeError to be raised + # when we tried to set `__orig_class__` on the `C` instance + # returned by the `A()` call self.assertIsInstance(A(), C) def test_cannot_instantiate_forward(self): diff --git a/Lib/typing.py b/Lib/typing.py index 92c00f0d28b33f9..ef0adc268015304 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -1128,7 +1128,7 @@ def __call__(self, *args, **kwargs): try: result.__orig_class__ = self # Some objects raise TypeError (or something even more exotic) - # if you try to set attributes on them; guarding against that here + # if you try to set attributes on them; we guard against that here except Exception: pass return result diff --git a/Misc/NEWS.d/next/Library/2024-02-09-07-20-16.gh-issue-115165.yfJLXA.rst b/Misc/NEWS.d/next/Library/2024-02-09-07-20-16.gh-issue-115165.yfJLXA.rst index 9b433bffe179726..73d3d001f07f3f4 100644 --- a/Misc/NEWS.d/next/Library/2024-02-09-07-20-16.gh-issue-115165.yfJLXA.rst +++ b/Misc/NEWS.d/next/Library/2024-02-09-07-20-16.gh-issue-115165.yfJLXA.rst @@ -1,3 +1,4 @@ -``typing.Annotated`` now ignores most exceptions when attempting to set the -``__orig_class__`` attribute on objects returned when calling a generic alias. +Most exceptions are now ignored when attempting to set the ``__orig_class__`` +attribute on objects returned when calling :mod:`typing` generic aliases +(including generic aliases created using :data:`typing.Annotated`). Previously only :exc:`AttributeError`` was ignored. Patch by Dave Shawley.