[Python-checkins] Better runtime TypedDict (GH-17214)

Ivan Levkivskyi webhook-mailer at python.org
Sun Nov 24 05:49:16 EST 2019


https://github.com/python/cpython/commit/665ad3dfa9993b9a4000b097ddead4e292590e8c
commit: 665ad3dfa9993b9a4000b097ddead4e292590e8c
branch: master
author: Zac Hatfield-Dodds <Zac-HD at users.noreply.github.com>
committer: Ivan Levkivskyi <levkivskyi at gmail.com>
date: 2019-11-24T10:48:48Z
summary:

Better runtime TypedDict (GH-17214)

This patch enables downstream projects inspecting a TypedDict subclass at runtime to tell which keys are optional.

This is essential for generating test data with Hypothesis or validating inputs with typeguard or pydantic.

files:
A Misc/NEWS.d/next/Library/2019-11-18-17-08-23.bpo-38834.abcdef.rst
M Lib/test/test_typing.py
M Lib/typing.py

diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py
index ccd617c1fdff0..5b4916f9c3260 100644
--- a/Lib/test/test_typing.py
+++ b/Lib/test/test_typing.py
@@ -3741,6 +3741,13 @@ def test_total(self):
         self.assertEqual(Options(log_level=2), {'log_level': 2})
         self.assertEqual(Options.__total__, False)
 
+    def test_optional_keys(self):
+        class Point2Dor3D(Point2D, total=False):
+            z: int
+
+        assert Point2Dor3D.__required_keys__ == frozenset(['x', 'y'])
+        assert Point2Dor3D.__optional_keys__ == frozenset(['z'])
+
 
 class IOTests(BaseTestCase):
 
diff --git a/Lib/typing.py b/Lib/typing.py
index 5523ee01e1f99..7de3e346eaa79 100644
--- a/Lib/typing.py
+++ b/Lib/typing.py
@@ -1715,9 +1715,20 @@ def __new__(cls, name, bases, ns, total=True):
         anns = ns.get('__annotations__', {})
         msg = "TypedDict('Name', {f0: t0, f1: t1, ...}); each t must be a type"
         anns = {n: _type_check(tp, msg) for n, tp in anns.items()}
+        required = set(anns if total else ())
+        optional = set(() if total else anns)
+
         for base in bases:
-            anns.update(base.__dict__.get('__annotations__', {}))
+            base_anns = base.__dict__.get('__annotations__', {})
+            anns.update(base_anns)
+            if getattr(base, '__total__', True):
+                required.update(base_anns)
+            else:
+                optional.update(base_anns)
+
         tp_dict.__annotations__ = anns
+        tp_dict.__required_keys__ = frozenset(required)
+        tp_dict.__optional_keys__ = frozenset(optional)
         if not hasattr(tp_dict, '__total__'):
             tp_dict.__total__ = total
         return tp_dict
@@ -1744,8 +1755,9 @@ class Point2D(TypedDict):
 
         assert Point2D(x=1, y=2, label='first') == dict(x=1, y=2, label='first')
 
-    The type info can be accessed via Point2D.__annotations__. TypedDict
-    supports two additional equivalent forms::
+    The type info can be accessed via the Point2D.__annotations__ dict, and
+    the Point2D.__required_keys__ and Point2D.__optional_keys__ frozensets.
+    TypedDict supports two additional equivalent forms::
 
         Point2D = TypedDict('Point2D', x=int, y=int, label=str)
         Point2D = TypedDict('Point2D', {'x': int, 'y': int, 'label': str})
diff --git a/Misc/NEWS.d/next/Library/2019-11-18-17-08-23.bpo-38834.abcdef.rst b/Misc/NEWS.d/next/Library/2019-11-18-17-08-23.bpo-38834.abcdef.rst
new file mode 100644
index 0000000000000..af108b1efbc3b
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2019-11-18-17-08-23.bpo-38834.abcdef.rst
@@ -0,0 +1,3 @@
+:class:`typing.TypedDict` subclasses now track which keys are optional using
+the ``__required_keys__`` and ``__optional_keys__`` attributes, to enable
+runtime validation by downstream projects.  Patch by Zac Hatfield-Dodds.



More information about the Python-checkins mailing list