https://github.com/python/cpython/commit/87312119dab72f23cf337bcd9c30889513f050ee commit: 87312119dab72f23cf337bcd9c30889513f050ee branch: main author: Bénédikt Tran <10796600+picn...@users.noreply.github.com> committer: picnixz <10796600+picn...@users.noreply.github.com> 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 -- python-checkins@python.org To unsubscribe send an email to python-checkins-le...@python.org https://mail.python.org/mailman3/lists/python-checkins.python.org/ Member address: arch...@mail-archive.com