Fix ClassVar as string fails when getting type hints (GH-6824)
This commit is contained in:
parent
8b94b41ab7
commit
2d2d3b170b
|
@ -1599,6 +1599,30 @@ class ForwardRefTests(BaseTestCase):
|
||||||
# verify that @no_type_check never affects bases
|
# verify that @no_type_check never affects bases
|
||||||
self.assertEqual(get_type_hints(C.meth), {'x': int})
|
self.assertEqual(get_type_hints(C.meth), {'x': int})
|
||||||
|
|
||||||
|
def test_no_type_check_forward_ref_as_string(self):
|
||||||
|
class C:
|
||||||
|
foo: typing.ClassVar[int] = 7
|
||||||
|
class D:
|
||||||
|
foo: ClassVar[int] = 7
|
||||||
|
class E:
|
||||||
|
foo: 'typing.ClassVar[int]' = 7
|
||||||
|
class F:
|
||||||
|
foo: 'ClassVar[int]' = 7
|
||||||
|
|
||||||
|
expected_result = {'foo': typing.ClassVar[int]}
|
||||||
|
for clazz in [C, D, E, F]:
|
||||||
|
self.assertEqual(get_type_hints(clazz), expected_result)
|
||||||
|
|
||||||
|
def test_nested_classvar_fails_forward_ref_check(self):
|
||||||
|
class E:
|
||||||
|
foo: 'typing.ClassVar[typing.ClassVar[int]]' = 7
|
||||||
|
class F:
|
||||||
|
foo: ClassVar['ClassVar[int]'] = 7
|
||||||
|
|
||||||
|
for clazz in [E, F]:
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
get_type_hints(clazz)
|
||||||
|
|
||||||
def test_meta_no_type_check(self):
|
def test_meta_no_type_check(self):
|
||||||
|
|
||||||
@no_type_check_decorator
|
@no_type_check_decorator
|
||||||
|
|
|
@ -106,7 +106,7 @@ __all__ = [
|
||||||
# legitimate imports of those modules.
|
# legitimate imports of those modules.
|
||||||
|
|
||||||
|
|
||||||
def _type_check(arg, msg):
|
def _type_check(arg, msg, is_argument=False):
|
||||||
"""Check that the argument is a type, and return it (internal helper).
|
"""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
|
As a special case, accept None and return type(None) instead. Also wrap strings
|
||||||
|
@ -118,12 +118,16 @@ def _type_check(arg, msg):
|
||||||
|
|
||||||
We append the repr() of the actual value (truncated to 100 chars).
|
We append the repr() of the actual value (truncated to 100 chars).
|
||||||
"""
|
"""
|
||||||
|
invalid_generic_forms = (Generic, _Protocol)
|
||||||
|
if not is_argument:
|
||||||
|
invalid_generic_forms = invalid_generic_forms + (ClassVar, )
|
||||||
|
|
||||||
if arg is None:
|
if arg is None:
|
||||||
return type(None)
|
return type(None)
|
||||||
if isinstance(arg, str):
|
if isinstance(arg, str):
|
||||||
return ForwardRef(arg)
|
return ForwardRef(arg)
|
||||||
if (isinstance(arg, _GenericAlias) and
|
if (isinstance(arg, _GenericAlias) and
|
||||||
arg.__origin__ in (Generic, _Protocol, ClassVar)):
|
arg.__origin__ in invalid_generic_forms):
|
||||||
raise TypeError(f"{arg} is not valid as type argument")
|
raise TypeError(f"{arg} is not valid as type argument")
|
||||||
if (isinstance(arg, _SpecialForm) and arg is not Any or
|
if (isinstance(arg, _SpecialForm) and arg is not Any or
|
||||||
arg in (Generic, _Protocol)):
|
arg in (Generic, _Protocol)):
|
||||||
|
@ -464,9 +468,10 @@ class ForwardRef(_Final, _root=True):
|
||||||
"""Internal wrapper to hold a forward reference."""
|
"""Internal wrapper to hold a forward reference."""
|
||||||
|
|
||||||
__slots__ = ('__forward_arg__', '__forward_code__',
|
__slots__ = ('__forward_arg__', '__forward_code__',
|
||||||
'__forward_evaluated__', '__forward_value__')
|
'__forward_evaluated__', '__forward_value__',
|
||||||
|
'__forward_is_argument__')
|
||||||
|
|
||||||
def __init__(self, arg):
|
def __init__(self, arg, is_argument=False):
|
||||||
if not isinstance(arg, str):
|
if not isinstance(arg, str):
|
||||||
raise TypeError(f"Forward reference must be a string -- got {arg!r}")
|
raise TypeError(f"Forward reference must be a string -- got {arg!r}")
|
||||||
try:
|
try:
|
||||||
|
@ -477,6 +482,7 @@ class ForwardRef(_Final, _root=True):
|
||||||
self.__forward_code__ = code
|
self.__forward_code__ = code
|
||||||
self.__forward_evaluated__ = False
|
self.__forward_evaluated__ = False
|
||||||
self.__forward_value__ = None
|
self.__forward_value__ = None
|
||||||
|
self.__forward_is_argument__ = is_argument
|
||||||
|
|
||||||
def _evaluate(self, globalns, localns):
|
def _evaluate(self, globalns, localns):
|
||||||
if not self.__forward_evaluated__ or localns is not globalns:
|
if not self.__forward_evaluated__ or localns is not globalns:
|
||||||
|
@ -488,7 +494,8 @@ class ForwardRef(_Final, _root=True):
|
||||||
localns = globalns
|
localns = globalns
|
||||||
self.__forward_value__ = _type_check(
|
self.__forward_value__ = _type_check(
|
||||||
eval(self.__forward_code__, globalns, localns),
|
eval(self.__forward_code__, globalns, localns),
|
||||||
"Forward references must evaluate to types.")
|
"Forward references must evaluate to types.",
|
||||||
|
is_argument=self.__forward_is_argument__)
|
||||||
self.__forward_evaluated__ = True
|
self.__forward_evaluated__ = True
|
||||||
return self.__forward_value__
|
return self.__forward_value__
|
||||||
|
|
||||||
|
@ -998,7 +1005,7 @@ def get_type_hints(obj, globalns=None, localns=None):
|
||||||
if value is None:
|
if value is None:
|
||||||
value = type(None)
|
value = type(None)
|
||||||
if isinstance(value, str):
|
if isinstance(value, str):
|
||||||
value = ForwardRef(value)
|
value = ForwardRef(value, is_argument=True)
|
||||||
value = _eval_type(value, base_globals, localns)
|
value = _eval_type(value, base_globals, localns)
|
||||||
hints[name] = value
|
hints[name] = value
|
||||||
return hints
|
return hints
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Fix failure in `typing.get_type_hints()` when ClassVar was provided as a string forward reference.
|
Loading…
Reference in New Issue