summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason Madden <jamadden@gmail.com>2021-03-25 10:30:47 -0500
committerJason Madden <jamadden@gmail.com>2021-03-25 10:30:47 -0500
commitce8f66f8bf4ebffca7cdf95dbb8c5add02251e91 (patch)
tree005840e6e690f96d690d189ea3c196317818fae1
parente6d3805c6e155a89a34d89601477881aec9b7df1 (diff)
downloadzope-interface-issue236.tar.gz
Update repr() and str() of some common objects.issue236
Only InterfaceClass got a str, so it now has a distinction. Other objects only got updated reprs. Note: This may potentially break some doctests. In many cases, the ``repr()`` is now something that can be evaluated to produce an equal object. For example, what was previously printed as ``<implementedBy builtins.list>`` is now shown as ``classImplements(list, IMutableSequence, IIterable)``. Fixes #236 Here's a before with ZOPE_INTERFACE_LOG_CHANGED_IRO=1 in BTrees: ``` Object <implementedBy builtins.list> has different legacy and C3 MROs: Legacy RO (len=11) C3 RO (len=11; inconsistent=no) ================================================================================================================================================== <implementedBy builtins.list> <implementedBy builtins.list> <ABCInterfaceClass zope.interface.common.collections.IMutableSequence> <ABCInterfaceClass zope.interface.common.collections.IMutableSequence> <ABCInterfaceClass zope.interface.common.collections.ISequence> <ABCInterfaceClass zope.interface.common.collections.ISequence> <ABCInterfaceClass zope.interface.common.collections.IReversible> <ABCInterfaceClass zope.interface.common.collections.IReversible> <ABCInterfaceClass zope.interface.common.collections.ICollection> <ABCInterfaceClass zope.interface.common.collections.ICollection> <ABCInterfaceClass zope.interface.common.collections.ISized> <ABCInterfaceClass zope.interface.common.collections.ISized> + <ABCInterfaceClass zope.interface.common.collections.IIterable> <ABCInterfaceClass zope.interface.common.collections.IContainer> <ABCInterfaceClass zope.interface.common.collections.IContainer> - <ABCInterfaceClass zope.interface.common.collections.IIterable> <ABCInterfaceClass zope.interface.common.ABCInterface> <ABCInterfaceClass zope.interface.common.ABCInterface> + <implementedBy builtins.object> <InterfaceClass zope.interface.Interface> <InterfaceClass zope.interface.Interface> - <implementedBy builtins.object> Object <ABCInterfaceClass zope.interface.common.mapping.IFullMapping> has different legacy and C3 MROs: Legacy RO (len=18) C3 RO (len=18; inconsistent=no) ================================================================================================================================================ <ABCInterfaceClass zope.interface.common.mapping.IFullMapping> <ABCInterfaceClass zope.interface.common.mapping.IFullMapping> <ABCInterfaceClass zope.interface.common.collections.IMutableMapping> <ABCInterfaceClass zope.interface.common.collections.IMutableMapping> <ABCInterfaceClass zope.interface.common.collections.IMapping> <ABCInterfaceClass zope.interface.common.collections.IMapping> <ABCInterfaceClass zope.interface.common.collections.ICollection> <ABCInterfaceClass zope.interface.common.collections.ICollection> - <ABCInterfaceClass zope.interface.common.collections.IIterable> <InterfaceClass zope.interface.common.mapping.IExtendedReadMapping> <InterfaceClass zope.interface.common.mapping.IExtendedReadMapping> <InterfaceClass zope.interface.common.mapping.IIterableMapping> <InterfaceClass zope.interface.common.mapping.IIterableMapping> <InterfaceClass zope.interface.common.mapping.IExtendedWriteMapping> <InterfaceClass zope.interface.common.mapping.IExtendedWriteMapping> <InterfaceClass zope.interface.common.mapping.IClonableMapping> <InterfaceClass zope.interface.common.mapping.IClonableMapping> <InterfaceClass zope.interface.common.mapping.IMapping> <InterfaceClass zope.interface.common.mapping.IMapping> <InterfaceClass zope.interface.common.mapping.IWriteMapping> <InterfaceClass zope.interface.common.mapping.IWriteMapping> <InterfaceClass zope.interface.common.mapping.IEnumerableMapping> <InterfaceClass zope.interface.common.mapping.IEnumerableMapping> <ABCInterfaceClass zope.interface.common.collections.ISized> <ABCInterfaceClass zope.interface.common.collections.ISized> + <ABCInterfaceClass zope.interface.common.collections.IIterable> <InterfaceClass zope.interface.common.mapping.IReadMapping> <InterfaceClass zope.interface.common.mapping.IReadMapping> <ABCInterfaceClass zope.interface.common.collections.IContainer> <ABCInterfaceClass zope.interface.common.collections.IContainer> <ABCInterfaceClass zope.interface.common.ABCInterface> <ABCInterfaceClass zope.interface.common.ABCInterface> <InterfaceClass zope.interface.common.mapping.IItemMapping> <InterfaceClass zope.interface.common.mapping.IItemMapping> <InterfaceClass zope.interface.Interface> <InterfaceClass zope.interface.Interface> Object <InterfaceClass BTrees.Interfaces.ISet> has different legacy and C3 MROs: Legacy RO (len=7) C3 RO (len=7; inconsistent=no) ==================================================================================================== <InterfaceClass BTrees.Interfaces.ISet> <InterfaceClass BTrees.Interfaces.ISet> <InterfaceClass BTrees.Interfaces.IKeySequence> <InterfaceClass BTrees.Interfaces.IKeySequence> - <InterfaceClass BTrees.Interfaces.ISized> <InterfaceClass BTrees.Interfaces.ISetMutable> <InterfaceClass BTrees.Interfaces.ISetMutable> <InterfaceClass BTrees.Interfaces.IKeyed> <InterfaceClass BTrees.Interfaces.IKeyed> <InterfaceClass BTrees.Interfaces.ICollection> <InterfaceClass BTrees.Interfaces.ICollection> + <InterfaceClass BTrees.Interfaces.ISized> <InterfaceClass zope.interface.Interface> <InterfaceClass zope.interface.Interface> ``` And here's after: ``` Object classImplements(list, IMutableSequence, IIterable) has different legacy and C3 MROs: Legacy RO (len=11) C3 RO (len=11; inconsistent=no) ========================================================================================================== classImplements(list, IMutableSequence, IIterable) classImplements(list, IMutableSequence, IIterable) zope.interface.common.collections.IMutableSequence zope.interface.common.collections.IMutableSequence zope.interface.common.collections.ISequence zope.interface.common.collections.ISequence zope.interface.common.collections.IReversible zope.interface.common.collections.IReversible zope.interface.common.collections.ICollection zope.interface.common.collections.ICollection zope.interface.common.collections.ISized zope.interface.common.collections.ISized + zope.interface.common.collections.IIterable zope.interface.common.collections.IContainer zope.interface.common.collections.IContainer - zope.interface.common.collections.IIterable zope.interface.common.ABCInterface zope.interface.common.ABCInterface + classImplements(object) zope.interface.Interface zope.interface.Interface - classImplements(object) Object <ABCInterfaceClass zope.interface.common.mapping.IFullMapping> has different legacy and C3 MROs: Legacy RO (len=18) C3 RO (len=18; inconsistent=no) ============================================================================================================ zope.interface.common.mapping.IFullMapping zope.interface.common.mapping.IFullMapping zope.interface.common.collections.IMutableMapping zope.interface.common.collections.IMutableMapping zope.interface.common.collections.IMapping zope.interface.common.collections.IMapping zope.interface.common.collections.ICollection zope.interface.common.collections.ICollection - zope.interface.common.collections.IIterable zope.interface.common.mapping.IExtendedReadMapping zope.interface.common.mapping.IExtendedReadMapping zope.interface.common.mapping.IIterableMapping zope.interface.common.mapping.IIterableMapping zope.interface.common.mapping.IExtendedWriteMapping zope.interface.common.mapping.IExtendedWriteMapping zope.interface.common.mapping.IClonableMapping zope.interface.common.mapping.IClonableMapping zope.interface.common.mapping.IMapping zope.interface.common.mapping.IMapping zope.interface.common.mapping.IWriteMapping zope.interface.common.mapping.IWriteMapping zope.interface.common.mapping.IEnumerableMapping zope.interface.common.mapping.IEnumerableMapping zope.interface.common.collections.ISized zope.interface.common.collections.ISized + zope.interface.common.collections.IIterable zope.interface.common.mapping.IReadMapping zope.interface.common.mapping.IReadMapping zope.interface.common.collections.IContainer zope.interface.common.collections.IContainer zope.interface.common.ABCInterface zope.interface.common.ABCInterface zope.interface.common.mapping.IItemMapping zope.interface.common.mapping.IItemMapping zope.interface.Interface zope.interface.Interface Object <InterfaceClass BTrees.Interfaces.ISet> has different legacy and C3 MROs: Legacy RO (len=7) C3 RO (len=7; inconsistent=no) ================================================================== BTrees.Interfaces.ISet BTrees.Interfaces.ISet BTrees.Interfaces.IKeySequence BTrees.Interfaces.IKeySequence - BTrees.Interfaces.ISized BTrees.Interfaces.ISetMutable BTrees.Interfaces.ISetMutable BTrees.Interfaces.IKeyed BTrees.Interfaces.IKeyed BTrees.Interfaces.ICollection BTrees.Interfaces.ICollection + BTrees.Interfaces.ISized zope.interface.Interface zope.interface.Interface ```
-rw-r--r--CHANGES.rst10
-rw-r--r--docs/README.rst12
-rw-r--r--docs/api/declarations.rst2
-rw-r--r--docs/verify.rst28
-rw-r--r--src/zope/interface/declarations.py130
-rw-r--r--src/zope/interface/interface.py22
-rw-r--r--src/zope/interface/tests/test_declarations.py307
-rw-r--r--src/zope/interface/tests/test_exceptions.py10
-rw-r--r--src/zope/interface/tests/test_ro.py20
9 files changed, 444 insertions, 97 deletions
diff --git a/CHANGES.rst b/CHANGES.rst
index 1cbff80..600fbb8 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -2,6 +2,16 @@
Changes
=========
+5.4.0 (unreleased)
+==================
+
+- Update the ``repr()`` and ``str()`` of various objects to be shorter
+ and more informative. In many cases, the ``repr()`` is now something
+ that can be evaluated to produce an equal object. For example, what
+ was previously printed as ``<implementedBy builtins.list>`` is now
+ shown as ``classImplements(list, IMutableSequence, IIterable)``. See
+ `issue 236 <https://github.com/zopefoundation/zope.interface/issues/236>`_.
+
5.3.0 (2020-03-21)
==================
diff --git a/docs/README.rst b/docs/README.rst
index 3daf865..b8eef13 100644
--- a/docs/README.rst
+++ b/docs/README.rst
@@ -677,7 +677,7 @@ interfaces that they declare:
>>> baz_implements = zope.interface.implementedBy(Baz)
>>> baz_implements.__bases__
- (<InterfaceClass builtins.IBaz>, <implementedBy ...object>)
+ (<InterfaceClass builtins.IBaz>, classImplements(object))
>>> baz_implements.extends(IFoo)
True
@@ -694,11 +694,11 @@ that lists the specification and all of it's ancestors:
>>> from pprint import pprint
>>> pprint(baz_implements.__sro__)
- (<implementedBy builtins.Baz>,
+ (classImplements(Baz, IBaz),
<InterfaceClass builtins.IBaz>,
<InterfaceClass builtins.IFoo>,
<InterfaceClass builtins.IBlat>,
- <implementedBy ...object>,
+ classImplements(object),
<InterfaceClass zope.interface.Interface>)
>>> class IBiz(zope.interface.Interface):
... pass
@@ -706,13 +706,13 @@ that lists the specification and all of it's ancestors:
... class Biz(Baz):
... pass
>>> pprint(zope.interface.implementedBy(Biz).__sro__)
- (<implementedBy builtins.Biz>,
+ (classImplements(Biz, IBiz),
<InterfaceClass builtins.IBiz>,
- <implementedBy builtins.Baz>,
+ classImplements(Baz, IBaz),
<InterfaceClass builtins.IBaz>,
<InterfaceClass builtins.IFoo>,
<InterfaceClass builtins.IBlat>,
- <implementedBy ...object>,
+ classImplements(object),
<InterfaceClass zope.interface.Interface>)
Tagged Values
diff --git a/docs/api/declarations.rst b/docs/api/declarations.rst
index 05d39eb..cdbf815 100644
--- a/docs/api/declarations.rst
+++ b/docs/api/declarations.rst
@@ -624,7 +624,7 @@ an instance:
... def __call__(self):
... return self
>>> implementedBy(Callable())
- <implementedBy builtins.?>
+ classImplements(builtins.?)
Note that the name of the spec ends with a '?', because the ``Callable``
instance does not have a ``__name__`` attribute.
diff --git a/docs/verify.rst b/docs/verify.rst
index bf602d0..6a313cd 100644
--- a/docs/verify.rst
+++ b/docs/verify.rst
@@ -49,7 +49,7 @@ defined.
.. doctest::
>>> verify_foo()
- The object <Foo...> has failed to implement interface <...IFoo>:
+ The object <Foo...> has failed to implement interface ...IFoo:
Does not declaratively implement the interface
The base.IBase.x attribute was not provided
The module.IFoo.y attribute was not provided
@@ -61,7 +61,7 @@ declaring the correct interface.
>>> Foo.x = Foo.y = 42
>>> verify_foo()
- The object <Foo...> has failed to implement interface <...IFoo>: Does not declaratively implement the interface.
+ The object <Foo...> has failed to implement interface ...IFoo: Does not declaratively implement the interface.
If we want to only check the structure of the object, without examining
its declarations, we can use the ``tentative`` argument.
@@ -119,13 +119,13 @@ exception.
... class Foo(object):
... x = 1
>>> verify_foo()
- The object <Foo...> has failed to implement interface <...IFoo>: The module.IFoo.y attribute was not provided.
+ The object <Foo...> has failed to implement interface ...IFoo: The module.IFoo.y attribute was not provided.
>>> @implementer(IFoo)
... class Foo(object):
... def __init__(self):
... self.y = 2
>>> verify_foo()
- The object <Foo...> has failed to implement interface <...IFoo>: The base.IBase.x attribute was not provided.
+ The object <Foo...> has failed to implement interface ...IFoo: The base.IBase.x attribute was not provided.
If both attributes are missing, an exception is raised reporting
both errors.
@@ -136,7 +136,7 @@ both errors.
... class Foo(object):
... pass
>>> verify_foo()
- The object <Foo ...> has failed to implement interface <...IFoo>:
+ The object <Foo ...> has failed to implement interface ...IFoo:
The base.IBase.x attribute was not provided
The module.IFoo.y attribute was not provided
@@ -155,7 +155,7 @@ when trying to get its value, the attribute is considered missing:
... def x(self):
... raise AttributeError
>>> verify_foo()
- The object <Foo...> has failed to implement interface <...IFoo>: The module.IFoo.x attribute was not provided.
+ The object <Foo...> has failed to implement interface ...IFoo: The module.IFoo.x attribute was not provided.
Any other exception raised by a property will propagate to the caller of
@@ -203,7 +203,7 @@ that takes one argument. If we don't provide it, we get an error.
... class Foo(object):
... pass
>>> verify_foo()
- The object <Foo...> has failed to implement interface <...IFoo>: The module.IFoo.simple(arg1) attribute was not provided.
+ The object <Foo...> has failed to implement interface ...IFoo: The module.IFoo.simple(arg1) attribute was not provided.
Once they exist, they are checked to be callable, and for compatible signatures.
@@ -213,7 +213,7 @@ Not being callable is an error.
>>> Foo.simple = 42
>>> verify_foo()
- The object <Foo...> has failed to implement interface <...IFoo>: The contract of module.IFoo.simple(arg1) is violated because '42' is not a method.
+ The object <Foo...> has failed to implement interface ...IFoo: The contract of module.IFoo.simple(arg1) is violated because '42' is not a method.
Taking too few arguments is an error. (Recall that the ``self``
argument is implicit.)
@@ -222,7 +222,7 @@ argument is implicit.)
>>> Foo.simple = lambda self: "I take no arguments"
>>> verify_foo()
- The object <Foo...> has failed to implement interface <...IFoo>: The contract of module.IFoo.simple(arg1) is violated because '<lambda>()' doesn't allow enough arguments.
+ The object <Foo...> has failed to implement interface ...IFoo: The contract of module.IFoo.simple(arg1) is violated because '<lambda>()' doesn't allow enough arguments.
Requiring too many arguments is an error.
@@ -230,7 +230,7 @@ Requiring too many arguments is an error.
>>> Foo.simple = lambda self, a, b: "I require two arguments"
>>> verify_foo()
- The object <Foo...> has failed to implement interface <...IFoo>: The contract of module.IFoo.simple(arg1) is violated because '<lambda>(a, b)' requires too many arguments.
+ The object <Foo...> has failed to implement interface ...IFoo: The contract of module.IFoo.simple(arg1) is violated because '<lambda>(a, b)' requires too many arguments.
Variable arguments can be used to implement the required number, as
can arguments with defaults.
@@ -257,7 +257,7 @@ variable keyword arguments, the implementation must also accept them.
... class Foo(object):
... def needs_kwargs(self, a=1, b=2): pass
>>> verify_foo()
- The object <Foo...> has failed to implement interface <...IFoo>: The contract of module.IFoo.needs_kwargs(**kwargs) is violated because 'Foo.needs_kwargs(a=1, b=2)' doesn't support keyword arguments.
+ The object <Foo...> has failed to implement interface ...IFoo: The contract of module.IFoo.needs_kwargs(**kwargs) is violated because 'Foo.needs_kwargs(a=1, b=2)' doesn't support keyword arguments.
>>> oname, __name__ = __name__, 'module'
>>> class IFoo(Interface):
@@ -267,7 +267,7 @@ variable keyword arguments, the implementation must also accept them.
... class Foo(object):
... def needs_varargs(self, **kwargs): pass
>>> verify_foo()
- The object <Foo...> has failed to implement interface <...IFoo>: The contract of module.IFoo.needs_varargs(*args) is violated because 'Foo.needs_varargs(**kwargs)' doesn't support variable arguments.
+ The object <Foo...> has failed to implement interface ...IFoo: The contract of module.IFoo.needs_varargs(*args) is violated because 'Foo.needs_varargs(**kwargs)' doesn't support variable arguments.
Of course, missing attributes are also found and reported, and the
source interface of the missing attribute is included. Similarly, when
@@ -288,7 +288,7 @@ the failing method is from a parent class, that is also reported.
... class Foo(Base):
... pass
>>> verify_foo()
- The object <Foo...> has failed to implement interface <...IFoo>:
+ The object <Foo...> has failed to implement interface ...IFoo:
The contract of base.IBase.method(arg1) is violated because 'Base.method()' doesn't allow enough arguments
The module.IFoo.x attribute was not provided
@@ -313,4 +313,4 @@ attributes, cannot be verified.
... print(e)
>>> verify_foo_class()
- The object <class 'Foo'> has failed to implement interface <...IFoo>: The contract of base.IBase.method(arg1) is violated because 'Base.method(self)' doesn't allow enough arguments.
+ The object <class 'Foo'> has failed to implement interface ...IFoo: The contract of base.IBase.method(arg1) is violated because 'Base.method(self)' doesn't allow enough arguments.
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