changeset 37320:39f7d4ee8bcd

repository: port peer interfaces to zope.interface zope.interface is superior. Let's switch to it. Unlike abc, which defines interfaces through a base class, zope.interface uses different types for interfaces and for implementations. So, we had to invent some new types to hold the interfaces in order to separate the interface from its default implementation. The names here could probably be better. I've been wanting to overhaul the peer interface for a while. And wire protocol version 2 will force that work. So anticipate a refactoring of these interfaces in later commits. With this commit, we no longer test abc interfaces in test-check-interfaces.py, so code for that has been removed. Differential Revision: https://phab.mercurial-scm.org/D3068 # no-check-commit because of stream_out()
author Gregory Szorc <gregory.szorc@gmail.com>
date Fri, 30 Mar 2018 18:53:17 -0700
parents 36d17f37db91
children e826fe7a08c7
files mercurial/httppeer.py mercurial/repository.py mercurial/sshpeer.py mercurial/wireproto.py tests/test-check-interfaces.py tests/test-check-interfaces.py.out
diffstat 6 files changed, 88 insertions(+), 118 deletions(-) [+]
line wrap: on
line diff
--- a/mercurial/httppeer.py	Fri Mar 30 14:52:32 2018 -0700
+++ b/mercurial/httppeer.py	Fri Mar 30 18:53:17 2018 -0700
@@ -184,7 +184,7 @@
                 % (util.timer() - start, ret.code))
         return ret
 
-    # Begin of _basepeer interface.
+    # Begin of ipeerconnection interface.
 
     @util.propertycache
     def ui(self):
@@ -205,9 +205,9 @@
     def close(self):
         pass
 
-    # End of _basepeer interface.
+    # End of ipeerconnection interface.
 
-    # Begin of _basewirepeer interface.
+    # Begin of ipeercommands interface.
 
     def capabilities(self):
         # self._fetchcaps() should have been called as part of peer
@@ -215,7 +215,7 @@
         assert self._caps is not None
         return self._caps
 
-    # End of _basewirepeer interface.
+    # End of ipeercommands interface.
 
     # look up capabilities only when needed
 
--- a/mercurial/repository.py	Fri Mar 30 14:52:32 2018 -0700
+++ b/mercurial/repository.py	Fri Mar 30 18:53:17 2018 -0700
@@ -7,8 +7,6 @@
 
 from __future__ import absolute_import
 
-import abc
-
 from .i18n import _
 from .thirdparty.zope import (
     interface as zi,
@@ -17,7 +15,7 @@
     error,
 )
 
-class _basepeer(object):
+class ipeerconnection(zi.Interface):
     """Represents a "connection" to a repository.
 
     This is the base interface for representing a connection to a repository.
@@ -26,14 +24,9 @@
     This is not a complete interface definition and should not be used
     outside of this module.
     """
-    __metaclass__ = abc.ABCMeta
+    ui = zi.Attribute("""ui.ui instance""")
 
-    @abc.abstractproperty
-    def ui(self):
-        """ui.ui instance."""
-
-    @abc.abstractmethod
-    def url(self):
+    def url():
         """Returns a URL string representing this peer.
 
         Currently, implementations expose the raw URL used to construct the
@@ -45,62 +38,53 @@
         value.
         """
 
-    @abc.abstractmethod
-    def local(self):
+    def local():
         """Returns a local repository instance.
 
         If the peer represents a local repository, returns an object that
         can be used to interface with it. Otherwise returns ``None``.
         """
 
-    @abc.abstractmethod
-    def peer(self):
+    def peer():
         """Returns an object conforming to this interface.
 
         Most implementations will ``return self``.
         """
 
-    @abc.abstractmethod
-    def canpush(self):
+    def canpush():
         """Returns a boolean indicating if this peer can be pushed to."""
 
-    @abc.abstractmethod
-    def close(self):
+    def close():
         """Close the connection to this peer.
 
         This is called when the peer will no longer be used. Resources
         associated with the peer should be cleaned up.
         """
 
-class _basewirecommands(object):
+class ipeercommands(zi.Interface):
     """Client-side interface for communicating over the wire protocol.
 
     This interface is used as a gateway to the Mercurial wire protocol.
     methods commonly call wire protocol commands of the same name.
     """
