From 480369f992a46c3511e4941342f890222f2f415a Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Wed, 14 Jun 2023 14:38:39 +0100 Subject: [PATCH] gh-105570: Deprecate unusual ways of creating empty TypedDicts --- Doc/library/typing.rst | 8 +++++ Doc/whatsnew/3.13.rst | 15 ++++---- Lib/test/test_typing.py | 34 +++++++++++++++++++ Lib/typing.py | 19 +++++++++-- ...-06-14-14-32-31.gh-issue-105570.sFTtQU.rst | 5 +++ 5 files changed, 73 insertions(+), 8 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-06-14-14-32-31.gh-issue-105570.sFTtQU.rst diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index aedef091e44c00..8b7c181741e894 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -2388,6 +2388,14 @@ These are not used in annotations. They are building blocks for declaring types. .. versionchanged:: 3.13 Removed support for the keyword-argument method of creating ``TypedDict``\ s. + .. deprecated-removed:: 3.13 3.15 + When using the functional syntax to create a TypedDict class, failing to + pass a value to the 'fields' parameter (``TD = TypedDict("TD")``) is + deprecated. Passing ``None`` to the 'fields' parameter + (``TD = TypedDict("TD", None)``) is also deprecated. Both will be + disallowed in Python 3.15. To create a TypedDict class with 0 fields, + use ``class TD(TypedDict): pass`` or ``TD = TypedDict("TD", {})``. + Generic concrete collections ---------------------------- diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index cf7c2ca24429d6..7f61ade808cce5 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -146,12 +146,15 @@ Deprecated be disallowed in Python 3.15. Use the class-based syntax or the functional syntax instead. (Contributed by Alex Waygood in :gh:`105566`.) * When using the functional syntax to create a :class:`typing.NamedTuple` - class, failing to pass a value to the 'fields' parameter - (``NT = NamedTuple("NT")``) is deprecated. Passing ``None`` to the 'fields' - parameter (``NT = NamedTuple("NT", None)``) is also deprecated. Both will be - disallowed in Python 3.15. To create a NamedTuple class with 0 fields, use - ``class NT(NamedTuple): pass`` or ``NT = NamedTuple("NT", [])``. - (Contributed by Alex Waygood in :gh:`105566`.) + class or a :class:`typing.TypedDict` class, failing to pass a value to the + 'fields' parameter (``NT = NamedTuple("NT")`` or ``TD = TypedDict("TD")``) is + deprecated. Passing ``None`` to the 'fields' parameter + (``NT = NamedTuple("NT", None)`` or ``TD = TypedDict("TD", None)``) is also + deprecated. Both will be disallowed in Python 3.15. To create a NamedTuple + class with 0 fields, use ``class NT(NamedTuple): pass`` or + ``NT = NamedTuple("NT", [])``. To create a TypedDict class with 0 fields, use + ``class TD(TypedDict): pass`` or ``TD = TypedDict("TD", {})``. + (Contributed by Alex Waygood in :gh:`105566` and :gh:`105570`.) * :mod:`array`'s ``'u'`` format code, deprecated in docs since Python 3.3, emits :exc:`DeprecationWarning` since 3.13 diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 92f38043af5c03..3eb0fcad69e5e5 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -7823,6 +7823,40 @@ class MultipleGenericBases(GenericParent[int], GenericParent[float]): self.assertEqual(MultipleGenericBases.__orig_bases__, (GenericParent[int], GenericParent[float])) self.assertEqual(CallTypedDict.__orig_bases__, (TypedDict,)) + def test_zero_fields_typeddicts(self): + T1 = TypedDict("T1", {}) + 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: + 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) + class RequiredTests(BaseTestCase): diff --git a/Lib/typing.py b/Lib/typing.py index 570cb80cfeeb2c..1dd9398344639b 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -2908,7 +2908,7 @@ def __subclasscheck__(cls, other): __instancecheck__ = __subclasscheck__ -def TypedDict(typename, fields=None, /, *, total=True): +def TypedDict(typename, fields=_sentinel, /, *, 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 @@ -2955,7 +2955,22 @@ class Point2D(TypedDict): See PEP 655 for more details on Required and NotRequired. """ - if fields is None: + 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)} diff --git a/Misc/NEWS.d/next/Library/2023-06-14-14-32-31.gh-issue-105570.sFTtQU.rst b/Misc/NEWS.d/next/Library/2023-06-14-14-32-31.gh-issue-105570.sFTtQU.rst new file mode 100644 index 00000000000000..68a5083563246d --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-06-14-14-32-31.gh-issue-105570.sFTtQU.rst @@ -0,0 +1,5 @@ +Deprecate two methods of creating :class:`typing.TypedDict` classes with 0 +fields using the functional syntax: ``TD = TypedDict("TD")`` and ``TD = +TypedDict("TD", None)``. Both will be disallowed in Python 3.15. To create a +``TypedDict`` class with 0 fields, either use ``class TD(TypedDict): pass`` +or ``TD = TypedDict("TD", {})``.