mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 13:41:24 +00:00 
			
		
		
		
	 90f476e0f8
			
		
	
	
		90f476e0f8
		
			
		
	
	
	
	
		
			
			I don't know why you'd use t-strings in annotations, but now if you do, the STRING format will do a great job of recovering the source code.
		
			
				
	
	
		
			1613 lines
		
	
	
	
		
			53 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			1613 lines
		
	
	
	
		
			53 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| """Tests for the annotations module."""
 | |
| 
 | |
| import textwrap
 | |
| import annotationlib
 | |
| import builtins
 | |
| import collections
 | |
| import functools
 | |
| import itertools
 | |
| import pickle
 | |
| from string.templatelib import Interpolation, Template
 | |
| import typing
 | |
| import unittest
 | |
| from annotationlib import (
 | |
|     Format,
 | |
|     ForwardRef,
 | |
|     get_annotations,
 | |
|     annotations_to_string,
 | |
|     type_repr,
 | |
| )
 | |
| from typing import Unpack, get_type_hints, List, Union
 | |
| 
 | |
| from test import support
 | |
| from test.support import import_helper
 | |
| from test.test_inspect import inspect_stock_annotations
 | |
| from test.test_inspect import inspect_stringized_annotations
 | |
| from test.test_inspect import inspect_stringized_annotations_2
 | |
| from test.test_inspect import inspect_stringized_annotations_pep695
 | |
| 
 | |
| 
 | |
| def times_three(fn):
 | |
|     @functools.wraps(fn)
 | |
|     def wrapper(a, b):
 | |
|         return fn(a * 3, b * 3)
 | |
| 
 | |
|     return wrapper
 | |
| 
 | |
| 
 | |
| class MyClass:
 | |
|     def __repr__(self):
 | |
|         return "my repr"
 | |
| 
 | |
| 
 | |
| class TestFormat(unittest.TestCase):
 | |
|     def test_enum(self):
 | |
|         self.assertEqual(Format.VALUE.value, 1)
 | |
|         self.assertEqual(Format.VALUE, 1)
 | |
| 
 | |
|         self.assertEqual(Format.VALUE_WITH_FAKE_GLOBALS.value, 2)
 | |
|         self.assertEqual(Format.VALUE_WITH_FAKE_GLOBALS, 2)
 | |
| 
 | |
|         self.assertEqual(Format.FORWARDREF.value, 3)
 | |
|         self.assertEqual(Format.FORWARDREF, 3)
 | |
| 
 | |
|         self.assertEqual(Format.STRING.value, 4)
 | |
|         self.assertEqual(Format.STRING, 4)
 | |
| 
 | |
| 
 | |
| class TestForwardRefFormat(unittest.TestCase):
 | |
|     def test_closure(self):
 | |
|         def inner(arg: x):
 | |
|             pass
 | |
| 
 | |
|         anno = get_annotations(inner, format=Format.FORWARDREF)
 | |
|         fwdref = anno["arg"]
 | |
|         self.assertIsInstance(fwdref, ForwardRef)
 | |
|         self.assertEqual(fwdref.__forward_arg__, "x")
 | |
|         with self.assertRaises(NameError):
 | |
|             fwdref.evaluate()
 | |
| 
 | |
|         x = 1
 | |
|         self.assertEqual(fwdref.evaluate(), x)
 | |
| 
 | |
|         anno = get_annotations(inner, format=Format.FORWARDREF)
 | |
|         self.assertEqual(anno["arg"], x)
 | |
| 
 | |
|     def test_function(self):
 | |
|         def f(x: int, y: doesntexist):
 | |
|             pass
 | |
| 
 | |
|         anno = get_annotations(f, format=Format.FORWARDREF)
 | |
|         self.assertIs(anno["x"], int)
 | |
|         fwdref = anno["y"]
 | |
|         self.assertIsInstance(fwdref, ForwardRef)
 | |
|         self.assertEqual(fwdref.__forward_arg__, "doesntexist")
 | |
|         with self.assertRaises(NameError):
 | |
|             fwdref.evaluate()
 | |
|         self.assertEqual(fwdref.evaluate(globals={"doesntexist": 1}), 1)
 | |
| 
 | |
|     def test_nonexistent_attribute(self):
 | |
|         def f(
 | |
|             x: some.module,
 | |
|             y: some[module],
 | |
|             z: some(module),
 | |
|             alpha: some | obj,
 | |
|             beta: +some,
 | |
|             gamma: some < obj,
 | |
|         ):
 | |
|             pass
 | |
| 
 | |
|         anno = get_annotations(f, format=Format.FORWARDREF)
 | |
|         x_anno = anno["x"]
 | |
|         self.assertIsInstance(x_anno, ForwardRef)
 | |
|         self.assertEqual(x_anno, support.EqualToForwardRef("some.module", owner=f))
 | |
| 
 | |
|         y_anno = anno["y"]
 | |
|         self.assertIsInstance(y_anno, ForwardRef)
 | |
|         self.assertEqual(y_anno, support.EqualToForwardRef("some[module]", owner=f))
 | |
| 
 | |
|         z_anno = anno["z"]
 | |
|         self.assertIsInstance(z_anno, ForwardRef)
 | |
|         self.assertEqual(z_anno, support.EqualToForwardRef("some(module)", owner=f))
 | |
| 
 | |
|         alpha_anno = anno["alpha"]
 | |
|         self.assertIsInstance(alpha_anno, ForwardRef)
 | |
|         self.assertEqual(alpha_anno, support.EqualToForwardRef("some | obj", owner=f))
 | |
| 
 | |
|         beta_anno = anno["beta"]
 | |
|         self.assertIsInstance(beta_anno, ForwardRef)
 | |
|         self.assertEqual(beta_anno, support.EqualToForwardRef("+some", owner=f))
 | |
| 
 | |
|         gamma_anno = anno["gamma"]
 | |
|         self.assertIsInstance(gamma_anno, ForwardRef)
 | |
|         self.assertEqual(gamma_anno, support.EqualToForwardRef("some < obj", owner=f))
 | |
| 
 | |
|     def test_partially_nonexistent_union(self):
 | |
|         # Test unions with '|' syntax equal unions with typing.Union[] with some forwardrefs
 | |
|         class UnionForwardrefs:
 | |
|             pipe: str | undefined
 | |
|             union: Union[str, undefined]
 | |
| 
 | |
|         annos = get_annotations(UnionForwardrefs, format=Format.FORWARDREF)
 | |
| 
 | |
|         pipe = annos["pipe"]
 | |
|         self.assertIsInstance(pipe, ForwardRef)
 | |
|         self.assertEqual(
 | |
|             pipe.evaluate(globals={"undefined": int}),
 | |
|             str | int,
 | |
|         )
 | |
|         union = annos["union"]
 | |
|         self.assertIsInstance(union, Union)
 | |
|         arg1, arg2 = typing.get_args(union)
 | |
|         self.assertIs(arg1, str)
 | |
|         self.assertEqual(
 | |
|             arg2, support.EqualToForwardRef("undefined", is_class=True, owner=UnionForwardrefs)
 | |
|         )
 | |
| 
 | |
| 
 | |
| class TestStringFormat(unittest.TestCase):
 | |
|     def test_closure(self):
 | |
|         x = 0
 | |
| 
 | |
|         def inner(arg: x):
 | |
|             pass
 | |
| 
 | |
|         anno = get_annotations(inner, format=Format.STRING)
 | |
|         self.assertEqual(anno, {"arg": "x"})
 | |
| 
 | |
|     def test_closure_undefined(self):
 | |
|         if False:
 | |
|             x = 0
 | |
| 
 | |
|         def inner(arg: x):
 | |
|             pass
 | |
| 
 | |
|         anno = get_annotations(inner, format=Format.STRING)
 | |
|         self.assertEqual(anno, {"arg": "x"})
 | |
| 
 | |
|     def test_function(self):
 | |
|         def f(x: int, y: doesntexist):
 | |
|             pass
 | |
| 
 | |
|         anno = get_annotations(f, format=Format.STRING)
 | |
|         self.assertEqual(anno, {"x": "int", "y": "doesntexist"})
 | |
| 
 | |
|     def test_expressions(self):
 | |
|         def f(
 | |
|             add: a + b,
 | |
|             sub: a - b,
 | |
|             mul: a * b,
 | |
|             matmul: a @ b,
 | |
|             truediv: a / b,
 | |
|             mod: a % b,
 | |
|             lshift: a << b,
 | |
|             rshift: a >> b,
 | |
|             or_: a | b,
 | |
|             xor: a ^ b,
 | |
|             and_: a & b,
 | |
|             floordiv: a // b,
 | |
|             pow_: a**b,
 | |
|             lt: a < b,
 | |
|             le: a <= b,
 | |
|             eq: a == b,
 | |
|             ne: a != b,
 | |
|             gt: a > b,
 | |
|             ge: a >= b,
 | |
|             invert: ~a,
 | |
|             neg: -a,
 | |
|             pos: +a,
 | |
|             getitem: a[b],
 | |
|             getattr: a.b,
 | |
|             call: a(b, *c, d=e),  # **kwargs are not supported
 | |
|             *args: *a,
 | |
|         ):
 | |
