From f5781366040ce68fc0588939aa0a79f6b5a828e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 11 May 2025 08:49:22 +0200 Subject: [PATCH 1/3] require explicit empty sequence for 0-field TypedDict objects --- Doc/whatsnew/3.15.rst | 9 ++++-- Lib/test/test_typing.py | 32 ++++++------------- Lib/typing.py | 20 +----------- ...-05-11-08-48-55.gh-issue-133823.F8udQy.rst | 3 ++ 4 files changed, 20 insertions(+), 44 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-05-11-08-48-55.gh-issue-133823.F8udQy.rst diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index d1e58c1b764eb9..41b625f8b611e9 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -118,10 +118,13 @@ Deprecated Removed ======= -module_name ------------ +typing +------ -* TODO +* Using ``TypedDict("T")`` or ``TypedDict("T", None)`` to construct + :class:`~typing.TypedDict` with zero fields is no more supported. + Use ``TypedDict("T", [])`` 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 8c55ba4623e719..96aae20c8ecb48 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -8904,39 +8904,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 2baf655256d1eb..f4bc17b8f60015 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -3198,7 +3198,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 @@ -3253,24 +3253,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..e0a6c412560fef --- /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 ``TypedDict("T")`` and ``TypedDict("T", None)`` calls for +constructing :class:`typing.TypedDict` objects with zero fields. Patch by +Bénédikt Tran. From be16551b6a581d3710145aa4f9aa383f0db0748b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 11 May 2025 09:01:06 +0200 Subject: [PATCH 2/3] improve wording --- Doc/whatsnew/3.15.rst | 7 ++++--- .../Library/2025-05-11-08-48-55.gh-issue-133823.F8udQy.rst | 6 +++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 41b625f8b611e9..b099844d1e0a2f 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -121,9 +121,10 @@ Removed typing ------ -* Using ``TypedDict("T")`` or ``TypedDict("T", None)`` to construct - :class:`~typing.TypedDict` with zero fields is no more supported. - Use ``TypedDict("T", [])`` instead. +* 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 TTD(TypedDict): pass`` + or ``TD = TypedDict("TD", {})`` instead. (Contributed by Bénédikt Tran in :gh:`133823`.) 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 index e0a6c412560fef..67b44ac3ef319d 100644 --- 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 @@ -1,3 +1,3 @@ -Remove support for ``TypedDict("T")`` and ``TypedDict("T", None)`` calls for -constructing :class:`typing.TypedDict` objects with zero fields. Patch by -Bénédikt Tran. +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. From 6a88cbc4e14fe0b6c7dabb618fabe2fe908ff994 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 11 May 2025 09:02:24 +0200 Subject: [PATCH 3/3] Update Doc/whatsnew/3.15.rst --- Doc/whatsnew/3.15.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index b099844d1e0a2f..3bde8cf5b96bfb 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -123,7 +123,7 @@ typing * 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 TTD(TypedDict): pass`` + longer supported. Use ``class TD(TypedDict): pass`` or ``TD = TypedDict("TD", {})`` instead. (Contributed by Bénédikt Tran in :gh:`133823`.)