summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason Madden <jamadden@gmail.com>2016-08-03 10:32:17 -0500
committerJason Madden <jamadden@gmail.com>2016-08-03 10:34:31 -0500
commitbbf1a3d4792bf5b0a3969746521b27d2a2b4bf06 (patch)
treeb3b799ea0bbc438eb9b0625e80e29904b030e5c1
parente79273048b92735fb23fcee7f695a5ed5d72f647 (diff)
downloadzope-interface-comp-implements.tar.gz
Make declarations.Implements sortable.comp-implements
Fixes #42
-rw-r--r--CHANGES.rst5
-rw-r--r--src/zope/interface/declarations.py88
-rw-r--r--src/zope/interface/interface.py2
-rw-r--r--src/zope/interface/tests/test_declarations.py25
-rw-r--r--src/zope/interface/tests/test_odd_declarations.py1
5 files changed, 107 insertions, 14 deletions
diff --git a/CHANGES.rst b/CHANGES.rst
index 7ac92c4..db52ae5 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -4,7 +4,10 @@ Changes
4.2.1 (unreleased)
------------------
-- TBD
+- Add the ability to sort the objects returned by ``implementedBy``.
+ This is compatible with the way interface classes sort so they can
+ be used together in ordered containers like BTrees.
+ (https://github.com/zopefoundation/zope.interface/issues/42)
4.2.0 (2016-06-10)
------------------
diff --git a/src/zope/interface/declarations.py b/src/zope/interface/declarations.py
index 5c4a20a..d770fa0 100644
--- a/src/zope/interface/declarations.py
+++ b/src/zope/interface/declarations.py
@@ -30,7 +30,6 @@ import sys
from types import FunctionType
from types import MethodType
from types import ModuleType
-import warnings
import weakref
from zope.interface.advice import addClassAdvisor
@@ -131,12 +130,86 @@ class Implements(Declaration):
__name__ = '?'
+ @classmethod
+ def named(cls, name, *interfaces):
+ # Implementation method: Produce an Implements interface with
+ # a fully fleshed out __name__ before calling the constructor, which
+ # sets bases to the given interfaces and which may pass this object to
+ # other objects (e.g., to adjust dependents). If they're sorting or comparing
+ # by name, this needs to be set.
+ inst = cls.__new__(cls)
+ inst.__name__ = name
+ inst.__init__(*interfaces)
+ return inst
+
def __repr__(self):
return '<implementedBy %s>' % (self.__name__)
def __reduce__(self):
return implementedBy, (self.inherit, )
+ def __cmp(self, other):
+ # Yes, I did mean to name this __cmp, rather than __cmp__.
+ # It is a private method used by __lt__ and __gt__.
+ # This is based on, and compatible with, InterfaceClass.
+ # (The two must be mutually comparable to be able to work in e.g., BTrees.)
+ # Instances of this class generally don't have a __module__ other than
+ # `zope.interface.declarations`, whereas they *do* have a __name__ that is the
+ # fully qualified name of the object they are representing.
+
+ # Note, though, that equality and hashing are still identity based. This
+ # accounts for things like nested objects that have the same name (typically
+ # only in tests) and is consistent with pickling. As far as comparisons to InterfaceClass
+ # goes, we'll never have equal name and module to those, so we're still consistent there.
+ # Instances of this class are essentially intended to be unique and are
+ # heavily cached (note how our __reduce__ handles this) so having identity
+ # based hash and eq should also work.
+ if other is None:
+ return -1
+
+ n1 = (self.__name__, self.__module__)
+ n2 = (getattr(other, '__name__', ''), getattr(other, '__module__', ''))
+
+ # This spelling works under Python3, which doesn't have cmp().
+ return (n1 > n2) - (n1 < n2)
+
+ def __hash__(self):
+ return Declaration.__hash__(self)
+
+ def __eq__(self, other):
+ return self is other
+
+ def __ne__(self, other):
+ return self is not other
+
+ def __lt__(self, other):
+ c = self.__cmp(other)
+ return c < 0
+
+ def __le__(self, other):
+ c = self.__cmp(other)
+ return c <= 0
+
+ def __gt__(self, other):
+ c = self.__cmp(other)
+ return c > 0
+
+ def __ge__(self, other):
+ c = self.__cmp(other)
+ return c >= 0
+
+def _implements_name(ob):
+ # Return the __name__ attribute to be used by its __implemented__
+ # property.
+ # This must be stable for the "same" object across processes
+ # because it is used for sorting. It needn't be unique, though, in cases
+ # like nested classes named Foo created by different functions, because
+ # equality and hashing is still based on identity.
+ # It might be nice to use __qualname__ on Python 3, but that would produce
+ # different values between Py2 and Py3.
+ return (getattr(ob, '__module__', '?') or '?') + \
+ '.' + (getattr(ob, '__name__', '?') or '?')
+
def implementedByFallback(cls):
"""Return the interfaces implemented for a class' instances
@@ -183,10 +256,11 @@ def implementedByFallback(cls):
return spec
# TODO: need old style __implements__ compatibility?
+ spec_name = _implements_name(cls)
if spec is not None:
# old-style __implemented__ = foo declaration
spec = (spec, ) # tuplefy, as it might be just an int
- spec = Implements(*_normalizeargs(spec))
+ spec = Implements.named(spec_name, *_normalizeargs(spec))
spec.inherit = None # old-style implies no inherit
del cls.__implemented__ # get rid of the old-style declaration
else:
@@ -197,12 +271,9 @@ def implementedByFallback(cls):
raise TypeError("ImplementedBy called for non-factory", cls)
bases = ()
- spec = Implements(*[implementedBy(c) for c in bases])
+ spec = Implements.named(spec_name, *[implementedBy(c) for c in bases])
spec.inherit = cls
- spec.__name__ = (getattr(cls, '__module__', '?') or '?') + \
- '.' + (getattr(cls, '__name__', '?') or '?')
-
try:
cls.__implemented__ = spec
if not hasattr(cls, '__providedBy__'):
@@ -314,7 +385,8 @@ class implementer:
classImplements(ob, *self.interfaces)
return ob
- spec = Implements(*self.interfaces)
+ spec_name = _implements_name(ob)
+ spec = Implements.named(spec_name, *self.interfaces)
try:
ob.__implemented__ = spec
except AttributeError:
@@ -641,7 +713,7 @@ def classProvides(*interfaces):
"""
# This entire approach is invalid under Py3K. Don't even try to fix
# the coverage for this block there. :(
-
+
if PYTHON3: #pragma NO COVER
raise TypeError(_ADVICE_ERROR % 'provider')
diff --git a/src/zope/interface/interface.py b/src/zope/interface/interface.py
index 5a77adb..e7eff5d 100644
--- a/src/zope/interface/interface.py
+++ b/src/zope/interface/interface.py
@@ -152,7 +152,7 @@ class InterfaceBasePy(object):
if adapter is not None:
return adapter
-
+
InterfaceBase = InterfaceBasePy
try:
from _zope_interface_coptimizations import InterfaceBase
diff --git a/src/zope/interface/tests/test_declarations.py b/src/zope/interface/tests/test_declarations.py
index 27a999a..83da6fa 100644
--- a/src/zope/interface/tests/test_declarations.py
+++ b/src/zope/interface/tests/test_declarations.py
@@ -238,7 +238,7 @@ class DeclarationTests(unittest.TestCase):
self.assertEqual(list(after), [IFoo, IBar, IBaz])
-class ImplementsTests(unittest.TestCase):
+class TestImplements(unittest.TestCase):
def _getTargetClass(self):
from zope.interface.declarations import Implements
@@ -264,6 +264,25 @@ class ImplementsTests(unittest.TestCase):
impl = self._makeOne()
self.assertEqual(impl.__reduce__(), (implementedBy, (None,)))
+ def test_sort(self):
+ from zope.interface.declarations import implementedBy
+ class A(object):
+ pass
+ class B(object):
+ pass
+ from zope.interface.interface import InterfaceClass
+ IFoo = InterfaceClass('IFoo')
+
+ self.assertEqual(implementedBy(A), implementedBy(A))
+ self.assertEqual(hash(implementedBy(A)), hash(implementedBy(A)))
+ self.assertTrue(implementedBy(A) < None)
+ self.assertTrue(None > implementedBy(A))
+ self.assertTrue(implementedBy(A) < implementedBy(B))
+ self.assertTrue(implementedBy(A) > IFoo)
+ self.assertTrue(implementedBy(A) <= implementedBy(B))
+ self.assertTrue(implementedBy(A) >= IFoo)
+ self.assertTrue(implementedBy(A) != IFoo)
+
class Test_implementedByFallback(unittest.TestCase):
@@ -597,7 +616,7 @@ class Test_implementer(unittest.TestCase):
returned = decorator(foo)
self.assertTrue(returned is foo)
spec = foo.__implemented__
- self.assertEqual(spec.__name__, '?')
+ self.assertEqual(spec.__name__, 'zope.interface.tests.test_declarations.?')
self.assertTrue(spec.inherit is None)
self.assertTrue(foo.__implemented__ is spec)
@@ -1567,7 +1586,7 @@ class _MonkeyDict(object):
def test_suite():
return unittest.TestSuite((
unittest.makeSuite(DeclarationTests),
- unittest.makeSuite(ImplementsTests),
+ unittest.makeSuite(TestImplements),
unittest.makeSuite(Test_implementedByFallback),
unittest.makeSuite(Test_implementedBy),
unittest.makeSuite(Test_classImplementsOnly),
diff --git a/src/zope/interface/tests/test_odd_declarations.py b/src/zope/interface/tests/test_odd_declarations.py
index 1e62a4f..e508d1d 100644
--- a/src/zope/interface/tests/test_odd_declarations.py
+++ b/src/zope/interface/tests/test_odd_declarations.py
@@ -46,7 +46,6 @@ class B(Odd): __implemented__ = I2
# a different mechanism.
# from zope.interface import classProvides
-
class A(Odd):
pass
classImplements(A, I1)