GH-103629: Update Unpack's repr in compliance with PEP 692 (#104048)

This commit is contained in:
Franek Magiera 2023-05-01 19:58:50 +02:00 committed by GitHub
parent a679c3d58d
commit 2d526cd32f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 45 additions and 28 deletions

View file

@ -880,6 +880,11 @@ class UnpackTests(BaseTestCase):
with self.assertRaises(TypeError): with self.assertRaises(TypeError):
Unpack() Unpack()
def test_usage_with_kwargs(self):
Movie = TypedDict('Movie', {'name': str, 'year': int})
def foo(**kwargs: Unpack[Movie]): ...
self.assertEqual(repr(foo.__annotations__['kwargs']),
f"typing.Unpack[{__name__}.Movie]")
class TypeVarTupleTests(BaseTestCase): class TypeVarTupleTests(BaseTestCase):
@ -1050,14 +1055,14 @@ class TypeVarTupleTests(BaseTestCase):
self.assertEqual(repr(Ts), 'Ts') self.assertEqual(repr(Ts), 'Ts')
self.assertEqual(repr((*Ts,)[0]), '*Ts') self.assertEqual(repr((*Ts,)[0]), 'typing.Unpack[Ts]')
self.assertEqual(repr(Unpack[Ts]), '*Ts') self.assertEqual(repr(Unpack[Ts]), 'typing.Unpack[Ts]')
self.assertEqual(repr(tuple[*Ts]), 'tuple[*Ts]') self.assertEqual(repr(tuple[*Ts]), 'tuple[typing.Unpack[Ts]]')
self.assertEqual(repr(Tuple[Unpack[Ts]]), 'typing.Tuple[*Ts]') self.assertEqual(repr(Tuple[Unpack[Ts]]), 'typing.Tuple[typing.Unpack[Ts]]')
self.assertEqual(repr(*tuple[*Ts]), '*tuple[*Ts]') self.assertEqual(repr(*tuple[*Ts]), '*tuple[typing.Unpack[Ts]]')
self.assertEqual(repr(Unpack[Tuple[Unpack[Ts]]]), '*typing.Tuple[*Ts]') self.assertEqual(repr(Unpack[Tuple[Unpack[Ts]]]), 'typing.Unpack[typing.Tuple[typing.Unpack[Ts]]]')
def test_variadic_class_repr_is_correct(self): def test_variadic_class_repr_is_correct(self):
Ts = TypeVarTuple('Ts') Ts = TypeVarTuple('Ts')
@ -1074,86 +1079,86 @@ class TypeVarTupleTests(BaseTestCase):
self.assertEndsWith(repr(A[*tuple[int, ...]]), self.assertEndsWith(repr(A[*tuple[int, ...]]),
'A[*tuple[int, ...]]') 'A[*tuple[int, ...]]')
self.assertEndsWith(repr(B[Unpack[Tuple[int, ...]]]), self.assertEndsWith(repr(B[Unpack[Tuple[int, ...]]]),
'B[*typing.Tuple[int, ...]]') 'B[typing.Unpack[typing.Tuple[int, ...]]]')
self.assertEndsWith(repr(A[float, *tuple[int, ...]]), self.assertEndsWith(repr(A[float, *tuple[int, ...]]),
'A[float, *tuple[int, ...]]') 'A[float, *tuple[int, ...]]')
self.assertEndsWith(repr(A[float, Unpack[Tuple[int, ...]]]), self.assertEndsWith(repr(A[float, Unpack[Tuple[int, ...]]]),
'A[float, *typing.Tuple[int, ...]]') 'A[float, typing.Unpack[typing.Tuple[int, ...]]]')
self.assertEndsWith(repr(A[*tuple[int, ...], str]), self.assertEndsWith(repr(A[*tuple[int, ...], str]),
'A[*tuple[int, ...], str]') 'A[*tuple[int, ...], str]')
self.assertEndsWith(repr(B[Unpack[Tuple[int, ...]], str]), self.assertEndsWith(repr(B[Unpack[Tuple[int, ...]], str]),
'B[*typing.Tuple[int, ...], str]') 'B[typing.Unpack[typing.Tuple[int, ...]], str]')
self.assertEndsWith(repr(A[float, *tuple[int, ...], str]), self.assertEndsWith(repr(A[float, *tuple[int, ...], str]),
'A[float, *tuple[int, ...], str]') 'A[float, *tuple[int, ...], str]')
self.assertEndsWith(repr(B[float, Unpack[Tuple[int, ...]], str]), self.assertEndsWith(repr(B[float, Unpack[Tuple[int, ...]], str]),
'B[float, *typing.Tuple[int, ...], str]') 'B[float, typing.Unpack[typing.Tuple[int, ...]], str]')
def test_variadic_class_alias_repr_is_correct(self): def test_variadic_class_alias_repr_is_correct(self):
Ts = TypeVarTuple('Ts') Ts = TypeVarTuple('Ts')
class A(Generic[Unpack[Ts]]): pass class A(Generic[Unpack[Ts]]): pass
B = A[*Ts] B = A[*Ts]
self.assertEndsWith(repr(B), 'A[*Ts]') self.assertEndsWith(repr(B), 'A[typing.Unpack[Ts]]')
self.assertEndsWith(repr(B[()]), 'A[()]') self.assertEndsWith(repr(B[()]), 'A[()]')
self.assertEndsWith(repr(B[float]), 'A[float]') self.assertEndsWith(repr(B[float]), 'A[float]')
self.assertEndsWith(repr(B[float, str]), 'A[float, str]') self.assertEndsWith(repr(B[float, str]), 'A[float, str]')
C = A[Unpack[Ts]] C = A[Unpack[Ts]]
self.assertEndsWith(repr(C), 'A[*Ts]') self.assertEndsWith(repr(C), 'A[typing.Unpack[Ts]]')
self.assertEndsWith(repr(C[()]), 'A[()]') self.assertEndsWith(repr(C[()]), 'A[()]')
self.assertEndsWith(repr(C[float]), 'A[float]') self.assertEndsWith(repr(C[float]), 'A[float]')
self.assertEndsWith(repr(C[float, str]), 'A[float, str]') self.assertEndsWith(repr(C[float, str]), 'A[float, str]')
D = A[*Ts, int] D = A[*Ts, int]
self.assertEndsWith(repr(D), 'A[*Ts, int]') self.assertEndsWith(repr(D), 'A[typing.Unpack[Ts], int]')
self.assertEndsWith(repr(D[()]), 'A[int]') self.assertEndsWith(repr(D[()]), 'A[int]')
self.assertEndsWith(repr(D[float]), 'A[float, int]') self.assertEndsWith(repr(D[float]), 'A[float, int]')
self.assertEndsWith(repr(D[float, str]), 'A[float, str, int]') self.assertEndsWith(repr(D[float, str]), 'A[float, str, int]')
E = A[Unpack[Ts], int] E = A[Unpack[Ts], int]
self.assertEndsWith(repr(E), 'A[*Ts, int]') self.assertEndsWith(repr(E), 'A[typing.Unpack[Ts], int]')
self.assertEndsWith(repr(E[()]), 'A[int]') self.assertEndsWith(repr(E[()]), 'A[int]')
self.assertEndsWith(repr(E[float]), 'A[float, int]') self.assertEndsWith(repr(E[float]), 'A[float, int]')
self.assertEndsWith(repr(E[float, str]), 'A[float, str, int]') self.assertEndsWith(repr(E[float, str]), 'A[float, str, int]')
F = A[int, *Ts] F = A[int, *Ts]
self.assertEndsWith(repr(F), 'A[int, *Ts]') self.assertEndsWith(repr(F), 'A[int, typing.Unpack[Ts]]')
self.assertEndsWith(repr(F[()]), 'A[int]') self.assertEndsWith(repr(F[()]), 'A[int]')
self.assertEndsWith(repr(F[float]), 'A[int, float]') self.assertEndsWith(repr(F[float]), 'A[int, float]')
self.assertEndsWith(repr(F[float, str]), 'A[int, float, str]') self.assertEndsWith(repr(F[float, str]), 'A[int, float, str]')
G = A[int, Unpack[Ts]] G = A[int, Unpack[Ts]]
self.assertEndsWith(repr(G), 'A[int, *Ts]') self.assertEndsWith(repr(G), 'A[int, typing.Unpack[Ts]]')
self.assertEndsWith(repr(G[()]), 'A[int]') self.assertEndsWith(repr(G[()]), 'A[int]')
self.assertEndsWith(repr(G[float]), 'A[int, float]') self.assertEndsWith(repr(G[float]), 'A[int, float]')
self.assertEndsWith(repr(G[float, str]), 'A[int, float, str]') self.assertEndsWith(repr(G[float, str]), 'A[int, float, str]')
H = A[int, *Ts, str] H = A[int, *Ts, str]
self.assertEndsWith(repr(H), 'A[int, *Ts, str]') self.assertEndsWith(repr(H), 'A[int, typing.Unpack[Ts], str]')
self.assertEndsWith(repr(H[()]), 'A[int, str]') self.assertEndsWith(repr(H[()]), 'A[int, str]')
self.assertEndsWith(repr(H[float]), 'A[int, float, str]') self.assertEndsWith(repr(H[float]), 'A[int, float, str]')
self.assertEndsWith(repr(H[float, str]), 'A[int, float, str, str]') self.assertEndsWith(repr(H[float, str]), 'A[int, float, str, str]')
I = A[int, Unpack[Ts], str] I = A[int, Unpack[Ts], str]
self.assertEndsWith(repr(I), 'A[int, *Ts, str]') self.assertEndsWith(repr(I), 'A[int, typing.Unpack[Ts], str]')
self.assertEndsWith(repr(I[()]), 'A[int, str]') self.assertEndsWith(repr(I[()]), 'A[int, str]')
self.assertEndsWith(repr(I[float]), 'A[int, float, str]') self.assertEndsWith(repr(I[float]), 'A[int, float, str]')
self.assertEndsWith(repr(I[float, str]), 'A[int, float, str, str]') self.assertEndsWith(repr(I[float, str]), 'A[int, float, str, str]')
J = A[*Ts, *tuple[str, ...]] J = A[*Ts, *tuple[str, ...]]
self.assertEndsWith(repr(J), 'A[*Ts, *tuple[str, ...]]') self.assertEndsWith(repr(J), 'A[typing.Unpack[Ts], *tuple[str, ...]]')
self.assertEndsWith(repr(J[()]), 'A[*tuple[str, ...]]') self.assertEndsWith(repr(J[()]), 'A[*tuple[str, ...]]')
self.assertEndsWith(repr(J[float]), 'A[float, *tuple[str, ...]]') self.assertEndsWith(repr(J[float]), 'A[float, *tuple[str, ...]]')
self.assertEndsWith(repr(J[float, str]), 'A[float, str, *tuple[str, ...]]') self.assertEndsWith(repr(J[float, str]), 'A[float, str, *tuple[str, ...]]')
K = A[Unpack[Ts], Unpack[Tuple[str, ...]]] K = A[Unpack[Ts], Unpack[Tuple[str, ...]]]
self.assertEndsWith(repr(K), 'A[*Ts, *typing.Tuple[str, ...]]') self.assertEndsWith(repr(K), 'A[typing.Unpack[Ts], typing.Unpack[typing.Tuple[str, ...]]]')
self.assertEndsWith(repr(K[()]), 'A[*typing.Tuple[str, ...]]') self.assertEndsWith(repr(K[()]), 'A[typing.Unpack[typing.Tuple[str, ...]]]')
self.assertEndsWith(repr(K[float]), 'A[float, *typing.Tuple[str, ...]]') self.assertEndsWith(repr(K[float]), 'A[float, typing.Unpack[typing.Tuple[str, ...]]]')
self.assertEndsWith(repr(K[float, str]), 'A[float, str, *typing.Tuple[str, ...]]') self.assertEndsWith(repr(K[float, str]), 'A[float, str, typing.Unpack[typing.Tuple[str, ...]]]')
def test_cannot_subclass(self): def test_cannot_subclass(self):
with self.assertRaisesRegex(TypeError, CANNOT_SUBCLASS_TYPE): with self.assertRaisesRegex(TypeError, CANNOT_SUBCLASS_TYPE):
@ -1171,9 +1176,9 @@ class TypeVarTupleTests(BaseTestCase):
with self.assertRaisesRegex(TypeError, with self.assertRaisesRegex(TypeError,
r'Cannot subclass typing\.Unpack'): r'Cannot subclass typing\.Unpack'):
class C(Unpack): pass class C(Unpack): pass
with self.assertRaisesRegex(TypeError, r'Cannot subclass \*Ts'): with self.assertRaisesRegex(TypeError, r'Cannot subclass typing.Unpack\[Ts\]'):
class C(*Ts): pass class C(*Ts): pass
with self.assertRaisesRegex(TypeError, r'Cannot subclass \*Ts'): with self.assertRaisesRegex(TypeError, r'Cannot subclass typing.Unpack\[Ts\]'):
class C(Unpack[Ts]): pass class C(Unpack[Ts]): pass
def test_variadic_class_args_are_correct(self): def test_variadic_class_args_are_correct(self):
@ -4108,13 +4113,13 @@ class GenericTests(BaseTestCase):
MyCallable[[int], bool]: "MyCallable[[int], bool]", MyCallable[[int], bool]: "MyCallable[[int], bool]",
MyCallable[[int, str], bool]: "MyCallable[[int, str], bool]", MyCallable[[int, str], bool]: "MyCallable[[int, str], bool]",
MyCallable[[int, list[int]], bool]: "MyCallable[[int, list[int]], bool]", MyCallable[[int, list[int]], bool]: "MyCallable[[int, list[int]], bool]",
MyCallable[Concatenate[*Ts, P], T]: "MyCallable[typing.Concatenate[*Ts, ~P], ~T]", MyCallable[Concatenate[*Ts, P], T]: "MyCallable[typing.Concatenate[typing.Unpack[Ts], ~P], ~T]",
DoubleSpec[P2, P, T]: "DoubleSpec[~P2, ~P, ~T]", DoubleSpec[P2, P, T]: "DoubleSpec[~P2, ~P, ~T]",
DoubleSpec[[int], [str], bool]: "DoubleSpec[[int], [str], bool]", DoubleSpec[[int], [str], bool]: "DoubleSpec[[int], [str], bool]",
DoubleSpec[[int, int], [str, str], bool]: "DoubleSpec[[int, int], [str, str], bool]", DoubleSpec[[int, int], [str, str], bool]: "DoubleSpec[[int, int], [str, str], bool]",
TsP[*Ts, P]: "TsP[*Ts, ~P]", TsP[*Ts, P]: "TsP[typing.Unpack[Ts], ~P]",
TsP[int, str, list[int], []]: "TsP[int, str, list[int], []]", TsP[int, str, list[int], []]: "TsP[int, str, list[int], []]",
TsP[int, [str, list[int]]]: "TsP[int, [str, list[int]]]", TsP[int, [str, list[int]]]: "TsP[int, [str, list[int]]]",

