zope.interface is superior to the abc module. Let's port to it.
As part of this, we add tests for interface conformance for
classes implementing the interface.
| durin42 |
| hg-reviewers |
zope.interface is superior to the abc module. Let's port to it.
As part of this, we add tests for interface conformance for
classes implementing the interface.
| Automatic diff as part of commit; lint not applicable. |
| Automatic diff as part of commit; unit tests not applicable. |
| Path | Packages | |||
|---|---|---|---|---|
| M | mercurial/wireprotoserver.py (12 lines) | |||
| M | mercurial/wireprototypes.py (31 lines) | |||
| M | tests/test-check-interfaces.py (21 lines) |
| # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net> | # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net> | ||||
| # Copyright 2005-2007 Matt Mackall <mpm@selenic.com> | # Copyright 2005-2007 Matt Mackall <mpm@selenic.com> | ||||
| # | # | ||||
| # This software may be used and distributed according to the terms of the | # This software may be used and distributed according to the terms of the | ||||
| # GNU General Public License version 2 or any later version. | # GNU General Public License version 2 or any later version. | ||||
| from __future__ import absolute_import | from __future__ import absolute_import | ||||
| import contextlib | import contextlib | ||||
| import struct | import struct | ||||
| import sys | import sys | ||||
| import threading | import threading | ||||
| from .i18n import _ | from .i18n import _ | ||||
| from .thirdparty.zope import ( | |||||
| interface as zi, | |||||
| ) | |||||
| from . import ( | from . import ( | ||||
| encoding, | encoding, | ||||
| error, | error, | ||||
| hook, | hook, | ||||
| pycompat, | pycompat, | ||||
| util, | util, | ||||
| wireproto, | wireproto, | ||||
| wireprotoframing, | wireprotoframing, | ||||
| v = req.headers.get(b'%s-%d' % (headerprefix, i)) | v = req.headers.get(b'%s-%d' % (headerprefix, i)) | ||||
| if v is None: | if v is None: | ||||
| break | break | ||||
| chunks.append(pycompat.bytesurl(v)) | chunks.append(pycompat.bytesurl(v)) | ||||
| i += 1 | i += 1 | ||||
| return ''.join(chunks) | return ''.join(chunks) | ||||
| class httpv1protocolhandler(wireprototypes.baseprotocolhandler): | @zi.implementer(wireprototypes.baseprotocolhandler) | ||||
| class httpv1protocolhandler(object): | |||||
| def __init__(self, req, ui, checkperm): | def __init__(self, req, ui, checkperm): | ||||
| self._req = req | self._req = req | ||||
| self._ui = ui | self._ui = ui | ||||
| self._checkperm = checkperm | self._checkperm = checkperm | ||||
| @property | @property | ||||
| def name(self): | def name(self): | ||||
| return 'http-v1' | return 'http-v1' | ||||
| # Maps API name to metadata so custom API can be registered. | # Maps API name to metadata so custom API can be registered. | ||||
| API_HANDLERS = { | API_HANDLERS = { | ||||
| HTTPV2: { | HTTPV2: { | ||||
| 'config': ('experimental', 'web.api.http-v2'), | 'config': ('experimental', 'web.api.http-v2'), | ||||
| 'handler': _handlehttpv2request, | 'handler': _handlehttpv2request, | ||||
| }, | }, | ||||
| } | } | ||||
| class httpv2protocolhandler(wireprototypes.baseprotocolhandler): | @zi.implementer(wireprototypes.baseprotocolhandler) | ||||
| class httpv2protocolhandler(object): | |||||
| def __init__(self, req, ui, args=None): | def __init__(self, req, ui, args=None): | ||||
| self._req = req | self._req = req | ||||
| self._ui = ui | self._ui = ui | ||||
| self._args = args | self._args = args | ||||
| @property | @property | ||||
| def name(self): | def name(self): | ||||
| return HTTPV2 | return HTTPV2 | ||||
| fout.flush() | fout.flush() | ||||
| def _sshv1respondooberror(fout, ferr, rsp): | def _sshv1respondooberror(fout, ferr, rsp): | ||||
| ferr.write(b'%s\n-\n' % rsp) | ferr.write(b'%s\n-\n' % rsp) | ||||
| ferr.flush() | ferr.flush() | ||||
| fout.write(b'\n') | fout.write(b'\n') | ||||
| fout.flush() | fout.flush() | ||||
| class sshv1protocolhandler(wireprototypes.baseprotocolhandler): | @zi.implementer(wireprototypes.baseprotocolhandler) | ||||
| class sshv1protocolhandler(object): | |||||
| """Handler for requests services via version 1 of SSH protocol.""" | """Handler for requests services via version 1 of SSH protocol.""" | ||||
| def __init__(self, ui, fin, fout): | def __init__(self, ui, fin, fout): | ||||
| self._ui = ui | self._ui = ui | ||||
| self._fin = fin | self._fin = fin | ||||
| self._fout = fout | self._fout = fout | ||||
| @property | @property | ||||
| def name(self): | def name(self): | ||||
| # Copyright 2018 Gregory Szorc <gregory.szorc@gmail.com> | # Copyright 2018 Gregory Szorc <gregory.szorc@gmail.com> | ||||
| # | # | ||||
| # This software may be used and distributed according to the terms of the | # This software may be used and distributed according to the terms of the | ||||
| # GNU General Public License version 2 or any later version. | # GNU General Public License version 2 or any later version. | ||||
| from __future__ import absolute_import | from __future__ import absolute_import | ||||
| import abc | from .thirdparty.zope import ( | ||||
| interface as zi, | |||||
| ) | |||||
| # Names of the SSH protocol implementations. | # Names of the SSH protocol implementations. | ||||
| SSHV1 = 'ssh-v1' | SSHV1 = 'ssh-v1' | ||||
| # These are advertised over the wire. Increment the counters at the end | # These are advertised over the wire. Increment the counters at the end | ||||
| # to reflect BC breakages. | # to reflect BC breakages. | ||||
| SSHV2 = 'exp-ssh-v2-0001' | SSHV2 = 'exp-ssh-v2-0001' | ||||
| HTTPV2 = 'exp-http-v2-0001' | HTTPV2 = 'exp-http-v2-0001' | ||||
| Accepts a generator containing chunks of data to be sent to the client. | Accepts a generator containing chunks of data to be sent to the client. | ||||
| Like ``streamres``, but sends an uncompressed data for "version 1" clients | Like ``streamres``, but sends an uncompressed data for "version 1" clients | ||||
| using the application/mercurial-0.1 media type. | using the application/mercurial-0.1 media type. | ||||
| """ | """ | ||||
| def __init__(self, gen=None): | def __init__(self, gen=None): | ||||
| self.gen = gen | self.gen = gen | ||||
| class baseprotocolhandler(object): | class baseprotocolhandler(zi.Interface): | ||||
| """Abstract base class for wire protocol handlers. | """Abstract base class for wire protocol handlers. | ||||
| A wire protocol handler serves as an interface between protocol command | A wire protocol handler serves as an interface between protocol command | ||||
| handlers and the wire protocol transport layer. Protocol handlers provide | handlers and the wire protocol transport layer. Protocol handlers provide | ||||
| methods to read command arguments, redirect stdio for the duration of | methods to read command arguments, redirect stdio for the duration of | ||||
| the request, handle response types, etc. | the request, handle response types, etc. | ||||
| """ | """ | ||||
| __metaclass__ = abc.ABCMeta | name = zi.Attribute( | ||||
| @abc.abstractproperty | |||||
| def name(self): | |||||
| """The name of the protocol implementation. | """The name of the protocol implementation. | ||||
| Used for uniquely identifying the transport type. | Used for uniquely identifying the transport type. | ||||
| """ | """) | ||||
| @abc.abstractmethod | def getargs(args): | ||||
| def getargs(self, args): | |||||
| """return the value for arguments in <args> | """return the value for arguments in <args> | ||||
| returns a list of values (same order as <args>)""" | returns a list of values (same order as <args>)""" | ||||
| @abc.abstractmethod | def forwardpayload(fp): | ||||
| def forwardpayload(self, fp): | |||||
| """Read the raw payload and forward to a file. | """Read the raw payload and forward to a file. | ||||
| The payload is read in full before the function returns. | The payload is read in full before the function returns. | ||||
| """ | """ | ||||
| @abc.abstractmethod | def mayberedirectstdio(): | ||||
| def mayberedirectstdio(self): | |||||
| """Context manager to possibly redirect stdio. | """Context manager to possibly redirect stdio. | ||||
| The context manager yields a file-object like object that receives | The context manager yields a file-object like object that receives | ||||
| stdout and stderr output when the context manager is active. Or it | stdout and stderr output when the context manager is active. Or it | ||||
| yields ``None`` if no I/O redirection occurs. | yields ``None`` if no I/O redirection occurs. | ||||
| The intent of this context manager is to capture stdio output | The intent of this context manager is to capture stdio output | ||||
| so it may be sent in the response. Some transports support streaming | so it may be sent in the response. Some transports support streaming | ||||
| stdio to the client in real time. For these transports, stdio output | stdio to the client in real time. For these transports, stdio output | ||||
| won't be captured. | won't be captured. | ||||
| """ | """ | ||||
| @abc.abstractmethod | def client(): | ||||
| def client(self): | |||||
| """Returns a string representation of this client (as bytes).""" | """Returns a string representation of this client (as bytes).""" | ||||
| @abc.abstractmethod | def addcapabilities(repo, caps): | ||||
| def addcapabilities(self, repo, caps): | |||||
| """Adds advertised capabilities specific to this protocol. | """Adds advertised capabilities specific to this protocol. | ||||
| Receives the list of capabilities collected so far. | Receives the list of capabilities collected so far. | ||||
| Returns a list of capabilities. The passed in argument can be returned. | Returns a list of capabilities. The passed in argument can be returned. | ||||
| """ | """ | ||||
| @abc.abstractmethod | def checkperm(perm): | ||||
| def checkperm(self, perm): | |||||
| """Validate that the client has permissions to perform a request. | """Validate that the client has permissions to perform a request. | ||||
| The argument is the permission required to proceed. If the client | The argument is the permission required to proceed. If the client | ||||
| doesn't have that permission, the exception should raise or abort | doesn't have that permission, the exception should raise or abort | ||||
| in a protocol specific manner. | in a protocol specific manner. | ||||
| """ | """ | ||||
| bundlerepo, | bundlerepo, | ||||
| httppeer, | httppeer, | ||||
| localrepo, | localrepo, | ||||
| repository, | repository, | ||||
| sshpeer, | sshpeer, | ||||
| statichttprepo, | statichttprepo, | ||||
| ui as uimod, | ui as uimod, | ||||
| unionrepo, | unionrepo, | ||||
| wireprotoserver, | |||||
| wireprototypes, | |||||
| ) | ) | ||||
| rootdir = os.path.normpath(os.path.join(os.path.dirname(__file__), '..')) | rootdir = os.path.normpath(os.path.join(os.path.dirname(__file__), '..')) | ||||
| def checkobject(o): | def checkobject(o): | ||||
| """Verify a constructed object conforms to interface rules. | """Verify a constructed object conforms to interface rules. | ||||
| An object must have __abstractmethods__ defined. | An object must have __abstractmethods__ defined. | ||||
| checkobject(statichttprepo.statichttppeer(dummyrepo())) | checkobject(statichttprepo.statichttppeer(dummyrepo())) | ||||
| checkobject(unionrepo.unionpeer(dummyrepo())) | checkobject(unionrepo.unionpeer(dummyrepo())) | ||||
| ziverify.verifyClass(repository.completelocalrepository, | ziverify.verifyClass(repository.completelocalrepository, | ||||
| localrepo.localrepository) | localrepo.localrepository) | ||||
| repo = localrepo.localrepository(ui, rootdir) | repo = localrepo.localrepository(ui, rootdir) | ||||
| checkzobject(repo) | checkzobject(repo) | ||||
| ziverify.verifyClass(wireprototypes.baseprotocolhandler, | |||||
| wireprotoserver.sshv1protocolhandler) | |||||
| ziverify.verifyClass(wireprototypes.baseprotocolhandler, | |||||
| wireprotoserver.sshv2protocolhandler) | |||||
| ziverify.verifyClass(wireprototypes.baseprotocolhandler, | |||||
| wireprotoserver.httpv1protocolhandler) | |||||
| ziverify.verifyClass(wireprototypes.baseprotocolhandler, | |||||
| wireprotoserver.httpv2protocolhandler) | |||||
| sshv1 = wireprotoserver.sshv1protocolhandler(None, None, None) | |||||
| checkzobject(sshv1) | |||||
| sshv2 = wireprotoserver.sshv2protocolhandler(None, None, None) | |||||
| checkzobject(sshv2) | |||||
| httpv1 = wireprotoserver.httpv1protocolhandler(None, None, None) | |||||
| checkzobject(httpv1) | |||||
| httpv2 = wireprotoserver.httpv2protocolhandler(None, None) | |||||
| checkzobject(httpv2) | |||||
| main() | main() | ||||