Skip to content

Commit 610ad76

Browse files
committed
Fix 3.14
1 parent d44e9cf commit 610ad76

File tree

4 files changed

+113
-21
lines changed

4 files changed

+113
-21
lines changed

.github/workflows/ci.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@ jobs:
7070
cd src
7171
python --version # just to make sure we're running the right one
7272
python -m unittest test_typing_extensions.py
73-
continue-on-error: ${{ endsWith(matrix.python-version, '-dev') }}
7473
7574
- name: Test CPython typing test suite
7675
# Test suite fails on PyPy even without typing_extensions
@@ -80,7 +79,6 @@ jobs:
8079
# Run the typing test suite from CPython with typing_extensions installed,
8180
# because we monkeypatch typing under some circumstances.
8281
python -c 'import typing_extensions; import test.__main__' test_typing -v
83-
continue-on-error: ${{ endsWith(matrix.python-version, '-dev') }}
8482
8583
linting:
8684
name: Lint

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ New features:
88
Patch by [Victorien Plot](https://github.com/Viicos).
99
- Add `typing_extensions.Reader` and `typing_extensions.Writer`. Patch by
1010
Sebastian Rittau.
11+
- Fix tests for Python 3.14. Patch by Jelle Zijlstra.
1112

1213
# Release 4.13.2 (April 10, 2025)
1314

src/test_typing_extensions.py

Lines changed: 60 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5152,6 +5152,65 @@ def test_inline(self):
51525152
self.assertIs(type(inst), dict)
51535153
self.assertEqual(inst["a"], 1)
51545154

5155+
def test_annotations(self):
5156+
# _type_check is applied
5157+
with self.assertRaisesRegex(TypeError, "Plain typing.Final is not valid as type argument"):
5158+
class X(TypedDict):
5159+
a: Final
5160+
5161+
# _type_convert is applied
5162+
class Y(TypedDict):
5163+
a: None
5164+
b: "int"
5165+
if sys.version_info >= (3, 14):
5166+
import annotationlib
5167+
from test.support import EqualToForwardRef
5168+
5169+
fwdref = EqualToForwardRef('int', module=__name__)
5170+
self.assertEqual(Y.__annotations__, {'a': type(None), 'b': fwdref})
5171+
self.assertEqual(Y.__annotate__(annotationlib.Format.FORWARDREF), {'a': type(None), 'b': fwdref})
5172+
else:
5173+
self.assertEqual(Y.__annotations__, {'a': type(None), 'b': typing.ForwardRef('int', module=__name__)})
5174+
5175+
@skipUnless(TYPING_3_14_0, "Only supported on 3.14")
5176+
def test_delayed_type_check(self):
5177+
# _type_check is also applied later
5178+
class Z(TypedDict):
5179+
a: undefined
5180+
5181+
with self.assertRaises(NameError):
5182+
Z.__annotations__
5183+
5184+
undefined = Final
5185+
with self.assertRaisesRegex(TypeError, "Plain typing.Final is not valid as type argument"):
5186+
Z.__annotations__
5187+
5188+
undefined = None
5189+
self.assertEqual(Z.__annotations__, {'a': type(None)})
5190+
5191+
@skipUnless(TYPING_3_14_0, "Only supported on 3.14")
5192+
def test_deferred_evaluation(self):
5193+
class A(TypedDict):
5194+
x: NotRequired[undefined]
5195+
y: ReadOnly[undefined]
5196+
z: Required[undefined]
5197+
5198+
self.assertEqual(A.__required_keys__, frozenset({'y', 'z'}))
5199+
self.assertEqual(A.__optional_keys__, frozenset({'x'}))
5200+
self.assertEqual(A.__readonly_keys__, frozenset({'y'}))
5201+
self.assertEqual(A.__mutable_keys__, frozenset({'x', 'z'}))
5202+
5203+
with self.assertRaises(NameError):
5204+
A.__annotations__
5205+
5206+
import annotationlib
5207+
self.assertEqual(
5208+
A.__annotate__(annotationlib.Format.STRING),
5209+
{'x': 'NotRequired[undefined]', 'y': 'ReadOnly[undefined]',
5210+
'z': 'Required[undefined]'},
5211+
)
5212+
5213+
51555214
class AnnotatedTests(BaseTestCase):
51565215

51575216
def test_repr(self):
@@ -8906,13 +8965,6 @@ def test_fwdref_with_globals(self):
89068965
obj = object()
89078966
self.assertIs(evaluate_forward_ref(typing.ForwardRef("int"), globals={"int": obj}), obj)
89088967

8909-
def test_fwdref_value_is_cached(self):
8910-
fr = typing.ForwardRef("hello")
8911-
with self.assertRaises(NameError):
8912-
evaluate_forward_ref(fr)
8913-
self.assertIs(evaluate_forward_ref(fr, globals={"hello": str}), str)
8914-
self.assertIs(evaluate_forward_ref(fr), str)
8915-
89168968
def test_fwdref_with_owner(self):
89178969
self.assertEqual(
89188970
evaluate_forward_ref(typing.ForwardRef("Counter[int]"), owner=collections),
@@ -8956,7 +9008,7 @@ class Y(Generic[Tx]):
89569008
self.assertEqual(get_args(evaluated_ref1b), (Y[Tx],))
89579009

89589010
with self.subTest("nested string of TypeVar"):
8959-
evaluated_ref2 = evaluate_forward_ref(typing.ForwardRef("""Y["Y['Tx']"]"""), locals={"Y": Y})
9011+
evaluated_ref2 = evaluate_forward_ref(typing.ForwardRef("""Y["Y['Tx']"]"""), locals={"Y": Y, "Tx": Tx})
89609012
self.assertEqual(get_origin(evaluated_ref2), Y)
89619013
self.assertEqual(get_args(evaluated_ref2), (Y[Tx],))
89629014

src/typing_extensions.py

Lines changed: 52 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
import typing
1515
import warnings
1616

17+
if sys.version_info >= (3, 14):
18+
import annotationlib
19+
1720
__all__ = [
1821
# Super-special typing primitives.
1922
'Any',
@@ -1018,21 +1021,27 @@ def __new__(cls, name, bases, ns, *, total=True, closed=None,
10181021
tp_dict.__orig_bases__ = bases
10191022

10201023
annotations = {}
1024+
own_annotate = None
10211025
if "__annotations__" in ns:
10221026
own_annotations = ns["__annotations__"]
1023-
elif "__annotate__" in ns:
1024-
# TODO: Use inspect.VALUE here, and make the annotations lazily evaluated
1025-
own_annotations = ns["__annotate__"](1)
1027+
elif sys.version_info >= (3, 14):
1028+
own_annotate = annotationlib.get_annotate_from_class_namespace(ns)
1029+
if own_annotate is not None:
1030+
own_annotations = annotationlib.call_annotate_function(
1031+
own_annotate, annotationlib.Format.FORWARDREF, owner=tp_dict
1032+
)
1033+
else:
1034+
own_annotations = {}
10261035
else:
10271036
own_annotations = {}
10281037
msg = "TypedDict('Name', {f0: t0, f1: t1, ...}); each t must be a type"
10291038
if _TAKES_MODULE:
1030-
own_annotations = {
1039+
own_checked_annotations = {
10311040
n: typing._type_check(tp, msg, module=tp_dict.__module__)
10321041
for n, tp in own_annotations.items()
10331042
}
10341043
else:
1035-
own_annotations = {
1044+
own_checked_annotations = {
10361045
n: typing._type_check(tp, msg)
10371046
for n, tp in own_annotations.items()
10381047
}
@@ -1045,7 +1054,8 @@ def __new__(cls, name, bases, ns, *, total=True, closed=None,
10451054
for base in bases:
10461055
base_dict = base.__dict__
10471056

1048-
annotations.update(base_dict.get('__annotations__', {}))
1057+
if sys.version_info <= (3, 14):
1058+
annotations.update(base_dict.get('__annotations__', {}))
10491059
required_keys.update(base_dict.get('__required_keys__', ()))
10501060
optional_keys.update(base_dict.get('__optional_keys__', ()))
10511061
readonly_keys.update(base_dict.get('__readonly_keys__', ()))
@@ -1055,8 +1065,8 @@ def __new__(cls, name, bases, ns, *, total=True, closed=None,
10551065
# is retained for backwards compatibility, but only for Python
10561066
# 3.13 and lower.
10571067
if (closed and sys.version_info < (3, 14)
1058-
and "__extra_items__" in own_annotations):
1059-
annotation_type = own_annotations.pop("__extra_items__")
1068+
and "__extra_items__" in own_checked_annotations):
1069+
annotation_type = own_checked_annotations.pop("__extra_items__")
10601070
qualifiers = set(_get_typeddict_qualifiers(annotation_type))
10611071
if Required in qualifiers:
10621072
raise TypeError(
@@ -1070,8 +1080,8 @@ def __new__(cls, name, bases, ns, *, total=True, closed=None,
10701080
)
10711081
extra_items_type = annotation_type
10721082

1073-
annotations.update(own_annotations)
1074-
for annotation_key, annotation_type in own_annotations.items():
1083+
annotations.update(own_checked_annotations)
1084+
for annotation_key, annotation_type in own_checked_annotations.items():
10751085
qualifiers = set(_get_typeddict_qualifiers(annotation_type))
10761086

10771087
if Required in qualifiers:
@@ -1089,7 +1099,38 @@ def __new__(cls, name, bases, ns, *, total=True, closed=None,
10891099
mutable_keys.add(annotation_key)
10901100
readonly_keys.discard(annotation_key)
10911101

1092-
tp_dict.__annotations__ = annotations
1102+
if sys.version_info >= (3, 14):
1103+
def __annotate__(format):
1104+
annos = {}
1105+
for base in bases:
1106+
if base is Generic:
1107+
continue
1108+
base_annotate = base.__annotate__
1109+
if base_annotate is None:
1110+
continue
1111+
base_annos = annotationlib.call_annotate_function(
1112+
base.__annotate__, format, owner=base)
1113+
annos.update(base_annos)
1114+
if own_annotate is not None:
1115+
own = annotationlib.call_annotate_function(
1116+
own_annotate, format, owner=tp_dict)
1117+
if format != annotationlib.Format.STRING:
1118+
own = {
1119+
n: typing._type_check(tp, msg, module=tp_dict.__module__)
1120+
for n, tp in own.items()
1121+
}
1122+
elif format == annotationlib.Format.STRING:
1123+
own = annotationlib.annotations_to_string(own_annotations)
1124+
elif format in (annotationlib.Format.FORWARDREF, annotationlib.Format.VALUE):
1125+
own = own_checked_annotations
1126+
else:
1127+
raise NotImplementedError(format)
1128+
annos.update(own)
1129+
return annos
1130+
1131+
tp_dict.__annotate__ = __annotate__
1132+
else:
1133+
tp_dict.__annotations__ = annotations
10931134
tp_dict.__required_keys__ = frozenset(required_keys)
10941135
tp_dict.__optional_keys__ = frozenset(optional_keys)
10951136
tp_dict.__readonly_keys__ = frozenset(readonly_keys)

0 commit comments

Comments
 (0)