mirror of https://github.com/python/cpython.git
gh-133823: require explicit empty sequence for 0-field `TypedDict` objects (#133863)
This commit is contained in:
parent
add828951e
commit
87312119da
|
@ -137,6 +137,12 @@ typing
|
||||||
Use the class-based syntax or the functional syntax instead.
|
Use the class-based syntax or the functional syntax instead.
|
||||||
(Contributed by Bénédikt Tran in :gh:`133817`.)
|
(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
|
Porting to Python 3.15
|
||||||
======================
|
======================
|
||||||
|
|
|
@ -8853,39 +8853,27 @@ class MultipleGenericBases(GenericParent[int], GenericParent[float]):
|
||||||
self.assertEqual(CallTypedDict.__orig_bases__, (TypedDict,))
|
self.assertEqual(CallTypedDict.__orig_bases__, (TypedDict,))
|
||||||
|
|
||||||
def test_zero_fields_typeddicts(self):
|
def test_zero_fields_typeddicts(self):
|
||||||
T1 = TypedDict("T1", {})
|
T1a = TypedDict("T1a", {})
|
||||||
|
T1b = TypedDict("T1b", [])
|
||||||
|
T1c = TypedDict("T1c", ())
|
||||||
class T2(TypedDict): pass
|
class T2(TypedDict): pass
|
||||||
class T3[tvar](TypedDict): pass
|
class T3[tvar](TypedDict): pass
|
||||||
S = TypeVar("S")
|
S = TypeVar("S")
|
||||||
class T4(TypedDict, Generic[S]): pass
|
class T4(TypedDict, Generic[S]): pass
|
||||||
|
|
||||||
expected_warning = re.escape(
|
for klass in T1a, T1b, T1c, T2, T3, T4:
|
||||||
"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__):
|
with self.subTest(klass=klass.__name__):
|
||||||
self.assertEqual(klass.__annotations__, {})
|
self.assertEqual(klass.__annotations__, {})
|
||||||
self.assertEqual(klass.__required_keys__, set())
|
self.assertEqual(klass.__required_keys__, set())
|
||||||
self.assertEqual(klass.__optional_keys__, set())
|
self.assertEqual(klass.__optional_keys__, set())
|
||||||
self.assertIsInstance(klass(), dict)
|
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):
|
def test_readonly_inheritance(self):
|
||||||
class Base1(TypedDict):
|
class Base1(TypedDict):
|
||||||
a: ReadOnly[int]
|
a: ReadOnly[int]
|
||||||
|
|
|
@ -3159,7 +3159,7 @@ def __subclasscheck__(cls, other):
|
||||||
__instancecheck__ = __subclasscheck__
|
__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.
|
"""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
|
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
|
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)}
|
ns = {'__annotations__': dict(fields)}
|
||||||
module = _caller()
|
module = _caller()
|
||||||
if module is not None:
|
if module is not None:
|
||||||
|
|
|
@ -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.
|
Loading…
Reference in New Issue