https://github.com/python/cpython/commit/06b62282c79dd69293a3eefb4c55f5acc6312cb2
commit: 06b62282c79dd69293a3eefb4c55f5acc6312cb2
branch: main
author: dr-carlos <[email protected]>
committer: JelleZijlstra <[email protected]>
date: 2025-11-10T14:45:22Z
summary:

gh-141174: Improve `annotationlib.get_annotations()` test coverage (#141286)

* Test `get_annotations(format=Format.VALUE)` for stringized annotations on 
custom objects

* Test `get_annotations(format=Format.VALUE)` for stringized annotations on 
wrapped partial functions

* Update test_stringized_annotations_with_star_unpack() to actually test 
stringized annotations

* Test __annotate__ returning a non-dict

* Test passing globals and locals to stringized `get_annotations()`

files:
M Lib/test/test_annotationlib.py

diff --git a/Lib/test/test_annotationlib.py b/Lib/test/test_annotationlib.py
index fd5d43b09b9702..f1d32ab50cf82b 100644
--- a/Lib/test/test_annotationlib.py
+++ b/Lib/test/test_annotationlib.py
@@ -9,6 +9,7 @@
 import pickle
 from string.templatelib import Template, Interpolation
 import typing
+import sys
 import unittest
 from annotationlib import (
     Format,
@@ -755,6 +756,8 @@ def test_stringized_annotations_in_module(self):
 
         for kwargs in [
             {"eval_str": True},
+            {"eval_str": True, "globals": isa.__dict__, "locals": {}},
+            {"eval_str": True, "globals": {}, "locals": isa.__dict__},
             {"format": Format.VALUE, "eval_str": True},
         ]:
             with self.subTest(**kwargs):
@@ -788,7 +791,7 @@ def test_stringized_annotations_in_empty_module(self):
         self.assertEqual(get_annotations(isa2, eval_str=False), {})
 
     def test_stringized_annotations_with_star_unpack(self):
-        def f(*args: *tuple[int, ...]): ...
+        def f(*args: "*tuple[int, ...]"): ...
         self.assertEqual(get_annotations(f, eval_str=True),
                          {'args': (*tuple[int, ...],)[0]})
 
@@ -811,6 +814,44 @@ def test_stringized_annotations_on_wrapper(self):
             {"a": "int", "b": "str", "return": "MyClass"},
         )
 
+    def test_stringized_annotations_on_partial_wrapper(self):
+        isa = inspect_stringized_annotations
+
+        def times_three_str(fn: typing.Callable[[str], isa.MyClass]):
+            @functools.wraps(fn)
+            def wrapper(b: "str") -> "MyClass":
+                return fn(b * 3)
+
+            return wrapper
+
+        wrapped = times_three_str(functools.partial(isa.function, 1))
+        self.assertEqual(wrapped("x"), isa.MyClass(1, "xxx"))
+        self.assertIsNot(wrapped.__globals__, isa.function.__globals__)
+        self.assertEqual(
+            get_annotations(wrapped, eval_str=True),
+            {"b": str, "return": isa.MyClass},
+        )
+        self.assertEqual(
+            get_annotations(wrapped, eval_str=False),
+            {"b": "str", "return": "MyClass"},
+        )
+
+        # If functools is not loaded, names will be evaluated in the current
+        # module instead of being unwrapped to the original.
+        functools_mod = sys.modules["functools"]
+        del sys.modules["functools"]
+
+        self.assertEqual(
+            get_annotations(wrapped, eval_str=True),
+            {"b": str, "return": MyClass},
+        )
+        self.assertEqual(
+            get_annotations(wrapped, eval_str=False),
+            {"b": "str", "return": "MyClass"},
+        )
+
+        sys.modules["functools"] = functools_mod
+
     def test_stringized_annotations_on_class(self):
         isa = inspect_stringized_annotations
         # test that local namespace lookups work
@@ -823,6 +864,16 @@ def test_stringized_annotations_on_class(self):
             {"x": int},
         )
 
+    def test_stringized_annotations_on_custom_object(self):
+        class HasAnnotations:
+            @property
+            def __annotations__(self):
+                return {"x": "int"}
+
+        ha = HasAnnotations()
+        self.assertEqual(get_annotations(ha), {"x": "int"})
+        self.assertEqual(get_annotations(ha, eval_str=True), {"x": int})
+
     def test_stringized_annotation_permutations(self):
         def define_class(name, has_future, has_annos, base_text, 
extra_names=None):
             lines = []
@@ -990,6 +1041,23 @@ def __annotate__(self):
             {"x": "int"},
         )
 
+    def test_non_dict_annotate(self):
+        class WeirdAnnotate:
+            def __annotate__(self, *args, **kwargs):
+                return "not a dict"
+
+        wa = WeirdAnnotate()
+        for format in Format:
+            if format == Format.VALUE_WITH_FAKE_GLOBALS:
+                continue
+            with (
+                self.subTest(format=format),
+                self.assertRaisesRegex(
+                    ValueError, r".*__annotate__ returned a non-dict"
+                ),
+            ):
+                get_annotations(wa, format=format)
+
     def test_no_annotations(self):
         class CustomClass:
             pass

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to