Skip to content

Commit 8b6582e

Browse files
Fix tests on Python 3.12 (#162)
Co-authored-by: Alex Waygood <[email protected]>
1 parent 09c1ed4 commit 8b6582e

File tree

3 files changed

+103
-44
lines changed

3 files changed

+103
-44
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@
7373
- Backport the implementation of `NewType` from 3.10 (where it is implemented
7474
as a class rather than a function). This allows user-defined `NewType`s to be
7575
pickled. Patch by Alex Waygood.
76+
- Fix tests and import on Python 3.12, where `typing.TypeVar` can no longer be
77+
subclassed. Patch by Jelle Zijlstra.
7678
- Add `typing_extensions.TypeAliasType`, a backport of `typing.TypeAliasType`
7779
from PEP 695. Patch by Jelle Zijlstra.
7880
- Backport changes to the repr of `typing.Unpack` that were made in order to

src/test_typing_extensions.py

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import re
1515
import subprocess
1616
import tempfile
17+
import textwrap
1718
import types
1819
from pathlib import Path
1920
from unittest import TestCase, main, skipUnless, skipIf
@@ -47,6 +48,9 @@
4748
# 3.11 makes runtime type checks (_type_check) more lenient.
4849
TYPING_3_11_0 = sys.version_info[:3] >= (3, 11, 0)
4950

51+
# 3.12 changes the representation of Unpack[] (PEP 692)
52+
TYPING_3_12_0 = sys.version_info[:3] >= (3, 12, 0)
53+
5054
# https://github.com/python/cpython/pull/27017 was backported into some 3.9 and 3.10
5155
# versions, but not all
5256
HAS_FORWARD_MODULE = "module" in inspect.signature(typing._type_check).parameters
@@ -2396,7 +2400,7 @@ def bar(self, x: str) -> str:
23962400
with self.assertRaises(TypeError):
23972401
PR[int, ClassVar]
23982402

2399-
if sys.version_info >= (3, 12):
2403+
if hasattr(typing, "TypeAliasType"):
24002404
exec(textwrap.dedent(
24012405
"""
24022406
def test_pep695_generic_protocol_callable_members(self):
@@ -4342,8 +4346,8 @@ def test_signature_on_37(self):
43424346
@skipUnless(TYPING_3_9_0, "NamedTuple was a class on 3.8 and lower")
43434347
def test_same_as_typing_NamedTuple_39_plus(self):
43444348
self.assertEqual(
4345-
set(dir(NamedTuple)),
4346-
set(dir(typing.NamedTuple)) | {"__text_signature__"}
4349+
set(dir(NamedTuple)) - {"__text_signature__"},
4350+
set(dir(typing.NamedTuple))
43474351
)
43484352
self.assertIs(type(NamedTuple), type(typing.NamedTuple))
43494353

@@ -4374,7 +4378,12 @@ class GenericNamedTuple(NamedTuple, Generic[T]):
43744378
class TypeVarLikeDefaultsTests(BaseTestCase):
43754379
def test_typevar(self):
43764380
T = typing_extensions.TypeVar('T', default=int)
4381+
typing_T = TypeVar('T')
43774382
self.assertEqual(T.__default__, int)
4383+
self.assertIsInstance(T, typing_extensions.TypeVar)
4384+
self.assertIsInstance(T, typing.TypeVar)
4385+
self.assertIsInstance(typing_T, typing.TypeVar)
4386+
self.assertIsInstance(typing_T, typing_extensions.TypeVar)
43784387

43794388
class A(Generic[T]): ...
43804389
Alias = Optional[T]
@@ -4388,13 +4397,25 @@ def test_typevar_none(self):
43884397
def test_paramspec(self):
43894398
P = ParamSpec('P', default=(str, int))
43904399
self.assertEqual(P.__default__, (str, int))
4400+
self.assertIsInstance(P, ParamSpec)
4401+
if hasattr(typing, "ParamSpec"):
4402+
self.assertIsInstance(P, typing.ParamSpec)
4403+
typing_P = typing.ParamSpec('P')
4404+
self.assertIsInstance(typing_P, typing.ParamSpec)
4405+
self.assertIsInstance(typing_P, ParamSpec)
43914406

43924407
class A(Generic[P]): ...
43934408
Alias = typing.Callable[P, None]
43944409

43954410
def test_typevartuple(self):
43964411
Ts = TypeVarTuple('Ts', default=Unpack[Tuple[str, int]])
43974412
self.assertEqual(Ts.__default__, Unpack[Tuple[str, int]])
4413+
self.assertIsInstance(Ts, TypeVarTuple)
4414+
if hasattr(typing, "TypeVarTuple"):
4415+
self.assertIsInstance(Ts, typing.TypeVarTuple)
4416+
typing_Ts = typing.TypeVarTuple('Ts')
4417+
self.assertIsInstance(typing_Ts, typing.TypeVarTuple)
4418+
self.assertIsInstance(typing_Ts, TypeVarTuple)
43984419

43994420
class A(Generic[Unpack[Ts]]): ...
44004421
Alias = Optional[Unpack[Ts]]
@@ -4454,8 +4475,13 @@ class MyRegisteredBuffer:
44544475
def __buffer__(self, flags: int) -> memoryview:
44554476
return memoryview(b'')
44564477

4457-
self.assertNotIsInstance(MyRegisteredBuffer(), Buffer)
4458-
self.assertNotIsSubclass(MyRegisteredBuffer, Buffer)
4478+
# On 3.12, collections.abc.Buffer does a structural compatibility check
4479+
if TYPING_3_12_0:
4480+
self.assertIsInstance(MyRegisteredBuffer(), Buffer)
4481+
self.assertIsSubclass(MyRegisteredBuffer, Buffer)
4482+
else:
4483+
self.assertNotIsInstance(MyRegisteredBuffer(), Buffer)
4484+
self.assertNotIsSubclass(MyRegisteredBuffer, Buffer)
44594485
Buffer.register(MyRegisteredBuffer)
44604486
self.assertIsInstance(MyRegisteredBuffer(), Buffer)
44614487
self.assertIsSubclass(MyRegisteredBuffer, Buffer)

src/typing_extensions.py

Lines changed: 70 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -759,6 +759,7 @@ def runtime_checkable(cls):
759759
SupportsInt = typing.SupportsInt
760760
SupportsFloat = typing.SupportsFloat
761761
SupportsComplex = typing.SupportsComplex
762+
SupportsBytes = typing.SupportsBytes
762763
SupportsIndex = typing.SupportsIndex
763764
SupportsAbs = typing.SupportsAbs
764765
SupportsRound = typing.SupportsRound
@@ -1343,39 +1344,53 @@ def __repr__(self):
13431344
above.""")
13441345

13451346

1347+
def _set_default(type_param, default):
1348+
if isinstance(default, (tuple, list)):
1349+
type_param.__default__ = tuple((typing._type_check(d, "Default must be a type")
1350+
for d in default))
1351+
elif default != _marker:
1352+
type_param.__default__ = typing._type_check(default, "Default must be a type")
1353+
else:
1354+
type_param.__default__ = None
1355+
1356+
13461357
class _DefaultMixin:
13471358
"""Mixin for TypeVarLike defaults."""
13481359

13491360
__slots__ = ()
1350-
1351-
def __init__(self, default):
1352-
if isinstance(default, (tuple, list)):
1353-
self.__default__ = tuple((typing._type_check(d, "Default must be a type")
1354-
for d in default))
1355-
elif default != _marker:
1356-
self.__default__ = typing._type_check(default, "Default must be a type")
1357-
else:
1358-
self.__default__ = None
1361+
__init__ = _set_default
13591362

13601363

13611364
# Add default and infer_variance parameters from PEP 696 and 695
1362-
class TypeVar(typing.TypeVar, _DefaultMixin, _root=True):
1363-
"""Type variable."""
1364-
1365-
__module__ = 'typing'
1366-
1367-
def __init__(self, name, *constraints, bound=None,
1365+
class _TypeVarMeta(type):
1366+
def __call__(self, name, *constraints, bound=None,
13681367
covariant=False, contravariant=False,
13691368
default=_marker, infer_variance=False):
1370-
super().__init__(name, *constraints, bound=bound, covariant=covariant,
1371-
contravariant=contravariant)
1372-
_DefaultMixin.__init__(self, default)
1373-
self.__infer_variance__ = infer_variance
1369+
if hasattr(typing, "TypeAliasType"):
1370+
# PEP 695 implemented, can pass infer_variance to typing.TypeVar
1371+
typevar = typing.TypeVar(name, *constraints, bound=bound,
1372+
covariant=covariant, contravariant=contravariant,
1373+
infer_variance=infer_variance)
1374+
else:
1375+
typevar = typing.TypeVar(name, *constraints, bound=bound,
1376+
covariant=covariant, contravariant=contravariant)
1377+
typevar.__infer_variance__ = infer_variance
1378+
_set_default(typevar, default)
13741379

13751380
# for pickling:
13761381
def_mod = _caller()
13771382
if def_mod != 'typing_extensions':
1378-
self.__module__ = def_mod
1383+
typevar.__module__ = def_mod
1384+
return typevar
1385+
1386+
def __instancecheck__(self, __instance: Any) -> bool:
1387+
return isinstance(__instance, typing.TypeVar)
1388+
1389+
1390+
class TypeVar(metaclass=_TypeVarMeta):
1391+
"""Type variable."""
1392+
1393+
__module__ = 'typing'
13791394

13801395

13811396
# Python 3.10+ has PEP 612
@@ -1443,22 +1458,28 @@ def __eq__(self, other):
14431458
# 3.10+
14441459
if hasattr(typing, 'ParamSpec'):
14451460

1446-
# Add default Parameter - PEP 696
1447-
class ParamSpec(typing.ParamSpec, _DefaultMixin, _root=True):
1448-
"""Parameter specification variable."""
1449-
1450-
__module__ = 'typing'
1451-
1452-
def __init__(self, name, *, bound=None, covariant=False, contravariant=False,
1461+
# Add default parameter - PEP 696
1462+
class _ParamSpecMeta(type):
1463+
def __call__(self, name, *, bound=None,
1464+
covariant=False, contravariant=False,
14531465
default=_marker):
1454-
super().__init__(name, bound=bound, covariant=covariant,
1455-
contravariant=contravariant)
1456-
_DefaultMixin.__init__(self, default)
1466+
paramspec = typing.ParamSpec(name, bound=bound,
1467+
covariant=covariant, contravariant=contravariant)
1468+
_set_default(paramspec, default)
14571469

14581470
# for pickling:
14591471
def_mod = _caller()
14601472
if def_mod != 'typing_extensions':
1461-
self.__module__ = def_mod
1473+
paramspec.__module__ = def_mod
1474+
return paramspec
1475+
1476+
def __instancecheck__(self, __instance: Any) -> bool:
1477+
return isinstance(__instance, typing.ParamSpec)
1478+
1479+
class ParamSpec(metaclass=_ParamSpecMeta):
1480+
"""Parameter specification."""
1481+
1482+
__module__ = 'typing'
14621483

14631484
# 3.7-3.9
14641485
else:
@@ -2061,18 +2082,28 @@ def _is_unpack(obj):
20612082

20622083
if hasattr(typing, "TypeVarTuple"): # 3.11+
20632084

2064-
# Add default Parameter - PEP 696
2065-
class TypeVarTuple(typing.TypeVarTuple, _DefaultMixin, _root=True):
2066-
"""Type variable tuple."""
2067-
2068-
def __init__(self, name, *, default=_marker):
2069-
super().__init__(name)
2070-
_DefaultMixin.__init__(self, default)
2085+
# Add default parameter - PEP 696
2086+
class _TypeVarTupleMeta(type):
2087+
def __call__(self, name, *, default=_marker):
2088+
tvt = typing.TypeVarTuple(name)
2089+
_set_default(tvt, default)
20712090

20722091
# for pickling:
20732092
def_mod = _caller()
20742093
if def_mod != 'typing_extensions':
2075-
self.__module__ = def_mod
2094+
tvt.__module__ = def_mod
2095+
return tvt
2096+
2097+
def __instancecheck__(self, __instance: Any) -> bool:
2098+
return isinstance(__instance, typing.TypeVarTuple)
2099+
2100+
class TypeVarTuple(metaclass=_TypeVarTupleMeta):
2101+
"""Type variable tuple."""
2102+
2103+
__module__ = 'typing'
2104+
2105+
def __init_subclass__(self, *args, **kwds):
2106+
raise TypeError("Cannot subclass special typing classes")
20762107

20772108
else:
20782109
class TypeVarTuple(_DefaultMixin):

0 commit comments

Comments
 (0)