55
55
Never ,
56
56
NewType ,
57
57
NoDefault ,
58
+ NoExtraItems ,
58
59
NoReturn ,
59
60
NotRequired ,
60
61
Optional ,
128
129
# 3.13.0.rc1 fixes a problem with @deprecated
129
130
TYPING_3_13_0_RC = sys .version_info [:4 ] >= (3 , 13 , 0 , "candidate" )
130
131
132
+ TYPING_3_14_0 = sys .version_info [:3 ] >= (3 , 14 , 0 )
133
+
131
134
# https://github.com/python/cpython/pull/27017 was backported into some 3.9 and 3.10
132
135
# versions, but not all
133
136
HAS_FORWARD_MODULE = "module" in inspect .signature (typing ._type_check ).parameters
@@ -4140,18 +4143,25 @@ def test_basics_keywords_syntax(self):
4140
4143
def test_typeddict_special_keyword_names (self ):
4141
4144
with self .assertWarns (DeprecationWarning ):
4142
4145
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 )
4144
4148
self .assertEqual (TD .__name__ , 'TD' )
4145
4149
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 )
4147
4154
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" )
4149
4157
self .assertEqual (a ['cls' ], str )
4150
4158
self .assertEqual (a ['self' ], 42 )
4151
4159
self .assertEqual (a ['typename' ], 'foo' )
4152
4160
self .assertEqual (a ['_typename' ], 53 )
4153
4161
self .assertEqual (a ['fields' ], [('bar' , tuple )])
4154
4162
self .assertEqual (a ['_fields' ], {'baz' , set })
4163
+ self .assertIsNone (a ['closed' ])
4164
+ self .assertEqual (a ['extra_items' ], "tea pot" )
4155
4165
4156
4166
def test_typeddict_create_errors (self ):
4157
4167
with self .assertRaises (TypeError ):
@@ -4414,24 +4424,6 @@ class ChildWithInlineAndOptional(Untotal, Inline):
4414
4424
{'inline' : bool , 'untotal' : str , 'child' : bool },
4415
4425
)
4416
4426
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
-
4435
4427
wrong_bases = [
4436
4428
(One , Regular ),
4437
4429
(Regular , One ),
@@ -4448,6 +4440,53 @@ class ChildClosed(Unclosed, Closed):
4448
4440
class Wrong (* bases ):
4449
4441
pass
4450
4442
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
+
4451
4490
def test_is_typeddict (self ):
4452
4491
self .assertIs (is_typeddict (Point2D ), True )
4453
4492
self .assertIs (is_typeddict (Point2Dor3D ), True )
@@ -4803,7 +4842,8 @@ class AllTheThings(TypedDict):
4803
4842
},
4804
4843
)
4805
4844
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 ):
4807
4847
class Base (TypedDict , closed = True ):
4808
4848
__extra_items__ : str
4809
4849
@@ -4815,7 +4855,8 @@ class Child(Base):
4815
4855
self .assertEqual (Child .__readonly_keys__ , frozenset ({}))
4816
4856
self .assertEqual (Child .__mutable_keys__ , frozenset ({'a' }))
4817
4857
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 ):
4819
4860
class Base (TypedDict , closed = True ):
4820
4861
__extra_items__ : ReadOnly [str ]
4821
4862
@@ -4827,7 +4868,21 @@ class Child(Base):
4827
4868
self .assertEqual (Child .__readonly_keys__ , frozenset ({}))
4828
4869
self .assertEqual (Child .__mutable_keys__ , frozenset ({'a' }))
4829
4870
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 ):
4831
4886
with self .assertRaisesRegex (
4832
4887
TypeError ,
4833
4888
"Special key __extra_items__ does not support Required"
@@ -4840,16 +4895,16 @@ def test_extra_key_required(self):
4840
4895
):
4841
4896
TypedDict ("A" , {"__extra_items__" : NotRequired [int ]}, closed = True )
4842
4897
4843
- def test_regular_extra_items (self ):
4898
+ def test_regular_extra_items_legacy (self ):
4844
4899
class ExtraReadOnly (TypedDict ):
4845
4900
__extra_items__ : ReadOnly [str ]
4846
4901
4847
4902
self .assertEqual (ExtraReadOnly .__required_keys__ , frozenset ({'__extra_items__' }))
4848
4903
self .assertEqual (ExtraReadOnly .__optional_keys__ , frozenset ({}))
4849
4904
self .assertEqual (ExtraReadOnly .__readonly_keys__ , frozenset ({'__extra_items__' }))
4850
4905
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__ )
4853
4908
4854
4909
class ExtraRequired (TypedDict ):
4855
4910
__extra_items__ : Required [str ]
@@ -4858,8 +4913,8 @@ class ExtraRequired(TypedDict):
4858
4913
self .assertEqual (ExtraRequired .__optional_keys__ , frozenset ({}))
4859
4914
self .assertEqual (ExtraRequired .__readonly_keys__ , frozenset ({}))
4860
4915
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__ )
4863
4918
4864
4919
class ExtraNotRequired (TypedDict ):
4865
4920
__extra_items__ : NotRequired [str ]
@@ -4868,10 +4923,11 @@ class ExtraNotRequired(TypedDict):
4868
4923
self .assertEqual (ExtraNotRequired .__optional_keys__ , frozenset ({'__extra_items__' }))
4869
4924
self .assertEqual (ExtraNotRequired .__readonly_keys__ , frozenset ({}))
4870
4925
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__ )
4873
4928
4874
- def test_closed_inheritance (self ):
4929
+ @skipIf (TYPING_3_14_0 , "Only supported on <3.14" )
4930
+ def test_closed_inheritance_legacy (self ):
4875
4931
class Base (TypedDict , closed = True ):
4876
4932
__extra_items__ : ReadOnly [Union [str , None ]]
4877
4933
@@ -4881,49 +4937,97 @@ class Base(TypedDict, closed=True):
4881
4937
self .assertEqual (Base .__mutable_keys__ , frozenset ({}))
4882
4938
self .assertEqual (Base .__annotations__ , {})
4883
4939
self .assertEqual (Base .__extra_items__ , ReadOnly [Union [str , None ]])
4884
- self .assertTrue (Base .__closed__ )
4940
+ self .assertIs (Base .__closed__ , True )
4885
4941
4886
- class Child (Base ):
4942
+ class Child (Base , closed = True ):
4887
4943
a : int
4888
4944
__extra_items__ : int
4889
4945
4890
- self .assertEqual (Child .__required_keys__ , frozenset ({'a' , "__extra_items__" }))
4946
+ self .assertEqual (Child .__required_keys__ , frozenset ({'a' }))
4891
4947
self .assertEqual (Child .__optional_keys__ , frozenset ({}))
4892
4948
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 )
4897
4953
4898
4954
class GrandChild (Child , closed = True ):
4899
4955
__extra_items__ : str
4900
4956
4901
- self .assertEqual (GrandChild .__required_keys__ , frozenset ({'a' , "__extra_items__" }))
4957
+ self .assertEqual (GrandChild .__required_keys__ , frozenset ({'a' }))
4902
4958
self .assertEqual (GrandChild .__optional_keys__ , frozenset ({}))
4903
4959
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__ )
4908
5008
4909
5009
def test_implicit_extra_items (self ):
4910
5010
class Base (TypedDict ):
4911
5011
a : int
4912
5012
4913
- self .assertEqual (Base .__extra_items__ , None )
4914
- self .assertFalse (Base .__closed__ )
5013
+ self .assertIs (Base .__extra_items__ , NoExtraItems )
5014
+ self .assertIsNone (Base .__closed__ )
4915
5015
4916
5016
class ChildA (Base , closed = True ):
4917
5017
...
4918
5018
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 )
4921
5021
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
4922
5026
class ChildB (Base , closed = True ):
4923
5027
__extra_items__ : None
4924
5028
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 )
4927
5031
4928
5032
@skipIf (
4929
5033
TYPING_3_13_0 ,
@@ -4933,9 +5037,14 @@ class ChildB(Base, closed=True):
4933
5037
def test_backwards_compatibility (self ):
4934
5038
with self .assertWarns (DeprecationWarning ):
4935
5039
TD = TypedDict ("TD" , closed = int )
4936
- self .assertFalse (TD .__closed__ )
5040
+ self .assertIs (TD .__closed__ , None )
4937
5041
self .assertEqual (TD .__annotations__ , {"closed" : int })
4938
5042
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
+
4939
5048
4940
5049
class AnnotatedTests (BaseTestCase ):
4941
5050
0 commit comments