From 67b187e064adb64f9fabf52093a4ee090850bb45 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 16 Apr 2022 10:58:25 -0700 Subject: [PATCH] Fix tests on Python 3.11 (#1139) - Defer to the PEP 646 implementation in typing.py on 3.11 - Adjust some tests accordingly. Noted a bug in https://github.com/python/cpython/pull/32341#issuecomment-1100466389 - typing._type_check() is more lenient in 3.11 and no longer rejects ints - The representation of the empty tuple type changed Tests pass for me on a 3.11 build from today now. --- typing_extensions/CHANGELOG | 1 + .../src/test_typing_extensions.py | 61 +++++---- typing_extensions/src/typing_extensions.py | 119 +++++++++--------- 3 files changed, 102 insertions(+), 79 deletions(-) diff --git a/typing_extensions/CHANGELOG b/typing_extensions/CHANGELOG index 569fe50c..8900c588 100644 --- a/typing_extensions/CHANGELOG +++ b/typing_extensions/CHANGELOG @@ -1,5 +1,6 @@ # Unreleased +- Re-export `typing.Unpack` and `typing.TypeVarTuple` on Python 3.11. - Add `ParamSpecArgs` and `ParamSpecKwargs` to `__all__`. - Improve "accepts only single type" error messages. - Improve the distributed package. Patch by Marc Mueller (@cdce8p). diff --git a/typing_extensions/src/test_typing_extensions.py b/typing_extensions/src/test_typing_extensions.py index 8e11eb67..7f14f3f9 100644 --- a/typing_extensions/src/test_typing_extensions.py +++ b/typing_extensions/src/test_typing_extensions.py @@ -32,6 +32,8 @@ # version of the typing module. TYPING_3_8_0 = sys.version_info[:3] >= (3, 8, 0) TYPING_3_10_0 = sys.version_info[:3] >= (3, 10, 0) + +# 3.11 makes runtime type checks (_type_check) more lenient. TYPING_3_11_0 = sys.version_info[:3] >= (3, 11, 0) @@ -157,8 +159,9 @@ def test_exception(self): class ClassVarTests(BaseTestCase): def test_basics(self): - with self.assertRaises(TypeError): - ClassVar[1] + if not TYPING_3_11_0: + with self.assertRaises(TypeError): + ClassVar[1] with self.assertRaises(TypeError): ClassVar[int, str] with self.assertRaises(TypeError): @@ -201,8 +204,9 @@ def test_no_isinstance(self): class FinalTests(BaseTestCase): def test_basics(self): - with self.assertRaises(TypeError): - Final[1] + if not TYPING_3_11_0: + with self.assertRaises(TypeError): + Final[1] with self.assertRaises(TypeError): Final[int, str] with self.assertRaises(TypeError): @@ -245,8 +249,9 @@ def test_no_isinstance(self): class RequiredTests(BaseTestCase): def test_basics(self): - with self.assertRaises(TypeError): - Required[1] + if not TYPING_3_11_0: + with self.assertRaises(TypeError): + Required[1] with self.assertRaises(TypeError): Required[int, str] with self.assertRaises(TypeError): @@ -289,8 +294,9 @@ def test_no_isinstance(self): class NotRequiredTests(BaseTestCase): def test_basics(self): - with self.assertRaises(TypeError): - NotRequired[1] + if not TYPING_3_11_0: + with self.assertRaises(TypeError): + NotRequired[1] with self.assertRaises(TypeError): NotRequired[int, str] with self.assertRaises(TypeError): @@ -738,7 +744,10 @@ class C(Generic[T]): pass self.assertEqual(get_args(Union[int, Callable[[Tuple[T, ...]], str]]), (int, Callable[[Tuple[T, ...]], str])) self.assertEqual(get_args(Tuple[int, ...]), (int, ...)) - self.assertEqual(get_args(Tuple[()]), ((),)) + if TYPING_3_11_0: + self.assertEqual(get_args(Tuple[()]), ()) + else: + self.assertEqual(get_args(Tuple[()]), ((),)) self.assertEqual(get_args(Annotated[T, 'one', 2, ['three']]), (T, 'one', 2, ['three'])) self.assertEqual(get_args(List), ()) self.assertEqual(get_args(Tuple), ()) @@ -1731,10 +1740,12 @@ def test_typeddict_errors(self): isinstance(jim, Emp) with self.assertRaises(TypeError): issubclass(dict, Emp) - with self.assertRaises(TypeError): - TypedDict('Hi', x=1) - with self.assertRaises(TypeError): - TypedDict('Hi', [('x', int), ('y', 1)]) + + if not TYPING_3_11_0: + with self.assertRaises(TypeError): + TypedDict('Hi', x=1) + with self.assertRaises(TypeError): + TypedDict('Hi', [('x', int), ('y', 1)]) with self.assertRaises(TypeError): TypedDict('Hi', [('x', int)], y=int) @@ -2313,11 +2324,12 @@ def test_invalid_uses(self): ): Concatenate[P, T] - with self.assertRaisesRegex( - TypeError, - 'each arg must be a type', - ): - Concatenate[1, P] + if not TYPING_3_11_0: + with self.assertRaisesRegex( + TypeError, + 'each arg must be a type', + ): + Concatenate[1, P] def test_basic_introspection(self): P = ParamSpec('P') @@ -2497,7 +2509,10 @@ def test_basic_plain(self): def test_repr(self): Ts = TypeVarTuple('Ts') - self.assertEqual(repr(Unpack[Ts]), 'typing_extensions.Unpack[Ts]') + if TYPING_3_11_0: + self.assertEqual(repr(Unpack[Ts]), '*Ts') + else: + self.assertEqual(repr(Unpack[Ts]), 'typing_extensions.Unpack[Ts]') def test_cannot_subclass_vars(self): with self.assertRaises(TypeError): @@ -2572,8 +2587,10 @@ class C(Generic[T1, T2, Unpack[Ts]]): pass self.assertEqual(C[int, str].__args__, (int, str)) self.assertEqual(C[int, str, float].__args__, (int, str, float)) self.assertEqual(C[int, str, float, bool].__args__, (int, str, float, bool)) - with self.assertRaises(TypeError): - C[int] + # TODO This should probably also fail on 3.11, pending changes to CPython. + if not TYPING_3_11_0: + with self.assertRaises(TypeError): + C[int] class TypeVarTupleTests(BaseTestCase): @@ -2617,7 +2634,7 @@ def test_args_and_parameters(self): Ts = TypeVarTuple('Ts') t = Tuple[tuple(Ts)] - self.assertEqual(t.__args__, (Ts.__unpacked__,)) + self.assertEqual(t.__args__, (Unpack[Ts],)) self.assertEqual(t.__parameters__, (Ts,)) diff --git a/typing_extensions/src/typing_extensions.py b/typing_extensions/src/typing_extensions.py index 1e3e1282..dc038819 100644 --- a/typing_extensions/src/typing_extensions.py +++ b/typing_extensions/src/typing_extensions.py @@ -1673,7 +1673,9 @@ class Movie(TypedDict): """) -if sys.version_info[:2] >= (3, 9): +if hasattr(typing, "Unpack"): # 3.11+ + Unpack = typing.Unpack +elif sys.version_info[:2] >= (3, 9): class _UnpackSpecialForm(typing._SpecialForm, _root=True): def __repr__(self): return 'typing_extensions.' + self._name @@ -1729,84 +1731,87 @@ def _is_unpack(obj): return isinstance(obj, _UnpackAlias) -class TypeVarTuple: - """Type variable tuple. +if hasattr(typing, "TypeVarTuple"): # 3.11+ + TypeVarTuple = typing.TypeVarTuple +else: + class TypeVarTuple: + """Type variable tuple. - Usage:: + Usage:: - Ts = TypeVarTuple('Ts') + Ts = TypeVarTuple('Ts') - In the same way that a normal type variable is a stand-in for a single - type such as ``int``, a type variable *tuple* is a stand-in for a *tuple* type such as - ``Tuple[int, str]``. + In the same way that a normal type variable is a stand-in for a single + type such as ``int``, a type variable *tuple* is a stand-in for a *tuple* + type such as ``Tuple[int, str]``. - Type variable tuples can be used in ``Generic`` declarations. - Consider the following example:: + Type variable tuples can be used in ``Generic`` declarations. + Consider the following example:: - class Array(Generic[*Ts]): ... + class Array(Generic[*Ts]): ... - The ``Ts`` type variable tuple here behaves like ``tuple[T1, T2]``, - where ``T1`` and ``T2`` are type variables. To use these type variables - as type parameters of ``Array``, we must *unpack* the type variable tuple using - the star operator: ``*Ts``. The signature of ``Array`` then behaves - as if we had simply written ``class Array(Generic[T1, T2]): ...``. - In contrast to ``Generic[T1, T2]``, however, ``Generic[*Shape]`` allows - us to parameterise the class with an *arbitrary* number of type parameters. + The ``Ts`` type variable tuple here behaves like ``tuple[T1, T2]``, + where ``T1`` and ``T2`` are type variables. To use these type variables + as type parameters of ``Array``, we must *unpack* the type variable tuple using + the star operator: ``*Ts``. The signature of ``Array`` then behaves + as if we had simply written ``class Array(Generic[T1, T2]): ...``. + In contrast to ``Generic[T1, T2]``, however, ``Generic[*Shape]`` allows + us to parameterise the class with an *arbitrary* number of type parameters. - Type variable tuples can be used anywhere a normal ``TypeVar`` can. - This includes class definitions, as shown above, as well as function - signatures and variable annotations:: + Type variable tuples can be used anywhere a normal ``TypeVar`` can. + This includes class definitions, as shown above, as well as function + signatures and variable annotations:: - class Array(Generic[*Ts]): + class Array(Generic[*Ts]): - def __init__(self, shape: Tuple[*Ts]): - self._shape: Tuple[*Ts] = shape + def __init__(self, shape: Tuple[*Ts]): + self._shape: Tuple[*Ts] = shape - def get_shape(self) -> Tuple[*Ts]: - return self._shape + def get_shape(self) -> Tuple[*Ts]: + return self._shape - shape = (Height(480), Width(640)) - x: Array[Height, Width] = Array(shape) - y = abs(x) # Inferred type is Array[Height, Width] - z = x + x # ... is Array[Height, Width] - x.get_shape() # ... is tuple[Height, Width] + shape = (Height(480), Width(640)) + x: Array[Height, Width] = Array(shape) + y = abs(x) # Inferred type is Array[Height, Width] + z = x + x # ... is Array[Height, Width] + x.get_shape() # ... is tuple[Height, Width] - """ + """ - # Trick Generic __parameters__. - __class__ = typing.TypeVar + # Trick Generic __parameters__. + __class__ = typing.TypeVar - def __iter__(self): - yield self.__unpacked__ + def __iter__(self): + yield self.__unpacked__ - def __init__(self, name): - self.__name__ = name + def __init__(self, name): + self.__name__ = name - # for pickling: - try: - def_mod = sys._getframe(1).f_globals.get('__name__', '__main__') - except (AttributeError, ValueError): - def_mod = None - if def_mod != 'typing_extensions': - self.__module__ = def_mod + # for pickling: + try: + def_mod = sys._getframe(1).f_globals.get('__name__', '__main__') + except (AttributeError, ValueError): + def_mod = None + if def_mod != 'typing_extensions': + self.__module__ = def_mod - self.__unpacked__ = Unpack[self] + self.__unpacked__ = Unpack[self] - def __repr__(self): - return self.__name__ + def __repr__(self): + return self.__name__ - def __hash__(self): - return object.__hash__(self) + def __hash__(self): + return object.__hash__(self) - def __eq__(self, other): - return self is other + def __eq__(self, other): + return self is other - def __reduce__(self): - return self.__name__ + def __reduce__(self): + return self.__name__ - def __init_subclass__(self, *args, **kwds): - if '_root' not in kwds: - raise TypeError("Cannot subclass special typing classes") + def __init_subclass__(self, *args, **kwds): + if '_root' not in kwds: + raise TypeError("Cannot subclass special typing classes") if hasattr(typing, "reveal_type"):