|             pass
 | |
| 
 | |
|         anno = get_annotations(f, format=Format.STRING)
 | |
|         self.assertEqual(
 | |
|             anno,
 | |
|             {
 | |
|                 "add": "a + b",
 | |
|                 "sub": "a - b",
 | |
|                 "mul": "a * b",
 | |
|                 "matmul": "a @ b",
 | |
|                 "truediv": "a / b",
 | |
|                 "mod": "a % b",
 | |
|                 "lshift": "a << b",
 | |
|                 "rshift": "a >> b",
 | |
|                 "or_": "a | b",
 | |
|                 "xor": "a ^ b",
 | |
|                 "and_": "a & b",
 | |
|                 "floordiv": "a // b",
 | |
|                 "pow_": "a ** b",
 | |
|                 "lt": "a < b",
 | |
|                 "le": "a <= b",
 | |
|                 "eq": "a == b",
 | |
|                 "ne": "a != b",
 | |
|                 "gt": "a > b",
 | |
|                 "ge": "a >= b",
 | |
|                 "invert": "~a",
 | |
|                 "neg": "-a",
 | |
|                 "pos": "+a",
 | |
|                 "getitem": "a[b]",
 | |
|                 "getattr": "a.b",
 | |
|                 "call": "a(b, *c, d=e)",
 | |
|                 "args": "*a",
 | |
|             },
 | |
|         )
 | |
| 
 | |
|     def test_reverse_ops(self):
 | |
|         def f(
 | |
|             radd: 1 + a,
 | |
|             rsub: 1 - a,
 | |
|             rmul: 1 * a,
 | |
|             rmatmul: 1 @ a,
 | |
|             rtruediv: 1 / a,
 | |
|             rmod: 1 % a,
 | |
|             rlshift: 1 << a,
 | |
|             rrshift: 1 >> a,
 | |
|             ror: 1 | a,
 | |
|             rxor: 1 ^ a,
 | |
|             rand: 1 & a,
 | |
|             rfloordiv: 1 // a,
 | |
|             rpow: 1**a,
 | |
|         ):
 | |
|             pass
 | |
| 
 | |
|         anno = get_annotations(f, format=Format.STRING)
 | |
|         self.assertEqual(
 | |
|             anno,
 | |
|             {
 | |
|                 "radd": "1 + a",
 | |
|                 "rsub": "1 - a",
 | |
|                 "rmul": "1 * a",
 | |
|                 "rmatmul": "1 @ a",
 | |
|                 "rtruediv": "1 / a",
 | |
|                 "rmod": "1 % a",
 | |
|                 "rlshift": "1 << a",
 | |
|                 "rrshift": "1 >> a",
 | |
|                 "ror": "1 | a",
 | |
|                 "rxor": "1 ^ a",
 | |
|                 "rand": "1 & a",
 | |
|                 "rfloordiv": "1 // a",
 | |
|                 "rpow": "1 ** a",
 | |
|             },
 | |
|         )
 | |
| 
 | |
|     def test_template_str(self):
 | |
|         def f(
 | |
|             x: t"{a}",
 | |
|             y: list[t"{a}"],
 | |
|             z: t"{a:b} {c!r} {d!s:t}",
 | |
|             a: t"a{b}c{d}e{f}g",
 | |
|             b: t"{a:{1}}",
 | |
|             c: t"{a | b * c}",
 | |
|         ): pass
 | |
| 
 | |
|         annos = get_annotations(f, format=Format.STRING)
 | |
|         self.assertEqual(annos, {
 | |
|             "x": "t'{a}'",
 | |
|             "y": "list[t'{a}']",
 | |
|             "z": "t'{a:b} {c!r} {d!s:t}'",
 | |
|             "a": "t'a{b}c{d}e{f}g'",
 | |
|             # interpolations in the format spec are eagerly evaluated so we can't recover the source
 | |
|             "b": "t'{a:1}'",
 | |
|             "c": "t'{a | b * c}'",
 | |
|         })
 | |
| 
 | |
|         def g(
 | |
|             x: t"{a}",
 | |
|         ): ...
 | |
| 
 | |
|         annos = get_annotations(g, format=Format.FORWARDREF)
 | |
|         templ = annos["x"]
 | |
|         # Template and Interpolation don't have __eq__ so we have to compare manually
 | |
|         self.assertIsInstance(templ, Template)
 | |
|         self.assertEqual(templ.strings, ("", ""))
 | |
|         self.assertEqual(len(templ.interpolations), 1)
 | |
|         interp = templ.interpolations[0]
 | |
|         self.assertEqual(interp.value, support.EqualToForwardRef("a", owner=g))
 | |
|         self.assertEqual(interp.expression, "a")
 | |
|         self.assertIsNone(interp.conversion)
 | |
|         self.assertEqual(interp.format_spec, "")
 | |
| 
 | |
|     def test_getitem(self):
 | |
|         def f(x: undef1[str, undef2]):
 | |
|             pass
 | |
|         anno = get_annotations(f, format=Format.STRING)
 | |
|         self.assertEqual(anno, {"x": "undef1[str, undef2]"})
 | |
| 
 | |
|         anno = get_annotations(f, format=Format.FORWARDREF)
 | |
|         fwdref = anno["x"]
 | |
|         self.assertIsInstance(fwdref, ForwardRef)
 | |
|         self.assertEqual(
 | |
|             fwdref.evaluate(globals={"undef1": dict, "undef2": float}), dict[str, float]
 | |
|         )
 | |
| 
 | |
|     def test_slice(self):
 | |
|         def f(x: a[b:c]):
 | |
|             pass
 | |
|         anno = get_annotations(f, format=Format.STRING)
 | |
|         self.assertEqual(anno, {"x": "a[b:c]"})
 | |
| 
 | |
|         def f(x: a[b:c, d:e]):
 | |
|             pass
 | |
|         anno = get_annotations(f, format=Format.STRING)
 | |
|         self.assertEqual(anno, {"x": "a[b:c, d:e]"})
 | |
| 
 | |
|         obj = slice(1, 1, 1)
 | |
|         def f(x: obj):
 | |
|             pass
 | |
|         anno = get_annotations(f, format=Format.STRING)
 | |
|         self.assertEqual(anno, {"x": "obj"})
 | |
| 
 | |
|     def test_literals(self):
 | |
|         def f(
 | |
|             a: 1,
 | |
|             b: 1.0,
 | |
|             c: "hello",
 | |
|             d: b"hello",
 | |
|             e: True,
 | |
|             f: None,
 | |
|             g: ...,
 | |
|             h: 1j,
 | |
|         ):
 | |
|             pass
 | |
| 
 | |
|         anno = get_annotations(f, format=Format.STRING)
 | |
|         self.assertEqual(
 | |
|             anno,
 | |
|             {
 | |
|                 "a": "1",
 | |
|                 "b": "1.0",
 | |
|                 "c": 'hello',
 | |
|                 "d": "b'hello'",
 | |
|                 "e": "True",
 | |
|                 "f": "None",
 | |
|                 "g": "...",
 | |
|                 "h": "1j",
 | |
|             },
 | |
|         )
 | |
| 
 | |
|     def test_displays(self):
 | |
|         # Simple case first
 | |
|         def f(x: a[[int, str], float]):
 | |
|             pass
 | |
|         anno = get_annotations(f, format=Format.STRING)
 | |
|         self.assertEqual(anno, {"x": "a[[int, str], float]"})
 | |
| 
 | |
|         def g(
 | |
|             w: a[[int, str], float],
 | |
|             x: a[{int, str}, 3],
 | |
|             y: a[{int: str}, 4],
 | |
|             z: a[(int, str), 5],
 | |
|         ):
 | |
|             pass
 | |
|         anno = get_annotations(g, format=Format.STRING)
 | |
|         self.assertEqual(
 | |
|             anno,
 | |
|             {
 | |
|                 "w": "a[[int, str], float]",
 | |
|                 "x": "a[{int, str}, 3]",
 | |
|                 "y": "a[{int: str}, 4]",
 | |
|                 "z": "a[(int, str), 5]",
 | |
|             },
 | |
|         )
 | |
| 
 | |
|     def test_nested_expressions(self):
 | |
|         def f(
 | |
|             nested: list[Annotated[set[int], "set of ints", 4j]],
 | |
|             set: {a + b},  # single element because order is not guaranteed
 | |
|             dict: {a + b: c + d, "key": e + g},
 | |
|             list: [a, b, c],
 | |
|             tuple: (a, b, c),
 | |
|             slice: (a[b:c], a[b:c:d], a[:c], a[b:], a[:], a[::d], a[b::d]),
 | |
|             extended_slice: a[:, :, c:d],
 | |
|             unpack1: [*a],
 | |
|             unpack2: [*a, b, c],
 | |
|         ):
 | |
|             pass
 | |
| 
 | |
|         anno = get_annotations(f, format=Format.STRING)
 | |
