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()`
This commit is contained in:
@@ -9,6 +9,7 @@ import itertools
|
||||
import pickle
|
||||
from string.templatelib import Template, Interpolation
|
||||
import typing
|
||||
import sys
|
||||
import unittest
|
||||
from annotationlib import (
|
||||
Format,
|
||||
@@ -755,6 +756,8 @@ class TestGetAnnotations(unittest.TestCase):
|
||||
|
||||
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 @@ class TestGetAnnotations(unittest.TestCase):
|
||||
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 @@ class TestGetAnnotations(unittest.TestCase):
|
||||
{"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 @@ class TestGetAnnotations(unittest.TestCase):
|
||||
{"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 @@ class TestGetAnnotations(unittest.TestCase):
|
||||
{"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
|
||||
|
||||
Reference in New Issue
Block a user