Skip to content

Commit

Permalink
Copy ClassVar upstream (#280)
Browse files Browse the repository at this point in the history
With some refactoring to avoid depending on core test infrastructure.
  • Loading branch information
ilevkivskyi authored and gvanrossum committed Sep 10, 2016
1 parent a1952c4 commit 2dd068b
Show file tree
Hide file tree
Showing 4 changed files with 412 additions and 40 deletions.
39 changes: 38 additions & 1 deletion python2/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from typing import Union, Optional
from typing import Tuple
from typing import Callable
from typing import Generic
from typing import Generic, ClassVar
from typing import cast
from typing import Type
from typing import NewType
Expand Down Expand Up @@ -802,6 +802,43 @@ class D(C):
with self.assertRaises(Exception):
D[T]

class ClassVarTests(BaseTestCase):

def test_basics(self):
with self.assertRaises(TypeError):
ClassVar[1]
with self.assertRaises(TypeError):
ClassVar[int, str]
with self.assertRaises(TypeError):
ClassVar[int][str]

def test_repr(self):
self.assertEqual(repr(ClassVar), 'typing.ClassVar')
cv = ClassVar[int]
self.assertEqual(repr(cv), 'typing.ClassVar[int]')
cv = ClassVar[Employee]
self.assertEqual(repr(cv), 'typing.ClassVar[%s.Employee]' % __name__)

def test_cannot_subclass(self):
with self.assertRaises(TypeError):
class C(type(ClassVar)):
pass
with self.assertRaises(TypeError):
class C(type(ClassVar[int])):
pass

def test_cannot_init(self):
with self.assertRaises(TypeError):
type(ClassVar)()
with self.assertRaises(TypeError):
type(ClassVar[Optional[int]])()

def test_no_isinstance(self):
with self.assertRaises(TypeError):
isinstance(1, ClassVar[int])
with self.assertRaises(TypeError):
issubclass(int, ClassVar)


class VarianceTests(BaseTestCase):

Expand Down
77 changes: 75 additions & 2 deletions python2/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
# Super-special typing primitives.
'Any',
'Callable',
'ClassVar',
'Generic',
'Optional',
'Tuple',
Expand Down Expand Up @@ -265,7 +266,7 @@ def __subclasscheck__(self, cls):

def _get_type_vars(types, tvars):
for t in types:
if isinstance(t, TypingMeta):
if isinstance(t, TypingMeta) or isinstance(t, _ClassVar):
t._get_type_vars(tvars)


Expand All @@ -276,7 +277,7 @@ def _type_vars(types):


def _eval_type(t, globalns, localns):
if isinstance(t, TypingMeta):
if isinstance(t, TypingMeta) or isinstance(t, _ClassVar):
return t._eval_type(globalns, localns)
else:
return t
Expand Down Expand Up @@ -320,6 +321,78 @@ def _type_repr(obj):
return repr(obj)


class ClassVarMeta(TypingMeta):
"""Metaclass for _ClassVar"""

def __new__(cls, name, bases, namespace):
cls.assert_no_subclassing(bases)
self = super(ClassVarMeta, cls).__new__(cls, name, bases, namespace)
return self


class _ClassVar(object):
"""Special type construct to mark class variables.
An annotation wrapped in ClassVar indicates that a given
attribute is intended to be used as a class variable and
should not be set on instances of that class. Usage::
class Starship:
stats = {} # type: ClassVar[Dict[str, int]] # class variable
damage = 10 # type: int # instance variable
ClassVar accepts only types and cannot be further subscribed.
Note that ClassVar is not a class itself, and should not
be used with isinstance() or issubclass().
"""

__metaclass__ = ClassVarMeta

def __init__(self, tp=None, _root=False):
cls = type(self)
if _root:
self.__type__ = tp
else:
raise TypeError('Cannot initialize {}'.format(cls.__name__[1:]))

def __getitem__(self, item):
cls = type(self)
if self.__type__ is None:
return cls(_type_check(item,
'{} accepts only types.'.format(cls.__name__[1:])),
_root=True)
raise TypeError('{} cannot be further subscripted'
.format(cls.__name__[1:]))

def _eval_type(self, globalns, localns):
return type(self)(_eval_type(self.__type__, globalns, localns),
_root=True)

def _get_type_vars(self, tvars):
if self.__type__:
_get_type_vars(self.__type__, tvars)

def __repr__(self):
cls = type(self)
if not self.__type__:
return '{}.{}'.format(cls.__module__, cls.__name__[1:])
return '{}.{}[{}]'.format(cls.__module__, cls.__name__[1:],
_type_repr(self.__type__))

def __hash__(self):
return hash((type(self).__name__, self.__type__))

def __eq__(self, other):
if not isinstance(other, _ClassVar):
return NotImplemented
if self.__type__ is not None:
return self.__type__ == other.__type__
return self is other

ClassVar = _ClassVar(_root=True)


class AnyMeta(TypingMeta):
"""Metaclass for Any."""

Expand Down
103 changes: 101 additions & 2 deletions src/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
from typing import TypeVar, AnyStr
from typing import T, KT, VT # Not in __all__.
from typing import Union, Optional
from typing import Tuple
from typing import Tuple, List
from typing import Callable
from typing import Generic
from typing import Generic, ClassVar
from typing import cast
from typing import get_type_hints
from typing import no_type_check, no_type_check_decorator
Expand Down Expand Up @@ -827,6 +827,43 @@ class D(C):
with self.assertRaises(Exception):
D[T]

class ClassVarTests(BaseTestCase):

def test_basics(self):
with self.assertRaises(TypeError):
ClassVar[1]
with self.assertRaises(TypeError):
ClassVar[int, str]
with self.assertRaises(TypeError):
ClassVar[int][str]

def test_repr(self):
self.assertEqual(repr(ClassVar), 'typing.ClassVar')
cv = ClassVar[int]
self.assertEqual(repr(cv), 'typing.ClassVar[int]')
cv = ClassVar[Employee]
self.assertEqual(repr(cv), 'typing.ClassVar[%s.Employee]' % __name__)

def test_cannot_subclass(self):
with self.assertRaises(TypeError):
class C(type(ClassVar)):
pass
with self.assertRaises(TypeError):
class C(type(ClassVar[int])):
pass

def test_cannot_init(self):
with self.assertRaises(TypeError):
type(ClassVar)()
with self.assertRaises(TypeError):
type(ClassVar[Optional[int]])()

def test_no_isinstance(self):
with self.assertRaises(TypeError):
isinstance(1, ClassVar[int])
with self.assertRaises(TypeError):
issubclass(int, ClassVar)


class VarianceTests(BaseTestCase):

Expand Down Expand Up @@ -1119,6 +1156,68 @@ def __anext__(self) -> 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]] = []
"""

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):
self.assertEqual(gth(ann_module2.NTC.meth), {})

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):

Expand Down
Loading

0 comments on commit 2dd068b

Please sign in to comment.