Skip to content

Commit

Permalink
Support XML-RPC multicall
Browse files Browse the repository at this point in the history
  • Loading branch information
di committed Apr 20, 2018
1 parent def489d commit 53fdade
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 0 deletions.
66 changes: 66 additions & 0 deletions tests/unit/legacy/api/xmlrpc/test_xmlrpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -792,3 +792,69 @@ def test_browse(db_request):
"Programming Language :: Python",
],
)) == {(expected_release.name, expected_release.version)}


class TestMulticall:

def test_multicall(self, monkeypatch):
dumped = pretend.stub(encode=lambda: None)
dumps = pretend.call_recorder(lambda *a, **kw: dumped)
monkeypatch.setattr(xmlrpc.xmlrpc.client, 'dumps', dumps)

loaded = pretend.stub()
loads = pretend.call_recorder(lambda *a, **kw: loaded)
monkeypatch.setattr(xmlrpc.xmlrpc.client, 'loads', loads)

subreq = pretend.stub()
blank = pretend.call_recorder(lambda *a, **kw: subreq)
monkeypatch.setattr(xmlrpc.Request, 'blank', blank)

body = pretend.stub()
response = pretend.stub(body=body)

invoke_subrequest = pretend.call_recorder(lambda *a, **kw: response)
request = pretend.stub(invoke_subrequest=invoke_subrequest)

args = [
{'methodName': 'search', 'params': [{'name': 'foo'}]},
{'methodName': 'browse', 'params': [{'classifiers': ['bar']}]},
]

responses = xmlrpc.multicall(request, args)

assert responses == [loaded, loaded]
assert blank.calls == [
pretend.call('/RPC2', headers={'Content-Type': 'text/xml'}),
pretend.call('/RPC2', headers={'Content-Type': 'text/xml'}),
]
assert invoke_subrequest.calls == [
pretend.call(subreq, use_tweens=True),
pretend.call(subreq, use_tweens=True),
]
assert dumps.calls == [
pretend.call(({'name': 'foo'},), methodname='search'),
pretend.call(({'classifiers': ['bar']},), methodname='browse'),
]
assert loads.calls == [pretend.call(body), pretend.call(body)]

def test_recursive_multicall(self):
request = pretend.stub()
args = [
{'methodName': 'system.multicall', 'params': []},
]
with pytest.raises(xmlrpc.XMLRPCWrappedError) as exc:
xmlrpc.multicall(request, args)

assert exc.value.faultString == (
'ValueError: Cannot use system.multicall inside a multicall'
)

def test_missing_multicall_method(self):
request = pretend.stub()
args = [{}]
with pytest.raises(xmlrpc.XMLRPCWrappedError) as exc:
xmlrpc.multicall(request, args)

assert exc.value.faultString == (
'ValueError: Method name not provided'
)
28 changes: 28 additions & 0 deletions warehouse/legacy/api/xmlrpc/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@
import collections.abc
import datetime
import functools
import xmlrpc.client
import xmlrpc.server

from elasticsearch_dsl import Q
from packaging.utils import canonicalize_name
from pyramid.request import Request
from pyramid.view import view_config
from pyramid_rpc.xmlrpc import (
exception_view as _exception_view, xmlrpc_method as _xmlrpc_method
Expand Down Expand Up @@ -441,3 +443,29 @@ def browse(request, classifiers):
)

return [(r.name, r.version) for r in releases]


@xmlrpc_method(method='system.multicall')
def multicall(request, args):
if any(arg.get('methodName') == 'system.multicall' for arg in args):
raise XMLRPCWrappedError(
ValueError('Cannot use system.multicall inside a multicall')
)

if not all(arg.get('methodName') for arg in args):
raise XMLRPCWrappedError(
ValueError('Method name not provided')
)

responses = []
for arg in args:
name = arg.get('methodName')
subreq = Request.blank('/RPC2', headers={'Content-Type': 'text/xml'})
subreq.method = 'POST'
subreq.body = xmlrpc.client.dumps(
tuple(arg.get('params')),
methodname=name,
).encode()
response = request.invoke_subrequest(subreq, use_tweens=True)
responses.append(xmlrpc.client.loads(response.body))
return responses

0 comments on commit 53fdade

Please sign in to comment.