Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
using the new release, and vice versa. Most users are unlikely to be affected
by this change. Patch by Alex Waygood.
- Backport the ability to define `__init__` methods on Protocol classes, a
change made in Python 3.11 (originally implemented in
change made in Python 3.11 (originally implemented in
https://github.com/python/cpython/pull/31628 by Adrian Garcia Badaracco).
Patch by Alex Waygood.
- Speedup `isinstance(3, typing_extensions.SupportsIndex)` by >10x on Python
Expand Down Expand Up @@ -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.
- Backport changes to the repr of `typing.Unpack` that were made in order to
implement [PEP 692](https://peps.python.org/pep-0692/) (backport of
https://github.com/python/cpython/pull/104048). Patch by Alex Waygood.
Expand Down
34 changes: 30 additions & 4 deletions src/test_typing_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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))

Expand Down Expand Up @@ -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]
Expand All @@ -4388,13 +4397,25 @@ 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]

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]]
Expand Down Expand Up @@ -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)
Expand Down
109 changes: 70 additions & 39 deletions src/typing_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -758,6 +758,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
Expand Down Expand Up @@ -1342,39 +1343,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'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

heh



# Python 3.10+ has PEP 612
Expand Down Expand Up @@ -1442,22 +1457,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:
Expand Down Expand Up @@ -2060,18 +2081,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):
Expand Down