|         self.assertEqual(
 | |
|             anno,
 | |
|             {
 | |
|                 "nested": "list[Annotated[set[int], 'set of ints', 4j]]",
 | |
|                 "set": "{a + b}",
 | |
|                 "dict": "{a + b: c + d, 'key': e + g}",
 | |
|                 "list": "[a, b, c]",
 | |
|                 "tuple": "(a, b, c)",
 | |
|                 "slice": "(a[b:c], a[b:c:d], a[:c], a[b:], a[:], a[::d], a[b::d])",
 | |
|                 "extended_slice": "a[:, :, c:d]",
 | |
|                 "unpack1": "[*a]",
 | |
|                 "unpack2": "[*a, b, c]",
 | |
|             },
 | |
|         )
 | |
| 
 | |
|     def test_unsupported_operations(self):
 | |
|         format_msg = "Cannot stringify annotation containing string formatting"
 | |
| 
 | |
|         def f(fstring: f"{a}"):
 | |
|             pass
 | |
| 
 | |
|         with self.assertRaisesRegex(TypeError, format_msg):
 | |
|             get_annotations(f, format=Format.STRING)
 | |
| 
 | |
|         def f(fstring_format: f"{a:02d}"):
 | |
|             pass
 | |
| 
 | |
|         with self.assertRaisesRegex(TypeError, format_msg):
 | |
|             get_annotations(f, format=Format.STRING)
 | |
| 
 | |
|     def test_shenanigans(self):
 | |
|         # In cases like this we can't reconstruct the source; test that we do something
 | |
|         # halfway reasonable.
 | |
|         def f(x: x | (1).__class__, y: (1).__class__):
 | |
|             pass
 | |
| 
 | |
|         self.assertEqual(
 | |
|             get_annotations(f, format=Format.STRING),
 | |
|             {"x": "x | <class 'int'>", "y": "<class 'int'>"},
 | |
|         )
 | |
| 
 | |
| 
 | |
| class TestGetAnnotations(unittest.TestCase):
 | |
|     def test_builtin_type(self):
 | |
|         self.assertEqual(get_annotations(int), {})
 | |
|         self.assertEqual(get_annotations(object), {})
 | |
| 
 | |
|     def test_custom_metaclass(self):
 | |
|         class Meta(type):
 | |
|             pass
 | |
| 
 | |
|         class C(metaclass=Meta):
 | |
|             x: int
 | |
| 
 | |
|         self.assertEqual(get_annotations(C), {"x": int})
 | |
| 
 | |
|     def test_missing_dunder_dict(self):
 | |
|         class NoDict(type):
 | |
|             @property
 | |
|             def __dict__(cls):
 | |
|                 raise AttributeError
 | |
| 
 | |
|             b: str
 | |
| 
 | |
|         class C1(metaclass=NoDict):
 | |
|             a: int
 | |
| 
 | |
|         self.assertEqual(get_annotations(C1), {"a": int})
 | |
|         self.assertEqual(
 | |
|             get_annotations(C1, format=Format.FORWARDREF),
 | |
|             {"a": int},
 | |
|         )
 | |
|         self.assertEqual(
 | |
|             get_annotations(C1, format=Format.STRING),
 | |
|             {"a": "int"},
 | |
|         )
 | |
|         self.assertEqual(get_annotations(NoDict), {"b": str})
 | |
|         self.assertEqual(
 | |
|             get_annotations(NoDict, format=Format.FORWARDREF),
 | |
|             {"b": str},
 | |
|         )
 | |
|         self.assertEqual(
 | |
|             get_annotations(NoDict, format=Format.STRING),
 | |
|             {"b": "str"},
 | |
|         )
 | |
| 
 | |
|     def test_format(self):
 | |
|         def f1(a: int):
 | |
|             pass
 | |
| 
 | |
|         def f2(a: undefined):
 | |
|             pass
 | |
| 
 | |
|         self.assertEqual(
 | |
|             get_annotations(f1, format=Format.VALUE),
 | |
|             {"a": int},
 | |
|         )
 | |
|         self.assertEqual(get_annotations(f1, format=1), {"a": int})
 | |
| 
 | |
|         fwd = support.EqualToForwardRef("undefined", owner=f2)
 | |
|         self.assertEqual(
 | |
|             get_annotations(f2, format=Format.FORWARDREF),
 | |
|             {"a": fwd},
 | |
|         )
 | |
|         self.assertEqual(get_annotations(f2, format=3), {"a": fwd})
 | |
| 
 | |
|         self.assertEqual(
 | |
|             get_annotations(f1, format=Format.STRING),
 | |
|             {"a": "int"},
 | |
|         )
 | |
|         self.assertEqual(get_annotations(f1, format=4), {"a": "int"})
 | |
| 
 | |
|         with self.assertRaises(ValueError):
 | |
|             get_annotations(f1, format=42)
 | |
| 
 | |
|         with self.assertRaisesRegex(
 | |
|             ValueError,
 | |
|             r"The VALUE_WITH_FAKE_GLOBALS format is for internal use only",
 | |
|         ):
 | |
|             get_annotations(f1, format=Format.VALUE_WITH_FAKE_GLOBALS)
 | |
| 
 | |
|         with self.assertRaisesRegex(
 | |
|             ValueError,
 | |
|             r"The VALUE_WITH_FAKE_GLOBALS format is for internal use only",
 | |
|         ):
 | |
|             get_annotations(f1, format=2)
 | |
| 
 | |
|     def test_custom_object_with_annotations(self):
 | |
|         class C:
 | |
|             def __init__(self):
 | |
|                 self.__annotations__ = {"x": int, "y": str}
 | |
| 
 | |
|         self.assertEqual(get_annotations(C()), {"x": int, "y": str})
 | |
| 
 | |
|     def test_custom_format_eval_str(self):
 | |
|         def foo():
 | |
|             pass
 | |
| 
 | |
|         with self.assertRaises(ValueError):
 | |
|             get_annotations(foo, format=Format.FORWARDREF, eval_str=True)
 | |
|             get_annotations(foo, format=Format.STRING, eval_str=True)
 | |
| 
 | |
|     def test_stock_annotations(self):
 | |
|         def foo(a: int, b: str):
 | |
|             pass
 | |
| 
 | |
|         for format in (Format.VALUE, Format.FORWARDREF):
 | |
|             with self.subTest(format=format):
 | |
|                 self.assertEqual(
 | |
|                     get_annotations(foo, format=format),
 | |
|                     {"a": int, "b": str},
 | |
|                 )
 | |
|         self.assertEqual(
 | |
|             get_annotations(foo, format=Format.STRING),
 | |
|             {"a": "int", "b": "str"},
 | |
|         )
 | |
| 
 | |
|         foo.__annotations__ = {"a": "foo", "b": "str"}
 | |
|         for format in Format:
 | |
|             if format == Format.VALUE_WITH_FAKE_GLOBALS:
 | |
|                 continue
 | |
|             with self.subTest(format=format):
 | |
|                 self.assertEqual(
 | |
|                     get_annotations(foo, format=format),
 | |
|                     {"a": "foo", "b": "str"},
 | |
|                 )
 | |
| 
 | |
|         self.assertEqual(
 | |
|             get_annotations(foo, eval_str=True, locals=locals()),
 | |
|             {"a": foo, "b": str},
 | |
|         )
 | |
|         self.assertEqual(
 | |
|             get_annotations(foo, eval_str=True, globals=locals()),
 | |
|             {"a": foo, "b": str},
 | |
|         )
 | |
| 
 | |
|     def test_stock_annotations_in_module(self):
 | |
|         isa = inspect_stock_annotations
 | |
| 
 | |
|         for kwargs in [
 | |
|             {},
 | |
|             {"eval_str": False},
 | |
|             {"format": Format.VALUE},
 | |
|             {"format": Format.FORWARDREF},
 | |
|             {"format": Format.VALUE, "eval_str": False},
 | |
|             {"format": Format.FORWARDREF, "eval_str": False},
 | |
|         ]:
 | |
|             with self.subTest(**kwargs):
 | |
|                 self.assertEqual(get_annotations(isa, **kwargs), {"a": int, "b": str})
 | |
|                 self.assertEqual(
 | |
|                     get_annotations(isa.MyClass, **kwargs),
 | |
|                     {"a": int, "b": str},
 | |
|                 )
 | |
|                 self.assertEqual(
 | |
|                     get_annotations(isa.function, **kwargs),
 | |
|                     {"a": int, "b": str, "return": isa.MyClass},
 | |
|                 )
 | |
|                 self.assertEqual(
 | |
|                     get_annotations(isa.function2, **kwargs),
 | |
|                     {"a": int, "b": "str", "c": isa.MyClass, "return": isa.MyClass},
 | |
|                 )
 | |
|                 self.assertEqual(
 | |
|                     get_annotations(isa.function3, **kwargs),
 | |
|                     {"a": "int", "b": "str", "c": "MyClass"},
 | |
|                 )
 | |
|                 self.assertEqual(
 | |
|                     get_annotations(annotationlib, **kwargs), {}
 | |
|                 )  # annotations module has no annotations
 | |
|                 self.assertEqual(get_annotations(isa.UnannotatedClass, **kwargs), {})
 | |
|                 self.assertEqual(
 | |
|                     get_annotations(isa.unannotated_function, **kwargs),
 | |
|                     {},
 | |
|                 )
 | |
