diff --git a/CHANGELOG.md b/CHANGELOG.md index c6d0233a..e3247b46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -73,6 +73,8 @@ - Backport the implementation of `NewType` from 3.10 (where it is implemented as a class rather than a function). This allows user-defined `NewType`s to be pickled. Patch by Alex Waygood. +- Fix tests and import on Python 3.12, where `typing.TypeVar` can no longer be + subclassed. Patch by Jelle Zijlstra. - Add `typing_extensions.TypeAliasType`, a backport of `typing.TypeAliasType` from PEP 695. Patch by Jelle Zijlstra. - Backport changes to the repr of `typing.Unpack` that were made in order to diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index dadf6e3c..7c940678 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -14,6 +14,7 @@ import re import subprocess import tempfile +import textwrap import types from pathlib import Path from unittest import TestCase, main, skipUnless, skipIf @@ -47,6 +48,9 @@ # 3.11 makes runtime type checks (_type_check) more lenient. TYPING_3_11_0 = sys.version_info[:3] >= (3, 11, 0) +# 3.12 changes the representation of Unpack[] (PEP 692) +TYPING_3_12_0 = sys.version_info[:3] >= (3, 12, 0) + # https://github.com/python/cpython/pull/27017 was backported into some 3.9 and 3.10 # versions, but not all HAS_FORWARD_MODULE = "module" in inspect.signature(typing._type_check).parameters @@ -2396,7 +2400,7 @@ def bar(self, x: str) -> str: with self.assertRaises(TypeError): PR[int, ClassVar] - if sys.version_info >= (3, 12): + if hasattr(typing, "TypeAliasType"): exec(textwrap.dedent( """ def test_pep695_generic_protocol_callable_members(self): @@ -4342,8 +4346,8 @@ def test_signature_on_37(self): @skipUnless(TYPING_3_9_0, "NamedTuple was a class on 3.8 and lower") def test_same_as_typing_NamedTuple_39_plus(self): self.assertEqual( - set(dir(NamedTuple)), - set(dir(typing.NamedTuple)) | {"__text_signature__"} + set(dir(NamedTuple)) - {"__text_signature__"}, + set(dir(typing.NamedTuple)) ) self.assertIs(type(NamedTuple), type(typing.NamedTuple)) @@ -4374,7 +4378,12 @@ class GenericNamedTuple(NamedTuple, Generic[T]): class TypeVarLikeDefaultsTests(BaseTestCase): def test_typevar(self): T = typing_extensions.TypeVar('T', default=int) + typing_T = TypeVar('T') self.assertEqual(T.__default__, int) + self.assertIsInstance(T, typing_extensions.TypeVar) + self.assertIsInstance(T, typing.TypeVar) + self.assertIsInstance(typing_T, typing.TypeVar) + self.assertIsInstance(typing_T, typing_extensions.TypeVar) class A(Generic[T]): ... Alias = Optional[T] @@ -4388,6 +4397,12 @@ def test_typevar_none(self): def test_paramspec(self): P = ParamSpec('P', default=(str, int)) self.assertEqual(P.__default__, (str, int)) + self.assertIsInstance(P, ParamSpec) + if hasattr(typing, "ParamSpec"): + self.assertIsInstance(P, typing.ParamSpec) + typing_P = typing.ParamSpec('P') + self.assertIsInstance(typing_P, typing.ParamSpec) + self.assertIsInstance(typing_P, ParamSpec) class A(Generic[P]): ... Alias = typing.Callable[P, None] @@ -4395,6 +4410,12 @@ class A(Generic[P]): ... def test_typevartuple(self): Ts = TypeVarTuple('Ts', default=Unpack[Tuple[str, int]]) self.assertEqual(Ts.__default__, Unpack[Tuple[str, int]]) + self.assertIsInstance(Ts, TypeVarTuple) + if hasattr(typing, "TypeVarTuple"): + self.assertIsInstance(Ts, typing.TypeVarTuple) + typing_Ts = typing.TypeVarTuple('Ts') + self.assertIsInstance(typing_Ts, typing.TypeVarTuple) + self.assertIsInstance(typing_Ts, TypeVarTuple) class A(Generic[Unpack[Ts]]): ... Alias = Optional[Unpack[Ts]] @@ -4454,8 +4475,13 @@ class MyRegisteredBuffer: def __buffer__(self, flags: int) -> memoryview: return memoryview(b'') - self.assertNotIsInstance(MyRegisteredBuffer(), Buffer) - self.assertNotIsSubclass(MyRegisteredBuffer, Buffer) + # On 3.12, collections.abc.Buffer does a structural compatibility check + if TYPING_3_12_0: + self.assertIsInstance(MyRegisteredBuffer(), Buffer) + self.assertIsSubclass(MyRegisteredBuffer, Buffer) + else: + self.assertNotIsInstance(MyRegisteredBuffer(), Buffer) + self.assertNotIsSubclass(MyRegisteredBuffer, Buffer) Buffer.register(MyRegisteredBuffer) self.assertIsInstance(MyRegisteredBuffer(), Buffer) self.assertIsSubclass(MyRegisteredBuffer, Buffer) diff --git a/src/typing_extensions.py b/src/typing_extensions.py index 82e4ba76..6fd0f241 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -759,6 +759,7 @@ def runtime_checkable(cls): SupportsInt = typing.SupportsInt SupportsFloat = typing.SupportsFloat SupportsComplex = typing.SupportsComplex + SupportsBytes = typing.SupportsBytes SupportsIndex = typing.SupportsIndex SupportsAbs = typing.SupportsAbs SupportsRound = typing.SupportsRound @@ -1343,39 +1344,53 @@ def __repr__(self): above.""") +def _set_default(type_param, default): + if isinstance(default, (tuple, list)): + type_param.__default__ = tuple((typing._type_check(d, "Default must be a type") + for d in default)) + elif default != _marker: + type_param.__default__ = typing._type_check(default, "Default must be a type") + else: + type_param.__default__ = None + + class _DefaultMixin: """Mixin for TypeVarLike defaults.""" __slots__ = () - - def __init__(self, default): - if isinstance(default, (tuple, list)): - self.__default__ = tuple((typing._type_check(d, "Default must be a type") - for d in default)) - elif default != _marker: - self.__default__ = typing._type_check(default, "Default must be a type") - else: - self.__default__ = None + __init__ = _set_default # Add default and infer_variance parameters from PEP 696 and 695 -class TypeVar(typing.TypeVar, _DefaultMixin, _root=True): - """Type variable.""" - - __module__ = 'typing' - - def __init__(self, name, *constraints, bound=None, +class _TypeVarMeta(type): + def __call__(self, name, *constraints, bound=None, covariant=False, contravariant=False, default=_marker, infer_variance=False): - super().__init__(name, *constraints, bound=bound, covariant=covariant, - contravariant=contravariant) - _DefaultMixin.__init__(self, default) - self.__infer_variance__ = infer_variance + if hasattr(typing, "TypeAliasType"): + # PEP 695 implemented, can pass infer_variance to typing.TypeVar + typevar = typing.TypeVar(name, *constraints, bound=bound, + covariant=covariant, contravariant=contravariant, + infer_variance=infer_variance) + else: + typevar = typing.TypeVar(name, *constraints, bound=bound, + covariant=covariant, contravariant=contravariant) + typevar.__infer_variance__ = infer_variance + _set_default(typevar, default) # for pickling: def_mod = _caller() if def_mod != 'typing_extensions': - self.__module__ = def_mod + typevar.__module__ = def_mod + return typevar + + def __instancecheck__(self, __instance: Any) -> bool: + return isinstance(__instance, typing.TypeVar) + + +class TypeVar(metaclass=_TypeVarMeta): + """Type variable.""" + + __module__ = 'typing' # Python 3.10+ has PEP 612 @@ -1443,22 +1458,28 @@ def __eq__(self, other): # 3.10+ if hasattr(typing, 'ParamSpec'): - # Add default Parameter - PEP 696 - class ParamSpec(typing.ParamSpec, _DefaultMixin, _root=True): - """Parameter specification variable.""" - - __module__ = 'typing' - - def __init__(self, name, *, bound=None, covariant=False, contravariant=False, + # Add default parameter - PEP 696 + class _ParamSpecMeta(type): + def __call__(self, name, *, bound=None, + covariant=False, contravariant=False, default=_marker): - super().__init__(name, bound=bound, covariant=covariant, - contravariant=contravariant) - _DefaultMixin.__init__(self, default) + paramspec = typing.ParamSpec(name, bound=bound, + covariant=covariant, contravariant=contravariant) + _set_default(paramspec, default) # for pickling: def_mod = _caller() if def_mod != 'typing_extensions': - self.__module__ = def_mod + paramspec.__module__ = def_mod + return paramspec + + def __instancecheck__(self, __instance: Any) -> bool: + return isinstance(__instance, typing.ParamSpec) + + class ParamSpec(metaclass=_ParamSpecMeta): + """Parameter specification.""" + + __module__ = 'typing' # 3.7-3.9 else: @@ -2061,18 +2082,28 @@ def _is_unpack(obj): if hasattr(typing, "TypeVarTuple"): # 3.11+ - # Add default Parameter - PEP 696 - class TypeVarTuple(typing.TypeVarTuple, _DefaultMixin, _root=True): - """Type variable tuple.""" - - def __init__(self, name, *, default=_marker): - super().__init__(name) - _DefaultMixin.__init__(self, default) + # Add default parameter - PEP 696 + class _TypeVarTupleMeta(type): + def __call__(self, name, *, default=_marker): + tvt = typing.TypeVarTuple(name) + _set_default(tvt, default) # for pickling: def_mod = _caller() if def_mod != 'typing_extensions': - self.__module__ = def_mod + tvt.__module__ = def_mod + return tvt + + def __instancecheck__(self, __instance: Any) -> bool: + return isinstance(__instance, typing.TypeVarTuple) + + class TypeVarTuple(metaclass=_TypeVarTupleMeta): + """Type variable tuple.""" + + __module__ = 'typing' + + def __init_subclass__(self, *args, **kwds): + raise TypeError("Cannot subclass special typing classes") else: class TypeVarTuple(_DefaultMixin):