mirror of
https://github.com/python/cpython.git
synced 2025-11-01 10:45:30 +00:00
bpo-39491: Merge PEP 593 (typing.Annotated) support (#18260)
* bpo-39491: Merge PEP 593 (typing.Annotated) support PEP 593 has been accepted some time ago. I got a green light for merging this from Till, so I went ahead and combined the code contributed to typing_extensions[1] and the documentation from the PEP 593 text[2]. My changes were limited to: * removing code designed for typing_extensions to run on older Python versions * removing some irrelevant parts of the PEP text when copying it over as documentation and otherwise changing few small bits to better serve the purpose * changing the get_type_hints signature to match reality (parameter names) I wasn't entirely sure how to go about crediting the authors but I used my best judgment, let me know if something needs changing in this regard. [1]8280de241f/typing_extensions/src_py3/typing_extensions.py[2]17710b8798/pep-0593.rst
This commit is contained in:
parent
89ae20b30e
commit
cf5b109dbb
5 changed files with 467 additions and 6 deletions
|
|
@ -22,6 +22,7 @@ from typing import NewType
|
|||
from typing import NamedTuple, TypedDict
|
||||
from typing import IO, TextIO, BinaryIO
|
||||
from typing import Pattern, Match
|
||||
from typing import Annotated
|
||||
import abc
|
||||
import typing
|
||||
import weakref
|
||||
|
|
@ -2891,6 +2892,64 @@ class GetTypeHintTests(BaseTestCase):
|
|||
self.assertEqual(gth(ForRefExample.func), expects)
|
||||
self.assertEqual(gth(ForRefExample.nested), expects)
|
||||
|
||||
def test_get_type_hints_annotated(self):
|
||||
def foobar(x: List['X']): ...
|
||||
X = Annotated[int, (1, 10)]
|
||||
self.assertEqual(
|
||||
get_type_hints(foobar, globals(), locals()),
|
||||
{'x': List[int]}
|
||||
)
|
||||
self.assertEqual(
|
||||
get_type_hints(foobar, globals(), locals(), include_extras=True),
|
||||
{'x': List[Annotated[int, (1, 10)]]}
|
||||
)
|
||||
BA = Tuple[Annotated[T, (1, 0)], ...]
|
||||
def barfoo(x: BA): ...
|
||||
self.assertEqual(get_type_hints(barfoo, globals(), locals())['x'], Tuple[T, ...])
|
||||
self.assertIs(
|
||||
get_type_hints(barfoo, globals(), locals(), include_extras=True)['x'],
|
||||
BA
|
||||
)
|
||||
def barfoo2(x: typing.Callable[..., Annotated[List[T], "const"]],
|
||||
y: typing.Union[int, Annotated[T, "mutable"]]): ...
|
||||
self.assertEqual(
|
||||
get_type_hints(barfoo2, globals(), locals()),
|
||||
{'x': typing.Callable[..., List[T]], 'y': typing.Union[int, T]}
|
||||
)
|
||||
BA2 = typing.Callable[..., List[T]]
|
||||
def barfoo3(x: BA2): ...
|
||||
self.assertIs(
|
||||
get_type_hints(barfoo3, globals(), locals(), include_extras=True)["x"],
|
||||
BA2
|
||||
)
|
||||
|
||||
def test_get_type_hints_annotated_refs(self):
|
||||
|
||||
Const = Annotated[T, "Const"]
|
||||
|
||||
class MySet(Generic[T]):
|
||||
|
||||
def __ior__(self, other: "Const[MySet[T]]") -> "MySet[T]":
|
||||
...
|
||||
|
||||
def __iand__(self, other: Const["MySet[T]"]) -> "MySet[T]":
|
||||
...
|
||||
|
||||
self.assertEqual(
|
||||
get_type_hints(MySet.__iand__, globals(), locals()),
|
||||
{'other': MySet[T], 'return': MySet[T]}
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
get_type_hints(MySet.__iand__, globals(), locals(), include_extras=True),
|
||||
{'other': Const[MySet[T]], 'return': MySet[T]}
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
get_type_hints(MySet.__ior__, globals(), locals()),
|
||||
{'other': MySet[T], 'return': MySet[T]}
|
||||
)
|
||||
|
||||
|
||||
class GetUtilitiesTestCase(TestCase):
|
||||
def test_get_origin(self):
|
||||
|
|
@ -2906,6 +2965,7 @@ class GetUtilitiesTestCase(TestCase):
|
|||
self.assertIs(get_origin(Generic), Generic)
|
||||
self.assertIs(get_origin(Generic[T]), Generic)
|
||||
self.assertIs(get_origin(List[Tuple[T, T]][int]), list)
|
||||
self.assertIs(get_origin(Annotated[T, 'thing']), Annotated)
|
||||
|
||||
def test_get_args(self):
|
||||
T = TypeVar('T')
|
||||
|
|
@ -2926,6 +2986,7 @@ class GetUtilitiesTestCase(TestCase):
|
|||
(int, Callable[[Tuple[T, ...]], str]))
|
||||
self.assertEqual(get_args(Tuple[int, ...]), (int, ...))
|
||||
self.assertEqual(get_args(Tuple[()]), ((),))
|
||||
self.assertEqual(get_args(Annotated[T, 'one', 2, ['three']]), (T, 'one', 2, ['three']))
|
||||
|
||||
|
||||
class CollectionsAbcTests(BaseTestCase):
|
||||
|
|
@ -3844,6 +3905,179 @@ class RETests(BaseTestCase):
|
|||
"type 're.Match' is not an acceptable base type")
|
||||
|
||||
|
||||
class AnnotatedTests(BaseTestCase):
|
||||
|
||||
def test_repr(self):
|
||||
self.assertEqual(
|
||||
repr(Annotated[int, 4, 5]),
|
||||
"typing.Annotated[int, 4, 5]"
|
||||
)
|
||||
self.assertEqual(
|
||||
repr(Annotated[List[int], 4, 5]),
|
||||
"typing.Annotated[typing.List[int], 4, 5]"
|
||||
)
|
||||
|
||||
def test_flatten(self):
|
||||
A = Annotated[Annotated[int, 4], 5]
|
||||
self.assertEqual(A, Annotated[int, 4, 5])
|
||||
self.assertEqual(A.__metadata__, (4, 5))
|
||||
self.assertEqual(A.__origin__, int)
|
||||
|
||||
def test_specialize(self):
|
||||
L = Annotated[List[T], "my decoration"]
|
||||
LI = Annotated[List[int], "my decoration"]
|
||||
self.assertEqual(L[int], Annotated[List[int], "my decoration"])
|
||||
self.assertEqual(L[int].__metadata__, ("my decoration",))
|
||||
self.assertEqual(L[int].__origin__, List[int])
|
||||
with self.assertRaises(TypeError):
|
||||
LI[int]
|
||||
with self.assertRaises(TypeError):
|
||||
L[int, float]
|
||||
|
||||
def test_hash_eq(self):
|
||||
self.assertEqual(len({Annotated[int, 4, 5], Annotated[int, 4, 5]}), 1)
|
||||
self.assertNotEqual(Annotated[int, 4, 5], Annotated[int, 5, 4])
|
||||
self.assertNotEqual(Annotated[int, 4, 5], Annotated[str, 4, 5])
|
||||
self.assertNotEqual(Annotated[int, 4], Annotated[int, 4, 4])
|
||||
self.assertEqual(
|
||||
{Annotated[int, 4, 5], Annotated[int, 4, 5], Annotated[T, 4, 5]},
|
||||
{Annotated[int, 4, 5], Annotated[T, 4, 5]}
|
||||
)
|
||||
|
||||
def test_instantiate(self):
|
||||
class C:
|
||||
classvar = 4
|
||||
|
||||
def __init__(self, x):
|
||||
self.x = x
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, C):
|
||||
return NotImplemented
|
||||
return other.x == self.x
|
||||
|
||||
A = Annotated[C, "a decoration"]
|
||||
a = A(5)
|
||||
c = C(5)
|
||||
self.assertEqual(a, c)
|
||||
self.assertEqual(a.x, c.x)
|
||||
self.assertEqual(a.classvar, c.classvar)
|
||||
|
||||
def test_instantiate_generic(self):
|
||||
MyCount = Annotated[typing.Counter[T], "my decoration"]
|
||||
self.assertEqual(MyCount([4, 4, 5]), {4: 2, 5: 1})
|
||||
self.assertEqual(MyCount[int]([4, 4, 5]), {4: 2, 5: 1})
|
||||
|
||||
def test_cannot_instantiate_forward(self):
|
||||
A = Annotated["int", (5, 6)]
|
||||
with self.assertRaises(TypeError):
|
||||
A(5)
|
||||
|
||||
def test_cannot_instantiate_type_var(self):
|
||||
A = Annotated[T, (5, 6)]
|
||||
with self.assertRaises(TypeError):
|
||||
A(5)
|
||||
|
||||
def test_cannot_getattr_typevar(self):
|
||||
with self.assertRaises(AttributeError):
|
||||
Annotated[T, (5, 7)].x
|
||||
|
||||
def test_attr_passthrough(self):
|
||||
class C:
|
||||
classvar = 4
|
||||
|
||||
A = Annotated[C, "a decoration"]
|
||||
self.assertEqual(A.classvar, 4)
|
||||
A.x = 5
|
||||
self.assertEqual(C.x, 5)
|
||||
|
||||
def test_hash_eq(self):
|
||||
self.assertEqual(len({Annotated[int, 4, 5], Annotated[int, 4, 5]}), 1)
|
||||
self.assertNotEqual(Annotated[int, 4, 5], Annotated[int, 5, 4])
|
||||
self.assertNotEqual(Annotated[int, 4, 5], Annotated[str, 4, 5])
|
||||
self.assertNotEqual(Annotated[int, 4], Annotated[int, 4, 4])
|
||||
self.assertEqual(
|
||||
{Annotated[int, 4, 5], Annotated[int, 4, 5], Annotated[T, 4, 5]},
|
||||
{Annotated[int, 4, 5], Annotated[T, 4, 5]}
|
||||
)
|
||||
|
||||
def test_cannot_subclass(self):
|
||||
with self.assertRaisesRegex(TypeError, "Cannot subclass .*Annotated"):
|
||||
class C(Annotated):
|
||||
pass
|
||||
|
||||
def test_cannot_check_instance(self):
|
||||
with self.assertRaises(TypeError):
|
||||
isinstance(5, Annotated[int, "positive"])
|
||||
|
||||
def test_cannot_check_subclass(self):
|
||||
with self.assertRaises(TypeError):
|
||||
issubclass(int, Annotated[int, "positive"])
|
||||
|
||||
def test_pickle(self):
|
||||
samples = [typing.Any, typing.Union[int, str],
|
||||
typing.Optional[str], Tuple[int, ...],
|
||||
typing.Callable[[str], bytes]]
|
||||
|
||||
for t in samples:
|
||||
x = Annotated[t, "a"]
|
||||
|
||||
for prot in range(pickle.HIGHEST_PROTOCOL + 1):
|
||||
with self.subTest(protocol=prot, type=t):
|
||||
pickled = pickle.dumps(x, prot)
|
||||
restored = pickle.loads(pickled)
|
||||
self.assertEqual(x, restored)
|
||||
|
||||
global _Annotated_test_G
|
||||
|
||||
class _Annotated_test_G(Generic[T]):
|
||||
x = 1
|
||||
|
||||
G = Annotated[_Annotated_test_G[int], "A decoration"]
|
||||
G.foo = 42
|
||||
G.bar = 'abc'
|
||||
|
||||
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
|
||||
z = pickle.dumps(G, proto)
|
||||
x = pickle.loads(z)
|
||||
self.assertEqual(x.foo, 42)
|
||||
self.assertEqual(x.bar, 'abc')
|
||||
self.assertEqual(x.x, 1)
|
||||
|
||||
def test_subst(self):
|
||||
dec = "a decoration"
|
||||
dec2 = "another decoration"
|
||||
|
||||
S = Annotated[T, dec2]
|
||||
self.assertEqual(S[int], Annotated[int, dec2])
|
||||
|
||||
self.assertEqual(S[Annotated[int, dec]], Annotated[int, dec, dec2])
|
||||
L = Annotated[List[T], dec]
|
||||
|
||||
self.assertEqual(L[int], Annotated[List[int], dec])
|
||||
with self.assertRaises(TypeError):
|
||||
L[int, int]
|
||||
|
||||
self.assertEqual(S[L[int]], Annotated[List[int], dec, dec2])
|
||||
|
||||
D = Annotated[typing.Dict[KT, VT], dec]
|
||||
self.assertEqual(D[str, int], Annotated[typing.Dict[str, int], dec])
|
||||
with self.assertRaises(TypeError):
|
||||
D[int]
|
||||
|
||||
It = Annotated[int, dec]
|
||||
with self.assertRaises(TypeError):
|
||||
It[None]
|
||||
|
||||
LI = L[int]
|
||||
with self.assertRaises(TypeError):
|
||||
LI[None]
|
||||
|
||||
def test_annotated_in_other_types(self):
|
||||
X = List[Annotated[T, 5]]
|
||||
self.assertEqual(X[int], List[Annotated[int, 5]])
|
||||
|
||||
|
||||
class AllTests(BaseTestCase):
|
||||
"""Tests for __all__."""
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue