classification
Title: TypedDict: total=False but still key required
Type: behavior Stage: resolved
Components: Library (Lib) Versions: Python 3.10, Python 3.9
process
Status: closed Resolution: duplicate
Dependencies: Superseder: TypedDict(...) as function does not respect "total" when setting __required_keys__ and __optional_keys__
View: 42059
Assigned To: brandtbucher Nosy List: brandtbucher, gvanrossum, pbryan, terry.reedy
Priority: normal Keywords:

Created on 2020-12-07 17:36 by pbryan, last changed 2020-12-07 19:50 by brandtbucher. This issue is now closed.

Messages (6)
msg382662 - (view) Author: Paul Bryan (pbryan) * Date: 2020-12-07 17:36
I believe "a" below should be an optional key, not a required one.

Python 3.9.0 (default, Oct  7 2020, 23:09:01) 
[GCC 10.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import typing
>>> TD = typing.TypedDict("TD", {"a": str}, total=False)
>>> TD.__total__
False
>>> TD.__required_keys__
frozenset({'a'})
>>> TD.__optional_keys__
frozenset()
>>>
msg382666 - (view) Author: Brandt Bucher (brandtbucher) * (Python committer) Date: 2020-12-07 18:12
It looks like the issue is that _TypedDictMeta only respects "total" as a keyword argument to __new__, but the TypedDict function passes it along by setting __total__ in the generated namespace instead.

This fixes it:

diff --git a/Lib/typing.py b/Lib/typing.py
index 46c54c4..bb0696b 100644
--- a/Lib/typing.py
+++ b/Lib/typing.py
@@ -2050,7 +2050,7 @@ class body be required.
     except (AttributeError, ValueError):
         pass
 
-    return _TypedDictMeta(typename, (), ns)
+    return _TypedDictMeta(typename, (), ns, total=total)
 
 _TypedDict = type.__new__(_TypedDictMeta, 'TypedDict', (), {})
 TypedDict.__mro_entries__ = lambda bases: (_TypedDict,)
msg382667 - (view) Author: Brandt Bucher (brandtbucher) * (Python committer) Date: 2020-12-07 18:13
I can fix this, Paul, unless you want to take it. Probably deserves a regression test or two as well.
msg382669 - (view) Author: Paul Bryan (pbryan) * Date: 2020-12-07 18:30
Your patch LGTM, Brandt.
msg382675 - (view) Author: Terry J. Reedy (terry.reedy) * (Python committer) Date: 2020-12-07 19:37
Some notes on needed TypedDict doc fixes.

https://docs.python.org/3/library/typing.html#typing.TypedDict

"class typing.TypedDict(dict)"

The actual signature from inspect.signature is "(typename, fields=None, /, *, total=True, **kwargs)".  I presume fields not None and kwargs != {} are mutually exclusive.  AFAIK, the kwargs version of the call alternative is not in PEP 589.

"The type info for introspection can be accessed via Point2D.__annotations__ and Point2D.__total__."

'__total__' is not indexed.  __required_keys__ and __optional_keys__ are neither documented (including not in the PEP, which does not get revised) nor indexed.
msg382676 - (view) Author: Brandt Bucher (brandtbucher) * (Python committer) Date: 2020-12-07 19:50
It looks like this is a duplicate of issue 42059. We should just use their existing PR instead (PR 22736).
History
Date User Action Args
2020-12-07 19:50:16brandtbuchersetstatus: open -> closed
superseder: TypedDict(...) as function does not respect "total" when setting __required_keys__ and __optional_keys__
messages: + msg382676

resolution: duplicate
stage: test needed -> resolved
2020-12-07 19:37:13terry.reedysetversions: + Python 3.10
nosy: + terry.reedy

messages: + msg382675

stage: test needed
2020-12-07 19:11:26brandtbuchersetassignee: brandtbucher
2020-12-07 18:30:17pbryansetmessages: + msg382669
2020-12-07 18:13:40brandtbuchersetmessages: + msg382667
2020-12-07 18:12:08brandtbuchersetnosy: + brandtbucher
messages: + msg382666
2020-12-07 17:36:35pbryancreate