Skip to content

Commit f578136

Browse files
committed
require explicit empty sequence for 0-field TypedDict objects
1 parent 30b1d8f commit f578136

File tree

4 files changed

+20
-44
lines changed

4 files changed

+20
-44
lines changed

Doc/whatsnew/3.15.rst

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,10 +118,13 @@ Deprecated
118118
Removed
119119
=======
120120

121-
module_name
122-
-----------
121+
typing
122+
------
123123

124-
* TODO
124+
* Using ``TypedDict("T")`` or ``TypedDict("T", None)`` to construct
125+
:class:`~typing.TypedDict` with zero fields is no more supported.
126+
Use ``TypedDict("T", [])`` instead.
127+
(Contributed by Bénédikt Tran in :gh:`133823`.)
125128

126129

127130
Porting to Python 3.15

Lib/test/test_typing.py

Lines changed: 10 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8904,39 +8904,27 @@ class MultipleGenericBases(GenericParent[int], GenericParent[float]):
89048904
self.assertEqual(CallTypedDict.__orig_bases__, (TypedDict,))
89058905

89068906
def test_zero_fields_typeddicts(self):
8907-
T1 = TypedDict("T1", {})
8907+
T1a = TypedDict("T1a", {})
8908+
T1b = TypedDict("T1b", [])
8909+
T1c = TypedDict("T1c", ())
89088910
class T2(TypedDict): pass
89098911
class T3[tvar](TypedDict): pass
89108912
S = TypeVar("S")
89118913
class T4(TypedDict, Generic[S]): pass
89128914

8913-
expected_warning = re.escape(
8914-
"Failing to pass a value for the 'fields' parameter is deprecated "
8915-
"and will be disallowed in Python 3.15. "
8916-
"To create a TypedDict class with 0 fields "
8917-
"using the functional syntax, "
8918-
"pass an empty dictionary, e.g. `T5 = TypedDict('T5', {})`."
8919-
)
8920-
with self.assertWarnsRegex(DeprecationWarning, fr"^{expected_warning}$"):
8921-
T5 = TypedDict('T5')
8922-
8923-
expected_warning = re.escape(
8924-
"Passing `None` as the 'fields' parameter is deprecated "
8925-
"and will be disallowed in Python 3.15. "
8926-
"To create a TypedDict class with 0 fields "
8927-
"using the functional syntax, "
8928-
"pass an empty dictionary, e.g. `T6 = TypedDict('T6', {})`."
8929-
)
8930-
with self.assertWarnsRegex(DeprecationWarning, fr"^{expected_warning}$"):
8931-
T6 = TypedDict('T6', None)
8932-
8933-
for klass in T1, T2, T3, T4, T5, T6:
8915+
for klass in T1a, T1b, T1c, T2, T3, T4:
89348916
with self.subTest(klass=klass.__name__):
89358917
self.assertEqual(klass.__annotations__, {})
89368918
self.assertEqual(klass.__required_keys__, set())
89378919
self.assertEqual(klass.__optional_keys__, set())
89388920
self.assertIsInstance(klass(), dict)
89398921

8922+
def test_errors(self):
8923+
with self.assertRaisesRegex(TypeError, "missing 1 required.*argument"):
8924+
TypedDict('TD')
8925+
with self.assertRaisesRegex(TypeError, "object is not iterable"):
8926+
TypedDict('TD', None)
8927+
89408928
def test_readonly_inheritance(self):
89418929
class Base1(TypedDict):
89428930
a: ReadOnly[int]

Lib/typing.py

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3198,7 +3198,7 @@ def __subclasscheck__(cls, other):
31983198
__instancecheck__ = __subclasscheck__
31993199

32003200

3201-
def TypedDict(typename, fields=_sentinel, /, *, total=True):
3201+
def TypedDict(typename, fields, /, *, total=True):
32023202
"""A simple typed namespace. At runtime it is equivalent to a plain dict.
32033203
32043204
TypedDict creates a dictionary type such that a type checker will expect all
@@ -3253,24 +3253,6 @@ class DatabaseUser(TypedDict):
32533253
username: str # the "username" key can be changed
32543254
32553255
"""
3256-
if fields is _sentinel or fields is None:
3257-
import warnings
3258-
3259-
if fields is _sentinel:
3260-
deprecated_thing = "Failing to pass a value for the 'fields' parameter"
3261-
else:
3262-
deprecated_thing = "Passing `None` as the 'fields' parameter"
3263-
3264-
example = f"`{typename} = TypedDict({typename!r}, {{{{}}}})`"
3265-
deprecation_msg = (
3266-
"{name} is deprecated and will be disallowed in Python {remove}. "
3267-
"To create a TypedDict class with 0 fields "
3268-
"using the functional syntax, "
3269-
"pass an empty dictionary, e.g. "
3270-
) + example + "."
3271-
warnings._deprecated(deprecated_thing, message=deprecation_msg, remove=(3, 15))
3272-
fields = {}
3273-
32743256
ns = {'__annotations__': dict(fields)}
32753257
module = _caller()
32763258
if module is not None:
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Remove support for ``TypedDict("T")`` and ``TypedDict("T", None)`` calls for
2+
constructing :class:`typing.TypedDict` objects with zero fields. Patch by
3+
Bénédikt Tran.

0 commit comments

Comments
 (0)