Ticket #5732: multicall.patch

File multicall.patch, 7.7 KB (added by braudel, 2 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.