diff --git a/mercurial/filelog.py b/mercurial/filelog.py --- a/mercurial/filelog.py +++ b/mercurial/filelog.py @@ -7,16 +7,16 @@ from __future__ import absolute_import -from .thirdparty.zope import ( - interface as zi, -) from . import ( error, repository, revlog, ) +from .utils import ( + interfaceutil, +) -@zi.implementer(repository.ifilestorage) +@interfaceutil.implementer(repository.ifilestorage) class filelog(object): def __init__(self, opener, path): self._revlog = revlog.revlog(opener, diff --git a/mercurial/httppeer.py b/mercurial/httppeer.py --- a/mercurial/httppeer.py +++ b/mercurial/httppeer.py @@ -20,9 +20,6 @@ from .i18n import _ from .thirdparty import ( cbor, ) -from .thirdparty.zope import ( - interface as zi, -) from . import ( bundle2, error, @@ -38,6 +35,9 @@ from . import ( wireprotov2peer, wireprotov2server, ) +from .utils import ( + interfaceutil, +) httplib = util.httplib urlerr = util.urlerr @@ -582,7 +582,7 @@ class queuedcommandfuture(pycompat.futur # will resolve to Future.result. return self.result(timeout) -@zi.implementer(repository.ipeercommandexecutor) +@interfaceutil.implementer(repository.ipeercommandexecutor) class httpv2executor(object): def __init__(self, ui, opener, requestbuilder, apiurl, descriptor): self._ui = ui @@ -731,8 +731,9 @@ class httpv2executor(object): pass # TODO implement interface for version 2 peers -@zi.implementer(repository.ipeerconnection, repository.ipeercapabilities, - repository.ipeerrequests) +@interfaceutil.implementer(repository.ipeerconnection, + repository.ipeercapabilities, + repository.ipeerrequests) class httpv2peer(object): def __init__(self, ui, repourl, apipath, opener, requestbuilder, apidescriptor): diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py --- a/mercurial/localrepo.py +++ b/mercurial/localrepo.py @@ -21,9 +21,6 @@ from .node import ( nullid, short, ) -from .thirdparty.zope import ( - interface as zi, -) from . import ( bookmarks, branchmap, @@ -68,6 +65,7 @@ from . import ( vfs as vfsmod, ) from .utils import ( + interfaceutil, procutil, stringutil, ) @@ -153,7 +151,7 @@ moderncaps = {'lookup', 'branchmap', 'pu 'unbundle'} legacycaps = moderncaps.union({'changegroupsubset'}) -@zi.implementer(repository.ipeercommandexecutor) +@interfaceutil.implementer(repository.ipeercommandexecutor) class localcommandexecutor(object): def __init__(self, peer): self._peer = peer @@ -196,7 +194,7 @@ class localcommandexecutor(object): def close(self): self._closed = True -@zi.implementer(repository.ipeercommands) +@interfaceutil.implementer(repository.ipeercommands) class localpeer(repository.peer): '''peer for a local repo; reflects only the most recent API''' @@ -324,7 +322,7 @@ class localpeer(repository.peer): # End of peer interface. -@zi.implementer(repository.ipeerlegacycommands) +@interfaceutil.implementer(repository.ipeerlegacycommands) class locallegacypeer(localpeer): '''peer extension which implements legacy methods too; used for tests with restricted capabilities''' @@ -365,7 +363,7 @@ REVLOGV2_REQUIREMENT = 'exp-revlogv2.0' # set to reflect that the extension knows how to handle that requirements. featuresetupfuncs = set() -@zi.implementer(repository.completelocalrepository) +@interfaceutil.implementer(repository.completelocalrepository) class localrepository(object): # obsolete experimental requirements: diff --git a/mercurial/repository.py b/mercurial/repository.py --- a/mercurial/repository.py +++ b/mercurial/repository.py @@ -8,14 +8,14 @@ from __future__ import absolute_import from .i18n import _ -from .thirdparty.zope import ( - interface as zi, -) from . import ( error, ) +from .utils import ( + interfaceutil, +) -class ipeerconnection(zi.Interface): +class ipeerconnection(interfaceutil.Interface): """Represents a "connection" to a repository. This is the base interface for representing a connection to a repository. @@ -24,7 +24,7 @@ class ipeerconnection(zi.Interface): This is not a complete interface definition and should not be used outside of this module. """ - ui = zi.Attribute("""ui.ui instance""") + ui = interfaceutil.Attribute("""ui.ui instance""") def url(): """Returns a URL string representing this peer. @@ -61,7 +61,7 @@ class ipeerconnection(zi.Interface): associated with the peer should be cleaned up. """ -class ipeercapabilities(zi.Interface): +class ipeercapabilities(interfaceutil.Interface): """Peer sub-interface related to capabilities.""" def capable(name): @@ -81,7 +81,7 @@ class ipeercapabilities(zi.Interface): Raises a ``CapabilityError`` if the capability isn't present. """ -class ipeercommands(zi.Interface): +class ipeercommands(interfaceutil.Interface): """Client-side interface for communicating over the wire protocol. This interface is used as a gateway to the Mercurial wire protocol. @@ -170,7 +170,7 @@ class ipeercommands(zi.Interface): Returns the integer number of heads added to the peer. """ -class ipeerlegacycommands(zi.Interface): +class ipeerlegacycommands(interfaceutil.Interface): """Interface for implementing support for legacy wire protocol commands. Wire protocol commands transition to legacy status when they are no longer @@ -202,7 +202,7 @@ class ipeerlegacycommands(zi.Interface): def changegroupsubset(bases, heads, source): pass -class ipeercommandexecutor(zi.Interface): +class ipeercommandexecutor(interfaceutil.Interface): """Represents a mechanism to execute remote commands. This is the primary interface for requesting that wire protocol commands @@ -259,7 +259,7 @@ class ipeercommandexecutor(zi.Interface) This method may call ``sendcommands()`` if there are buffered commands. """ -class ipeerrequests(zi.Interface): +class ipeerrequests(interfaceutil.Interface): """Interface for executing commands on a peer.""" def commandexecutor(): @@ -290,7 +290,7 @@ class ipeerbase(ipeerconnection, ipeerca All peer instances must conform to this interface. """ -@zi.implementer(ipeerbase) +@interfaceutil.implementer(ipeerbase) class peer(object): """Base class for peer repositories.""" @@ -314,7 +314,7 @@ class peer(object): _('cannot %s; remote repository does not support the %r ' 'capability') % (purpose, name)) -class ifilerevisionssequence(zi.Interface): +class ifilerevisionssequence(interfaceutil.Interface): """Contains index data for all revisions of a file. Types implementing this behave like lists of tuples. The index @@ -365,7 +365,7 @@ class ifilerevisionssequence(zi.Interfac def insert(self, i, entry): """Add an item to the index at specific revision.""" -class ifileindex(zi.Interface): +class ifileindex(interfaceutil.Interface): """Storage interface for index data of a single file. File storage data is divided into index metadata and data storage. @@ -377,7 +377,7 @@ class ifileindex(zi.Interface): * DAG data (storing and querying the relationship between nodes). * Metadata to facilitate storage. """ - index = zi.Attribute( + index = interfaceutil.Attribute( """An ``ifilerevisionssequence`` instance.""") def __len__(): @@ -470,7 +470,7 @@ class ifileindex(zi.Interface): def candelta(baserev, rev): """"Whether a delta can be generated between two revisions.""" -class ifiledata(zi.Interface): +class ifiledata(interfaceutil.Interface): """Storage interface for data storage of a specific file. This complements ``ifileindex`` and provides an interface for accessing @@ -536,7 +536,7 @@ class ifiledata(zi.Interface): revision data. """ -class ifilemutation(zi.Interface): +class ifilemutation(interfaceutil.Interface): """Storage interface for mutation events of a tracked file.""" def add(filedata, meta, transaction, linkrev, p1, p2): @@ -608,13 +608,13 @@ class ifilemutation(zi.Interface): class ifilestorage(ifileindex, ifiledata, ifilemutation): """Complete storage interface for a single tracked file.""" - version = zi.Attribute( + version = interfaceutil.Attribute( """Version number of storage. TODO this feels revlog centric and could likely be removed. """) - storedeltachains = zi.Attribute( + storedeltachains = interfaceutil.Attribute( """Whether the store stores deltas. TODO deltachains are revlog centric. This can probably removed @@ -622,7 +622,7 @@ class ifilestorage(ifileindex, ifiledata data. """) - _generaldelta = zi.Attribute( + _generaldelta = interfaceutil.Attribute( """Whether deltas can be against any parent revision. TODO this is used by changegroup code and it could probably be @@ -642,59 +642,59 @@ class ifilestorage(ifileindex, ifiledata TODO this is used by verify and it should not be part of the interface. """ -class completelocalrepository(zi.Interface): +class completelocalrepository(interfaceutil.Interface): """Monolithic interface for local repositories. This currently captures the reality of things - not how things should be. """ - supportedformats = zi.Attribute( + supportedformats = interfaceutil.Attribute( """Set of requirements that apply to stream clone. This is actually a class attribute and is shared among all instances. """) - openerreqs = zi.Attribute( + openerreqs = interfaceutil.Attribute( """Set of requirements that are passed to the opener. This is actually a class attribute and is shared among all instances. """) - supported = zi.Attribute( + supported = interfaceutil.Attribute( """Set of requirements that this repo is capable of opening.""") - requirements = zi.Attribute( + requirements = interfaceutil.Attribute( """Set of requirements this repo uses.""") - filtername = zi.Attribute( + filtername = interfaceutil.Attribute( """Name of the repoview that is active on this repo.""") - wvfs = zi.Attribute( + wvfs = interfaceutil.Attribute( """VFS used to access the working directory.""") - vfs = zi.Attribute( + vfs = interfaceutil.Attribute( """VFS rooted at the .hg directory. Used to access repository data not in the store. """) - svfs = zi.Attribute( + svfs = interfaceutil.Attribute( """VFS rooted at the store. Used to access repository data in the store. Typically .hg/store. But can point elsewhere if the store is shared. """) - root = zi.Attribute( + root = interfaceutil.Attribute( """Path to the root of the working directory.""") - path = zi.Attribute( + path = interfaceutil.Attribute( """Path to the .hg directory.""") - origroot = zi.Attribute( + origroot = interfaceutil.Attribute( """The filesystem path that was used to construct the repo.""") - auditor = zi.Attribute( + auditor = interfaceutil.Attribute( """A pathauditor for the working directory. This checks if a path refers to a nested repository. @@ -702,40 +702,40 @@ class completelocalrepository(zi.Interfa Operates on the filesystem. """) - nofsauditor = zi.Attribute( + nofsauditor = interfaceutil.Attribute( """A pathauditor for the working directory. This is like ``auditor`` except it doesn't do filesystem checks. """) - baseui = zi.Attribute( + baseui = interfaceutil.Attribute( """Original ui instance passed into constructor.""") - ui = zi.Attribute( + ui = interfaceutil.Attribute( """Main ui instance for this instance.""") - sharedpath = zi.Attribute( + sharedpath = interfaceutil.Attribute( """Path to the .hg directory of the repo this repo was shared from.""") - store = zi.Attribute( + store = interfaceutil.Attribute( """A store instance.""") - spath = zi.Attribute( + spath = interfaceutil.Attribute( """Path to the store.""") - sjoin = zi.Attribute( + sjoin = interfaceutil.Attribute( """Alias to self.store.join.""") - cachevfs = zi.Attribute( + cachevfs = interfaceutil.Attribute( """A VFS used to access the cache directory. Typically .hg/cache. """) - filteredrevcache = zi.Attribute( + filteredrevcache = interfaceutil.Attribute( """Holds sets of revisions to be filtered.""") - names = zi.Attribute( + names = interfaceutil.Attribute( """A ``namespaces`` instance.""") def close(): @@ -750,19 +750,19 @@ class completelocalrepository(zi.Interfa def filtered(name, visibilityexceptions=None): """Obtain a named view of this repository.""" - obsstore = zi.Attribute( + obsstore = interfaceutil.Attribute( """A store of obsolescence data.""") - changelog = zi.Attribute( + changelog = interfaceutil.Attribute( """A handle on the changelog revlog.""") - manifestlog = zi.Attribute( + manifestlog = interfaceutil.Attribute( """A handle on the root manifest revlog.""") - dirstate = zi.Attribute( + dirstate = interfaceutil.Attribute( """Working directory state.""") - narrowpats = zi.Attribute( + narrowpats = interfaceutil.Attribute( """Matcher patterns for this repository's narrowspec.""") def narrowmatch(): @@ -978,7 +978,7 @@ class completelocalrepository(zi.Interfa def checkpush(pushop): pass - prepushoutgoinghooks = zi.Attribute( + prepushoutgoinghooks = interfaceutil.Attribute( """util.hooks instance.""") def pushkey(namespace, key, old, new): diff --git a/mercurial/utils/interfaceutil.py b/mercurial/utils/interfaceutil.py new file mode 100644 --- /dev/null +++ b/mercurial/utils/interfaceutil.py @@ -0,0 +1,40 @@ +# interfaceutil.py - Utilities for declaring interfaces. +# +# Copyright 2018 Gregory Szorc +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +# zope.interface imposes a run-time cost due to module import overhead and +# bookkeeping for declaring interfaces. So, we use stubs for various +# zope.interface primitives unless instructed otherwise. + +from __future__ import absolute_import + +from .. import ( + encoding, +) + +if encoding.environ.get('HGREALINTERFACES'): + from ..thirdparty.zope import ( + interface as zi, + ) + + Attribute = zi.Attribute + Interface = zi.Interface + implementer = zi.implementer +else: + class Attribute(object): + def __init__(self, __name__, __doc__=''): + pass + + class Interface(object): + def __init__(self, name, bases=(), attrs=None, __doc__=None, + __module__=None): + pass + + def implementer(*ifaces): + def wrapper(cls): + return cls + + return wrapper diff --git a/mercurial/wireprotoserver.py b/mercurial/wireprotoserver.py --- a/mercurial/wireprotoserver.py +++ b/mercurial/wireprotoserver.py @@ -15,9 +15,6 @@ from .i18n import _ from .thirdparty import ( cbor, ) -from .thirdparty.zope import ( - interface as zi, -) from . import ( encoding, error, @@ -29,6 +26,7 @@ from . import ( wireprotov2server, ) from .utils import ( + interfaceutil, procutil, ) @@ -62,7 +60,7 @@ def decodevaluefromheaders(req, headerpr return ''.join(chunks) -@zi.implementer(wireprototypes.baseprotocolhandler) +@interfaceutil.implementer(wireprototypes.baseprotocolhandler) class httpv1protocolhandler(object): def __init__(self, req, ui, checkperm): self._req = req @@ -489,7 +487,7 @@ def _sshv1respondooberror(fout, ferr, rs fout.write(b'\n') fout.flush() -@zi.implementer(wireprototypes.baseprotocolhandler) +@interfaceutil.implementer(wireprototypes.baseprotocolhandler) class sshv1protocolhandler(object): """Handler for requests services via version 1 of SSH protocol.""" def __init__(self, ui, fin, fout): diff --git a/mercurial/wireprototypes.py b/mercurial/wireprototypes.py --- a/mercurial/wireprototypes.py +++ b/mercurial/wireprototypes.py @@ -9,14 +9,14 @@ from .node import ( bin, hex, ) -from .thirdparty.zope import ( - interface as zi, -) from .i18n import _ from . import ( error, util, ) +from .utils import ( + interfaceutil, +) # Names of the SSH protocol implementations. SSHV1 = 'ssh-v1' @@ -179,7 +179,7 @@ GETBUNDLE_ARGUMENTS = { 'stream': 'boolean', } -class baseprotocolhandler(zi.Interface): +class baseprotocolhandler(interfaceutil.Interface): """Abstract base class for wire protocol handlers. A wire protocol handler serves as an interface between protocol command @@ -188,7 +188,7 @@ class baseprotocolhandler(zi.Interface): the request, handle response types, etc. """ - name = zi.Attribute( + name = interfaceutil.Attribute( """The name of the protocol implementation. Used for uniquely identifying the transport type. diff --git a/mercurial/wireprotov1peer.py b/mercurial/wireprotov1peer.py --- a/mercurial/wireprotov1peer.py +++ b/mercurial/wireprotov1peer.py @@ -15,9 +15,6 @@ from .i18n import _ from .node import ( bin, ) -from .thirdparty.zope import ( - interface as zi, -) from . import ( bundle2, changegroup as changegroupmod, @@ -29,6 +26,9 @@ from . import ( util, wireprototypes, ) +from .utils import ( + interfaceutil, +) urlreq = util.urlreq @@ -110,7 +110,7 @@ class unsentfuture(pycompat.futures.Futu # on that. return self.result(timeout) -@zi.implementer(repository.ipeercommandexecutor) +@interfaceutil.implementer(repository.ipeercommandexecutor) class peerexecutor(object): def __init__(self, peer): self._peer = peer @@ -308,7 +308,8 @@ class peerexecutor(object): else: f.set_result(result) -@zi.implementer(repository.ipeercommands, repository.ipeerlegacycommands) +@interfaceutil.implementer(repository.ipeercommands, + repository.ipeerlegacycommands) class wirepeer(repository.peer): """Client-side interface for communicating with a peer repository. diff --git a/mercurial/wireprotov2server.py b/mercurial/wireprotov2server.py --- a/mercurial/wireprotov2server.py +++ b/mercurial/wireprotov2server.py @@ -12,9 +12,6 @@ from .i18n import _ from .thirdparty import ( cbor, ) -from .thirdparty.zope import ( - interface as zi, -) from . import ( encoding, error, @@ -24,6 +21,9 @@ from . import ( wireprotoframing, wireprototypes, ) +from .utils import ( + interfaceutil, +) FRAMINGTYPE = b'application/mercurial-exp-framing-0005' @@ -340,7 +340,7 @@ def dispatch(repo, proto, command): return func(repo, proto, **args) -@zi.implementer(wireprototypes.baseprotocolhandler) +@interfaceutil.implementer(wireprototypes.baseprotocolhandler) class httpv2protocolhandler(object): def __init__(self, req, ui, args=None): self._req = req diff --git a/tests/test-check-interfaces.py b/tests/test-check-interfaces.py --- a/tests/test-check-interfaces.py +++ b/tests/test-check-interfaces.py @@ -2,6 +2,9 @@ from __future__ import absolute_import, print_function +from mercurial import encoding +encoding.environ[b'HGREALINTERFACES'] = b'1' + import os from mercurial.thirdparty.zope import ( diff --git a/tests/test-check-module-imports.t b/tests/test-check-module-imports.t --- a/tests/test-check-module-imports.t +++ b/tests/test-check-module-imports.t @@ -27,6 +27,7 @@ outputs, which should be fixed later. > -X i18n/posplit \ > -X mercurial/thirdparty \ > -X tests/hypothesishelpers.py \ + > -X tests/test-check-interfaces.py \ > -X tests/test-commit-interactive.t \ > -X tests/test-contrib-check-code.t \ > -X tests/test-demandimport.py \