| 
 | |
|         for kwargs in [
 | |
|             {"eval_str": True},
 | |
|             {"format": Format.VALUE, "eval_str": True},
 | |
|         ]:
 | |
|             with self.subTest(**kwargs):
 | |
|                 self.assertEqual(get_annotations(isa, **kwargs), {"a": int, "b": str})
 | |
|                 self.assertEqual(
 | |
|                     get_annotations(isa.MyClass, **kwargs),
 | |
|                     {"a": int, "b": str},
 | |
|                 )
 | |
|                 self.assertEqual(
 | |
|                     get_annotations(isa.function, **kwargs),
 | |
|                     {"a": int, "b": str, "return": isa.MyClass},
 | |
|                 )
 | |
|                 self.assertEqual(
 | |
|                     get_annotations(isa.function2, **kwargs),
 | |
|                     {"a": int, "b": str, "c": isa.MyClass, "return": isa.MyClass},
 | |
|                 )
 | |
|                 self.assertEqual(
 | |
|                     get_annotations(isa.function3, **kwargs),
 | |
|                     {"a": int, "b": str, "c": isa.MyClass},
 | |
|                 )
 | |
|                 self.assertEqual(get_annotations(annotationlib, **kwargs), {})
 | |
|                 self.assertEqual(get_annotations(isa.UnannotatedClass, **kwargs), {})
 | |
|                 self.assertEqual(
 | |
|                     get_annotations(isa.unannotated_function, **kwargs),
 | |
|                     {},
 | |
|                 )
 | |
| 
 | |
|         self.assertEqual(
 | |
|             get_annotations(isa, format=Format.STRING),
 | |
|             {"a": "int", "b": "str"},
 | |
|         )
 | |
|         self.assertEqual(
 | |
|             get_annotations(isa.MyClass, format=Format.STRING),
 | |
|             {"a": "int", "b": "str"},
 | |
|         )
 | |
|         self.assertEqual(
 | |
|             get_annotations(isa.function, format=Format.STRING),
 | |
|             {"a": "int", "b": "str", "return": "MyClass"},
 | |
|         )
 | |
|         self.assertEqual(
 | |
|             get_annotations(isa.function2, format=Format.STRING),
 | |
|             {"a": "int", "b": "str", "c": "MyClass", "return": "MyClass"},
 | |
|         )
 | |
|         self.assertEqual(
 | |
|             get_annotations(isa.function3, format=Format.STRING),
 | |
|             {"a": "int", "b": "str", "c": "MyClass"},
 | |
|         )
 | |
|         self.assertEqual(
 | |
|             get_annotations(annotationlib, format=Format.STRING),
 | |
|             {},
 | |
|         )
 | |
|         self.assertEqual(
 | |
|             get_annotations(isa.UnannotatedClass, format=Format.STRING),
 | |
|             {},
 | |
|         )
 | |
|         self.assertEqual(
 | |
|             get_annotations(isa.unannotated_function, format=Format.STRING),
 | |
|             {},
 | |
|         )
 | |
| 
 | |
|     def test_stock_annotations_on_wrapper(self):
 | |
|         isa = inspect_stock_annotations
 | |
| 
 | |
|         wrapped = times_three(isa.function)
 | |
|         self.assertEqual(wrapped(1, "x"), isa.MyClass(3, "xxx"))
 | |
|         self.assertIsNot(wrapped.__globals__, isa.function.__globals__)
 | |
|         self.assertEqual(
 | |
|             get_annotations(wrapped),
 | |
|             {"a": int, "b": str, "return": isa.MyClass},
 | |
|         )
 | |
|         self.assertEqual(
 | |
|             get_annotations(wrapped, format=Format.FORWARDREF),
 | |
|             {"a": int, "b": str, "return": isa.MyClass},
 | |
|         )
 | |
|         self.assertEqual(
 | |
|             get_annotations(wrapped, format=Format.STRING),
 | |
|             {"a": "int", "b": "str", "return": "MyClass"},
 | |
|         )
 | |
|         self.assertEqual(
 | |
|             get_annotations(wrapped, eval_str=True),
 | |
|             {"a": int, "b": str, "return": isa.MyClass},
 | |
|         )
 | |
|         self.assertEqual(
 | |
|             get_annotations(wrapped, eval_str=False),
 | |
|             {"a": int, "b": str, "return": isa.MyClass},
 | |
|         )
 | |
| 
 | |
|     def test_stringized_annotations_in_module(self):
 | |
|         isa = inspect_stringized_annotations
 | |
|         for kwargs in [
 | |
|             {},
 | |
|             {"eval_str": False},
 | |
|             {"format": Format.VALUE},
 | |
|             {"format": Format.FORWARDREF},
 | |
|             {"format": Format.STRING},
 | |
|             {"format": Format.VALUE, "eval_str": False},
 | |
|             {"format": Format.FORWARDREF, "eval_str": False},
 | |
|             {"format": Format.STRING, "eval_str": False},
 | |
|         ]:
 | |
|             with self.subTest(**kwargs):
 | |
|                 self.assertEqual(
 | |
|                     get_annotations(isa, **kwargs),
 | |
|                     {"a": "int", "b": "str"},
 | |
|                 )
 | |
|                 self.assertEqual(
 | |
|                     get_annotations(isa.MyClass, **kwargs),
 | |
|                     {"a": "int", "b": "str"},
 | |
|                 )
 | |
|                 self.assertEqual(
 | |
|                     get_annotations(isa.function, **kwargs),
 | |
|                     {"a": "int", "b": "str", "return": "MyClass"},
 | |
|                 )
 | |
|                 self.assertEqual(
 | |
|                     get_annotations(isa.function2, **kwargs),
 | |
|                     {"a": "int", "b": "'str'", "c": "MyClass", "return": "MyClass"},
 | |
|                 )
 | |
|                 self.assertEqual(
 | |
|                     get_annotations(isa.function3, **kwargs),
 | |
|                     {"a": "'int'", "b": "'str'", "c": "'MyClass'"},
 | |
|                 )
 | |
|                 self.assertEqual(get_annotations(isa.UnannotatedClass, **kwargs), {})
 | |
|                 self.assertEqual(
 | |
|                     get_annotations(isa.unannotated_function, **kwargs),
 | |
|                     {},
 | |
|                 )
 | |
| 
 | |
|         for kwargs in [
 | |
|             {"eval_str": True},
 | |
|             {"format": Format.VALUE, "eval_str": True},
 | |
|         ]:
 | |
|             with self.subTest(**kwargs):
 | |
|                 self.assertEqual(get_annotations(isa, **kwargs), {"a": int, "b": str})
 | |
|                 self.assertEqual(
 | |
|                     get_annotations(isa.MyClass, **kwargs),
 | |
|                     {"a": int, "b": str},
 | |
|                 )
 | |
|                 self.assertEqual(
 | |
|                     get_annotations(isa.function, **kwargs),
 | |
|                     {"a": int, "b": str, "return": isa.MyClass},
 | |
|                 )
 | |
|                 self.assertEqual(
 | |
|                     get_annotations(isa.function2, **kwargs),
 | |
|                     {"a": int, "b": "str", "c": isa.MyClass, "return": isa.MyClass},
 | |
|                 )
 | |
|                 self.assertEqual(
 | |
|                     get_annotations(isa.function3, **kwargs),
 | |
|                     {"a": "int", "b": "str", "c": "MyClass"},
 | |
|                 )
 | |
|                 self.assertEqual(get_annotations(isa.UnannotatedClass, **kwargs), {})
 | |
|                 self.assertEqual(
 | |
|                     get_annotations(isa.unannotated_function, **kwargs),
 | |
|                     {},
 | |
|                 )
 | |
| 
 | |
|     def test_stringized_annotations_in_empty_module(self):
 | |
|         isa2 = inspect_stringized_annotations_2
 | |
|         self.assertEqual(get_annotations(isa2), {})
 | |
|         self.assertEqual(get_annotations(isa2, eval_str=True), {})
 | |
|         self.assertEqual(get_annotations(isa2, eval_str=False), {})
 | |
| 
 | |
|     def test_stringized_annotations_on_wrapper(self):
 | |
|         isa = inspect_stringized_annotations
 | |
|         wrapped = times_three(isa.function)
 | |
|         self.assertEqual(wrapped(1, "x"), isa.MyClass(3, "xxx"))
 | |
|         self.assertIsNot(wrapped.__globals__, isa.function.__globals__)
 | |
|         self.assertEqual(
 | |
|             get_annotations(wrapped),
 | |
|             {"a": "int", "b": "str", "return": "MyClass"},
 | |
|         )
 | |
|         self.assertEqual(
 | |
|             get_annotations(wrapped, eval_str=True),
 | |
|             {"a": int, "b": str, "return": isa.MyClass},
 | |
|         )
 | |
|         self.assertEqual(
 | |
|             get_annotations(wrapped, eval_str=False),
 | |
|             {"a": "int", "b": "str", "return": "MyClass"},
 | |
|         )
 | |
| 
 | |
|     def test_stringized_annotations_on_class(self):
 | |
