summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Lib/test/_typed_dict_helper.py18
-rw-r--r--Lib/test/test_typing.py10
-rw-r--r--Lib/typing.py20
-rw-r--r--Misc/NEWS.d/next/Library/2021-07-04-11-33-34.bpo-41249.sHdwBE.rst2
4 files changed, 43 insertions, 7 deletions
diff --git a/Lib/test/_typed_dict_helper.py b/Lib/test/_typed_dict_helper.py
new file mode 100644
index 0000000..d333db1
--- /dev/null
+++ b/Lib/test/_typed_dict_helper.py
@@ -0,0 +1,18 @@
+"""Used to test `get_type_hints()` on a cross-module inherited `TypedDict` class
+
+This script uses future annotations to postpone a type that won't be available
+on the module inheriting from to `Foo`. The subclass in the other module should
+look something like this:
+
+ class Bar(_typed_dict_helper.Foo, total=False):
+ b: int
+"""
+
+from __future__ import annotations
+
+from typing import Optional, TypedDict
+
+OptionalIntType = Optional[int]
+
+class Foo(TypedDict):
+ a: OptionalIntType
diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py
index 95803d5..da6775e 100644
--- a/Lib/test/test_typing.py
+++ b/Lib/test/test_typing.py
@@ -34,6 +34,7 @@ import weakref
import types
from test import mod_generics_cache
+from test import _typed_dict_helper
class BaseTestCase(TestCase):
@@ -2819,6 +2820,9 @@ class Point2D(TypedDict):
x: int
y: int
+class Bar(_typed_dict_helper.Foo, total=False):
+ b: int
+
class LabelPoint2D(Point2D, Label): ...
class Options(TypedDict, total=False):
@@ -3995,6 +3999,12 @@ class TypedDictTests(BaseTestCase):
# classes, not instances
assert is_typeddict(Point2D()) is False
+ def test_get_type_hints(self):
+ self.assertEqual(
+ get_type_hints(Bar),
+ {'a': typing.Optional[int], 'b': int}
+ )
+
class IOTests(BaseTestCase):
diff --git a/Lib/typing.py b/Lib/typing.py
index f7386ea..cc7f41d 100644
--- a/Lib/typing.py
+++ b/Lib/typing.py
@@ -135,16 +135,16 @@ __all__ = [
# legitimate imports of those modules.
-def _type_convert(arg):
+def _type_convert(arg, module=None):
"""For converting None to type(None), and strings to ForwardRef."""
if arg is None:
return type(None)
if isinstance(arg, str):
- return ForwardRef(arg)
+ return ForwardRef(arg, module=module)
return arg
-def _type_check(arg, msg, is_argument=True):
+def _type_check(arg, msg, is_argument=True, module=None):
"""Check that the argument is a type, and return it (internal helper).
As a special case, accept None and return type(None) instead. Also wrap strings
@@ -160,7 +160,7 @@ def _type_check(arg, msg, is_argument=True):
if is_argument:
invalid_generic_forms = invalid_generic_forms + (ClassVar, Final)
- arg = _type_convert(arg)
+ arg = _type_convert(arg, module=module)
if (isinstance(arg, _GenericAlias) and
arg.__origin__ in invalid_generic_forms):
raise TypeError(f"{arg} is not valid as type argument")
@@ -633,9 +633,9 @@ class ForwardRef(_Final, _root=True):
__slots__ = ('__forward_arg__', '__forward_code__',
'__forward_evaluated__', '__forward_value__',
- '__forward_is_argument__')
+ '__forward_is_argument__', '__forward_module__')
- def __init__(self, arg, is_argument=True):
+ def __init__(self, arg, is_argument=True, module=None):
if not isinstance(arg, str):
raise TypeError(f"Forward reference must be a string -- got {arg!r}")
try:
@@ -647,6 +647,7 @@ class ForwardRef(_Final, _root=True):
self.__forward_evaluated__ = False
self.__forward_value__ = None
self.__forward_is_argument__ = is_argument
+ self.__forward_module__ = module
def _evaluate(self, globalns, localns, recursive_guard):
if self.__forward_arg__ in recursive_guard:
@@ -658,6 +659,10 @@ class ForwardRef(_Final, _root=True):
globalns = localns
elif localns is None:
localns = globalns
+ if self.__forward_module__ is not None:
+ globalns = getattr(
+ sys.modules.get(self.__forward_module__, None), '__dict__', globalns
+ )
type_ =_type_check(
eval(self.__forward_code__, globalns, localns),
"Forward references must evaluate to types.",
@@ -2242,7 +2247,8 @@ class _TypedDictMeta(type):
own_annotation_keys = set(own_annotations.keys())
msg = "TypedDict('Name', {f0: t0, f1: t1, ...}); each t must be a type"
own_annotations = {
- n: _type_check(tp, msg) for n, tp in own_annotations.items()
+ n: _type_check(tp, msg, module=tp_dict.__module__)
+ for n, tp in own_annotations.items()
}
required_keys = set()
optional_keys = set()
diff --git a/Misc/NEWS.d/next/Library/2021-07-04-11-33-34.bpo-41249.sHdwBE.rst b/Misc/NEWS.d/next/Library/2021-07-04-11-33-34.bpo-41249.sHdwBE.rst
new file mode 100644
index 0000000..06dae4a
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2021-07-04-11-33-34.bpo-41249.sHdwBE.rst
@@ -0,0 +1,2 @@
+Fixes ``TypedDict`` to work with ``typing.get_type_hints()`` and postponed evaluation of
+annotations across modules.