summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlex Waygood <Alex.Waygood@Gmail.com>2023-06-14 14:58:41 (GMT)
committerGitHub <noreply@github.com>2023-06-14 14:58:41 (GMT)
commit7b1f0f204a785485de1daf9d26828a81953537e4 (patch)
tree6f11456f04444190aa5b2e3bf1cc5ff41f915575
parentd32e8d6070057eb7ad0eb2f9d9f1efab38b2cff4 (diff)
downloadcpython-7b1f0f204a785485de1daf9d26828a81953537e4.zip
cpython-7b1f0f204a785485de1daf9d26828a81953537e4.tar.gz
cpython-7b1f0f204a785485de1daf9d26828a81953537e4.tar.bz2
gh-105570: Deprecate unusual ways of creating empty TypedDicts (#105780)
Deprecate two methods of creating 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", {})`.
-rw-r--r--Doc/library/typing.rst8
-rw-r--r--Doc/whatsnew/3.13.rst15
-rw-r--r--Lib/test/test_typing.py34
-rw-r--r--Lib/typing.py19
-rw-r--r--Misc/NEWS.d/next/Library/2023-06-14-14-32-31.gh-issue-105570.sFTtQU.rst5
5 files changed, 73 insertions, 8 deletions
diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst
index 31861a0..73555d5 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", {})``.
+
Protocols
---------
diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst
index cf7c2ca..7f61ade 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 92f3804..3eb0fca 100644
--- a/Lib/test/test_typing.py
+++ b/Lib/test/test_typing.py
@@ -7823,6 +7823,40 @@ class TypedDictTests(BaseTestCase):
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 570cb80..1dd9398 100644
--- a/Lib/typing.py
+++ b/Lib/typing.py
@@ -2908,7 +2908,7 @@ class _TypedDictMeta(type):
__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 @@ def TypedDict(typename, fields=None, /, *, total=True):
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 0000000..e31a8ee
--- /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", {})``.