-    __metaclass__ = abc.ABCMeta
 
-    @abc.abstractmethod
-    def branchmap(self):
+    def branchmap():
         """Obtain heads in named branches.
 
         Returns a dict mapping branch name to an iterable of nodes that are
         heads on that branch.
         """
 
-    @abc.abstractmethod
-    def capabilities(self):
+    def capabilities():
         """Obtain capabilities of the peer.
 
         Returns a set of string capabilities.
         """
 
-    @abc.abstractmethod
-    def debugwireargs(self, one, two, three=None, four=None, five=None):
+    def debugwireargs(one, two, three=None, four=None, five=None):
         """Used to facilitate debugging of arguments passed over the wire."""
 
-    @abc.abstractmethod
-    def getbundle(self, source, **kwargs):
+    def getbundle(source, **kwargs):
         """Obtain remote repository data as a bundle.
 
         This command is how the bulk of repository data is transferred from
@@ -109,15 +93,13 @@
         Returns a generator of bundle data.
         """
 
-    @abc.abstractmethod
-    def heads(self):
+    def heads():
         """Determine all known head revisions in the peer.
 
         Returns an iterable of binary nodes.
         """
 
-    @abc.abstractmethod
-    def known(self, nodes):
+    def known(nodes):
         """Determine whether multiple nodes are known.
 
         Accepts an iterable of nodes whose presence to check for.
@@ -126,22 +108,19 @@
         at that index is known to the peer.
         """
 
-    @abc.abstractmethod
-    def listkeys(self, namespace):
+    def listkeys(namespace):
         """Obtain all keys in a pushkey namespace.
 
         Returns an iterable of key names.
         """
 
-    @abc.abstractmethod
-    def lookup(self, key):
+    def lookup(key):
         """Resolve a value to a known revision.
 
         Returns a binary node of the resolved revision on success.
         """
 
-    @abc.abstractmethod
-    def pushkey(self, namespace, key, old, new):
+    def pushkey(namespace, key, old, new):
         """Set a value using the ``pushkey`` protocol.
 
         Arguments correspond to the pushkey namespace and key to operate on and
@@ -151,15 +130,13 @@
         namespace.
         """
 
-    @abc.abstractmethod
-    def stream_out(self):
+    def stream_out():
         """Obtain streaming clone data.
 
         Successful result should be a generator of data chunks.
         """
 
-    @abc.abstractmethod
-    def unbundle(self, bundle, heads, url):
+    def unbundle(bundle, heads, url):
         """Transfer repository data to the peer.
 
         This is how the bulk of data during a push is transferred.
@@ -167,17 +144,15 @@
         Returns the integer number of heads added to the peer.
         """
 
-class _baselegacywirecommands(object):
+class ipeerlegacycommands(zi.Interface):
     """Interface for implementing support for legacy wire protocol commands.
 
     Wire protocol commands transition to legacy status when they are no longer
     used by modern clients. To facilitate identifying which commands are
     legacy, the interfaces are split.
     """
-    __metaclass__ = abc.ABCMeta
 
-    @abc.abstractmethod
-    def between(self, pairs):
+    def between(pairs):
         """Obtain nodes between pairs of nodes.
 
         ``pairs`` is an iterable of node pairs.
@@ -186,8 +161,7 @@
         requested pair.
         """
 
-    @abc.abstractmethod
-    def branches(self, nodes):
+    def branches(nodes):
         """Obtain ancestor changesets of specific nodes back to a branch point.
 
         For each requested node, the peer finds the first ancestor node that is
@@ -196,23 +170,18 @@
         Returns an iterable of iterables with the resolved values for each node.
         """
 
-    @abc.abstractmethod
-    def changegroup(self, nodes, kind):
+    def changegroup(nodes, kind):
         """Obtain a changegroup with data for descendants of specified nodes."""
 
-    @abc.abstractmethod
-    def changegroupsubset(self, bases, heads, kind):
+    def changegroupsubset(bases, heads, kind):
         pass
 
