Commit 969c0d17 authored by Guido van Rossum's avatar Guido van Rossum

Issue #28079: Update typing and test typing from python/typing repo.

Ivan Levkivskyi (3.6 version)
parent 91aa5f36
......@@ -4,8 +4,6 @@ import pickle
import re
import sys
from unittest import TestCase, main, skipUnless, SkipTest
from collections import ChainMap
from test import ann_module, ann_module2, ann_module3
from typing import Any
from typing import TypeVar, AnyStr
......@@ -969,46 +967,6 @@ class ForwardRefTests(BaseTestCase):
right_hints = get_type_hints(t.add_right, globals(), locals())
self.assertEqual(right_hints['node'], Optional[Node[T]])
def test_get_type_hints(self):
gth = get_type_hints
self.assertEqual(gth(ann_module), {'x': int, 'y': str})
self.assertEqual(gth(ann_module.C, ann_module.__dict__),
ChainMap({'y': Optional[ann_module.C]}, {}))
self.assertEqual(gth(ann_module2), {})
self.assertEqual(gth(ann_module3), {})
self.assertEqual(repr(gth(ann_module.j_class)), 'ChainMap({}, {})')
self.assertEqual(gth(ann_module.M), ChainMap({'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}, {}))
self.assertEqual(gth(ann_module.h_class),
ChainMap({}, {'y': Optional[ann_module.C]}, {}))
self.assertEqual(gth(ann_module.S), ChainMap({'x': str, 'y': str},
{}))
self.assertEqual(gth(ann_module.foo), {'x': int})
def testf(x, y): ...
testf.__annotations__['x'] = 'int'
self.assertEqual(gth(testf), {'x': int})
self.assertEqual(gth(ann_module2.NTC.meth), {})
# interactions with ClassVar
class B:
x: ClassVar[Optional['B']] = None
y: int
class C(B):
z: ClassVar['C'] = B()
class G(Generic[T]):
lst: ClassVar[List[T]] = []
self.assertEqual(gth(B, locals()),
ChainMap({'y': int, 'x': ClassVar[Optional[B]]}, {}))
self.assertEqual(gth(C, locals()),
ChainMap({'z': ClassVar[C]},
{'y': int, 'x': ClassVar[Optional[B]]}, {}))
self.assertEqual(gth(G), ChainMap({'lst': ClassVar[List[T]]},{},{}))
def test_forwardref_instance_type_error(self):
fr = typing._ForwardRef('int')
with self.assertRaises(TypeError):
......@@ -1198,6 +1156,84 @@ class AsyncIteratorWrapper(typing.AsyncIterator[T_a]):
if PY35:
exec(PY35_TESTS)
PY36 = sys.version_info[:2] >= (3, 6)
PY36_TESTS = """
from test import ann_module, ann_module2, ann_module3
from collections import ChainMap
class B:
x: ClassVar[Optional['B']] = None
y: int
class CSub(B):
z: ClassVar['CSub'] = B()
class G(Generic[T]):
lst: ClassVar[List[T]] = []
class CoolEmployee(NamedTuple):
name: str
cool: int
"""
if PY36:
exec(PY36_TESTS)
gth = get_type_hints
class GetTypeHintTests(BaseTestCase):
@skipUnless(PY36, 'Python 3.6 required')
def test_get_type_hints_modules(self):
self.assertEqual(gth(ann_module), {'x': int, 'y': str})
self.assertEqual(gth(ann_module2), {})
self.assertEqual(gth(ann_module3), {})
@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},
{}, {}))
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}, {}))
self.assertEqual(gth(ann_module.h_class),
ChainMap({}, {'y': Optional[ann_module.C]}, {}))
self.assertEqual(gth(ann_module.S), ChainMap({'x': str, 'y': str},
{}))
self.assertEqual(gth(ann_module.foo), {'x': int})
@skipUnless(PY36, 'Python 3.6 required')
def test_respect_no_type_check(self):
@no_type_check
class NoTpCheck:
class Inn:
def __init__(self, x: 'not a type'): ...
self.assertTrue(NoTpCheck.__no_type_check__)
self.assertTrue(NoTpCheck.Inn.__init__.__no_type_check__)
self.assertEqual(gth(ann_module2.NTC.meth), {})
class ABase(Generic[T]):
def meth(x: int): ...
@no_type_check
class Der(ABase): ...
self.assertEqual(gth(ABase.meth), {'x': int})
def test_previous_behavior(self):
def testf(x, y): ...
testf.__annotations__['x'] = 'int'
self.assertEqual(gth(testf), {'x': int})
@skipUnless(PY36, 'Python 3.6 required')
def test_get_type_hints_ClassVar(self):
self.assertEqual(gth(B, globals()),
ChainMap({'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]]},{},{}))
class CollectionsAbcTests(BaseTestCase):
......@@ -1505,6 +1541,18 @@ class TypeTests(BaseTestCase):
joe = new_user(BasicUser)
def test_type_optional(self):
A = Optional[Type[BaseException]]
def foo(a: A) -> Optional[BaseException]:
if a is None:
return None
else:
return a()
assert isinstance(foo(KeyboardInterrupt), KeyboardInterrupt)
assert foo(None) is None
class NewTypeTests(BaseTestCase):
......@@ -1542,6 +1590,17 @@ class NamedTupleTests(BaseTestCase):
self.assertEqual(Emp._fields, ('name', 'id'))
self.assertEqual(Emp._field_types, dict(name=str, id=int))
@skipUnless(PY36, 'Python 3.6 required')
def test_annotation_usage(self):
tim = CoolEmployee('Tim', 9000)
self.assertIsInstance(tim, CoolEmployee)
self.assertIsInstance(tim, tuple)
self.assertEqual(tim.name, 'Tim')
self.assertEqual(tim.cool, 9000)
self.assertEqual(CoolEmployee.__name__, 'CoolEmployee')
self.assertEqual(CoolEmployee._fields, ('name', 'cool'))
self.assertEqual(CoolEmployee._field_types, dict(name=str, cool=int))
def test_pickle(self):
global Emp # pickle wants to reference the class by name
Emp = NamedTuple('Emp', [('name', str), ('id', int)])
......
......@@ -6,11 +6,12 @@ import functools
import re as stdlib_re # Avoid confusion with the re we export.
import sys
import types
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.
......@@ -1204,7 +1205,8 @@ def _get_defaults(func):
return res
def get_type_hints(obj, globalns=None, localns=None):
if sys.version_info[:2] >= (3, 3):
def get_type_hints(obj, globalns=None, localns=None):
"""Return type hints for an object.
This is often the same as obj.__annotations__, but it handles
......@@ -1299,22 +1301,71 @@ def get_type_hints(obj, globalns=None, localns=None):
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__)
for name, value in hints.items():
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
def no_type_check(arg):
"""Decorator to indicate that annotations are not type hints.
The argument must be a class or function; if it is a class, it
applies recursively to all methods defined in that class (but not
to methods defined in its superclasses or subclasses).
applies recursively to all methods and classes defined in that class
(but not to methods defined in its superclasses or subclasses).
This mutates the function(s) in place.
This mutates the function(s) or class(es) in place.
"""
if isinstance(arg, type):
for obj in arg.__dict__.values():
arg_attrs = arg.__dict__.copy()
for attr, val in arg.__dict__.items():
if val in arg.__bases__:
arg_attrs.pop(attr)
for obj in arg_attrs.values():
if isinstance(obj, types.FunctionType):
obj.__no_type_check__ = True
else:
if isinstance(obj, type):
no_type_check(obj)
try:
arg.__no_type_check__ = True
except TypeError: # built-in classes
pass
return arg
......@@ -1725,7 +1776,7 @@ CT_co = TypeVar('CT_co', covariant=True, bound=type)
# This is not a real generic class. Don't use outside annotations.
class Type(type, Generic[CT_co], extra=type):
class Type(Generic[CT_co], extra=type):
"""A special construct usable to annotate class objects.
For example, suppose we have the following classes::
......@@ -1750,7 +1801,50 @@ class Type(type, Generic[CT_co], extra=type):
"""
def NamedTuple(typename, fields):
def _make_nmtuple(name, types):
nm_tpl = collections.namedtuple(name, [n for n, t in types])
nm_tpl._field_types = dict(types)
try:
nm_tpl.__module__ = sys._getframe(2).f_globals.get('__name__', '__main__')
except (AttributeError, ValueError):
pass
return nm_tpl
if sys.version_info[:2] >= (3, 6):
class NamedTupleMeta(type):
def __new__(cls, typename, bases, ns, *, _root=False):
if _root:
return super().__new__(cls, typename, bases, ns)
types = ns.get('__annotations__', {})
return _make_nmtuple(typename, types.items())
class NamedTuple(metaclass=NamedTupleMeta, _root=True):
"""Typed version of namedtuple.
Usage::
class Employee(NamedTuple):
name: str
id: int
This is equivalent to::
Employee = collections.namedtuple('Employee', ['name', 'id'])
The resulting class has one extra attribute: _field_types,
giving a dict mapping field names to types. (The field names
are in the _fields attribute, which is part of the namedtuple
API.) Backward-compatible usage::
Employee = NamedTuple('Employee', [('name', str), ('id', int)])
"""
def __new__(self, typename, fields):
return _make_nmtuple(typename, fields)
else:
def NamedTuple(typename, fields):
"""Typed version of namedtuple.
Usage::
......@@ -1766,15 +1860,7 @@ def NamedTuple(typename, fields):
are in the _fields attribute, which is part of the namedtuple
API.)
"""
fields = [(n, t) for n, t in fields]
cls = collections.namedtuple(typename, [n for n, t in fields])
cls._field_types = dict(fields)
# Set the module to the caller's module (otherwise it'd be 'typing').
try:
cls.__module__ = sys._getframe(1).f_globals.get('__name__', '__main__')
except (AttributeError, ValueError):
pass
return cls
return _make_nmtuple(typename, fields)
def NewType(name, tp):
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment