Skip to content

Commit 75c95c4

Browse files
Start PEP 728 implementation (#519)
1 parent 7def253 commit 75c95c4

File tree

2 files changed

+249
-92
lines changed

2 files changed

+249
-92
lines changed

src/test_typing_extensions.py

Lines changed: 160 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
Never,
5656
NewType,
5757
NoDefault,
58+
NoExtraItems,
5859
NoReturn,
5960
NotRequired,
6061
Optional,
@@ -128,6 +129,8 @@
128129
# 3.13.0.rc1 fixes a problem with @deprecated
129130
TYPING_3_13_0_RC = sys.version_info[:4] >= (3, 13, 0, "candidate")
130131

132+
TYPING_3_14_0 = sys.version_info[:3] >= (3, 14, 0)
133+
131134
# https://github.com/python/cpython/pull/27017 was backported into some 3.9 and 3.10
132135
# versions, but not all
133136
HAS_FORWARD_MODULE = "module" in inspect.signature(typing._type_check).parameters
@@ -4140,18 +4143,25 @@ def test_basics_keywords_syntax(self):
41404143
def test_typeddict_special_keyword_names(self):
41414144
with self.assertWarns(DeprecationWarning):
41424145
TD = TypedDict("TD", cls=type, self=object, typename=str, _typename=int,
4143-
fields=list, _fields=dict)
4146+
fields=list, _fields=dict,
4147+
closed=bool, extra_items=bool)
41444148
self.assertEqual(TD.__name__, 'TD')
41454149
self.assertEqual(TD.__annotations__, {'cls': type, 'self': object, 'typename': str,
4146-
'_typename': int, 'fields': list, '_fields': dict})
4150+
'_typename': int, 'fields': list, '_fields': dict,
4151+
'closed': bool, 'extra_items': bool})
4152+
self.assertIsNone(TD.__closed__)
4153+
self.assertIs(TD.__extra_items__, NoExtraItems)
41474154
a = TD(cls=str, self=42, typename='foo', _typename=53,
4148-
fields=[('bar', tuple)], _fields={'baz', set})
4155+
fields=[('bar', tuple)], _fields={'baz', set},
4156+
closed=None, extra_items="tea pot")
41494157
self.assertEqual(a['cls'], str)
41504158
self.assertEqual(a['self'], 42)
41514159
self.assertEqual(a['typename'], 'foo')
41524160
self.assertEqual(a['_typename'], 53)
41534161
self.assertEqual(a['fields'], [('bar', tuple)])
41544162
self.assertEqual(a['_fields'], {'baz', set})
4163+
self.assertIsNone(a['closed'])
4164+
self.assertEqual(a['extra_items'], "tea pot")
41554165

41564166
def test_typeddict_create_errors(self):
41574167
with self.assertRaises(TypeError):
@@ -4414,24 +4424,6 @@ class ChildWithInlineAndOptional(Untotal, Inline):
44144424
{'inline': bool, 'untotal': str, 'child': bool},
44154425
)
44164426

4417-
class Closed(TypedDict, closed=True):
4418-
__extra_items__: None
4419-
4420-
class Unclosed(TypedDict, closed=False):
4421-
...
4422-
4423-
class ChildUnclosed(Closed, Unclosed):
4424-
...
4425-
4426-
self.assertFalse(ChildUnclosed.__closed__)
4427-
self.assertEqual(ChildUnclosed.__extra_items__, type(None))
4428-
4429-
class ChildClosed(Unclosed, Closed):
4430-
...
4431-
4432-
self.assertFalse(ChildClosed.__closed__)
4433-
self.assertEqual(ChildClosed.__extra_items__, type(None))
4434-
44354427
wrong_bases = [
44364428
(One, Regular),
44374429
(Regular, One),
@@ -4448,6 +4440,53 @@ class ChildClosed(Unclosed, Closed):
44484440
class Wrong(*bases):
44494441
pass
44504442

4443+
def test_closed_values(self):
4444+
class Implicit(TypedDict): ...
4445+
class ExplicitTrue(TypedDict, closed=True): ...
4446+
class ExplicitFalse(TypedDict, closed=False): ...
4447+
4448+
self.assertIsNone(Implicit.__closed__)
4449+
self.assertIs(ExplicitTrue.__closed__, True)
4450+
self.assertIs(ExplicitFalse.__closed__, False)
4451+
4452+
4453+
@skipIf(TYPING_3_14_0, "only supported on older versions")
4454+
def test_closed_typeddict_compat(self):
4455+
class Closed(TypedDict, closed=True):
4456+
__extra_items__: None
4457+
4458+
class Unclosed(TypedDict, closed=False):
4459+
...
4460+
4461+
class ChildUnclosed(Closed, Unclosed):
4462+
...
4463+
4464+
self.assertIsNone(ChildUnclosed.__closed__)
4465+
self.assertEqual(ChildUnclosed.__extra_items__, NoExtraItems)
4466+
4467+
class ChildClosed(Unclosed, Closed):
4468+
...
4469+
4470+
self.assertIsNone(ChildClosed.__closed__)
4471+
self.assertEqual(ChildClosed.__extra_items__, NoExtraItems)
4472+
4473+
def test_extra_items_class_arg(self):
4474+
class TD(TypedDict, extra_items=int):
4475+
a: str
4476+
4477+
self.assertIs(TD.__extra_items__, int)
4478+
self.assertEqual(TD.__annotations__, {'a': str})
4479+
self.assertEqual(TD.__required_keys__, frozenset({'a'}))
4480+
self.assertEqual(TD.__optional_keys__, frozenset())
4481+
4482+
class NoExtra(TypedDict):
4483+
a: str
4484+
4485+
self.assertIs(NoExtra.__extra_items__, NoExtraItems)
4486+
self.assertEqual(NoExtra.__annotations__, {'a': str})
4487+
self.assertEqual(NoExtra.__required_keys__, frozenset({'a'}))
4488+
self.assertEqual(NoExtra.__optional_keys__, frozenset())
4489+
44514490
def test_is_typeddict(self):
44524491
self.assertIs(is_typeddict(Point2D), True)
44534492
self.assertIs(is_typeddict(Point2Dor3D), True)
@@ -4803,7 +4842,8 @@ class AllTheThings(TypedDict):
48034842
},
48044843
)
48054844

4806-
def test_extra_keys_non_readonly(self):
4845+
@skipIf(TYPING_3_14_0, "Old syntax only supported on <3.14")
4846+
def test_extra_keys_non_readonly_legacy(self):
48074847
class Base(TypedDict, closed=True):
48084848
__extra_items__: str
48094849

@@ -4815,7 +4855,8 @@ class Child(Base):
48154855
self.assertEqual(Child.__readonly_keys__, frozenset({}))
48164856
self.assertEqual(Child.__mutable_keys__, frozenset({'a'}))
48174857

4818-
def test_extra_keys_readonly(self):
4858+
@skipIf(TYPING_3_14_0, "Only supported on <3.14")
4859+
def test_extra_keys_readonly_legacy(self):
48194860
class Base(TypedDict, closed=True):
48204861
__extra_items__: ReadOnly[str]
48214862

@@ -4827,7 +4868,21 @@ class Child(Base):
48274868
self.assertEqual(Child.__readonly_keys__, frozenset({}))
48284869
self.assertEqual(Child.__mutable_keys__, frozenset({'a'}))
48294870

4830-
def test_extra_key_required(self):
4871+
@skipIf(TYPING_3_14_0, "Only supported on <3.14")
4872+
def test_extra_keys_readonly_explicit_closed_legacy(self):
4873+
class Base(TypedDict, closed=True):
4874+
__extra_items__: ReadOnly[str]
4875+
4876+
class Child(Base, closed=True):
4877+
a: NotRequired[str]
4878+
4879+
self.assertEqual(Child.__required_keys__, frozenset({}))
4880+
self.assertEqual(Child.__optional_keys__, frozenset({'a'}))
4881+
self.assertEqual(Child.__readonly_keys__, frozenset({}))
4882+
self.assertEqual(Child.__mutable_keys__, frozenset({'a'}))
4883+
4884+
@skipIf(TYPING_3_14_0, "Only supported on <3.14")
4885+
def test_extra_key_required_legacy(self):
48314886
with self.assertRaisesRegex(
48324887
TypeError,
48334888
"Special key __extra_items__ does not support Required"
@@ -4840,16 +4895,16 @@ def test_extra_key_required(self):
48404895
):
48414896
TypedDict("A", {"__extra_items__": NotRequired[int]}, closed=True)
48424897

4843-
def test_regular_extra_items(self):
4898+
def test_regular_extra_items_legacy(self):
48444899
class ExtraReadOnly(TypedDict):
48454900
__extra_items__: ReadOnly[str]
48464901

48474902
self.assertEqual(ExtraReadOnly.__required_keys__, frozenset({'__extra_items__'}))
48484903
self.assertEqual(ExtraReadOnly.__optional_keys__, frozenset({}))
48494904
self.assertEqual(ExtraReadOnly.__readonly_keys__, frozenset({'__extra_items__'}))
48504905
self.assertEqual(ExtraReadOnly.__mutable_keys__, frozenset({}))
4851-
self.assertEqual(ExtraReadOnly.__extra_items__, None)
4852-
self.assertFalse(ExtraReadOnly.__closed__)
4906+
self.assertIs(ExtraReadOnly.__extra_items__, NoExtraItems)
4907+
self.assertIsNone(ExtraReadOnly.__closed__)
48534908

