8000 gh-133823: require explicit empty sequence for 0-field `TypedDict` ob… · python/cpython@8731211 · GitHub
[go: up one dir, main page]

Skip to content

Commit 8731211

Browse files
authored
gh-133823: require explicit empty sequence for 0-field TypedDict objects (#133863)
1 parent add8289 commit 8731211

File tree

4 files changed

+20
-41
lines changed

4 files changed

+20
-41
lines changed

Doc/whatsnew/3.15.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,12 @@ typing
137137
Use the class-based syntax or the functional syntax instead.
138138
(Contributed by Bénédikt Tran in :gh:`133817`.)
139139

140+
* Using ``TD = TypedDict("TD")`` or ``TD = TypedDict("TD", None)`` to
141+
construct a :class:`~typing.TypedDict` type with zero field is no
142+
longer supported. Use ``class TD(TypedDict): pass``
143+
or ``TD = TypedDict("TD", {})`` instead.
144+
(Contributed by Bénédikt Tran in :gh:`133823`.)
145+
140146

141147
Porting to Python 3.15
142148
======================

Lib/test/test_typing.py

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

88558855
def test_zero_fields_typeddicts(self):
8856-
T1 = TypedDict("T1", {})
8856+
T1a = TypedDict("T1a", {})
8857+
T1b = TypedDict("T1b", [])
8858+
T1c = TypedDict("T1c", ())
88578859
class T2(TypedDict): pass
88588860
class T3[tvar](TypedDict): pass
88598861
S = TypeVar("S")
88608862
class T4(TypedDict, Generic[S]): pass
88618863

8862-
expected_warning = re.escape(
8863-
"Failing to pass a value for the 'fields' parameter is deprecated "
8864-
"and will be disallowed in Python 3.15. "
8865-
"To create a TypedDict class with 0 fields "
8866-
"using the functional syntax, "
8867-
"pass an empty dictionary, e.g. `T5 = TypedDict('T5', {})`."
8868-
)
8869-
with self.assertWarnsRegex(DeprecationWarning, fr"^{expected_warning}$"):
8870-
T5 = TypedDict('T5')
8871-
8872-
expected_warning = re.escape(
8873-
"Passing `None` as the 'fields' parameter is deprecated "
8874-
"and will be disallowed in Python 3.15. "
8875-
"To create a TypedDict class with 0 fields "
8876-
"using the functional syntax, "
8877-
"pass an empty dictionary, e.g. `T6 = TypedDict('T6', {})`."
8878-
)
8879-
with self.assertWarnsRegex(DeprecationWarning, fr"^{expected_warning}$"):
8880-
T6 = TypedDict('T6', None)
8881-
8882-
for klass in T1, T2, T3, T4, T5, T6:
8864+
for klass in T1a, T1b, T1c, T2, T3, T4:
88838865
with self.subTest(klass=klass.__name__):
88848866
self.assertEqual(klass.__annotations__, {})
88858867
self.assertEqual(klass.__required_keys__, set())
88868868
self.assertEqual(klass.__optional_keys__, set())
88878869
self.assertIsInstance(klass(), dict)
88888870

8871+
def test_errors(self):
8872+
with self.assertRaisesRegex(TypeError, "missing 1 required.*argument"):
8873+
TypedDict('TD')
8874+
with self.assertRaisesRegex(TypeError, "object is not iterable"):
8875+
TypedDict('TD', None)
8876+
88898877
def test_readonly_inheritance(self):
88908878
class Base1(TypedDict):
88918879
a: ReadOnly[int]

Lib/typing.py

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

31613161

3162-
def TypedDict(typename, fields=_sentinel, /, *, total=True):
3162+
def TypedDict(typename, fields, /, *, total=True):
31633163
"""A simple typed namespace. At runtime it is equivalent to a plain dict.
31643164
31653165
TypedDict creates a dictionary type such that a type checker will expect all
@@ -3214,24 +3214,6 @@ class DatabaseUser(TypedDict):
32143214
username: str # the "username" key can be changed
32153215
32163216
"""
3217-
if fields is _sentinel or fields is None:
3218-
import warnings
3219-
3220-
if fields is _sentinel:
3221-
deprecated_thing = "Failing to pass a value for the 'fields' parameter"
3222-
else:
3223-
deprecated_thing = "Passing `None` as the 'fields' parameter"
3224-
3225-
example = f"`{typename} = TypedDict({typename!r}, {{{{}}}})`"
3226-
deprecation_msg = (
3227-
"{name} is deprecated and will be disallowed in Python {remove}. "
3228-
"To create a TypedDict class with 0 fields "
3229-
"using the functional syntax, "
3230-
"pass an empty dictionary, e.g. "
3231-
) + example + "."
3232-
warnings._deprecated(deprecated_thing, message=deprecation_msg, remove=(3, 15))
3233-
fields = {}
3234-
32353217
ns = {'__annotations__': dict(fields)}
32363218
module = _caller()
32373219
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 ``TD = TypedDict("TD")`` and ``TD = TypedDict("TD", None)``
2+
calls for constructing :class:`typing.TypedDict` objects with zero field.
3+
Patch by Bénédikt Tran.

0 commit comments

Comments
 (0)
0