-class peer(_basepeer, _basewirecommands):
-    """Unified interface and base class for peer repositories.
+class ipeerbase(ipeerconnection, ipeercommands):
+    """Unified interface for peer repositories.
 
-    All peer instances must inherit from this class and conform to its
-    interface.
+    All peer instances must conform to this interface.
     """
-
-    @abc.abstractmethod
-    def iterbatch(self):
+    def iterbatch():
         """Obtain an object to be used for multiple method calls.
 
         Various operations call several methods on peer instances. If each
@@ -236,7 +205,7 @@
         calls. However, they must all support this API.
         """
 
-    def capable(self, name):
+    def capable(name):
         """Determine support for a named capability.
 
         Returns ``False`` if capability not supported.
@@ -244,6 +213,21 @@
         Returns ``True`` if boolean capability is supported. Returns a string
         if capability support is non-boolean.
         """
+
+    def requirecap(name, purpose):
+        """Require a capability to be present.
+
+        Raises a ``CapabilityError`` if the capability isn't present.
+        """
+
+class ipeerbaselegacycommands(ipeerbase, ipeerlegacycommands):
+    """Unified peer interface that supports legacy commands."""
+
+@zi.implementer(ipeerbase)
+class peer(object):
+    """Base class for peer repositories."""
+
+    def capable(self, name):
         caps = self.capabilities()
         if name in caps:
             return True
@@ -256,10 +240,6 @@
         return False
 
     def requirecap(self, name, purpose):
-        """Require a capability to be present.
-
-        Raises a ``CapabilityError`` if the capability isn't present.
-        """
         if self.capable(name):
             return
 
@@ -267,7 +247,8 @@
             _('cannot %s; remote repository does not support the %r '
               'capability') % (purpose, name))
 
-class legacypeer(peer, _baselegacywirecommands):
+@zi.implementer(ipeerbaselegacycommands)
+class legacypeer(peer):
     """peer but with support for legacy wire protocol commands."""
 
 class completelocalrepository(zi.Interface):
--- a/mercurial/sshpeer.py	Fri Mar 30 14:52:32 2018 -0700
+++ b/mercurial/sshpeer.py	Fri Mar 30 18:53:17 2018 -0700
@@ -377,7 +377,7 @@
         'batch',
     }
 
-    # Begin of _basepeer interface.
+    # Begin of ipeerconnection interface.
 
     @util.propertycache
     def ui(self):
@@ -398,14 +398,14 @@
     def close(self):
         pass
 
-    # End of _basepeer interface.
+    # End of ipeerconnection interface.
 
-    # Begin of _basewirecommands interface.
+    # Begin of ipeercommands interface.
 
     def capabilities(self):
         return self._caps
 
-    # End of _basewirecommands interface.
+    # End of ipeercommands interface.
 
     def _readerr(self):
         _forwardoutput(self.ui, self._pipee)
--- a/mercurial/wireproto.py	Fri Mar 30 14:52:32 2018 -0700
+++ b/mercurial/wireproto.py	Fri Mar 30 18:53:17 2018 -0700
@@ -192,7 +192,7 @@
     See also httppeer.py and sshpeer.py for protocol-specific
     implementations of this interface.
     """
-    # Begin of basewirepeer interface.
+    # Begin of ipeercommands interface.
 
     def iterbatch(self):
         return remoteiterbatcher(self)
@@ -353,9 +353,9 @@
             ret = bundle2.getunbundler(self.ui, stream)
         return ret
 
-    # End of basewirepeer interface.
+    # End of ipeercommands interface.
 
-    # Begin of baselegacywirepeer interface.
+    # Begin of ipeerlegacycommands interface.
 
     def branches(self, nodes):
         n = encodelist(nodes)
@@ -391,7 +391,7 @@
                                    bases=bases, heads=heads)
         return changegroupmod.cg1unpacker(f, 'UN')
 