View file

@ -1753,6 +1753,17 @@ def Unpack(self, parameters):
Foo[*tuple[int, str]] Foo[*tuple[int, str]]
class Bar(Generic[*Ts]): ... class Bar(Generic[*Ts]): ...
The operator can also be used along with a `TypedDict` to annotate
`**kwargs` in a function signature. For instance:
class Movie(TypedDict):
name: str
year: int
# This function expects two keyword arguments - *name* of type `str` and
# *year* of type `int`.
def foo(**kwargs: Unpack[Movie]): ...
Note that there is only some runtime checking of this operator. Not Note that there is only some runtime checking of this operator. Not
everything the runtime allows may be accepted by static type checkers. everything the runtime allows may be accepted by static type checkers.
@ -1767,7 +1778,7 @@ class _UnpackGenericAlias(_GenericAlias, _root=True):
def __repr__(self): def __repr__(self):
# `Unpack` only takes one argument, so __args__ should contain only # `Unpack` only takes one argument, so __args__ should contain only
# a single item. # a single item.
return '*' + repr(self.__args__[0]) return f'typing.Unpack[{_type_repr(self.__args__[0])}]'
def __getitem__(self, args): def __getitem__(self, args):
if self.__typing_is_unpacked_typevartuple__: if self.__typing_is_unpacked_typevartuple__:

View file

@ -0,0 +1 @@
Update the ``repr`` of :class:`typing.Unpack` according to :pep:`692`.