Ticket #5732: multicall.patch

File multicall.patch, 7.7 KB (added by braudel, 4 years ago)

patch to add support for multicall

  • twisted/web/test/test_xmlrpc.py

     
    1414from twisted.web import xmlrpc
    1515from twisted.web.xmlrpc import (
    1616    XMLRPC, payloadTemplate, addIntrospection, _QueryFactory, Proxy,
    17     withRequest)
     17    withRequest, MultiCall)
    1818from twisted.web import server, static, client, error, http
    1919from twisted.internet import reactor, defer
    2020from twisted.internet.error import ConnectionDone
     
    686686                 'deferFault', 'dict', 'echo', 'fail', 'fault',
    687687                 'pair', 'system.listMethods',
    688688                 'system.methodHelp',
    689                  'system.methodSignature', 'withRequest'])
     689                 'system.methodSignature', 'system.multicall',
     690                 'withRequest'])
    690691
    691692        d = self.proxy().callRemote("system.listMethods")
    692693        d.addCallback(cbMethods)
     
    719720            dl.append(d)
    720721        return defer.DeferredList(dl, fireOnOneErrback=True)
    721722
     723    def test_multicall(self):
     724        '''
     725        test a suscessfull multicall
     726        '''
     727        inputs = range(5)
     728        m = MultiCall(self.proxy())
     729        for x in inputs:
     730            m.echo(x)
    722731
     732        def testResults(iterator):
     733            self.assertEqual(inputs, list(iterator))
     734
     735        return m().addCallback(testResults)
     736
     737
     738    def test_multicall_error(self):
     739        ''' test that a multicall which has an error
     740        (an invalid (not found) method ) raises an
     741        exception when the multicall results
     742        iterator is used
     743        '''
     744        m = MultiCall(self.proxy())
     745        m.echo(1)
     746        m.foo() # method not present on server
     747        m.echo(2)
     748
     749        def testResults(iterator):
     750            ''' using the iterator should raise a Fault
     751            '''
     752            self.assertRaises(xmlrpc.Fault, list, iterator)
     753            self.flushLoggedErrors(failure.DefaultException)
     754
     755        return m().addCallback(testResults)
     756
     757
    723758class XMLRPCClientErrorHandling(unittest.TestCase):
    724759    """
    725760    Test error handling on the xmlrpc client.
  • twisted/web/xmlrpc.py

     
    318318    xmlrpc_methodSignature.signature = [['array', 'string'],
    319319                                        ['string', 'string']]
    320320
     321    @withRequest
     322    def xmlrpc_multicall(self, request, procedureList):
     323        """
     324        Execute several RPC methods in a single XMLRPC request using the
     325        multicall object.
    321326
     327        Example:
     328            On the server side, just load the instrospection so your
     329            server has system.multicall. Then on the client:
     330
     331            from twisted.web.xmlrpc import Proxy
     332            from twisted.web.xmlrpc import MultiCall
     333
     334            proxy = Proxy('url of your server')
     335
     336            multiRPC = Multicall( proxy )
     337            # queue a few calls
     338            multiRPC.system.listMethods()
     339            multiRPC.system.methodHelp('system.listMethods')
     340            multiRPC.system.methodSignature('system.listMethods')
     341
     342            def handleResults(resultsIterator):
     343                for result in resultsIterator:
     344                    print result
     345
     346            multiRPC().addCallback(handleResults)
     347
     348        @param request: the http C{request} object
     349        @type request: L{http.Request}
     350
     351        @param procedureList: A list of dictionaries, each representing an
     352            individual rpc call, containing the C{methodName} and the
     353            C{params}
     354        @type procedureList: list
     355
     356        @return: L{defer.DeferredList} of the deferreds for each procedure
     357            in procedure list
     358        @rtype: L{defer.DeferredList}
     359        """
     360        def callError(failure):
     361            """
     362            errorback to handle individual call errors
     363
     364            Individual errors in a multicall are returned as
     365            dictionaries. See U{http://www.xmlrpc.com/discuss/msgReader$1208}.
     366
     367            @param result: C{failure}
     368            @type result: L{Failure}
     369
     370            @rtype: dict
     371            @return: a dict with keys C{faultCode} and C{faultString}
     372            """
     373            log.err(failure.value)
     374            return {'faultCode':   self.FAILURE,
     375                    'faultString': failure.value}
     376
     377        def prepareCallResponse(result):
     378            """
     379            callback to convert a call C{response} to a list
     380
     381            The xmlrpc multicall spec expects a list wrapping
     382            each call response.
     383            See U{http://www.xmlrpc.com/discuss/msgReader$1208}.
     384
     385            @param result: C{response}
     386            @type result: any python type
     387
     388            @rtype: list
     389            @return: a list with response as element 0 
     390            """
     391            return [result]
     392
     393        def run(procedurePath, params):
     394            """
     395            run an individual procedure from the L{procedureList} and
     396            returns a C{deferred}
     397
     398
     399            @param procedurePath: string naming a procedure
     400            @type procedurePath: str
     401           
     402            @param params: list of arguments to be passed to the procedure
     403            @type params: list
     404
     405            @return: a C{deferred} object with prepareCallResponse and
     406            callError attached.
     407            @rtype: L{defer.Deferred}
     408            """
     409            try:
     410                procedure = self._xmlrpc_parent.lookupProcedure(procedurePath)
     411            except Exception, e:
     412                return defer.fail(str(e)).addErrback(callError)
     413            else:
     414                if getattr(procedure, 'withRequest', False):
     415                    call = defer.maybeDeferred(procedure, request, *params)
     416                else:
     417                    call = defer.maybeDeferred(procedure, *params)
     418               
     419                call.addErrback(callError)
     420                call.addCallback(prepareCallResponse)
     421                return call
     422
     423        def filterResults(results):
     424            return map(lambda x: x[1], results)
     425
     426        results = [
     427            run(procedure['methodName'], procedure['params'])
     428            for procedure in procedureList]
     429
     430        return (defer.DeferredList(results)
     431            .addCallback(filterResults))
     432
     433    xmlrpc_multicall.signature = [['array', 'array']]
     434
     435
     436class MultiCall(xmlrpclib.MultiCall):
     437    """
     438    server -> a object used to boxcar method calls
     439
     440    server should be a twisted xmlrpc Proxy object.
     441
     442    Methods can be added to the MultiCall using normal
     443    method call syntax e.g.:
     444   
     445    proxy = Proxy('http://advogato.org/XMLRPC')
     446
     447    multicall = MultiCall(proxy)
     448    multicall.add(2,3)
     449    multicall.add(5,6)
     450   
     451    To execute the multicall, call the MultiCall object
     452    and attach callbacks, errbacks to the returned
     453    deferred e.g.:
     454   
     455    def printResults(iterator):
     456        for result in iterator:
     457            print result
     458
     459    d = multicall()
     460    d.addCallback(printResults)
     461    """
     462    def __call__(self):
     463        """
     464        execute the multicall
     465        """
     466        marshalled_list = []
     467        for name, args in self.__call_list:
     468            marshalled_list.append({
     469                'methodName': name,
     470                'params': args})
     471
     472        def getIterator(results):
     473            '''
     474            callback to return an xmlrpclib
     475            MultiCallIterator of the results
     476            '''
     477            return xmlrpclib.MultiCallIterator(results)
     478
     479        return self.__server.callRemote(
     480            'system.multicall', marshalled_list
     481        ).addCallback(getIterator)
     482
     483
    322484def addIntrospection(xmlrpc):
    323485    """
    324486    Add Introspection support to an XMLRPC server.