|         isa = inspect_stringized_annotations
 | |
|         # test that local namespace lookups work
 | |
|         self.assertEqual(
 | |
|             get_annotations(isa.MyClassWithLocalAnnotations),
 | |
|             {"x": "mytype"},
 | |
|         )
 | |
|         self.assertEqual(
 | |
|             get_annotations(isa.MyClassWithLocalAnnotations, eval_str=True),
 | |
|             {"x": int},
 | |
|         )
 | |
| 
 | |
|     def test_modify_annotations(self):
 | |
|         def f(x: int):
 | |
|             pass
 | |
| 
 | |
|         self.assertEqual(get_annotations(f), {"x": int})
 | |
|         self.assertEqual(
 | |
|             get_annotations(f, format=Format.FORWARDREF),
 | |
|             {"x": int},
 | |
|         )
 | |
| 
 | |
|         f.__annotations__["x"] = str
 | |
|         # The modification is reflected in VALUE (the default)
 | |
|         self.assertEqual(get_annotations(f), {"x": str})
 | |
|         # ... and also in FORWARDREF, which tries __annotations__ if available
 | |
|         self.assertEqual(
 | |
|             get_annotations(f, format=Format.FORWARDREF),
 | |
|             {"x": str},
 | |
|         )
 | |
|         # ... but not in STRING which always uses __annotate__
 | |
|         self.assertEqual(
 | |
|             get_annotations(f, format=Format.STRING),
 | |
|             {"x": "int"},
 | |
|         )
 | |
| 
 | |
|     def test_non_dict_annotations(self):
 | |
|         class WeirdAnnotations:
 | |
|             @property
 | |
|             def __annotations__(self):
 | |
|                 return "not a dict"
 | |
| 
 | |
|         wa = WeirdAnnotations()
 | |
|         for format in Format:
 | |
|             if format == Format.VALUE_WITH_FAKE_GLOBALS:
 | |
|                 continue
 | |
|             with (
 | |
|                 self.subTest(format=format),
 | |
|                 self.assertRaisesRegex(
 | |
|                     ValueError, r".*__annotations__ is neither a dict nor None"
 | |
|                 ),
 | |
|             ):
 | |
|                 get_annotations(wa, format=format)
 | |
| 
 | |
|     def test_annotations_on_custom_object(self):
 | |
|         class HasAnnotations:
 | |
|             @property
 | |
|             def __annotations__(self):
 | |
|                 return {"x": int}
 | |
| 
 | |
|         ha = HasAnnotations()
 | |
|         self.assertEqual(get_annotations(ha, format=Format.VALUE), {"x": int})
 | |
|         self.assertEqual(get_annotations(ha, format=Format.FORWARDREF), {"x": int})
 | |
| 
 | |
|         self.assertEqual(get_annotations(ha, format=Format.STRING), {"x": "int"})
 | |
| 
 | |
|     def test_raising_annotations_on_custom_object(self):
 | |
|         class HasRaisingAnnotations:
 | |
|             @property
 | |
|             def __annotations__(self):
 | |
|                 return {"x": undefined}
 | |
| 
 | |
|         hra = HasRaisingAnnotations()
 | |
| 
 | |
|         with self.assertRaises(NameError):
 | |
|             get_annotations(hra, format=Format.VALUE)
 | |
| 
 | |
|         with self.assertRaises(NameError):
 | |
|             get_annotations(hra, format=Format.FORWARDREF)
 | |
| 
 | |
|         undefined = float
 | |
|         self.assertEqual(get_annotations(hra, format=Format.VALUE), {"x": float})
 | |
| 
 | |
|     def test_forwardref_prefers_annotations(self):
 | |
|         class HasBoth:
 | |
|             @property
 | |
|             def __annotations__(self):
 | |
|                 return {"x": int}
 | |
| 
 | |
|             @property
 | |
|             def __annotate__(self):
 | |
|                 return lambda format: {"x": str}
 | |
| 
 | |
|         hb = HasBoth()
 | |
|         self.assertEqual(get_annotations(hb, format=Format.VALUE), {"x": int})
 | |
|         self.assertEqual(get_annotations(hb, format=Format.FORWARDREF), {"x": int})
 | |
|         self.assertEqual(get_annotations(hb, format=Format.STRING), {"x": str})
 | |
| 
 | |
|     def test_only_annotate(self):
 | |
|         def f(x: int):
 | |
|             pass
 | |
| 
 | |
|         class OnlyAnnotate:
 | |
|             @property
 | |
|             def __annotate__(self):
 | |
|                 return f.__annotate__
 | |
| 
 | |
|         oa = OnlyAnnotate()
 | |
|         self.assertEqual(get_annotations(oa, format=Format.VALUE), {"x": int})
 | |
|         self.assertEqual(get_annotations(oa, format=Format.FORWARDREF), {"x": int})
 | |
|         self.assertEqual(
 | |
|             get_annotations(oa, format=Format.STRING),
 | |
|             {"x": "int"},
 | |
|         )
 | |
| 
 | |
|     def test_no_annotations(self):
 | |
|         class CustomClass:
 | |
|             pass
 | |
| 
 | |
|         class MyCallable:
 | |
|             def __call__(self):
 | |
|                 pass
 | |
| 
 | |
|         for format in Format:
 | |
|             if format == Format.VALUE_WITH_FAKE_GLOBALS:
 | |
|                 continue
 | |
|             for obj in (None, 1, object(), CustomClass()):
 | |
|                 with self.subTest(format=format, obj=obj):
 | |
|                     with self.assertRaises(TypeError):
 | |
|                         get_annotations(obj, format=format)
 | |
| 
 | |
|             # Callables and types with no annotations return an empty dict
 | |
|             for obj in (int, len, MyCallable()):
 | |
|                 with self.subTest(format=format, obj=obj):
 | |
|                     self.assertEqual(get_annotations(obj, format=format), {})
 | |
| 
 | |
|     def test_pep695_generic_class_with_future_annotations(self):
 | |
|         ann_module695 = inspect_stringized_annotations_pep695
 | |
|         A_annotations = get_annotations(ann_module695.A, eval_str=True)
 | |
|         A_type_params = ann_module695.A.__type_params__
 | |
|         self.assertIs(A_annotations["x"], A_type_params[0])
 | |
|         self.assertEqual(A_annotations["y"].__args__[0], Unpack[A_type_params[1]])
 | |
|         self.assertIs(A_annotations["z"].__args__[0], A_type_params[2])
 | |
| 
 | |
|     def test_pep695_generic_class_with_future_annotations_and_local_shadowing(self):
 | |
|         B_annotations = get_annotations(
 | |
|             inspect_stringized_annotations_pep695.B, eval_str=True
 | |
|         )
 | |
|         self.assertEqual(B_annotations, {"x": int, "y": str, "z": bytes})
 | |
| 
 | |
|     def test_pep695_generic_class_with_future_annotations_name_clash_with_global_vars(
 | |
|         self,
 | |
|     ):
 | |
|         ann_module695 = inspect_stringized_annotations_pep695
 | |
|         C_annotations = get_annotations(ann_module695.C, eval_str=True)
 | |
|         self.assertEqual(
 | |
|             set(C_annotations.values()), set(ann_module695.C.__type_params__)
 | |
|         )
 | |
| 
 | |
|     def test_pep_695_generic_function_with_future_annotations(self):
 | |
|         ann_module695 = inspect_stringized_annotations_pep695
 | |
|         generic_func_annotations = get_annotations(
 | |
|             ann_module695.generic_function, eval_str=True
 | |
|         )
 | |
|         func_t_params = ann_module695.generic_function.__type_params__
 | |
|         self.assertEqual(
 | |
|             generic_func_annotations.keys(), {"x", "y", "z", "zz", "return"}
 | |
|         )
 | |
|         self.assertIs(generic_func_annotations["x"], func_t_params[0])
 | |
|         self.assertEqual(generic_func_annotations["y"], Unpack[func_t_params[1]])
 | |
|         self.assertIs(generic_func_annotations["z"].__origin__, func_t_params[2])
 | |
|         self.assertIs(generic_func_annotations["zz"].__origin__, func_t_params[2])
 | |
| 
 | |
|     def test_pep_695_generic_function_with_future_annotations_name_clash_with_global_vars(
 | |
|         self,
 | |
|     ):
 | |
|         self.assertEqual(
 | |
|             set(
 | |
|                 get_annotations(
 | |
|                     inspect_stringized_annotations_pep695.generic_function_2,
 | |
|                     eval_str=True,
 | |
|                 ).values()
 | |
|             ),
 | |
|             set(
 | |
|                 inspect_stringized_annotations_pep695.generic_function_2.__type_params__
 | |
|             ),
 | |
|         )
 | |
| 
 | |
|     def test_pep_695_generic_method_with_future_annotations(self):
 | |
|         ann_module695 = inspect_stringized_annotations_pep695
 | |
|         generic_method_annotations = get_annotations(
 | |
|             ann_module695.D.generic_method, eval_str=True
 | |
|         )
 | |
|         params = {
 | |
|             param.__name__: param
 | |
|             for param in ann_module695.D.generic_method.__type_params__
 | |
|         }
 | |
