Issue #28556: More typing.py updates from upstream.
This commit is contained in:
parent
a1a8b7d3d7
commit
991d14fee1
|
@ -378,6 +378,16 @@ class CallableTests(BaseTestCase):
|
|||
with self.assertRaises(TypeError):
|
||||
type(c)()
|
||||
|
||||
def test_callable_wrong_forms(self):
|
||||
with self.assertRaises(TypeError):
|
||||
Callable[[...], int]
|
||||
with self.assertRaises(TypeError):
|
||||
Callable[(), int]
|
||||
with self.assertRaises(TypeError):
|
||||
Callable[[()], int]
|
||||
with self.assertRaises(TypeError):
|
||||
Callable[[int, 1], 2]
|
||||
|
||||
def test_callable_instance_works(self):
|
||||
def f():
|
||||
pass
|
||||
|
@ -1296,9 +1306,10 @@ PY36 = sys.version_info[:2] >= (3, 6)
|
|||
|
||||
PY36_TESTS = """
|
||||
from test import ann_module, ann_module2, ann_module3
|
||||
from collections import ChainMap
|
||||
|
||||
class B:
|
||||
class A:
|
||||
y: float
|
||||
class B(A):
|
||||
x: ClassVar[Optional['B']] = None
|
||||
y: int
|
||||
class CSub(B):
|
||||
|
@ -1317,6 +1328,15 @@ if PY36:
|
|||
gth = get_type_hints
|
||||
|
||||
class GetTypeHintTests(BaseTestCase):
|
||||
def test_get_type_hints_from_various_objects(self):
|
||||
# For invalid objects should fail with TypeError (not AttributeError etc).
|
||||
with self.assertRaises(TypeError):
|
||||
gth(123)
|
||||
with self.assertRaises(TypeError):
|
||||
gth('abc')
|
||||
with self.assertRaises(TypeError):
|
||||
gth(None)
|
||||
|
||||
@skipUnless(PY36, 'Python 3.6 required')
|
||||
def test_get_type_hints_modules(self):
|
||||
self.assertEqual(gth(ann_module), {1: 2, 'f': Tuple[int, int], 'x': int, 'y': str})
|
||||
|
@ -1326,18 +1346,15 @@ class GetTypeHintTests(BaseTestCase):
|
|||
@skipUnless(PY36, 'Python 3.6 required')
|
||||
def test_get_type_hints_classes(self):
|
||||
self.assertEqual(gth(ann_module.C, ann_module.__dict__),
|
||||
ChainMap({'y': Optional[ann_module.C]}, {}))
|
||||
self.assertEqual(repr(gth(ann_module.j_class)), 'ChainMap({}, {})')
|
||||
self.assertEqual(gth(ann_module.M), ChainMap({'123': 123, 'o': type},
|
||||
{}, {}))
|
||||
{'y': Optional[ann_module.C]})
|
||||
self.assertIsInstance(gth(ann_module.j_class), dict)
|
||||
self.assertEqual(gth(ann_module.M), {'123': 123, 'o': type})
|
||||
self.assertEqual(gth(ann_module.D),
|
||||
ChainMap({'j': str, 'k': str,
|
||||
'y': Optional[ann_module.C]}, {}))
|
||||
self.assertEqual(gth(ann_module.Y), ChainMap({'z': int}, {}))
|
||||
{'j': str, 'k': str, 'y': Optional[ann_module.C]})
|
||||
self.assertEqual(gth(ann_module.Y), {'z': int})
|
||||
self.assertEqual(gth(ann_module.h_class),
|
||||
ChainMap({}, {'y': Optional[ann_module.C]}, {}))
|
||||
self.assertEqual(gth(ann_module.S), ChainMap({'x': str, 'y': str},
|
||||
{}))
|
||||
{'y': Optional[ann_module.C]})
|
||||
self.assertEqual(gth(ann_module.S), {'x': str, 'y': str})
|
||||
self.assertEqual(gth(ann_module.foo), {'x': int})
|
||||
|
||||
@skipUnless(PY36, 'Python 3.6 required')
|
||||
|
@ -1355,20 +1372,34 @@ class GetTypeHintTests(BaseTestCase):
|
|||
class Der(ABase): ...
|
||||
self.assertEqual(gth(ABase.meth), {'x': int})
|
||||
|
||||
def test_get_type_hints_for_builins(self):
|
||||
# Should not fail for built-in classes and functions.
|
||||
self.assertEqual(gth(int), {})
|
||||
self.assertEqual(gth(type), {})
|
||||
self.assertEqual(gth(dir), {})
|
||||
self.assertEqual(gth(len), {})
|
||||
|
||||
def test_previous_behavior(self):
|
||||
def testf(x, y): ...
|
||||
testf.__annotations__['x'] = 'int'
|
||||
self.assertEqual(gth(testf), {'x': int})
|
||||
|
||||
def test_get_type_hints_for_object_with_annotations(self):
|
||||
class A: ...
|
||||
class B: ...
|
||||
b = B()
|
||||
b.__annotations__ = {'x': 'A'}
|
||||
self.assertEqual(gth(b, locals()), {'x': A})
|
||||
|
||||
@skipUnless(PY36, 'Python 3.6 required')
|
||||
def test_get_type_hints_ClassVar(self):
|
||||
self.assertEqual(gth(ann_module2.CV, ann_module2.__dict__),
|
||||
{'var': typing.ClassVar[ann_module2.CV]})
|
||||
self.assertEqual(gth(B, globals()),
|
||||
ChainMap({'y': int, 'x': ClassVar[Optional[B]]}, {}))
|
||||
{'y': int, 'x': ClassVar[Optional[B]]})
|
||||
self.assertEqual(gth(CSub, globals()),
|
||||
ChainMap({'z': ClassVar[CSub]},
|
||||
{'y': int, 'x': ClassVar[Optional[B]]}, {}))
|
||||
self.assertEqual(gth(G), ChainMap({'lst': ClassVar[List[T]]},{},{}))
|
||||
{'z': ClassVar[CSub], 'y': int, 'x': ClassVar[Optional[B]]})
|
||||
self.assertEqual(gth(G), {'lst': ClassVar[List[T]]})
|
||||
|
||||
|
||||
class CollectionsAbcTests(BaseTestCase):
|
||||
|
|
134
Lib/typing.py
134
Lib/typing.py
|
@ -10,8 +10,6 @@ try:
|
|||
import collections.abc as collections_abc
|
||||
except ImportError:
|
||||
import collections as collections_abc # Fallback for PY3.2.
|
||||
if sys.version_info[:2] >= (3, 3):
|
||||
from collections import ChainMap
|
||||
|
||||
|
||||
# Please keep __all__ alphabetized within each category.
|
||||
|
@ -1194,14 +1192,12 @@ class CallableMeta(GenericMeta):
|
|||
# super()._tree_repr() for nice formatting.
|
||||
arg_list = []
|
||||
for arg in tree[1:]:
|
||||
if arg == ():
|
||||
arg_list.append('[]')
|
||||
elif not isinstance(arg, tuple):
|
||||
if not isinstance(arg, tuple):
|
||||
arg_list.append(_type_repr(arg))
|
||||
else:
|
||||
arg_list.append(arg[0]._tree_repr(arg))
|
||||
if len(arg_list) == 2:
|
||||
return repr(tree[0]) + '[%s]' % ', '.join(arg_list)
|
||||
if arg_list[0] == '...':
|
||||
return repr(tree[0]) + '[..., %s]' % arg_list[1]
|
||||
return (repr(tree[0]) +
|
||||
'[[%s], %s]' % (', '.join(arg_list[:-1]), arg_list[-1]))
|
||||
|
||||
|
@ -1216,26 +1212,22 @@ class CallableMeta(GenericMeta):
|
|||
raise TypeError("Callable must be used as "
|
||||
"Callable[[arg, ...], result].")
|
||||
args, result = parameters
|
||||
if args is ...:
|
||||
parameters = (..., result)
|
||||
elif args == []:
|
||||
parameters = ((), result)
|
||||
if args is Ellipsis:
|
||||
parameters = (Ellipsis, result)
|
||||
else:
|
||||
if not isinstance(args, list):
|
||||
raise TypeError("Callable[args, result]: args must be a list."
|
||||
" Got %.100r." % (args,))
|
||||
parameters = tuple(args) + (result,)
|
||||
parameters = (tuple(args), result)
|
||||
return self.__getitem_inner__(parameters)
|
||||
|
||||
@_tp_cache
|
||||
def __getitem_inner__(self, parameters):
|
||||
*args, result = parameters
|
||||
args, result = parameters
|
||||
msg = "Callable[args, result]: result must be a type."
|
||||
result = _type_check(result, msg)
|
||||
if args == [...,]:
|
||||
if args is Ellipsis:
|
||||
return super().__getitem__((_TypingEllipsis, result))
|
||||
if args == [(),]:
|
||||
return super().__getitem__((_TypingEmpty, result))
|
||||
msg = "Callable[[arg, ...], result]: each arg must be a type."
|
||||
args = tuple(_type_check(arg, msg) for arg in args)
|
||||
parameters = args + (result,)
|
||||
|
@ -1332,7 +1324,11 @@ def cast(typ, val):
|
|||
|
||||
def _get_defaults(func):
|
||||
"""Internal helper to extract the default arguments, by name."""
|
||||
try:
|
||||
code = func.__code__
|
||||
except AttributeError:
|
||||
# Some built-in functions don't have __code__, __defaults__, etc.
|
||||
return {}
|
||||
pos_count = code.co_argcount
|
||||
arg_names = code.co_varnames
|
||||
arg_names = arg_names[:pos_count]
|
||||
|
@ -1346,7 +1342,6 @@ def _get_defaults(func):
|
|||
return res
|
||||
|
||||
|
||||
if sys.version_info[:2] >= (3, 3):
|
||||
def get_type_hints(obj, globalns=None, localns=None):
|
||||
"""Return type hints for an object.
|
||||
|
||||
|
@ -1355,8 +1350,8 @@ if sys.version_info[:2] >= (3, 3):
|
|||
adds Optional[t] if a default value equal to None is set.
|
||||
|
||||
The argument may be a module, class, method, or function. The annotations
|
||||
are returned as a dictionary, or in the case of a class, a ChainMap of
|
||||
dictionaries.
|
||||
are returned as a dictionary. For classes, annotations include also
|
||||
inherited members.
|
||||
|
||||
TypeError is raised if the argument is not of a type that can contain
|
||||
annotations, and an empty dictionary is returned if no annotations are
|
||||
|
@ -1385,92 +1380,35 @@ if sys.version_info[:2] >= (3, 3):
|
|||
localns = globalns
|
||||
elif localns is None:
|
||||
localns = globalns
|
||||
|
||||
# Classes require a special treatment.
|
||||
if isinstance(obj, type):
|
||||
hints = {}
|
||||
for base in reversed(obj.__mro__):
|
||||
ann = base.__dict__.get('__annotations__', {})
|
||||
for name, value in ann.items():
|
||||
if value is None:
|
||||
value = type(None)
|
||||
if isinstance(value, str):
|
||||
value = _ForwardRef(value)
|
||||
value = _eval_type(value, globalns, localns)
|
||||
hints[name] = value
|
||||
return hints
|
||||
hints = getattr(obj, '__annotations__', None)
|
||||
if hints is None:
|
||||
# Return empty annotations for something that _could_ have them.
|
||||
if (isinstance(obj, types.FunctionType) or
|
||||
isinstance(obj, types.BuiltinFunctionType) or
|
||||
isinstance(obj, types.MethodType)):
|
||||
defaults = _get_defaults(obj)
|
||||
hints = obj.__annotations__
|
||||
for name, value in hints.items():
|
||||
if value is None:
|
||||
value = type(None)
|
||||
if isinstance(value, str):
|
||||
value = _ForwardRef(value)
|
||||
value = _eval_type(value, globalns, localns)
|
||||
if name in defaults and defaults[name] is None:
|
||||
value = Optional[value]
|
||||
hints[name] = value
|
||||
return hints
|
||||
|
||||
if isinstance(obj, types.ModuleType):
|
||||
try:
|
||||
hints = obj.__annotations__
|
||||
except AttributeError:
|
||||
isinstance(obj, types.MethodType) or
|
||||
isinstance(obj, types.ModuleType)):
|
||||
return {}
|
||||
for name, value in hints.items():
|
||||
if value is None:
|
||||
value = type(None)
|
||||
if isinstance(value, str):
|
||||
value = _ForwardRef(value)
|
||||
value = _eval_type(value, globalns, localns)
|
||||
hints[name] = value
|
||||
return hints
|
||||
|
||||
if isinstance(object, type):
|
||||
cmap = None
|
||||
for base in reversed(obj.__mro__):
|
||||
new_map = collections.ChainMap if cmap is None else cmap.new_child
|
||||
try:
|
||||
hints = base.__dict__['__annotations__']
|
||||
except KeyError:
|
||||
cmap = new_map()
|
||||
else:
|
||||
for name, value in hints.items():
|
||||
if value is None:
|
||||
value = type(None)
|
||||
if isinstance(value, str):
|
||||
value = _ForwardRef(value)
|
||||
value = _eval_type(value, globalns, localns)
|
||||
hints[name] = value
|
||||
cmap = new_map(hints)
|
||||
return cmap
|
||||
|
||||
raise TypeError('{!r} is not a module, class, method, '
|
||||
'or function.'.format(obj))
|
||||
|
||||
else:
|
||||
def get_type_hints(obj, globalns=None, localns=None):
|
||||
"""Return type hints for a function or method object.
|
||||
|
||||
This is often the same as obj.__annotations__, but it handles
|
||||
forward references encoded as string literals, and if necessary
|
||||
adds Optional[t] if a default value equal to None is set.
|
||||
|
||||
BEWARE -- the behavior of globalns and localns is counterintuitive
|
||||
(unless you are familiar with how eval() and exec() work). The
|
||||
search order is locals first, then globals.
|
||||
|
||||
- If no dict arguments are passed, an attempt is made to use the
|
||||
globals from obj, and these are also used as the locals. If the
|
||||
object does not appear to have globals, an exception is raised.
|
||||
|
||||
- If one dict argument is passed, it is used for both globals and
|
||||
locals.
|
||||
|
||||
- If two dict arguments are passed, they specify globals and
|
||||
locals, respectively.
|
||||
"""
|
||||
if getattr(obj, '__no_type_check__', None):
|
||||
return {}
|
||||
if globalns is None:
|
||||
globalns = getattr(obj, '__globals__', {})
|
||||
if localns is None:
|
||||
localns = globalns
|
||||
elif localns is None:
|
||||
localns = globalns
|
||||
defaults = _get_defaults(obj)
|
||||
hints = dict(obj.__annotations__)
|
||||
hints = dict(hints)
|
||||
for name, value in hints.items():
|
||||
if value is None:
|
||||
value = type(None)
|
||||
if isinstance(value, str):
|
||||
value = _ForwardRef(value)
|
||||
value = _eval_type(value, globalns, localns)
|
||||
|
@ -2160,7 +2098,7 @@ class TextIO(IO[str]):
|
|||
pass
|
||||
|
||||
@abstractproperty
|
||||
def errors(self) -> str:
|
||||
def errors(self) -> Optional[str]:
|
||||
pass
|
||||
|
||||
@abstractproperty
|
||||
|
|
Loading…
Reference in New Issue