48544909
class ExtraRequired(TypedDict):
48554910
__extra_items__: Required[str]
@@ -4858,8 +4913,8 @@ class ExtraRequired(TypedDict):
48584913
self.assertEqual(ExtraRequired.__optional_keys__, frozenset({}))
48594914
self.assertEqual(ExtraRequired.__readonly_keys__, frozenset({}))
48604915
self.assertEqual(ExtraRequired.__mutable_keys__, frozenset({'__extra_items__'}))
4861-
self.assertEqual(ExtraRequired.__extra_items__, None)
4862-
self.assertFalse(ExtraRequired.__closed__)
4916+
self.assertIs(ExtraRequired.__extra_items__, NoExtraItems)
4917+
self.assertIsNone(ExtraRequired.__closed__)
48634918

48644919
class ExtraNotRequired(TypedDict):
48654920
__extra_items__: NotRequired[str]
@@ -4868,10 +4923,11 @@ class ExtraNotRequired(TypedDict):
48684923
self.assertEqual(ExtraNotRequired.__optional_keys__, frozenset({'__extra_items__'}))
48694924
self.assertEqual(ExtraNotRequired.__readonly_keys__, frozenset({}))
48704925
self.assertEqual(ExtraNotRequired.__mutable_keys__, frozenset({'__extra_items__'}))
4871-
self.assertEqual(ExtraNotRequired.__extra_items__, None)
4872-
self.assertFalse(ExtraNotRequired.__closed__)
4926+
self.assertIs(ExtraNotRequired.__extra_items__, NoExtraItems)
4927+
self.assertIsNone(ExtraNotRequired.__closed__)
48734928

4874-
def test_closed_inheritance(self):
4929+
@skipIf(TYPING_3_14_0, "Only supported on <3.14")
4930+
def test_closed_inheritance_legacy(self):
48754931
class Base(TypedDict, closed=True):
48764932
__extra_items__: ReadOnly[Union[str, None]]
48774933

@@ -4881,49 +4937,97 @@ class Base(TypedDict, closed=True):
48814937
self.assertEqual(Base.__mutable_keys__, frozenset({}))
48824938
self.assertEqual(Base.__annotations__, {})
48834939
self.assertEqual(Base.__extra_items__, ReadOnly[Union[str, None]])
4884-
self.assertTrue(Base.__closed__)
4940+
self.assertIs(Base.__closed__, True)
48854941

4886-
class Child(Base):
4942+
class Child(Base, closed=True):
48874943
a: int
48884944
__extra_items__: int
48894945

4890-
self.assertEqual(Child.__required_keys__, frozenset({'a', "__extra_items__"}))
4946+
self.assertEqual(Child.__required_keys__, frozenset({'a'}))
48914947
self.assertEqual(Child.__optional_keys__, frozenset({}))
48924948
self.assertEqual(Child.__readonly_keys__, frozenset({}))
4893-
self.assertEqual(Child.__mutable_keys__, frozenset({'a', "__extra_items__"}))
4894-
self.assertEqual(Child.__annotations__, {"__extra_items__": int, "a": int})
4895-
self.assertEqual(Child.__extra_items__, ReadOnly[Union[str, None]])
4896-
self.assertFalse(Child.__closed__)
4949+
self.assertEqual(Child.__mutable_keys__, frozenset({'a'}))
4950+
self.assertEqual(Child.__annotations__, {"a": int})
4951+
self.assertIs(Child.__extra_items__, int)
4952+
self.assertIs(Child.__closed__, True)
48974953

48984954
class GrandChild(Child, closed=True):
48994955
__extra_items__: str
49004956

