diff -r ed291f85434b Doc/library/abc.rst --- a/Doc/library/abc.rst Wed Apr 23 12:17:25 2014 -0500 +++ b/Doc/library/abc.rst Fri Apr 25 00:35:20 2014 +0300 @@ -62,6 +62,25 @@ To detect calls to :meth:`register`, you can use the :func:`get_cache_token` function. + .. classmethod:: verify_full_api(subclass) + + Verify that *subclass* implements all the methods + required by this ABC, returning ``True`` in this case. + For example:: + + class MyABC(metaclass=abc.ABCMeta): + @abc.abstractmethod + def method(self): + pass + + class Implements(MyABC): + def method1(self): + pass + + MyABC.verify_full_api(Implements) + + .. versionadded:: 3.5 + You can also override this method in an abstract base class: .. method:: __subclasshook__(subclass) diff -r ed291f85434b Lib/abc.py --- a/Lib/abc.py Wed Apr 23 12:17:25 2014 -0500 +++ b/Lib/abc.py Fri Apr 25 00:35:20 2014 +0300 @@ -166,6 +166,19 @@ ABCMeta._abc_invalidation_counter += 1 # Invalidate negative cache return subclass + def verify_full_api(cls, subclass): + """ Verify that :class:`subclass` implements all the + methods required by the current ABC. + + Returns True iff subclass implements the appropriate methods. + """ + subclass_names = dir(subclass) + has_abstract = ('__abstractmethods__' in subclass_names and + subclass.__abstractmethods__) + return (not has_abstract and + all(meth in subclass_names + for meth in cls.__abstractmethods__)) + def _dump_registry(cls, file=None): """Debug helper to print the ABC registry.""" print("Class: %s.%s" % (cls.__module__, cls.__name__), file=file) diff -r ed291f85434b Lib/test/test_abc.py --- a/Lib/test/test_abc.py Wed Apr 23 12:17:25 2014 -0500 +++ b/Lib/test/test_abc.py Fri Apr 25 00:35:20 2014 +0300 @@ -403,6 +403,31 @@ C() self.assertEqual(B.counter, 1) + def test_verify_full_api(self): + class A(metaclass=abc.ABCMeta): + @abc.abstractmethod + def method(self): pass + + @staticmethod + @abc.abstractmethod + def static(): pass + + class Implements(A): + def method(self): pass + @staticmethod + def static(): pass + + class ImplementsPartially(A): + # initialisation won't fail + method = None + static = None + + class NotImplements(A): + def method(self): pass + + self.assertTrue(A.verify_full_api(Implements)) + self.assertTrue(A.verify_full_api(ImplementsPartially)) + self.assertFalse(A.verify_full_api(NotImplements)) if __name__ == "__main__": unittest.main() diff -r ed291f85434b Lib/test/test_collections.py --- a/Lib/test/test_collections.py Wed Apr 23 12:17:25 2014 -0500 +++ b/Lib/test/test_collections.py Fri Apr 25 00:35:20 2014 +0300 @@ -16,7 +16,10 @@ from collections.abc import Hashable, Iterable, Iterator from collections.abc import Sized, Container, Callable from collections.abc import Set, MutableSet -from collections.abc import Mapping, MutableMapping, KeysView, ItemsView +from collections.abc import ( + Mapping, MutableMapping, + KeysView, ItemsView, ValuesView +) from collections.abc import Sequence, MutableSequence from collections.abc import ByteString @@ -848,6 +851,57 @@ mss.clear() self.assertEqual(len(mss), 0) + def verify_full_api(self, cls, obj): + # checks for issue9731 + self.assertTrue(cls.verify_full_api(obj)) + + def test_verify_full_api(self): + bytes_iterator = type(iter(b'')) + bytearray_iterator = type(iter(bytearray())) + dict_keyiterator = type(iter({}.keys())) + dict_valueiterator = type(iter({}.values())) + dict_itemiterator = type(iter({}.items())) + list_iterator = type(iter([])) + list_reverseiterator = type(iter(reversed([]))) + range_iterator = type(iter(range(0))) + set_iterator = type(iter(set())) + str_iterator = type(iter("")) + tuple_iterator = type(iter(())) + zip_iterator = type(iter(zip())) + dict_keys = type({}.keys()) + dict_values = type({}.values()) + dict_items = type({}.items()) + mappingproxy = type(type.__dict__) + + self.verify_full_api(Iterator, bytes_iterator) + self.verify_full_api(Iterator, bytearray_iterator) + self.verify_full_api(Iterator, dict_keyiterator) + self.verify_full_api(Iterator, dict_valueiterator) + self.verify_full_api(Iterator, dict_itemiterator) + self.verify_full_api(Iterator, list_iterator) + self.verify_full_api(Iterator, list_reverseiterator) + self.verify_full_api(Iterator, range_iterator) + self.verify_full_api(Iterator, set_iterator) + self.verify_full_api(Iterator, str_iterator) + self.verify_full_api(Iterator, tuple_iterator) + self.verify_full_api(Iterator, zip_iterator) + self.verify_full_api(Mapping, mappingproxy) + self.verify_full_api(KeysView, dict_keys) + self.verify_full_api(ItemsView, dict_items) + self.verify_full_api(ValuesView, dict_values) + self.verify_full_api(ByteString, bytes) + self.verify_full_api(ByteString, bytearray) + self.verify_full_api(MutableMapping, dict) + self.verify_full_api(Sequence, tuple) + self.verify_full_api(Sequence, str) + self.verify_full_api(Sequence, range) + self.verify_full_api(Sequence, memoryview) + self.verify_full_api(Set, frozenset) + self.verify_full_api(MutableSet, set) + self.verify_full_api(MutableSequence, list) + self.verify_full_api(MutableSequence, bytearray) + + ################################################################################ ### Counter ################################################################################