diff options
author | Jason Madden <jamadden@gmail.com> | 2021-03-25 10:30:47 -0500 |
---|---|---|
committer | Jason Madden <jamadden@gmail.com> | 2021-03-25 10:30:47 -0500 |
commit | ce8f66f8bf4ebffca7cdf95dbb8c5add02251e91 (patch) | |
tree | 005840e6e690f96d690d189ea3c196317818fae1 /src/zope/interface/declarations.py | |
parent | e6d3805c6e155a89a34d89601477881aec9b7df1 (diff) | |
download | zope-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
```
Diffstat (limited to 'src/zope/interface/declarations.py')
-rw-r--r-- | src/zope/interface/declarations.py | 130 |
1 files changed, 114 insertions, 16 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__'],) ############################################################################## |