Skip to content

Commit e0c6670

Browse files
authored
Deprecate mypy_extensions.TypedDict (#47)
* Deprecate `mypy_extensions.TypedDict` * Formatting
1 parent a1b59f7 commit e0c6670

File tree

3 files changed

+63
-19
lines changed

3 files changed

+63
-19
lines changed

mypy_extensions.py

+17-2
Original file line numberDiff line numberDiff line change
@@ -42,17 +42,32 @@ def _typeddict_new(cls, _typename, _fields=None, **kwargs):
4242
except (AttributeError, ValueError):
4343
pass
4444

45-
return _TypedDictMeta(_typename, (), ns)
45+
return _TypedDictMeta(_typename, (), ns, _from_functional_call=True)
4646

4747

4848
class _TypedDictMeta(type):
49-
def __new__(cls, name, bases, ns, total=True):
49+
def __new__(cls, name, bases, ns, total=True, _from_functional_call=False):
5050
# Create new typed dict class object.
5151
# This method is called directly when TypedDict is subclassed,
5252
# or via _typeddict_new when TypedDict is instantiated. This way
5353
# TypedDict supports all three syntaxes described in its docstring.
5454
# Subclasses and instances of TypedDict return actual dictionaries
5555
# via _dict_new.
56+
57+
# We need the `if TypedDict in globals()` check,
58+
# or we emit a DeprecationWarning when creating mypy_extensions.TypedDict itself
59+
if 'TypedDict' in globals():
60+
import warnings
61+
warnings.warn(
62+
(
63+
"mypy_extensions.TypedDict is deprecated, "
64+
"and will be removed in a future version. "
65+
"Use typing.TypedDict or typing_extensions.TypedDict instead."
66+
),
67+
DeprecationWarning,
68+
stacklevel=(3 if _from_functional_call else 2)
69+
)
70+
5671
ns['__new__'] = _typeddict_new if name == 'TypedDict' else _dict_new
5772
tp_dict = super(_TypedDictMeta, cls).__new__(cls, name, (dict,), ns)
5873

tests/testextensions.py

+45-16
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import sys
22
import pickle
33
import typing
4+
from contextlib import contextmanager
5+
from textwrap import dedent
46
from unittest import TestCase, main, skipUnless
57
from mypy_extensions import TypedDict, i64, i32, i16, u8
68

@@ -25,27 +27,39 @@ def assertNotIsSubclass(self, cls, class_or_tuple, msg=None):
2527
PY36 = sys.version_info[:2] >= (3, 6)
2628

2729
PY36_TESTS = """
28-
Label = TypedDict('Label', [('label', str)])
30+
import warnings
2931
30-
class Point2D(TypedDict):
31-
x: int
32-
y: int
32+
with warnings.catch_warnings():
33+
warnings.simplefilter("ignore", category=DeprecationWarning)
3334
34-
class LabelPoint2D(Point2D, Label): ...
35+
Label = TypedDict('Label', [('label', str)])
3536
36-
class Options(TypedDict, total=False):
37-
log_level: int
38-
log_path: str
37+
class Point2D(TypedDict):
38+
x: int
39+
y: int
40+
41+
class LabelPoint2D(Point2D, Label): ...
42+
43+
class Options(TypedDict, total=False):
44+
log_level: int
45+
log_path: str
3946
"""
4047

4148
if PY36:
4249
exec(PY36_TESTS)
4350

4451

4552
class TypedDictTests(BaseTestCase):
53+
@contextmanager
54+
def assert_typeddict_deprecated(self):
55+
with self.assertWarnsRegex(
56+
DeprecationWarning, "mypy_extensions.TypedDict is deprecated"
57+
):
58+
yield
4659

4760
def test_basics_iterable_syntax(self):
48-
Emp = TypedDict('Emp', {'name': str, 'id': int})
61+
with self.assert_typeddict_deprecated():
62+
Emp = TypedDict('Emp', {'name': str, 'id': int})
4963
self.assertIsSubclass(Emp, dict)
5064
self.assertIsSubclass(Emp, typing.MutableMapping)
5165
if sys.version_info[0] >= 3:
@@ -62,7 +76,8 @@ def test_basics_iterable_syntax(self):
6276
self.assertEqual(Emp.__total__, True)
6377

6478
def test_basics_keywords_syntax(self):
65-
Emp = TypedDict('Emp', name=str, id=int)
79+
with self.assert_typeddict_deprecated():
80+
Emp = TypedDict('Emp', name=str, id=int)
6681
self.assertIsSubclass(Emp, dict)
6782
self.assertIsSubclass(Emp, typing.MutableMapping)
6883
if sys.version_info[0] >= 3:
@@ -79,7 +94,8 @@ def test_basics_keywords_syntax(self):
7994
self.assertEqual(Emp.__total__, True)
8095

8196
def test_typeddict_errors(self):
82-
Emp = TypedDict('Emp', {'name': str, 'id': int})
97+
with self.assert_typeddict_deprecated():
98+
Emp = TypedDict('Emp', {'name': str, 'id': int})
8399
self.assertEqual(TypedDict.__module__, 'mypy_extensions')
84100
jim = Emp(name='Jim', id=1)
85101
with self.assertRaises(TypeError):
@@ -88,9 +104,9 @@ def test_typeddict_errors(self):
88104
isinstance(jim, Emp) # type: ignore
89105
with self.assertRaises(TypeError):
90106
issubclass(dict, Emp) # type: ignore
91-
with self.assertRaises(TypeError):
107+
with self.assertRaises(TypeError), self.assert_typeddict_deprecated():
92108
TypedDict('Hi', x=())
93-
with self.assertRaises(TypeError):
109+
with self.assertRaises(TypeError), self.assert_typeddict_deprecated():
94110
TypedDict('Hi', [('x', int), ('y', ())])
95111
with self.assertRaises(TypeError):
96112
TypedDict('Hi', [('x', int)], y=int)
@@ -109,9 +125,20 @@ def test_py36_class_syntax_usage(self):
109125
other = LabelPoint2D(x=0, y=1, label='hi') # noqa
110126
self.assertEqual(other['label'], 'hi')
111127

128+
if PY36:
129+
exec(dedent(
130+
"""
131+
def test_py36_class_usage_emits_deprecations(self):
132+
with self.assert_typeddict_deprecated():
133+
class Foo(TypedDict):
134+
bar: int
135+
"""
136+
))
137+
112138
def test_pickle(self):
113139
global EmpD # pickle wants to reference the class by name
114-
EmpD = TypedDict('EmpD', name=str, id=int)
140+
with self.assert_typeddict_deprecated():
141+
EmpD = TypedDict('EmpD', name=str, id=int)
115142
jane = EmpD({'name': 'jane', 'id': 37})
116143
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
117144
z = pickle.dumps(jane, proto)
@@ -123,13 +150,15 @@ def test_pickle(self):
123150
self.assertEqual(EmpDnew({'name': 'jane', 'id': 37}), jane)
124151

125152
def test_optional(self):
126-
EmpD = TypedDict('EmpD', name=str, id=int)
153+
with self.assert_typeddict_deprecated():
154+
EmpD = TypedDict('EmpD', name=str, id=int)
127155

128156
self.assertEqual(typing.Optional[EmpD], typing.Union[None, EmpD])
129157
self.assertNotEqual(typing.List[EmpD], typing.Tuple[EmpD])
130158

131159
def test_total(self):
132-
D = TypedDict('D', {'x': int}, total=False)
160+
with self.assert_typeddict_deprecated():
161+
D = TypedDict('D', {'x': int}, total=False)
133162
self.assertEqual(D(), {})
134163
self.assertEqual(D(x=1), {'x': 1})
135164
self.assertEqual(D.__total__, False)

tox.ini

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ envlist = py35, py36, py37, py38, py39, py310, py311
55

66
[testenv]
77
description = run the test driver with {basepython}
8-
commands = python -m unittest discover tests
8+
commands = python -We -m unittest discover tests
99

1010
[testenv:lint]
1111
description = check the code style

0 commit comments

Comments
 (0)