Skip to content

Commit

Permalink
Add more tests for ABC behaviour of typing classes (#294)
Browse files Browse the repository at this point in the history
 Here I am adding tests as discussed with @bintoro (related to #207).

These tests actually revealed three small bugs:

- Old style classes in Python don't have `__mro__`.
- We should use `__extra__` instead of extra in Python2 (since no kwargs in classes).
- We should allow overriding `__subclasshook__` by subclasses.
  • Loading branch information
ilevkivskyi authored and gvanrossum committed Oct 9, 2016
1 parent 2dbf3fd commit 126d3be
Show file tree
Hide file tree
Showing 4 changed files with 169 additions and 4 deletions.
78 changes: 78 additions & 0 deletions python2/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from typing import NamedTuple
from typing import IO, TextIO, BinaryIO
from typing import Pattern, Match
import abc
import typing
try:
import collections.abc as collections_abc
Expand Down Expand Up @@ -1059,6 +1060,8 @@ def __len__(self):
return 0

self.assertEqual(len(MMC()), 0)
assert callable(MMC.update)
self.assertIsInstance(MMC(), typing.Mapping)

class MMB(typing.MutableMapping[KT, VT]):
def __getitem__(self, k):
Expand All @@ -1083,6 +1086,81 @@ def __len__(self):
self.assertIsSubclass(MMB, typing.Mapping)
self.assertIsSubclass(MMC, typing.Mapping)

self.assertIsInstance(MMB[KT, VT](), typing.Mapping)
self.assertIsInstance(MMB[KT, VT](), collections.Mapping)

self.assertIsSubclass(MMA, collections.Mapping)
self.assertIsSubclass(MMB, collections.Mapping)
self.assertIsSubclass(MMC, collections.Mapping)

self.assertIsSubclass(MMB[str, str], typing.Mapping)
self.assertIsSubclass(MMC, MMA)

class I(typing.Iterable): pass
self.assertNotIsSubclass(list, I)

class G(typing.Generator[int, int, int]): pass
def g(): yield 0
self.assertIsSubclass(G, typing.Generator)
self.assertIsSubclass(G, typing.Iterable)
if hasattr(collections, 'Generator'):
self.assertIsSubclass(G, collections.Generator)
self.assertIsSubclass(G, collections.Iterable)
self.assertNotIsSubclass(type(g), G)

def test_subclassing_subclasshook(self):

class Base(typing.Iterable):
@classmethod
def __subclasshook__(cls, other):
if other.__name__ == 'Foo':
return True
else:
return False

class C(Base): pass
class Foo: pass
class Bar: pass
self.assertIsSubclass(Foo, Base)
self.assertIsSubclass(Foo, C)
self.assertNotIsSubclass(Bar, C)

def test_subclassing_register(self):

class A(typing.Container): pass
class B(A): pass

class C: pass
A.register(C)
self.assertIsSubclass(C, A)
self.assertNotIsSubclass(C, B)

class D: pass
B.register(D)
self.assertIsSubclass(D, A)
self.assertIsSubclass(D, B)

class M(): pass
collections.MutableMapping.register(M)
self.assertIsSubclass(M, typing.Mapping)

def test_collections_as_base(self):

class M(collections.Mapping): pass
self.assertIsSubclass(M, typing.Mapping)
self.assertIsSubclass(M, typing.Iterable)

class S(collections.MutableSequence): pass
self.assertIsSubclass(S, typing.MutableSequence)
self.assertIsSubclass(S, typing.Iterable)

class I(collections.Iterable): pass
self.assertIsSubclass(I, typing.Iterable)

class A(collections.Mapping): pass
class B: pass
A.register(B)
self.assertIsSubclass(B, typing.Mapping)

class TypeTests(BaseTestCase):

Expand Down
11 changes: 8 additions & 3 deletions python2/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -1023,7 +1023,7 @@ def __extrahook__(cls, subclass):
res = cls.__extra__.__subclasshook__(subclass)
if res is not NotImplemented:
return res
if cls.__extra__ in subclass.__mro__:
if cls.__extra__ in getattr(subclass, '__mro__', ()):
return True
for scls in cls.__extra__.__subclasses__():
if isinstance(scls, GenericMeta):
Expand All @@ -1046,6 +1046,8 @@ class GenericMeta(TypingMeta, abc.ABCMeta):

def __new__(cls, name, bases, namespace,
tvars=None, args=None, origin=None, extra=None):
if extra is None:
extra = namespace.get('__extra__')
if extra is not None and type(extra) is abc.ABCMeta and extra not in bases:
bases = (extra,) + bases
self = super(GenericMeta, cls).__new__(cls, name, bases, namespace)
Expand Down Expand Up @@ -1093,14 +1095,17 @@ def __new__(cls, name, bases, namespace,
self.__parameters__ = tvars
self.__args__ = args
self.__origin__ = origin
self.__extra__ = namespace.get('__extra__')
self.__extra__ = extra
# Speed hack (https://github.com/python/typing/issues/196).
self.__next_in_mro__ = _next_in_mro(self)

# This allows unparameterized generic collections to be used
# with issubclass() and isinstance() in the same way as their
# collections.abc counterparts (e.g., isinstance([], Iterable)).
self.__subclasshook__ = _make_subclasshook(self)
if ('__subclasshook__' not in namespace and extra # allow overriding
or hasattr(self.__subclasshook__, '__name__') and
self.__subclasshook__.__name__ == '__extrahook__'):
self.__subclasshook__ = _make_subclasshook(self)
if isinstance(extra, abc.ABCMeta):
self._abc_registry = extra._abc_registry
return self
Expand Down
79 changes: 79 additions & 0 deletions src/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from typing import NamedTuple
from typing import IO, TextIO, BinaryIO
from typing import Pattern, Match
import abc
import typing
try:
import collections.abc as collections_abc
Expand Down Expand Up @@ -1385,6 +1386,8 @@ def __len__(self):
return 0

self.assertEqual(len(MMC()), 0)
assert callable(MMC.update)
self.assertIsInstance(MMC(), typing.Mapping)

class MMB(typing.MutableMapping[KT, VT]):
def __getitem__(self, k):
Expand All @@ -1409,6 +1412,82 @@ def __len__(self):
self.assertIsSubclass(MMB, typing.Mapping)
self.assertIsSubclass(MMC, typing.Mapping)

self.assertIsInstance(MMB[KT, VT](), typing.Mapping)
self.assertIsInstance(MMB[KT, VT](), collections.Mapping)

self.assertIsSubclass(MMA, collections.Mapping)
self.assertIsSubclass(MMB, collections.Mapping)
self.assertIsSubclass(MMC, collections.Mapping)

self.assertIsSubclass(MMB[str, str], typing.Mapping)
self.assertIsSubclass(MMC, MMA)

class I(typing.Iterable): ...
self.assertNotIsSubclass(list, I)

class G(typing.Generator[int, int, int]): ...
def g(): yield 0
self.assertIsSubclass(G, typing.Generator)
self.assertIsSubclass(G, typing.Iterable)
if hasattr(collections, 'Generator'):
self.assertIsSubclass(G, collections.Generator)
self.assertIsSubclass(G, collections.Iterable)
self.assertNotIsSubclass(type(g), G)

def test_subclassing_subclasshook(self):

class Base(typing.Iterable):
@classmethod
def __subclasshook__(cls, other):
if other.__name__ == 'Foo':
return True
else:
return False

class C(Base): ...
class Foo: ...
class Bar: ...
self.assertIsSubclass(Foo, Base)
self.assertIsSubclass(Foo, C)
self.assertNotIsSubclass(Bar, C)

def test_subclassing_register(self):

class A(typing.Container): ...
class B(A): ...

class C: ...
A.register(C)
self.assertIsSubclass(C, A)
self.assertNotIsSubclass(C, B)

class D: ...
B.register(D)
self.assertIsSubclass(D, A)
self.assertIsSubclass(D, B)

class M(): ...
collections.MutableMapping.register(M)
self.assertIsSubclass(M, typing.Mapping)

def test_collections_as_base(self):

class M(collections.Mapping): ...
self.assertIsSubclass(M, typing.Mapping)
self.assertIsSubclass(M, typing.Iterable)

class S(collections.MutableSequence): ...
self.assertIsSubclass(S, typing.MutableSequence)
self.assertIsSubclass(S, typing.Iterable)

class I(collections.Iterable): ...
self.assertIsSubclass(I, typing.Iterable)

class A(collections.Mapping, metaclass=abc.ABCMeta): ...
class B: ...
A.register(B)
self.assertIsSubclass(B, typing.Mapping)


class OtherABCTests(BaseTestCase):

Expand Down
5 changes: 4 additions & 1 deletion src/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -993,7 +993,10 @@ def __new__(cls, name, bases, namespace,
# This allows unparameterized generic collections to be used
# with issubclass() and isinstance() in the same way as their
# collections.abc counterparts (e.g., isinstance([], Iterable)).
self.__subclasshook__ = _make_subclasshook(self)
if ('__subclasshook__' not in namespace and extra # allow overriding
or hasattr(self.__subclasshook__, '__name__') and
self.__subclasshook__.__name__ == '__extrahook__'):
self.__subclasshook__ = _make_subclasshook(self)
if isinstance(extra, abc.ABCMeta):
self._abc_registry = extra._abc_registry
return self
Expand Down

0 comments on commit 126d3be

Please sign in to comment.