-    # End of baselegacywirepeer interface.
+    # End of ipeerlegacycommands interface.
 
     def _submitbatch(self, req):
         """run batch request <req> on the server
--- a/tests/test-check-interfaces.py	Fri Mar 30 14:52:32 2018 -0700
+++ b/tests/test-check-interfaces.py	Fri Mar 30 18:53:17 2018 -0700
@@ -25,35 +25,6 @@
 
 rootdir = os.path.normpath(os.path.join(os.path.dirname(__file__), '..'))
 
-def checkobject(o):
-    """Verify a constructed object conforms to interface rules.
-
-    An object must have __abstractmethods__ defined.
-
-    All "public" attributes of the object (attributes not prefixed with
-    an underscore) must be in __abstractmethods__ or appear on a base class
-    with __abstractmethods__.
-    """
-    name = o.__class__.__name__
-
-    allowed = set()
-    for cls in o.__class__.__mro__:
-        if not getattr(cls, '__abstractmethods__', set()):
-            continue
-
-        allowed |= cls.__abstractmethods__
-        allowed |= {a for a in dir(cls) if not a.startswith('_')}
-
-    if not allowed:
-        print('%s does not have abstract methods' % name)
-        return
-
-    public = {a for a in dir(o) if not a.startswith('_')}
-
-    for attr in sorted(public - allowed):
-        print('public attributes not in abstract interface: %s.%s' % (
-            name, attr))
-
 def checkzobject(o):
     """Verify an object with a zope interface."""
     ifaces = zi.providedBy(o)
@@ -108,16 +79,34 @@
     # Needed so we can open a local repo with obsstore without a warning.
     ui.setconfig('experimental', 'evolution.createmarkers', True)
 
-    checkobject(badpeer())
-    checkobject(httppeer.httppeer(None, None, None, dummyopener()))
-    checkobject(localrepo.localpeer(dummyrepo()))
-    checkobject(sshpeer.sshv1peer(ui, 'ssh://localhost/foo', None, dummypipe(),
-                                  dummypipe(), None, None))
-    checkobject(sshpeer.sshv2peer(ui, 'ssh://localhost/foo', None, dummypipe(),
-                                  dummypipe(), None, None))
-    checkobject(bundlerepo.bundlepeer(dummyrepo()))
-    checkobject(statichttprepo.statichttppeer(dummyrepo()))
-    checkobject(unionrepo.unionpeer(dummyrepo()))
+    checkzobject(badpeer())
+
+    ziverify.verifyClass(repository.ipeerbaselegacycommands,
+                         httppeer.httppeer)
+    checkzobject(httppeer.httppeer(None, None, None, dummyopener()))
+
+    ziverify.verifyClass(repository.ipeerbase,
+                         localrepo.localpeer)
+    checkzobject(localrepo.localpeer(dummyrepo()))
+
+    ziverify.verifyClass(repository.ipeerbaselegacycommands,
+                         sshpeer.sshv1peer)
+    checkzobject(sshpeer.sshv1peer(ui, 'ssh://localhost/foo', None, dummypipe(),
+                                   dummypipe(), None, None))
+
+    ziverify.verifyClass(repository.ipeerbaselegacycommands,
+                         sshpeer.sshv2peer)
+    checkzobject(sshpeer.sshv2peer(ui, 'ssh://localhost/foo', None, dummypipe(),
+                                   dummypipe(), None, None))
+
+    ziverify.verifyClass(repository.ipeerbase, bundlerepo.bundlepeer)
+    checkzobject(bundlerepo.bundlepeer(dummyrepo()))
+
+    ziverify.verifyClass(repository.ipeerbase, statichttprepo.statichttppeer)
+    checkzobject(statichttprepo.statichttppeer(dummyrepo()))
+
+    ziverify.verifyClass(repository.ipeerbase, unionrepo.unionpeer)
+    checkzobject(unionrepo.unionpeer(dummyrepo()))
 
     ziverify.verifyClass(repository.completelocalrepository,
                          localrepo.localrepository)
--- a/tests/test-check-interfaces.py.out	Fri Mar 30 14:52:32 2018 -0700
+++ b/tests/test-check-interfaces.py.out	Fri Mar 30 18:53:17 2018 -0700
@@ -1,2 +1,2 @@
-public attributes not in abstract interface: badpeer.badattribute
-public attributes not in abstract interface: badpeer.badmethod
+public attribute not declared in interfaces: badpeer.badattribute
+public attribute not declared in interfaces: badpeer.badmethod