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.
Lint Skipped |
Unit Tests Skipped |
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() |