diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/zope/interface/declarations.py | 130 | ||||
-rw-r--r-- | src/zope/interface/interface.py | 22 | ||||
-rw-r--r-- | src/zope/interface/tests/test_declarations.py | 307 | ||||
-rw-r--r-- | src/zope/interface/tests/test_exceptions.py | 10 | ||||
-rw-r--r-- | src/zope/interface/tests/test_ro.py | 20 |
5 files changed, 413 insertions, 76 deletions
diff --git a/src/zope/interface/declarations.py b/src/zope/interface/declarations.py index 45d2998..935b026 100644 --- a/src/zope/interface/declarations.py +++ b/src/zope/interface/declarations.py @@ -144,6 +144,43 @@ class Declaration(Specification): ]) return interfaces + (implemented_by_cls,) + @staticmethod + def _argument_names_for_repr(interfaces): + # These don't actually have to be interfaces, they could be other + # Specification objects like Implements. Also, the first + # one is typically/nominally the cls. + ordered_names = [] + names = set() + for iface in interfaces: + duplicate_transform = repr + if isinstance(iface, InterfaceClass): + # Special case to get 'foo.bar.IFace' + # instead of '<InterfaceClass foo.bar.IFace>' + this_name = iface.__name__ + duplicate_transform = str + elif isinstance(iface, type): + # Likewise for types. (Ignoring legacy old-style + # classes.) + this_name = iface.__name__ + duplicate_transform = _implements_name + elif (isinstance(iface, Implements) + and not iface.declared + and iface.inherit in interfaces): + # If nothing is declared, there's no need to even print this; + # it would just show as ``classImplements(Class)``, and the + # ``Class`` has typically already. + continue + else: + this_name = repr(iface) + + already_seen = this_name in names + names.add(this_name) + if already_seen: + this_name = duplicate_transform(iface) + + ordered_names.append(this_name) + return ', '.join(ordered_names) + class _ImmutableDeclaration(Declaration): # A Declaration that is immutable. Used as a singleton to @@ -286,7 +323,14 @@ class Implements(NameAndModuleComparisonMixin, return super(Implements, self).changed(originally_changed) def __repr__(self): - return '<implementedBy %s>' % (self.__name__) + if self.inherit: + name = getattr(self.inherit, '__name__', None) or _implements_name(self.inherit) + else: + name = self.__name__ + declared_names = self._argument_names_for_repr(self.declared) + if declared_names: + declared_names = ', ' + declared_names + return 'classImplements(%s%s)' % (name, declared_names) def __reduce__(self): return implementedBy, (self.inherit, ) @@ -762,15 +806,44 @@ class Provides(Declaration): # Really named ProvidesClass self._cls = cls Declaration.__init__(self, *self._add_interfaces_to_cls(interfaces, cls)) + # Added to by ``moduleProvides``, et al + _v_module_names = () + def __repr__(self): - return "<%s.%s for instances of %s providing %s>" % ( - self.__class__.__module__, - self.__class__.__name__, - self._cls, - self.__args[1:], + # The typical way to create instances of this + # object is via calling ``directlyProvides(...)`` or ``alsoProvides()``, + # but that's not the only way. Proxies, for example, + # directly use the ``Provides(...)`` function (which is the + # more generic method, and what we pickle as). We're after the most + # readable, useful repr in the common case, so we use the most + # common name. + # + # We also cooperate with ``moduleProvides`` to attempt to do the + # right thing for that API. See it for details. + function_name = 'directlyProvides' + if self._cls is ModuleType and self._v_module_names: + # See notes in ``moduleProvides``/``directlyProvides`` + providing_on_module = True + interfaces = self.__args[1:] + else: + providing_on_module = False + interfaces = (self._cls,) + self.__bases__ + ordered_names = self._argument_names_for_repr(interfaces) + if providing_on_module: + mod_names = self._v_module_names + if len(mod_names) == 1: + mod_names = "sys.modules[%r]" % mod_names[0] + ordered_names = ( + '%s, ' % (mod_names,) + ) + ordered_names + return "%s(%s)" % ( + function_name, + ordered_names, ) def __reduce__(self): + # This reduces to the Provides *function*, not + # this class. return Provides, self.__args __module__ = 'zope.interface' @@ -841,7 +914,11 @@ def directlyProvides(object, *interfaces): # pylint:disable=redefined-builtin # that provides some extra caching object.__provides__ = ClassProvides(object, cls, *interfaces) else: - object.__provides__ = Provides(cls, *interfaces) + provides = object.__provides__ = Provides(cls, *interfaces) + # See notes in ``moduleProvides``. + if issubclass(cls, ModuleType) and hasattr(object, '__name__'): + provides._v_module_names += (object.__name__,) + def alsoProvides(object, *interfaces): # pylint:disable=redefined-builtin @@ -907,11 +984,19 @@ class ClassProvides(Declaration, ClassProvidesBase): Declaration.__init__(self, *self._add_interfaces_to_cls(interfaces, metacls)) def __repr__(self): - return "<%s.%s for %s>" % ( - self.__class__.__module__, - self.__class__.__name__, - self._cls, - ) + # There are two common ways to get instances of this object: + # The most interesting way is calling ``@provider(..)`` as a decorator + # of a class; this is the same as calling ``directlyProvides(cls, ...)``. + # + # The other way is by default: anything that invokes ``implementedBy(x)`` + # will wind up putting an instance in ``type(x).__provides__``; this includes + # the ``@implementer(...)`` decorator. Those instances won't have any + # interfaces. + # + # Thus, as our repr, we go with the ``directlyProvides()`` syntax. + interfaces = (self._cls, ) + self.__args[2:] + ordered_names = self._argument_names_for_repr(interfaces) + return "directlyProvides(%s)" % (ordered_names,) def __reduce__(self): return self.__class__, self.__args @@ -1026,7 +1111,7 @@ def moduleProvides(*interfaces): This function is provided for convenience. It provides a more convenient way to call directlyProvides. For example:: - moduleImplements(I1) + moduleProvides(I1) is equivalent to:: @@ -1035,7 +1120,7 @@ def moduleProvides(*interfaces): frame = sys._getframe(1) # pylint:disable=protected-access locals = frame.f_locals # pylint:disable=redefined-builtin - # Try to make sure we were called from a class def + # Try to make sure we were called from a module body if (locals is not frame.f_globals) or ('__name__' not in locals): raise TypeError( "moduleProvides can only be used from a module definition.") @@ -1044,8 +1129,21 @@ def moduleProvides(*interfaces): raise TypeError( "moduleProvides can only be used once in a module definition.") - locals["__provides__"] = Provides(ModuleType, - *_normalizeargs(interfaces)) + # Note: This is cached based on the key ``(ModuleType, *interfaces)``; + # One consequence is that any module that provides the same interfaces + # gets the same ``__repr__``, meaning that you can't tell what module + # such a declaration came from. Adding the module name to ``_v_module_names`` + # attempts to correct for this; it works in some common situations, but fails + # (1) after pickling (the data is lost) and (2) if declarations are + # actually shared and (3) if the alternate spelling of ``directlyProvides()`` + # is used. Problem (3) is fixed by cooperating with ``directlyProvides`` + # to maintain this information, and problem (2) is worked around by + # printing all the names, but (1) is unsolvable without introducing + # new classes or changing the stored data...but it doesn't actually matter, + # because ``ModuleType`` can't be pickled! + p = locals["__provides__"] = Provides(ModuleType, + *_normalizeargs(interfaces)) + p._v_module_names += (locals['__name__'],) ############################################################################## diff --git a/src/zope/interface/interface.py b/src/zope/interface/interface.py index 53049e2..7447641 100644 --- a/src/zope/interface/interface.py +++ b/src/zope/interface/interface.py @@ -890,10 +890,10 @@ class InterfaceClass(_InterfaceClassBase): try: invariant(obj) except Invalid as error: - if errors is not None: - errors.append(error) - else: - raise + if errors is not None: + errors.append(error) + else: + raise if errors: raise Invalid(errors) @@ -925,18 +925,22 @@ class InterfaceClass(_InterfaceClassBase): keys.update(base.getDirectTaggedValueTags()) return keys - def __repr__(self): # pragma: no cover + def __repr__(self): try: return self._v_repr except AttributeError: - name = self.__name__ - m = self.__ibmodule__ - if m: - name = '%s.%s' % (m, name) + name = str(self) r = "<%s %s>" % (self.__class__.__name__, name) self._v_repr = r # pylint:disable=attribute-defined-outside-init return r + def __str__(self): + name = self.__name__ + m = self.__ibmodule__ + if m: + name = '%s.%s' % (m, name) + return name + def _call_conform(self, conform): try: return conform(self) diff --git a/src/zope/interface/tests/test_declarations.py b/src/zope/interface/tests/test_declarations.py index 0c21b8c..8efe2d8 100644 --- a/src/zope/interface/tests/test_declarations.py +++ b/src/zope/interface/tests/test_declarations.py @@ -384,7 +384,7 @@ class TestImplements(NameAndModuleComparisonTestsMixin, def test___repr__(self): impl = self._makeOne() impl.__name__ = 'Testing' - self.assertEqual(repr(impl), '<implementedBy Testing>') + self.assertEqual(repr(impl), 'classImplements(Testing)') def test___reduce__(self): from zope.interface.declarations import implementedBy @@ -544,7 +544,6 @@ class Test_implementedByFallback(unittest.TestCase): def test_builtins_added_to_cache(self): from zope.interface import declarations from zope.interface.declarations import Implements - from zope.interface._compat import _BUILTINS with _MonkeyDict(declarations, 'BuiltinImplementationSpecifications') as specs: self.assertEqual(list(self._callFUT(tuple)), []) @@ -554,8 +553,8 @@ class Test_implementedByFallback(unittest.TestCase): spec = specs[typ] self.assertIsInstance(spec, Implements) self.assertEqual(repr(spec), - '<implementedBy %s.%s>' - % (_BUILTINS, typ.__name__)) + 'classImplements(%s)' + % (typ.__name__,)) def test_builtins_w_existing_cache(self): from zope.interface import declarations @@ -1304,25 +1303,6 @@ class ProvidesClassTests(unittest.TestCase): return foo.__provides__ self.assertRaises(AttributeError, _test) - def test__repr__(self): - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass("IFoo") - assert IFoo.__name__ == 'IFoo' - assert IFoo.__module__ == __name__ - assert repr(IFoo) == '<InterfaceClass %s.IFoo>' % (__name__,) - - IBar = InterfaceClass("IBar") - - inst = self._makeOne(type(self), IFoo, IBar) - self.assertEqual( - repr(inst), - "<zope.interface.Provides " - "for instances of <class '%(mod)s.ProvidesClassTests'> " - "providing (<InterfaceClass %(mod)s.IFoo>, <InterfaceClass %(mod)s.IBar>)>" % { - 'mod': __name__, - } - ) - class ProvidesClassStrictTests(ProvidesClassTests): # Tests that require the strict C3 resolution order. @@ -1334,9 +1314,6 @@ class ProvidesClassStrictTests(ProvidesClassTests): return ProvidesClass._do_calculate_ro(self, base_mros=base_mros, strict=True) return StrictProvides - def test__repr__(self): - self.skipTest("Not useful for the subclass.") - def test_overlapping_interfaces_corrected(self): # Giving Provides(cls, IFace), where IFace is already # provided by cls, doesn't produce invalid resolution orders. @@ -1361,6 +1338,195 @@ class ProvidesClassStrictTests(ProvidesClassTests): )) +class TestProvidesClassRepr(unittest.TestCase): + + def _getTargetClass(self): + from zope.interface.declarations import ProvidesClass + return ProvidesClass + + def _makeOne(self, *args, **kw): + return self._getTargetClass()(*args, **kw) + + def test__repr__(self): + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass("IFoo") + assert IFoo.__name__ == 'IFoo' + assert IFoo.__module__ == __name__ + assert repr(IFoo) == '<InterfaceClass %s.IFoo>' % (__name__,) + + IBar = InterfaceClass("IBar") + + inst = self._makeOne(type(self), IFoo, IBar) + self.assertEqual( + repr(inst), + "directlyProvides(TestProvidesClassRepr, IFoo, IBar)" + ) + + def test__repr__module_provides_typical_use(self): + # as created through a ``moduleProvides()`` statement + # in a module body + from zope.interface.tests import dummy + provides = dummy.__provides__ # pylint:disable=no-member + self.assertEqual( + repr(provides), + "directlyProvides(sys.modules['zope.interface.tests.dummy'], IDummyModule)" + ) + + def test__repr__module_after_pickle(self): + # It doesn't matter, these objects can't be pickled. + import pickle + from zope.interface.tests import dummy + provides = dummy.__provides__ # pylint:disable=no-member + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + with self.assertRaises(pickle.PicklingError): + pickle.dumps(provides, proto) + + def test__repr__directlyProvides_module(self): + import sys + from zope.interface.tests import dummy + from zope.interface.declarations import directlyProvides + from zope.interface.declarations import alsoProvides + from zope.interface.interface import InterfaceClass + + IFoo = InterfaceClass('IFoo') + IBar = InterfaceClass('IBar') + + orig_provides = dummy.__provides__ # pylint:disable=no-member + del dummy.__provides__ # pylint:disable=no-member + self.addCleanup(setattr, dummy, '__provides__', orig_provides) + + directlyProvides(dummy, IFoo) + provides = dummy.__provides__ # pylint:disable=no-member + + self.assertEqual( + repr(provides), + "directlyProvides(sys.modules['zope.interface.tests.dummy'], IFoo)" + ) + + alsoProvides(dummy, IBar) + provides = dummy.__provides__ # pylint:disable=no-member + + self.assertEqual( + repr(provides), + "directlyProvides(sys.modules['zope.interface.tests.dummy'], IFoo, IBar)" + ) + + # If we make this module also provide IFoo and IBar, then the repr + # lists both names. + my_module = sys.modules[__name__] + assert not hasattr(my_module, '__provides__') + + directlyProvides(my_module, IFoo, IBar) + self.addCleanup(delattr, my_module, '__provides__') + self.assertIs(my_module.__provides__, provides) + self.assertEqual( + repr(provides), + "directlyProvides(('zope.interface.tests.dummy', " + "'zope.interface.tests.test_declarations'), " + "IFoo, IBar)" + ) + + def test__repr__module_provides_cached_shared(self): + from zope.interface.interface import InterfaceClass + from zope.interface.declarations import ModuleType + IFoo = InterfaceClass("IFoo") + + inst = self._makeOne(ModuleType, IFoo) + inst._v_module_names += ('some.module',) + inst._v_module_names += ('another.module',) + self.assertEqual( + repr(inst), + "directlyProvides(('some.module', 'another.module'), IFoo)" + ) + + def test__repr__duplicate_names(self): + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass("IFoo", __module__='mod1') + IFoo2 = InterfaceClass("IFoo", __module__='mod2') + IBaz = InterfaceClass("IBaz") + + inst = self._makeOne(type(self), IFoo, IBaz, IFoo2) + self.assertEqual( + repr(inst), + "directlyProvides(TestProvidesClassRepr, IFoo, IBaz, mod2.IFoo)" + ) + + def test__repr__implementedBy_in_interfaces(self): + from zope.interface import Interface + from zope.interface import implementedBy + class IFoo(Interface): + "Does nothing" + + class Bar(object): + "Does nothing" + + impl = implementedBy(type(self)) + + inst = self._makeOne(Bar, IFoo, impl) + self.assertEqual( + repr(inst), + 'directlyProvides(Bar, IFoo, classImplements(TestProvidesClassRepr))' + ) + + def test__repr__empty_interfaces(self): + inst = self._makeOne(type(self)) + self.assertEqual( + repr(inst), + 'directlyProvides(TestProvidesClassRepr)', + ) + + def test__repr__non_class(self): + class Object(object): + __bases__ = () + __str__ = lambda _: self.fail("Should not call str") + + def __repr__(self): + return '<Object>' + inst = self._makeOne(Object()) + self.assertEqual( + repr(inst), + 'directlyProvides(<Object>)', + ) + + def test__repr__providedBy_from_class(self): + from zope.interface.declarations import implementer + from zope.interface.declarations import providedBy + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass("IFoo") + + @implementer(IFoo) + class Foo(object): + pass + + inst = providedBy(Foo()) + self.assertEqual( + repr(inst), + 'classImplements(Foo, IFoo)' + ) + + def test__repr__providedBy_alsoProvides(self): + from zope.interface.declarations import implementer + from zope.interface.declarations import providedBy + from zope.interface.declarations import alsoProvides + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass("IFoo") + IBar = InterfaceClass("IBar") + + @implementer(IFoo) + class Foo(object): + pass + + foo = Foo() + alsoProvides(foo, IBar) + + inst = providedBy(foo) + self.assertEqual( + repr(inst), + "directlyProvides(Foo, IBar, classImplements(Foo, IFoo))" + ) + + + class Test_Provides(unittest.TestCase): def _callFUT(self, *args, **kw): @@ -1631,13 +1797,6 @@ class ClassProvidesTests(unittest.TestCase): self.assertEqual(cp.__reduce__(), (type(cp), (Foo, type(Foo), IBar))) - def test__repr__(self): - inst = self._makeOne(type(self), type) - self.assertEqual( - repr(inst), - "<zope.interface.declarations.ClassProvides for %r>" % type(self) - ) - class ClassProvidesStrictTests(ClassProvidesTests): # Tests that require the strict C3 resolution order. @@ -1649,9 +1808,6 @@ class ClassProvidesStrictTests(ClassProvidesTests): return ClassProvides._do_calculate_ro(self, base_mros=base_mros, strict=True) return StrictClassProvides - def test__repr__(self): - self.skipTest("Not useful for the subclass.") - def test_overlapping_interfaces_corrected(self): # Giving ClassProvides(cls, metaclass, IFace), where IFace is already # provided by metacls, doesn't produce invalid resolution orders. @@ -1682,6 +1838,85 @@ class ClassProvidesStrictTests(ClassProvidesTests): Interface )) + +class TestClassProvidesRepr(unittest.TestCase): + + def _getTargetClass(self): + from zope.interface.declarations import ClassProvides + return ClassProvides + + def _makeOne(self, *args, **kw): + return self._getTargetClass()(*args, **kw) + + def test__repr__empty(self): + inst = self._makeOne(type(self), type) + self.assertEqual( + repr(inst), + "directlyProvides(TestClassProvidesRepr)" + ) + + def test__repr__providing_one(self): + from zope.interface import Interface + class IFoo(Interface): + "Does nothing" + + inst = self._makeOne(type(self), type, IFoo) + self.assertEqual( + repr(inst), + "directlyProvides(TestClassProvidesRepr, IFoo)" + ) + + def test__repr__duplicate_names(self): + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass("IFoo", __module__='mod1') + IFoo2 = InterfaceClass("IFoo", __module__='mod2') + IBaz = InterfaceClass("IBaz") + + inst = self._makeOne(type(self), type, IFoo, IBaz, IFoo2) + self.assertEqual( + repr(inst), + "directlyProvides(TestClassProvidesRepr, IFoo, IBaz, mod2.IFoo)" + ) + + def test__repr__implementedBy(self): + from zope.interface.declarations import implementer + from zope.interface.declarations import implementedBy + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass("IFoo") + + @implementer(IFoo) + class Foo(object): + pass + + inst = implementedBy(Foo) + self.assertEqual( + repr(inst), + 'classImplements(Foo, IFoo)' + ) + + def test__repr__implementedBy_generic_callable(self): + from zope.interface.declarations import implementedBy + # We can't get a __name__ by default, so we get a + # module name and a question mark + class Callable(object): + def __call__(self): + return self + + inst = implementedBy(Callable()) + self.assertEqual( + repr(inst), + 'classImplements(%s.?)' % (__name__,) + ) + + c = Callable() + c.__name__ = 'Callable' + inst = implementedBy(c) + self.assertEqual( + repr(inst), + 'classImplements(Callable)' + ) + + class Test_directlyProvidedBy(unittest.TestCase): def _callFUT(self, *args, **kw): diff --git a/src/zope/interface/tests/test_exceptions.py b/src/zope/interface/tests/test_exceptions.py index a55f522..ecebf91 100644 --- a/src/zope/interface/tests/test_exceptions.py +++ b/src/zope/interface/tests/test_exceptions.py @@ -36,7 +36,7 @@ class DoesNotImplementTests(unittest.TestCase): self.assertEqual( str(dni), "An object has failed to implement interface " - "<InterfaceClass zope.interface.tests.test_exceptions.IDummy>: " + "zope.interface.tests.test_exceptions.IDummy: " "Does not declaratively implement the interface." ) @@ -45,7 +45,7 @@ class DoesNotImplementTests(unittest.TestCase): self.assertEqual( str(dni), "The object 'candidate' has failed to implement interface " - "<InterfaceClass zope.interface.tests.test_exceptions.IDummy>: " + "zope.interface.tests.test_exceptions.IDummy: " "Does not declaratively implement the interface." ) @@ -65,7 +65,7 @@ class BrokenImplementationTests(unittest.TestCase): self.assertEqual( str(dni), 'An object has failed to implement interface ' - '<InterfaceClass zope.interface.tests.test_exceptions.IDummy>: ' + 'zope.interface.tests.test_exceptions.IDummy: ' "The 'missing' attribute was not provided.") def test___str__w_candidate(self): @@ -73,7 +73,7 @@ class BrokenImplementationTests(unittest.TestCase): self.assertEqual( str(dni), 'The object \'candidate\' has failed to implement interface ' - '<InterfaceClass zope.interface.tests.test_exceptions.IDummy>: ' + 'zope.interface.tests.test_exceptions.IDummy: ' "The 'missing' attribute was not provided.") @@ -161,7 +161,7 @@ class MultipleInvalidTests(unittest.TestCase): self.assertEqual( str(dni), "The object 'target' has failed to implement interface " - "<InterfaceClass zope.interface.tests.test_exceptions.IDummy>:\n" + "zope.interface.tests.test_exceptions.IDummy:\n" " The contract of 'aMethod' is violated because I said so\n" " Regular exception" ) diff --git a/src/zope/interface/tests/test_ro.py b/src/zope/interface/tests/test_ro.py index 61f92b6..5542d28 100644 --- a/src/zope/interface/tests/test_ro.py +++ b/src/zope/interface/tests/test_ro.py @@ -259,16 +259,16 @@ class Test_c3_ro(Test_ro): self.assertEqual('\n'.join(l.rstrip() for l in record.getMessage().splitlines()), """\ Object <InterfaceClass zope.interface.tests.test_ro.A> has different legacy and C3 MROs: - Legacy RO (len=7) C3 RO (len=7; inconsistent=no) - ==================================================================================================== - <InterfaceClass zope.interface.tests.test_ro.A> <InterfaceClass zope.interface.tests.test_ro.A> - <InterfaceClass zope.interface.tests.test_ro.B> <InterfaceClass zope.interface.tests.test_ro.B> - - <InterfaceClass zope.interface.tests.test_ro.E> - <InterfaceClass zope.interface.tests.test_ro.C> <InterfaceClass zope.interface.tests.test_ro.C> - <InterfaceClass zope.interface.tests.test_ro.D> <InterfaceClass zope.interface.tests.test_ro.D> - + <InterfaceClass zope.interface.tests.test_ro.E> - <InterfaceClass zope.interface.tests.test_ro.F> <InterfaceClass zope.interface.tests.test_ro.F> - <InterfaceClass zope.interface.Interface> <InterfaceClass zope.interface.Interface>""") + Legacy RO (len=7) C3 RO (len=7; inconsistent=no) + ================================================================== + zope.interface.tests.test_ro.A zope.interface.tests.test_ro.A + zope.interface.tests.test_ro.B zope.interface.tests.test_ro.B + - zope.interface.tests.test_ro.E + zope.interface.tests.test_ro.C zope.interface.tests.test_ro.C + zope.interface.tests.test_ro.D zope.interface.tests.test_ro.D + + zope.interface.tests.test_ro.E + zope.interface.tests.test_ro.F zope.interface.tests.test_ro.F + zope.interface.Interface zope.interface.Interface""") def test_ExtendedPathIndex_implement_thing_implementedby_super(self): # See https://github.com/zopefoundation/zope.interface/pull/182#issuecomment-598754056 |