4901-
self.assertEqual(GrandChild.__required_keys__, frozenset({'a', "__extra_items__"}))
4957+
self.assertEqual(GrandChild.__required_keys__, frozenset({'a'}))
49024958
self.assertEqual(GrandChild.__optional_keys__, frozenset({}))
49034959
self.assertEqual(GrandChild.__readonly_keys__, frozenset({}))
4904-
self.assertEqual(GrandChild.__mutable_keys__, frozenset({'a', "__extra_items__"}))
4905-
self.assertEqual(GrandChild.__annotations__, {"__extra_items__": int, "a": int})
4906-
self.assertEqual(GrandChild.__extra_items__, str)
4907-
self.assertTrue(GrandChild.__closed__)
4960+
self.assertEqual(GrandChild.__mutable_keys__, frozenset({'a'}))
4961+
self.assertEqual(GrandChild.__annotations__, {"a": int})
4962+
self.assertIs(GrandChild.__extra_items__, str)
4963+
self.assertIs(GrandChild.__closed__, True)
4964+
4965+
def test_closed_inheritance(self):
4966+
class Base(TypedDict, extra_items=ReadOnly[Union[str, None]]):
4967+
a: int
4968+
4969+
self.assertEqual(Base.__required_keys__, frozenset({"a"}))
4970+
self.assertEqual(Base.__optional_keys__, frozenset({}))
4971+
self.assertEqual(Base.__readonly_keys__, frozenset({}))
4972+
self.assertEqual(Base.__mutable_keys__, frozenset({"a"}))
4973+
self.assertEqual(Base.__annotations__, {"a": int})
4974+
self.assertEqual(Base.__extra_items__, ReadOnly[Union[str, None]])
4975+
self.assertIsNone(Base.__closed__)
4976+
4977+
class Child(Base, extra_items=int):
4978+
a: str
4979+
4980+
self.assertEqual(Child.__required_keys__, frozenset({'a'}))
4981+
self.assertEqual(Child.__optional_keys__, frozenset({}))
4982+
self.assertEqual(Child.__readonly_keys__, frozenset({}))
4983+
self.assertEqual(Child.__mutable_keys__, frozenset({'a'}))
4984+
self.assertEqual(Child.__annotations__, {"a": str})
4985+
self.assertIs(Child.__extra_items__, int)
4986+
self.assertIsNone(Child.__closed__)
4987+
4988+
class GrandChild(Child, closed=True):
4989+
a: float
4990+
4991+
self.assertEqual(GrandChild.__required_keys__, frozenset({'a'}))
4992+
self.assertEqual(GrandChild.__optional_keys__, frozenset({}))
4993+
self.assertEqual(GrandChild.__readonly_keys__, frozenset({}))
4994+
self.assertEqual(GrandChild.__mutable_keys__, frozenset({'a'}))
4995+
self.assertEqual(GrandChild.__annotations__, {"a": float})
4996+
self.assertIs(GrandChild.__extra_items__, NoExtraItems)
4997+
self.assertIs(GrandChild.__closed__, True)
4998+
4999+
class GrandGrandChild(GrandChild):
5000+
...
5001+
self.assertEqual(GrandGrandChild.__required_keys__, frozenset({'a'}))
5002+
self.assertEqual(GrandGrandChild.__optional_keys__, frozenset({}))
5003+
self.assertEqual(GrandGrandChild.__readonly_keys__, frozenset({}))
5004+
self.assertEqual(GrandGrandChild.__mutable_keys__, frozenset({'a'}))
5005+
self.assertEqual(GrandGrandChild.__annotations__, {"a": float})
5006+
self.assertIs(GrandGrandChild.__extra_items__, NoExtraItems)
5007+
self.assertIsNone(GrandGrandChild.__closed__)
49085008

49095009
def test_implicit_extra_items(self):
49105010
class Base(TypedDict):
49115011
a: int
49125012

4913-
self.assertEqual(Base.__extra_items__, None)
4914-
self.assertFalse(Base.__closed__)
5013+
self.assertIs(Base.__extra_items__, NoExtraItems)
5014+
self.assertIsNone(Base.__closed__)
49155015

49165016
class ChildA(Base, closed=True):
49175017
...
49185018

4919-
self.assertEqual(ChildA.__extra_items__, Never)
4920-
self.assertTrue(ChildA.__closed__)
5019+
self.assertEqual(ChildA.__extra_items__, NoExtraItems)
5020+
self.assertIs(ChildA.__closed__, True)
49215021

5022+
@skipIf(TYPING_3_14_0, "Backwards compatibility only for Python 3.13")
5023+
def test_implicit_extra_items_before_3_14(self):
5024+
class Base(TypedDict):
5025+
a: int
49225026
class ChildB(Base, closed=True):
49235027
__extra_items__: None
49245028

4925-
self.assertEqual(ChildB.__extra_items__, type(None))
4926-
self.assertTrue(ChildB.__closed__)
5029+
self.assertIs(ChildB.__extra_items__, type(None))
5030+
self.assertIs(ChildB.__closed__, True)
49275031

49285032
@skipIf(
49295033
TYPING_3_13_0,
@@ -4933,9 +5037,14 @@ class ChildB(Base, closed=True):
49335037
def test_backwards_compatibility(self):
49345038
with self.assertWarns(DeprecationWarning):
49355039
TD = TypedDict("TD", closed=int)
4936-
self.assertFalse(TD.__closed__)
5040+
self.assertIs(TD.__closed__, None)
49375041
self.assertEqual(TD.__annotations__, {"closed": int})
49385042

5043+
with self.assertWarns(DeprecationWarning):
5044+
TD = TypedDict("TD", extra_items=int)
5045+
self.assertIs(TD.__extra_items__, NoExtraItems)
5046+
self.assertEqual(TD.__annotations__, {"extra_items": int})
5047+
49395048

49405049
class AnnotatedTests(BaseTestCase):
49415050

0 commit comments

Comments
 (0)