|         self.assertEqual(
 | |
|             generic_method_annotations,
 | |
|             {"x": params["Foo"], "y": params["Bar"], "return": None},
 | |
|         )
 | |
| 
 | |
|     def test_pep_695_generic_method_with_future_annotations_name_clash_with_global_vars(
 | |
|         self,
 | |
|     ):
 | |
|         self.assertEqual(
 | |
|             set(
 | |
|                 get_annotations(
 | |
|                     inspect_stringized_annotations_pep695.D.generic_method_2,
 | |
|                     eval_str=True,
 | |
|                 ).values()
 | |
|             ),
 | |
|             set(
 | |
|                 inspect_stringized_annotations_pep695.D.generic_method_2.__type_params__
 | |
|             ),
 | |
|         )
 | |
| 
 | |
|     def test_pep_695_generic_method_with_future_annotations_name_clash_with_global_and_local_vars(
 | |
|         self,
 | |
|     ):
 | |
|         self.assertEqual(
 | |
|             get_annotations(inspect_stringized_annotations_pep695.E, eval_str=True),
 | |
|             {"x": str},
 | |
|         )
 | |
| 
 | |
|     def test_pep_695_generics_with_future_annotations_nested_in_function(self):
 | |
|         results = inspect_stringized_annotations_pep695.nested()
 | |
| 
 | |
|         self.assertEqual(
 | |
|             set(results.F_annotations.values()), set(results.F.__type_params__)
 | |
|         )
 | |
|         self.assertEqual(
 | |
|             set(results.F_meth_annotations.values()),
 | |
|             set(results.F.generic_method.__type_params__),
 | |
|         )
 | |
|         self.assertNotEqual(
 | |
|             set(results.F_meth_annotations.values()), set(results.F.__type_params__)
 | |
|         )
 | |
|         self.assertEqual(
 | |
|             set(results.F_meth_annotations.values()).intersection(
 | |
|                 results.F.__type_params__
 | |
|             ),
 | |
|             set(),
 | |
|         )
 | |
| 
 | |
|         self.assertEqual(results.G_annotations, {"x": str})
 | |
| 
 | |
|         self.assertEqual(
 | |
|             set(results.generic_func_annotations.values()),
 | |
|             set(results.generic_func.__type_params__),
 | |
|         )
 | |
| 
 | |
|     def test_partial_evaluation(self):
 | |
|         def f(
 | |
|             x: builtins.undef,
 | |
|             y: list[int],
 | |
|             z: 1 + int,
 | |
|             a: builtins.int,
 | |
|             b: [builtins.undef, builtins.int],
 | |
|         ):
 | |
|             pass
 | |
| 
 | |
|         self.assertEqual(
 | |
|             get_annotations(f, format=Format.FORWARDREF),
 | |
|             {
 | |
|                 "x": support.EqualToForwardRef("builtins.undef", owner=f),
 | |
|                 "y": list[int],
 | |
|                 "z": support.EqualToForwardRef("1 + int", owner=f),
 | |
|                 "a": int,
 | |
|                 "b": [
 | |
|                     support.EqualToForwardRef("builtins.undef", owner=f),
 | |
|                     # We can't resolve this because we have to evaluate the whole annotation
 | |
|                     support.EqualToForwardRef("builtins.int", owner=f),
 | |
|                 ],
 | |
|             },
 | |
|         )
 | |
| 
 | |
|         self.assertEqual(
 | |
|             get_annotations(f, format=Format.STRING),
 | |
|             {
 | |
|                 "x": "builtins.undef",
 | |
|                 "y": "list[int]",
 | |
|                 "z": "1 + int",
 | |
|                 "a": "builtins.int",
 | |
|                 "b": "[builtins.undef, builtins.int]",
 | |
|             },
 | |
|         )
 | |
| 
 | |
|     def test_partial_evaluation_error(self):
 | |
|         def f(x: range[1]):
 | |
|             pass
 | |
|         with self.assertRaisesRegex(
 | |
|             TypeError, "type 'range' is not subscriptable"
 | |
|         ):
 | |
|             f.__annotations__
 | |
| 
 | |
|         self.assertEqual(
 | |
|             get_annotations(f, format=Format.FORWARDREF),
 | |
|             {
 | |
|                 "x": support.EqualToForwardRef("range[1]", owner=f),
 | |
|             },
 | |
|         )
 | |
| 
 | |
|     def test_partial_evaluation_cell(self):
 | |
|         obj = object()
 | |
| 
 | |
|         class RaisesAttributeError:
 | |
|             attriberr: obj.missing
 | |
| 
 | |
|         anno = get_annotations(RaisesAttributeError, format=Format.FORWARDREF)
 | |
|         self.assertEqual(
 | |
|             anno,
 | |
|             {
 | |
|                 "attriberr": support.EqualToForwardRef(
 | |
|                     "obj.missing", is_class=True, owner=RaisesAttributeError
 | |
|                 )
 | |
|             },
 | |
|         )
 | |
| 
 | |
| 
 | |
| class TestCallEvaluateFunction(unittest.TestCase):
 | |
|     def test_evaluation(self):
 | |
|         def evaluate(format, exc=NotImplementedError):
 | |
|             if format > 2:
 | |
|                 raise exc
 | |
|             return undefined
 | |
| 
 | |
|         with self.assertRaises(NameError):
 | |
|             annotationlib.call_evaluate_function(evaluate, Format.VALUE)
 | |
|         self.assertEqual(
 | |
|             annotationlib.call_evaluate_function(evaluate, Format.FORWARDREF),
 | |
|             support.EqualToForwardRef("undefined"),
 | |
|         )
 | |
|         self.assertEqual(
 | |
|             annotationlib.call_evaluate_function(evaluate, Format.STRING),
 | |
|             "undefined",
 | |
|         )
 | |
| 
 | |
| 
 | |
| class MetaclassTests(unittest.TestCase):
 | |
|     def test_annotated_meta(self):
 | |
|         class Meta(type):
 | |
|             a: int
 | |
| 
 | |
|         class X(metaclass=Meta):
 | |
|             pass
 | |
| 
 | |
|         class Y(metaclass=Meta):
 | |
|             b: float
 | |
| 
 | |
|         self.assertEqual(get_annotations(Meta), {"a": int})
 | |
|         self.assertEqual(Meta.__annotate__(Format.VALUE), {"a": int})
 | |
| 
 | |
|         self.assertEqual(get_annotations(X), {})
 | |
|         self.assertIs(X.__annotate__, None)
 | |
| 
 | |
|         self.assertEqual(get_annotations(Y), {"b": float})
 | |
|         self.assertEqual(Y.__annotate__(Format.VALUE), {"b": float})
 | |
| 
 | |
|     def test_unannotated_meta(self):
 | |
|         class Meta(type):
 | |
|             pass
 | |
| 
 | |
|         class X(metaclass=Meta):
 | |
|             a: str
 | |
| 
 | |
|         class Y(X):
 | |
|             pass
 | |
| 
 | |
|         self.assertEqual(get_annotations(Meta), {})
 | |
|         self.assertIs(Meta.__annotate__, None)
 | |
| 
 | |
|         self.assertEqual(get_annotations(Y), {})
 | |
|         self.assertIs(Y.__annotate__, None)
 | |
| 
 | |
|         self.assertEqual(get_annotations(X), {"a": str})
 | |
|         self.assertEqual(X.__annotate__(Format.VALUE), {"a": str})
 | |
| 
 | |
|     def test_ordering(self):
 | |
|         # Based on a sample by David Ellis
 | |
|         # https://discuss.python.org/t/pep-749-implementing-pep-649/54974/38
 | |
| 
 | |
|         def make_classes():
 | |
|             class Meta(type):
 | |
|                 a: int
 | |
|                 expected_annotations = {"a": int}
 | |
| 
 | |
|             class A(type, metaclass=Meta):
 | |
|                 b: float
 | |
|                 expected_annotations = {"b": float}
 | |
| 
 | |
|             class B(metaclass=A):
 | |
|                 c: str
 | |
|                 expected_annotations = {"c": str}
 | |
| 
 | |
|             class C(B):
 | |
|                 expected_annotations = {}
 | |
| 
 | |
|             class D(metaclass=Meta):
 | |
|                 expected_annotations = {}
 | |
| 
 | |
|             return Meta, A, B, C, D
 | |
| 
 | |
|         classes = make_classes()
 | |
|         class_count = len(classes)
 | |
|         for order in itertools.permutations(range(class_count), class_count):
 | |
|             names = ", ".join(classes[i].__name__ for i in order)
 | |
|             with self.subTest(names=names):
 | |
|                 classes = make_classes()  # Regenerate classes
 | |
|                 for i in order:
 | |
|                     get_annotations(classes[i])
 | |
|                 for c in classes:
 | |
|                     with self.subTest(c=c):
 | |
|                         self.assertEqual(get_annotations(c), c.expected_annotations)
 | |
|                         annotate_func = getattr(c, "__annotate__", None)
 | |
|                         if c.expected_annotations:
 | |
|                             self.assertEqual(
 | |
|                                 annotate_func(Format.VALUE), c.expected_annotations
 | |
|                             )
 | |
