Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions Lib/test/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -7132,6 +7132,19 @@ def add_right(self, node: 'Node[T]' = None):
right_hints = get_type_hints(t.add_right, globals(), locals())
self.assertEqual(right_hints['node'], Node[T])

def test_stringified_typeddict(self):
ns = run_code(
"""
from __future__ import annotations
from typing import TypedDict
class TD[UniqueT](TypedDict):
a: UniqueT
"""
)
TD = ns['TD']
self.assertEqual(TD.__annotations__, {'a': EqualToForwardRef('UniqueT', owner=TD, module=TD.__module__)})
self.assertEqual(get_type_hints(TD), {'a': TD.__type_params__[0]})


class GetUtilitiesTestCase(TestCase):
def test_get_origin(self):
Expand Down Expand Up @@ -8668,8 +8681,8 @@ def _make_td(future, class_name, annos, base, extra_names=None):
child = _make_td(
child_future, "Child", {"child": "int"}, "Base", {"Base": base}
)
base_anno = ForwardRef("int", module="builtins") if base_future else int
child_anno = ForwardRef("int", module="builtins") if child_future else int
base_anno = ForwardRef("int", module="builtins", owner=base) if base_future else int
child_anno = ForwardRef("int", module="builtins", owner=child) if child_future else int
self.assertEqual(base.__annotations__, {'base': base_anno})
self.assertEqual(
child.__annotations__, {'child': child_anno, 'base': base_anno}
Expand Down
32 changes: 22 additions & 10 deletions Lib/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,16 +171,16 @@ def __getattr__(self, attr):
_lazy_annotationlib = _LazyAnnotationLib()


def _type_convert(arg, module=None, *, allow_special_forms=False):
def _type_convert(arg, module=None, *, allow_special_forms=False, owner=None):
"""For converting None to type(None), and strings to ForwardRef."""
if arg is None:
return type(None)
if isinstance(arg, str):
return _make_forward_ref(arg, module=module, is_class=allow_special_forms)
return _make_forward_ref(arg, module=module, is_class=allow_special_forms, owner=owner)
return arg


def _type_check(arg, msg, is_argument=True, module=None, *, allow_special_forms=False):
def _type_check(arg, msg, is_argument=True, module=None, *, allow_special_forms=False, owner=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
Expand All @@ -198,7 +198,7 @@ def _type_check(arg, msg, is_argument=True, module=None, *, allow_special_forms=
if is_argument:
invalid_generic_forms += (Final,)

arg = _type_convert(arg, module=module, allow_special_forms=allow_special_forms)
arg = _type_convert(arg, module=module, allow_special_forms=allow_special_forms, owner=owner)
if (isinstance(arg, _GenericAlias) and
arg.__origin__ in invalid_generic_forms):
raise TypeError(f"{arg} is not valid as type argument")
Expand Down Expand Up @@ -430,7 +430,7 @@ def __repr__(self):


def _eval_type(t, globalns, localns, type_params=_sentinel, *, recursive_guard=frozenset(),
format=None, owner=None, parent_fwdref=None):
format=None, owner=None, parent_fwdref=None, prefer_fwd_module=False):
"""Evaluate all forward references in the given type t.

For use of globalns and localns see the docstring for get_type_hints().
Expand All @@ -443,8 +443,20 @@ def _eval_type(t, globalns, localns, type_params=_sentinel, *, recursive_guard=f
if isinstance(t, _lazy_annotationlib.ForwardRef):
# If the forward_ref has __forward_module__ set, evaluate() infers the globals
# from the module, and it will probably pick better than the globals we have here.
if t.__forward_module__ is not None:
# We do this only for calls from get_type_hints() (which opts in through the
# prefer_fwd_module flag), so that the default behavior remains more straightforward.
if prefer_fwd_module and t.__forward_module__ is not None:
globalns = None
# If there are type params on the owner, we need to add them back, because
# annotationlib won't.
if owner_type_params := getattr(owner, "__type_params__", None):
globalns = getattr(
sys.modules.get(t.__forward_module__, None), "__dict__", None
)
if globalns is not None:
globalns = dict(globalns)
for type_param in owner_type_params:
globalns[type_param.__name__] = type_param
return evaluate_forward_ref(t, globals=globalns, locals=localns,
type_params=type_params, owner=owner,
_recursive_guard=recursive_guard, format=format)
Expand All @@ -465,7 +477,7 @@ def _eval_type(t, globalns, localns, type_params=_sentinel, *, recursive_guard=f
ev_args = tuple(
_eval_type(
a, globalns, localns, type_params, recursive_guard=recursive_guard,
format=format, owner=owner,
format=format, owner=owner, prefer_fwd_module=prefer_fwd_module,
)
for a in t.__args__
)
Expand Down Expand Up @@ -2347,7 +2359,7 @@ def get_type_hints(obj, globalns=None, localns=None, include_extras=False,
if isinstance(value, str):
value = _make_forward_ref(value, is_argument=False, is_class=True)
value = _eval_type(value, base_globals, base_locals, (),
format=format, owner=obj)
format=format, owner=obj, prefer_fwd_module=True)
if value is None:
value = type(None)
hints[name] = value
Expand Down Expand Up @@ -2392,7 +2404,7 @@ def get_type_hints(obj, globalns=None, localns=None, include_extras=False,
is_argument=not isinstance(obj, types.ModuleType),
is_class=False,
)
value = _eval_type(value, globalns, localns, (), format=format, owner=obj)
value = _eval_type(value, globalns, localns, (), format=format, owner=obj, prefer_fwd_module=True)
if value is None:
value = type(None)
hints[name] = value
Expand Down Expand Up @@ -3128,7 +3140,7 @@ def __new__(cls, name, bases, ns, total=True):
own_annotations = {}
msg = "TypedDict('Name', {f0: t0, f1: t1, ...}); each t must be a type"
own_checked_annotations = {
n: _type_check(tp, msg, module=tp_dict.__module__)
n: _type_check(tp, msg, owner=tp_dict, module=tp_dict.__module__)
for n, tp in own_annotations.items()
}
required_keys = set()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix :func:`typing.get_type_hints` calls on generic :class:`typing.TypedDict`
classes defined with string annotations.
Loading