summaryrefslogtreecommitdiff
path: root/src/zope/interface/common
diff options
context:
space:
mode:
authorJason Madden <jamadden@gmail.com>2020-03-05 14:38:39 -0600
committerJason Madden <jamadden@gmail.com>2020-03-15 09:56:14 -0500
commit024f6432270afd021da2d9fff5c3f496f788e54d (patch)
treed9732ae94de818f2e3ea8ac144e1b932cbefa133 /src/zope/interface/common
parent354faccebd5b612a2ac8e081a7e5d2f7fb1089c1 (diff)
downloadzope-interface-issue21.tar.gz
Use C3 (mostly) to compute IRO.issue21
Fixes #21 The 'mostly' is because interfaces are used in cases that C3 forbids; when there's a conflict, we fallback to the legacy algorithm. It turns out there are few conflicts (13K out of 149K total orderings in Plone). I hoped the fix for #8 might shake out automatically, but it didn't. Optimize the extremely common case of a __bases__ of length one. In the benchmark, 4/5 of the interfaces and related objects have a base of length one. Fix the bad IROs in the bundled ABC interfaces, and implement a way to get warnings or errors. In running plone/buildout.coredev and tracking the RO requests, the stats for equal, not equal, and inconsistent-so-fallback, I got {'ros': 148868, 'eq': 138461, 'ne': 10407, 'inconsistent': 12934} Add the interface module to the Attribute str. This was extremely helpful tracking down the Plone problem; IDate is defined in multiple modules.
Diffstat (limited to 'src/zope/interface/common')
-rw-r--r--src/zope/interface/common/__init__.py16
-rw-r--r--src/zope/interface/common/builtins.py1
-rw-r--r--src/zope/interface/common/collections.py4
-rw-r--r--src/zope/interface/common/mapping.py6
-rw-r--r--src/zope/interface/common/tests/__init__.py31
-rw-r--r--src/zope/interface/common/tests/test_collections.py4
6 files changed, 48 insertions, 14 deletions
diff --git a/src/zope/interface/common/__init__.py b/src/zope/interface/common/__init__.py
index acbc581..a8bedf0 100644
--- a/src/zope/interface/common/__init__.py
+++ b/src/zope/interface/common/__init__.py
@@ -121,19 +121,20 @@ class ABCInterfaceClass(InterfaceClass):
# go ahead and give us a name to ease debugging.
self.__name__ = name
extra_classes = attrs.pop('extra_classes', ())
+ ignored_classes = attrs.pop('ignored_classes', ())
if 'abc' not in attrs:
# Something like ``IList(ISequence)``: We're extending
# abc interfaces but not an ABC interface ourself.
- self.__class__ = InterfaceClass
InterfaceClass.__init__(self, name, bases, attrs)
- for cls in extra_classes:
- classImplements(cls, self)
+ ABCInterfaceClass.__register_classes(self, extra_classes, ignored_classes)
+ self.__class__ = InterfaceClass
return
based_on = attrs.pop('abc')
self.__abc = based_on
self.__extra_classes = tuple(extra_classes)
+ self.__ignored_classes = tuple(ignored_classes)
assert name[1:] == based_on.__name__, (name, based_on)
methods = {
@@ -216,11 +217,14 @@ class ABCInterfaceClass(InterfaceClass):
method.positional = method.positional[1:]
return method
- def __register_classes(self):
+ def __register_classes(self, conformers=None, ignored_classes=None):
# Make the concrete classes already present in our ABC's registry
# declare that they implement this interface.
-
- for cls in self.getRegisteredConformers():
+ conformers = conformers if conformers is not None else self.getRegisteredConformers()
+ ignored = ignored_classes if ignored_classes is not None else self.__ignored_classes
+ for cls in conformers:
+ if cls in ignored:
+ continue
classImplements(cls, self)
def getABC(self):
diff --git a/src/zope/interface/common/builtins.py b/src/zope/interface/common/builtins.py
index 9262340..a07c0a3 100644
--- a/src/zope/interface/common/builtins.py
+++ b/src/zope/interface/common/builtins.py
@@ -37,7 +37,6 @@ __all__ = [
]
# pylint:disable=no-self-argument
-
class IList(collections.IMutableSequence):
"""
Interface for :class:`list`
diff --git a/src/zope/interface/common/collections.py b/src/zope/interface/common/collections.py
index 9731069..6c0496e 100644
--- a/src/zope/interface/common/collections.py
+++ b/src/zope/interface/common/collections.py
@@ -177,6 +177,10 @@ class ISequence(IReversible,
ICollection):
abc = abc.Sequence
extra_classes = (UserString,)
+ # On Python 2, basestring is registered as an ISequence, and
+ # its subclass str is an IByteString. If we also register str as
+ # an ISequence, that tends to lead to inconsistent resolution order.
+ ignored_classes = (basestring,) if str is bytes else () # pylint:disable=undefined-variable
@optional
def __reversed__():
diff --git a/src/zope/interface/common/mapping.py b/src/zope/interface/common/mapping.py
index 13fa317..de56cf8 100644
--- a/src/zope/interface/common/mapping.py
+++ b/src/zope/interface/common/mapping.py
@@ -43,7 +43,7 @@ class IItemMapping(Interface):
"""
-class IReadMapping(IItemMapping, collections.IContainer):
+class IReadMapping(collections.IContainer, IItemMapping):
"""
Basic mapping interface.
@@ -72,7 +72,7 @@ class IWriteMapping(Interface):
"""Set a new item in the mapping."""
-class IEnumerableMapping(IReadMapping, collections.ISized):
+class IEnumerableMapping(collections.ISized, IReadMapping):
"""
Mapping objects whose items can be enumerated.
@@ -171,7 +171,7 @@ class IExtendedWriteMapping(IWriteMapping):
class IFullMapping(
collections.IMutableMapping,
- IExtendedReadMapping, IExtendedWriteMapping, IClonableMapping, IMapping):
+ IExtendedReadMapping, IExtendedWriteMapping, IClonableMapping, IMapping,):
"""
Full mapping interface.
diff --git a/src/zope/interface/common/tests/__init__.py b/src/zope/interface/common/tests/__init__.py
index 059e46c..ade2bf3 100644
--- a/src/zope/interface/common/tests/__init__.py
+++ b/src/zope/interface/common/tests/__init__.py
@@ -38,7 +38,8 @@ def iter_abc_interfaces(predicate=lambda iface: True):
if not predicate(iface):
continue
- registered = list(iface.getRegisteredConformers())
+ registered = set(iface.getRegisteredConformers())
+ registered -= set(iface._ABCInterfaceClass__ignored_classes)
if registered:
yield iface, registered
@@ -50,24 +51,46 @@ def add_abc_interface_tests(cls, module):
def add_verify_tests(cls, iface_classes_iter):
+ cls.maxDiff = None
for iface, registered_classes in iface_classes_iter:
for stdlib_class in registered_classes:
-
def test(self, stdlib_class=stdlib_class, iface=iface):
if stdlib_class in self.UNVERIFIABLE or stdlib_class.__name__ in self.UNVERIFIABLE:
self.skipTest("Unable to verify %s" % stdlib_class)
self.assertTrue(self.verify(iface, stdlib_class))
- name = 'test_auto_' + stdlib_class.__name__ + '_' + iface.__name__
+ suffix = "%s_%s_%s" % (
+ stdlib_class.__name__,
+ iface.__module__.replace('.', '_'),
+ iface.__name__
+ )
+ name = 'test_auto_' + suffix
test.__name__ = name
- assert not hasattr(cls, name)
+ assert not hasattr(cls, name), (name, list(cls.__dict__))
setattr(cls, name, test)
+ def test_ro(self, stdlib_class=stdlib_class, iface=iface):
+ from zope.interface import ro
+ from zope.interface import implementedBy
+ self.assertEqual(
+ tuple(ro.ro(iface, strict=True)),
+ iface.__sro__)
+ implements = implementedBy(stdlib_class)
+ strict = stdlib_class not in self.NON_STRICT_RO
+ self.assertEqual(
+ tuple(ro.ro(implements, strict=strict)),
+ implements.__sro__)
+
+ name = 'test_auto_ro_' + suffix
+ test_ro.__name__ = name
+ assert not hasattr(cls, name)
+ setattr(cls, name, test_ro)
class VerifyClassMixin(unittest.TestCase):
verifier = staticmethod(verifyClass)
UNVERIFIABLE = ()
+ NON_STRICT_RO = ()
def _adjust_object_before_verify(self, iface, x):
return x
diff --git a/src/zope/interface/common/tests/test_collections.py b/src/zope/interface/common/tests/test_collections.py
index 32ab801..f06e12e 100644
--- a/src/zope/interface/common/tests/test_collections.py
+++ b/src/zope/interface/common/tests/test_collections.py
@@ -17,6 +17,7 @@ try:
except ImportError:
import collections as abc
from collections import deque
+from collections import OrderedDict
try:
@@ -118,6 +119,9 @@ class TestVerifyClass(VerifyClassMixin, unittest.TestCase):
type({}.viewitems()),
type({}.viewkeys()),
})
+ NON_STRICT_RO = {
+ OrderedDict
+ }
add_abc_interface_tests(TestVerifyClass, collections.ISet.__module__)