Skip to content

Commit dcdc53f

Browse files
authored
Restore compatibility with PyPy <3.9 (#262)
1 parent bc9bc06 commit dcdc53f

File tree

3 files changed

+39
-13
lines changed

3 files changed

+39
-13
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ jobs:
5353
- "3.11"
5454
- "3.11.0"
5555
- "3.12"
56+
- "pypy3.7"
57+
- "pypy3.8"
5658
- "pypy3.9"
5759
- "pypy3.10"
5860

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
# Release 4.7.1 (???)
2+
3+
- Fix `TypedDict`, `NamedTuple` and `is_protocol` tests on PyPy-3.7 and
4+
PyPy-3.8. Patch by Alex Waygood.
5+
16
# Release 4.7.0 (June 28, 2023)
27

38
- This is expected to be the last feature release supporting Python 3.7,

src/typing_extensions.py

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -953,6 +953,21 @@ def __round__(self, ndigits: int = 0) -> T_co:
953953
pass
954954

955955

956+
def _ensure_subclassable(mro_entries):
957+
def inner(func):
958+
if sys.implementation.name == "pypy" and sys.version_info < (3, 9):
959+
cls_dict = {
960+
"__call__": staticmethod(func),
961+
"__mro_entries__": staticmethod(mro_entries)
962+
}
963+
t = type(func.__name__, (), cls_dict)
964+
return functools.update_wrapper(t(), func)
965+
else:
966+
func.__mro_entries__ = mro_entries
967+
return func
968+
return inner
969+
970+
956971
if sys.version_info >= (3, 13):
957972
# The standard library TypedDict in Python 3.8 does not store runtime information
958973
# about which (if any) keys are optional. See https://bugs.python.org/issue38834
@@ -1059,6 +1074,9 @@ def __subclasscheck__(cls, other):
10591074

10601075
__instancecheck__ = __subclasscheck__
10611076

1077+
_TypedDict = type.__new__(_TypedDictMeta, 'TypedDict', (), {})
1078+
1079+
@_ensure_subclassable(lambda bases: (_TypedDict,))
10621080
def TypedDict(__typename, __fields=_marker, *, total=True, **kwargs):
10631081
"""A simple typed namespace. At runtime it is equivalent to a plain dict.
10641082
@@ -1142,9 +1160,6 @@ class Point2D(TypedDict):
11421160
td.__orig_bases__ = (TypedDict,)
11431161
return td
11441162

1145-
_TypedDict = type.__new__(_TypedDictMeta, 'TypedDict', (), {})
1146-
TypedDict.__mro_entries__ = lambda bases: (_TypedDict,)
1147-
11481163
if hasattr(typing, "_TypedDictMeta"):
11491164
_TYPEDDICT_TYPES = (typing._TypedDictMeta, _TypedDictMeta)
11501165
else:
@@ -2633,6 +2648,13 @@ def __new__(cls, typename, bases, ns):
26332648
nm_tpl.__init_subclass__()
26342649
return nm_tpl
26352650

2651+
_NamedTuple = type.__new__(_NamedTupleMeta, 'NamedTuple', (), {})
2652+
2653+
def _namedtuple_mro_entries(bases):
2654+
assert NamedTuple in bases
2655+
return (_NamedTuple,)
2656+
2657+
@_ensure_subclassable(_namedtuple_mro_entries)
26362658
def NamedTuple(__typename, __fields=_marker, **kwargs):
26372659
"""Typed version of namedtuple.
26382660
@@ -2698,19 +2720,15 @@ class Employee(NamedTuple):
26982720
nt.__orig_bases__ = (NamedTuple,)
26992721
return nt
27002722

2701-
_NamedTuple = type.__new__(_NamedTupleMeta, 'NamedTuple', (), {})
2702-
27032723
# On 3.8+, alter the signature so that it matches typing.NamedTuple.
27042724
# The signature of typing.NamedTuple on >=3.8 is invalid syntax in Python 3.7,
27052725
# so just leave the signature as it is on 3.7.
27062726
if sys.version_info >= (3, 8):
2707-
NamedTuple.__text_signature__ = '(typename, fields=None, /, **kwargs)'
2708-
2709-
def _namedtuple_mro_entries(bases):
2710-
assert NamedTuple in bases
2711-
return (_NamedTuple,)
2712-
2713-
NamedTuple.__mro_entries__ = _namedtuple_mro_entries
2727+
_new_signature = '(typename, fields=None, /, **kwargs)'
2728+
if isinstance(NamedTuple, _types.FunctionType):
2729+
NamedTuple.__text_signature__ = _new_signature
2730+
else:
2731+
NamedTuple.__call__.__text_signature__ = _new_signature
27142732

27152733

27162734
if hasattr(collections.abc, "Buffer"):
@@ -2986,7 +3004,8 @@ def is_protocol(__tp: type) -> bool:
29863004
return (
29873005
isinstance(__tp, type)
29883006
and getattr(__tp, '_is_protocol', False)
2989-
and __tp != Protocol
3007+
and __tp is not Protocol
3008+
and __tp is not getattr(typing, "Protocol", object())
29903009
)
29913010

29923011
def get_protocol_members(__tp: type) -> typing.FrozenSet[str]:

0 commit comments

Comments
 (0)