|                         else:
 | |
|                             self.assertIs(annotate_func, None)
 | |
| 
 | |
| 
 | |
| class TestGetAnnotateFromClassNamespace(unittest.TestCase):
 | |
|     def test_with_metaclass(self):
 | |
|         class Meta(type):
 | |
|             def __new__(mcls, name, bases, ns):
 | |
|                 annotate = annotationlib.get_annotate_from_class_namespace(ns)
 | |
|                 expected = ns["expected_annotate"]
 | |
|                 with self.subTest(name=name):
 | |
|                     if expected:
 | |
|                         self.assertIsNotNone(annotate)
 | |
|                     else:
 | |
|                         self.assertIsNone(annotate)
 | |
|                 return super().__new__(mcls, name, bases, ns)
 | |
| 
 | |
|         class HasAnnotations(metaclass=Meta):
 | |
|             expected_annotate = True
 | |
|             a: int
 | |
| 
 | |
|         class NoAnnotations(metaclass=Meta):
 | |
|             expected_annotate = False
 | |
| 
 | |
|         class CustomAnnotate(metaclass=Meta):
 | |
|             expected_annotate = True
 | |
|             def __annotate__(format):
 | |
|                 return {}
 | |
| 
 | |
|         code = """
 | |
|             from __future__ import annotations
 | |
| 
 | |
|             class HasFutureAnnotations(metaclass=Meta):
 | |
|                 expected_annotate = False
 | |
|                 a: int
 | |
|         """
 | |
|         exec(textwrap.dedent(code), {"Meta": Meta})
 | |
| 
 | |
| 
 | |
| class TestTypeRepr(unittest.TestCase):
 | |
|     def test_type_repr(self):
 | |
|         class Nested:
 | |
|             pass
 | |
| 
 | |
|         def nested():
 | |
|             pass
 | |
| 
 | |
|         self.assertEqual(type_repr(int), "int")
 | |
|         self.assertEqual(type_repr(MyClass), f"{__name__}.MyClass")
 | |
|         self.assertEqual(
 | |
|             type_repr(Nested), f"{__name__}.TestTypeRepr.test_type_repr.<locals>.Nested"
 | |
|         )
 | |
|         self.assertEqual(
 | |
|             type_repr(nested), f"{__name__}.TestTypeRepr.test_type_repr.<locals>.nested"
 | |
|         )
 | |
|         self.assertEqual(type_repr(len), "len")
 | |
|         self.assertEqual(type_repr(type_repr), "annotationlib.type_repr")
 | |
|         self.assertEqual(type_repr(times_three), f"{__name__}.times_three")
 | |
|         self.assertEqual(type_repr(...), "...")
 | |
|         self.assertEqual(type_repr(None), "None")
 | |
|         self.assertEqual(type_repr(1), "1")
 | |
|         self.assertEqual(type_repr("1"), "'1'")
 | |
|         self.assertEqual(type_repr(Format.VALUE), repr(Format.VALUE))
 | |
|         self.assertEqual(type_repr(MyClass()), "my repr")
 | |
| 
 | |
| 
 | |
| class TestAnnotationsToString(unittest.TestCase):
 | |
|     def test_annotations_to_string(self):
 | |
|         self.assertEqual(annotations_to_string({}), {})
 | |
|         self.assertEqual(annotations_to_string({"x": int}), {"x": "int"})
 | |
|         self.assertEqual(annotations_to_string({"x": "int"}), {"x": "int"})
 | |
|         self.assertEqual(
 | |
|             annotations_to_string({"x": int, "y": str}), {"x": "int", "y": "str"}
 | |
|         )
 | |
| 
 | |
| 
 | |
| class A:
 | |
|     pass
 | |
| 
 | |
| 
 | |
| class TestForwardRefClass(unittest.TestCase):
 | |
|     def test_forwardref_instance_type_error(self):
 | |
|         fr = ForwardRef("int")
 | |
|         with self.assertRaises(TypeError):
 | |
|             isinstance(42, fr)
 | |
| 
 | |
|     def test_forwardref_subclass_type_error(self):
 | |
|         fr = ForwardRef("int")
 | |
|         with self.assertRaises(TypeError):
 | |
|             issubclass(int, fr)
 | |
| 
 | |
|     def test_forwardref_only_str_arg(self):
 | |
|         with self.assertRaises(TypeError):
 | |
|             ForwardRef(1)  # only `str` type is allowed
 | |
| 
 | |
|     def test_forward_equality(self):
 | |
|         fr = ForwardRef("int")
 | |
|         self.assertEqual(fr, ForwardRef("int"))
 | |
|         self.assertNotEqual(List["int"], List[int])
 | |
|         self.assertNotEqual(fr, ForwardRef("int", module=__name__))
 | |
|         frm = ForwardRef("int", module=__name__)
 | |
|         self.assertEqual(frm, ForwardRef("int", module=__name__))
 | |
|         self.assertNotEqual(frm, ForwardRef("int", module="__other_name__"))
 | |
| 
 | |
|     def test_forward_equality_get_type_hints(self):
 | |
|         c1 = ForwardRef("C")
 | |
|         c1_gth = ForwardRef("C")
 | |
|         c2 = ForwardRef("C")
 | |
|         c2_gth = ForwardRef("C")
 | |
| 
 | |
|         class C:
 | |
|             pass
 | |
| 
 | |
|         def foo(a: c1_gth, b: c2_gth):
 | |
|             pass
 | |
| 
 | |
|         self.assertEqual(get_type_hints(foo, globals(), locals()), {"a": C, "b": C})
 | |
|         self.assertEqual(c1, c2)
 | |
|         self.assertEqual(c1, c1_gth)
 | |
|         self.assertEqual(c1_gth, c2_gth)
 | |
|         self.assertEqual(List[c1], List[c1_gth])
 | |
|         self.assertNotEqual(List[c1], List[C])
 | |
|         self.assertNotEqual(List[c1_gth], List[C])
 | |
|         self.assertEqual(Union[c1, c1_gth], Union[c1])
 | |
|         self.assertEqual(Union[c1, c1_gth, int], Union[c1, int])
 | |
| 
 | |
|     def test_forward_equality_hash(self):
 | |
|         c1 = ForwardRef("int")
 | |
|         c1_gth = ForwardRef("int")
 | |
|         c2 = ForwardRef("int")
 | |
|         c2_gth = ForwardRef("int")
 | |
| 
 | |
|         def foo(a: c1_gth, b: c2_gth):
 | |
|             pass
 | |
| 
 | |
|         get_type_hints(foo, globals(), locals())
 | |
| 
 | |
|         self.assertEqual(hash(c1), hash(c2))
 | |
|         self.assertEqual(hash(c1_gth), hash(c2_gth))
 | |
|         self.assertEqual(hash(c1), hash(c1_gth))
 | |
| 
 | |
|         c3 = ForwardRef("int", module=__name__)
 | |
|         c4 = ForwardRef("int", module="__other_name__")
 | |
| 
 | |
|         self.assertNotEqual(hash(c3), hash(c1))
 | |
|         self.assertNotEqual(hash(c3), hash(c1_gth))
 | |
|         self.assertNotEqual(hash(c3), hash(c4))
 | |
|         self.assertEqual(hash(c3), hash(ForwardRef("int", module=__name__)))
 | |
| 
 | |
|     def test_forward_equality_namespace(self):
 | |
|         def namespace1():
 | |
|             a = ForwardRef("A")
 | |
| 
 | |
|             def fun(x: a):
 | |
|                 pass
 | |
| 
 | |
|             get_type_hints(fun, globals(), locals())
 | |
|             return a
 | |
| 
 | |
|         def namespace2():
 | |
|             a = ForwardRef("A")
 | |
| 
 | |
|             class A:
 | |
|                 pass
 | |
| 
 | |
|             def fun(x: a):
 | |
|                 pass
 | |
| 
 | |
|             get_type_hints(fun, globals(), locals())
 | |
|             return a
 | |
| 
 | |
|         self.assertEqual(namespace1(), namespace1())
 | |
|         self.assertEqual(namespace1(), namespace2())
 | |
| 
 | |
|     def test_forward_repr(self):
 | |
|         self.assertEqual(repr(List["int"]), "typing.List[ForwardRef('int')]")
 | |
|         self.assertEqual(
 | |
|             repr(List[ForwardRef("int", module="mod")]),
 | |
|             "typing.List[ForwardRef('int', module='mod')]",
 | |
|         )
 | |
| 
 | |
|     def test_forward_recursion_actually(self):
 | |
|         def namespace1():
 | |
|             a = ForwardRef("A")
 | |
|             A = a
 | |
| 
 | |
|             def fun(x: a):
 | |
|                 pass
 | |
| 
 | |
|             ret = get_type_hints(fun, globals(), locals())
 | |
|             return a
 | |
| 
 | |
|         def namespace2():
 | |
|             a = ForwardRef("A")
 | |
|             A = a
 | |
| 
 | |
|             def fun(x: a):
 | |
|                 pass
 | |
| 
 | |
|             ret = get_type_hints(fun, globals(), locals())
 | |
|             return a
 | |
