From b39823ccb60df3e1531866f0f161914e7d3578cb Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 9 Oct 2016 01:10:36 +0200 Subject: [PATCH 1/2] Add more tests for ABC behaviour of typing classes --- python2/test_typing.py | 76 +++++++++++++++++++++++++++++++++++++++++ python2/typing.py | 11 ++++-- src/test_typing.py | 77 ++++++++++++++++++++++++++++++++++++++++++ src/typing.py | 5 ++- 4 files changed, 165 insertions(+), 4 deletions(-) diff --git a/python2/test_typing.py b/python2/test_typing.py index 73a9f0d7..d4b7e459 100644 --- a/python2/test_typing.py +++ b/python2/test_typing.py @@ -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 @@ -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): @@ -1083,6 +1086,79 @@ 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 + self.assertIsSubclass(Foo, Base) + self.assertNotIsSubclass(Foo, 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): diff --git a/python2/typing.py b/python2/typing.py index 6cd51c6b..410abf53 100644 --- a/python2/typing.py +++ b/python2/typing.py @@ -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): @@ -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) @@ -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 or # allow overriding + 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 diff --git a/src/test_typing.py b/src/test_typing.py index cf3171f7..f9e29ab3 100644 --- a/src/test_typing.py +++ b/src/test_typing.py @@ -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 @@ -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): @@ -1409,6 +1412,80 @@ 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: ... + self.assertIsSubclass(Foo, Base) + self.assertNotIsSubclass(Foo, 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): diff --git a/src/typing.py b/src/typing.py index 05b65b70..1a45a5e4 100644 --- a/src/typing.py +++ b/src/typing.py @@ -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 or # allow overriding + 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 From e8c2d87cd46b34457970bd3021853a1376205f5a Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 9 Oct 2016 02:14:45 +0200 Subject: [PATCH 2/2] Finetune to match typing.X with collections.abc.X exactly --- python2/test_typing.py | 4 +++- python2/typing.py | 4 ++-- src/test_typing.py | 4 +++- src/typing.py | 4 ++-- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/python2/test_typing.py b/python2/test_typing.py index d4b7e459..8dd1acb0 100644 --- a/python2/test_typing.py +++ b/python2/test_typing.py @@ -1120,8 +1120,10 @@ def __subclasshook__(cls, other): class C(Base): pass class Foo: pass + class Bar: pass self.assertIsSubclass(Foo, Base) - self.assertNotIsSubclass(Foo, C) + self.assertIsSubclass(Foo, C) + self.assertNotIsSubclass(Bar, C) def test_subclassing_register(self): diff --git a/python2/typing.py b/python2/typing.py index 410abf53..cb5b3edd 100644 --- a/python2/typing.py +++ b/python2/typing.py @@ -1102,8 +1102,8 @@ 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)). - if ('__subclasshook__' not in namespace or # allow overriding - hasattr(self.__subclasshook__, '__name__') and + 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): diff --git a/src/test_typing.py b/src/test_typing.py index f9e29ab3..dff737ae 100644 --- a/src/test_typing.py +++ b/src/test_typing.py @@ -1446,8 +1446,10 @@ def __subclasshook__(cls, other): class C(Base): ... class Foo: ... + class Bar: ... self.assertIsSubclass(Foo, Base) - self.assertNotIsSubclass(Foo, C) + self.assertIsSubclass(Foo, C) + self.assertNotIsSubclass(Bar, C) def test_subclassing_register(self): diff --git a/src/typing.py b/src/typing.py index 1a45a5e4..557fa589 100644 --- a/src/typing.py +++ b/src/typing.py @@ -993,8 +993,8 @@ 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)). - if ('__subclasshook__' not in namespace or # allow overriding - hasattr(self.__subclasshook__, '__name__') and + 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):