https://github.com/python/cpython/commit/87312119dab72f23cf337bcd9c30889513f050ee
commit: 87312119dab72f23cf337bcd9c30889513f050ee
branch: main
author: Bénédikt Tran <[email protected]>
committer: picnixz <[email protected]>
date: 2025-05-11T08:04:45Z
summary:
gh-133823: require explicit empty sequence for 0-field `TypedDict` objects
(#133863)
files:
A Misc/NEWS.d/next/Library/2025-05-11-08-48-55.gh-issue-133823.F8udQy.rst
M Doc/whatsnew/3.15.rst
M Lib/test/test_typing.py
M Lib/typing.py
diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst
index b543f71a44d5b3..5a9bf1ae3c97bf 100644
--- a/Doc/whatsnew/3.15.rst
+++ b/Doc/whatsnew/3.15.rst
@@ -137,6 +137,12 @@ typing
Use the class-based syntax or the functional syntax instead.
(Contributed by Bénédikt Tran in :gh:`133817`.)
+* Using ``TD = TypedDict("TD")`` or ``TD = TypedDict("TD", None)`` to
+ construct a :class:`~typing.TypedDict` type with zero field is no
+ longer supported. Use ``class TD(TypedDict): pass``
+ or ``TD = TypedDict("TD", {})`` instead.
+ (Contributed by Bénédikt Tran in :gh:`133823`.)
+
Porting to Python 3.15
======================
diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py
index f4d75c4376f0a1..bc03e6bf2d7962 100644
--- a/Lib/test/test_typing.py
+++ b/Lib/test/test_typing.py
@@ -8853,39 +8853,27 @@ class MultipleGenericBases(GenericParent[int],
GenericParent[float]):
self.assertEqual(CallTypedDict.__orig_bases__, (TypedDict,))
def test_zero_fields_typeddicts(self):
- T1 = TypedDict("T1", {})
+ T1a = TypedDict("T1a", {})
+ T1b = TypedDict("T1b", [])
+ T1c = TypedDict("T1c", ())
class T2(TypedDict): pass
class T3[tvar](TypedDict): pass
S = TypeVar("S")
class T4(TypedDict, Generic[S]): pass
- expected_warning = re.escape(
- "Failing to pass a value for the 'fields' parameter is deprecated "
- "and will be disallowed in Python 3.15. "
- "To create a TypedDict class with 0 fields "
- "using the functional syntax, "
- "pass an empty dictionary, e.g. `T5 = TypedDict('T5', {})`."
- )
- with self.assertWarnsRegex(DeprecationWarning,
fr"^{expected_warning}$"):
- T5 = TypedDict('T5')
-
- expected_warning = re.escape(
- "Passing `None` as the 'fields' parameter is deprecated "
- "and will be disallowed in Python 3.15. "
- "To create a TypedDict class with 0 fields "
- "using the functional syntax, "
- "pass an empty dictionary, e.g. `T6 = TypedDict('T6', {})`."
- )
- with self.assertWarnsRegex(DeprecationWarning,
fr"^{expected_warning}$"):
- T6 = TypedDict('T6', None)
-
- for klass in T1, T2, T3, T4, T5, T6:
+ for klass in T1a, T1b, T1c, T2, T3, T4:
with self.subTest(klass=klass.__name__):
self.assertEqual(klass.__annotations__, {})
self.assertEqual(klass.__required_keys__, set())
self.assertEqual(klass.__optional_keys__, set())
self.assertIsInstance(klass(), dict)
+ def test_errors(self):
+ with self.assertRaisesRegex(TypeError, "missing 1 required.*argument"):
+ TypedDict('TD')
+ with self.assertRaisesRegex(TypeError, "object is not iterable"):
+ TypedDict('TD', None)
+
def test_readonly_inheritance(self):
class Base1(TypedDict):
a: ReadOnly[int]
diff --git a/Lib/typing.py b/Lib/typing.py
index d4c808050b35bc..44f39e9672f46b 100644
--- a/Lib/typing.py
+++ b/Lib/typing.py
@@ -3159,7 +3159,7 @@ def __subclasscheck__(cls, other):
__instancecheck__ = __subclasscheck__
-def TypedDict(typename, fields=_sentinel, /, *, total=True):
+def TypedDict(typename, fields, /, *, total=True):
"""A simple typed namespace. At runtime it is equivalent to a plain dict.
TypedDict creates a dictionary type such that a type checker will expect
all
@@ -3214,24 +3214,6 @@ class DatabaseUser(TypedDict):
username: str # the "username" key can be changed
"""
- if fields is _sentinel or fields is None:
- import warnings
-
- if fields is _sentinel:
- deprecated_thing = "Failing to pass a value for the 'fields'
parameter"
- else:
- deprecated_thing = "Passing `None` as the 'fields' parameter"
-
- example = f"`{typename} = TypedDict({typename!r}, {{{{}}}})`"
- deprecation_msg = (
- "{name} is deprecated and will be disallowed in Python {remove}. "
- "To create a TypedDict class with 0 fields "
- "using the functional syntax, "
- "pass an empty dictionary, e.g. "
- ) + example + "."
- warnings._deprecated(deprecated_thing, message=deprecation_msg,
remove=(3, 15))
- fields = {}
-
ns = {'__annotations__': dict(fields)}
module = _caller()
if module is not None:
diff --git
a/Misc/NEWS.d/next/Library/2025-05-11-08-48-55.gh-issue-133823.F8udQy.rst
b/Misc/NEWS.d/next/Library/2025-05-11-08-48-55.gh-issue-133823.F8udQy.rst
new file mode 100644
index 00000000000000..67b44ac3ef319d
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-11-08-48-55.gh-issue-133823.F8udQy.rst
@@ -0,0 +1,3 @@
+Remove support for ``TD = TypedDict("TD")`` and ``TD = TypedDict("TD", None)``
+calls for constructing :class:`typing.TypedDict` objects with zero field.
+Patch by Bénédikt Tran.
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: [email protected]