| 
 | |
|         r1 = namespace1()
 | |
|         r2 = namespace2()
 | |
|         self.assertIsNot(r1, r2)
 | |
|         self.assertEqual(r1, r2)
 | |
| 
 | |
|     def test_syntax_error(self):
 | |
| 
 | |
|         with self.assertRaises(SyntaxError):
 | |
|             typing.Generic["/T"]
 | |
| 
 | |
|     def test_delayed_syntax_error(self):
 | |
| 
 | |
|         def foo(a: "Node[T"):
 | |
|             pass
 | |
| 
 | |
|         with self.assertRaises(SyntaxError):
 | |
|             get_type_hints(foo)
 | |
| 
 | |
|     def test_syntax_error_empty_string(self):
 | |
|         for form in [typing.List, typing.Set, typing.Type, typing.Deque]:
 | |
|             with self.subTest(form=form):
 | |
|                 with self.assertRaises(SyntaxError):
 | |
|                     form[""]
 | |
| 
 | |
|     def test_or(self):
 | |
|         X = ForwardRef("X")
 | |
|         # __or__/__ror__ itself
 | |
|         self.assertEqual(X | "x", Union[X, "x"])
 | |
|         self.assertEqual("x" | X, Union["x", X])
 | |
| 
 | |
|     def test_multiple_ways_to_create(self):
 | |
|         X1 = Union["X"]
 | |
|         self.assertIsInstance(X1, ForwardRef)
 | |
|         X2 = ForwardRef("X")
 | |
|         self.assertIsInstance(X2, ForwardRef)
 | |
|         self.assertEqual(X1, X2)
 | |
| 
 | |
|     def test_special_attrs(self):
 | |
|         # Forward refs provide a different introspection API. __name__ and
 | |
|         # __qualname__ make little sense for forward refs as they can store
 | |
|         # complex typing expressions.
 | |
|         fr = ForwardRef("set[Any]")
 | |
|         self.assertNotHasAttr(fr, "__name__")
 | |
|         self.assertNotHasAttr(fr, "__qualname__")
 | |
|         self.assertEqual(fr.__module__, "annotationlib")
 | |
|         # Forward refs are currently unpicklable once they contain a code object.
 | |
|         fr.__forward_code__  # fill the cache
 | |
|         for proto in range(pickle.HIGHEST_PROTOCOL + 1):
 | |
|             with self.assertRaises(TypeError):
 | |
|                 pickle.dumps(fr, proto)
 | |
| 
 | |
|     def test_evaluate_string_format(self):
 | |
|         fr = ForwardRef("set[Any]")
 | |
|         self.assertEqual(fr.evaluate(format=Format.STRING), "set[Any]")
 | |
| 
 | |
|     def test_evaluate_forwardref_format(self):
 | |
|         fr = ForwardRef("undef")
 | |
|         evaluated = fr.evaluate(format=Format.FORWARDREF)
 | |
|         self.assertIs(fr, evaluated)
 | |
| 
 | |
|         fr = ForwardRef("set[undefined]")
 | |
|         evaluated = fr.evaluate(format=Format.FORWARDREF)
 | |
|         self.assertEqual(
 | |
|             evaluated,
 | |
|             set[support.EqualToForwardRef("undefined")],
 | |
|         )
 | |
| 
 | |
|         fr = ForwardRef("a + b")
 | |
|         self.assertEqual(
 | |
|             fr.evaluate(format=Format.FORWARDREF),
 | |
|             support.EqualToForwardRef("a + b"),
 | |
|         )
 | |
|         self.assertEqual(
 | |
|             fr.evaluate(format=Format.FORWARDREF, locals={"a": 1, "b": 2}),
 | |
|             3,
 | |
|         )
 | |
| 
 | |
|         fr = ForwardRef('"a" + 1')
 | |
|         self.assertEqual(
 | |
|             fr.evaluate(format=Format.FORWARDREF),
 | |
|             support.EqualToForwardRef('"a" + 1'),
 | |
|         )
 | |
| 
 | |
|     def test_evaluate_with_type_params(self):
 | |
|         class Gen[T]:
 | |
|             alias = int
 | |
| 
 | |
|         with self.assertRaises(NameError):
 | |
|             ForwardRef("T").evaluate()
 | |
|         with self.assertRaises(NameError):
 | |
|             ForwardRef("T").evaluate(type_params=())
 | |
|         with self.assertRaises(NameError):
 | |
|             ForwardRef("T").evaluate(owner=int)
 | |
| 
 | |
|         (T,) = Gen.__type_params__
 | |
|         self.assertIs(ForwardRef("T").evaluate(type_params=Gen.__type_params__), T)
 | |
|         self.assertIs(ForwardRef("T").evaluate(owner=Gen), T)
 | |
| 
 | |
|         with self.assertRaises(NameError):
 | |
|             ForwardRef("alias").evaluate(type_params=Gen.__type_params__)
 | |
|         self.assertIs(ForwardRef("alias").evaluate(owner=Gen), int)
 | |
|         # If you pass custom locals, we don't look at the owner's locals
 | |
|         with self.assertRaises(NameError):
 | |
|             ForwardRef("alias").evaluate(owner=Gen, locals={})
 | |
|         # But if the name exists in the locals, it works
 | |
|         self.assertIs(
 | |
|             ForwardRef("alias").evaluate(owner=Gen, locals={"alias": str}), str
 | |
|         )
 | |
| 
 | |
|     def test_fwdref_with_module(self):
 | |
|         self.assertIs(ForwardRef("Format", module="annotationlib").evaluate(), Format)
 | |
|         self.assertIs(
 | |
|             ForwardRef("Counter", module="collections").evaluate(), collections.Counter
 | |
|         )
 | |
|         self.assertEqual(
 | |
|             ForwardRef("Counter[int]", module="collections").evaluate(),
 | |
|             collections.Counter[int],
 | |
|         )
 | |
| 
 | |
|         with self.assertRaises(NameError):
 | |
|             # If globals are passed explicitly, we don't look at the module dict
 | |
|             ForwardRef("Format", module="annotationlib").evaluate(globals={})
 | |
| 
 | |
|     def test_fwdref_to_builtin(self):
 | |
|         self.assertIs(ForwardRef("int").evaluate(), int)
 | |
|         self.assertIs(ForwardRef("int", module="collections").evaluate(), int)
 | |
|         self.assertIs(ForwardRef("int", owner=str).evaluate(), int)
 | |
| 
 | |
|         # builtins are still searched with explicit globals
 | |
|         self.assertIs(ForwardRef("int").evaluate(globals={}), int)
 | |
| 
 | |
|         # explicit values in globals have precedence
 | |
|         obj = object()
 | |
|         self.assertIs(ForwardRef("int").evaluate(globals={"int": obj}), obj)
 | |
| 
 | |
|     def test_fwdref_value_is_not_cached(self):
 | |
|         fr = ForwardRef("hello")
 | |
|         with self.assertRaises(NameError):
 | |
|             fr.evaluate()
 | |
|         self.assertIs(fr.evaluate(globals={"hello": str}), str)
 | |
|         with self.assertRaises(NameError):
 | |
|             fr.evaluate()
 | |
| 
 | |
|     def test_fwdref_with_owner(self):
 | |
|         self.assertEqual(
 | |
|             ForwardRef("Counter[int]", owner=collections).evaluate(),
 | |
|             collections.Counter[int],
 | |
|         )
 | |
| 
 | |
|     def test_name_lookup_without_eval(self):
 | |
|         # test the codepath where we look up simple names directly in the
 | |
|         # namespaces without going through eval()
 | |
|         self.assertIs(ForwardRef("int").evaluate(), int)
 | |
|         self.assertIs(ForwardRef("int").evaluate(locals={"int": str}), str)
 | |
|         self.assertIs(
 | |
|             ForwardRef("int").evaluate(locals={"int": float}, globals={"int": str}),
 | |
|             float,
 | |
|         )
 | |
|         self.assertIs(ForwardRef("int").evaluate(globals={"int": str}), str)
 | |
|         with support.swap_attr(builtins, "int", dict):
 | |
|             self.assertIs(ForwardRef("int").evaluate(), dict)
 | |
| 
 | |
|         with self.assertRaises(NameError):
 | |
|             ForwardRef("doesntexist").evaluate()
 | |
| 
 | |
|     def test_fwdref_invalid_syntax(self):
 | |
|         fr = ForwardRef("if")
 | |
|         with self.assertRaises(SyntaxError):
 | |
|             fr.evaluate()
 | |
|         fr = ForwardRef("1+")
 | |
|         with self.assertRaises(SyntaxError):
 | |
|             fr.evaluate()
 | |
| 
 | |
| 
 | |
| class TestAnnotationLib(unittest.TestCase):
 | |
|     def test__all__(self):
 | |
|         support.check__all__(self, annotationlib)
 | |
| 
 | |
|     @support.cpython_only
 | |
|     def test_lazy_imports(self):
 | |
|         import_helper.ensure_lazy_imports(
 | |
|             "annotationlib",
 | |
|             {
 | |
|                 "typing",
 | |
|                 "warnings",
 | |
|             },
 | |
|         )
 |