Ticket #5732: multicall2.2.patch
| File multicall2.2.patch, 18.7 KB (added by braudel, 8 months ago) |
|---|
-
twisted/web/xmlrpc.py
319 319 ['string', 'string']] 320 320 321 321 322 @withRequest 323 def xmlrpc_multicall(self, request, procedureList): 324 """ 325 Execute several RPC methods in a single XMLRPC request using the 326 multicall object. 327 328 Example: 329 On the server side, just load the instrospection so your 330 server has system.multicall. Then on the client: 331 332 from twisted.web.xmlrpc import Proxy 333 from twisted.web.xmlrpc import MultiCall 334 335 proxy = Proxy('url of your server') 336 337 multiRPC = Multicall( proxy ) 338 # queue a few calls 339 multiRPC.system.listMethods() 340 multiRPC.system.methodHelp('system.listMethods') 341 multiRPC.system.methodSignature('system.listMethods') 342 343 def handleResults(results): 344 for success, result in results: 345 print result 346 347 multiRPC().addCallback(handleResults) 348 349 @param request: the http C{request} object, obtained 350 via the @withRequest decorator 351 @type request: L{http.Request} 352 353 @param procedureList: A list of dictionaries, each representing an 354 individual rpc call, containing the C{methodName} and the 355 C{params} 356 @type procedureList: list 357 358 @return: L{defer.DeferredList} of the deferreds for each procedure 359 in procedure list 360 @rtype: L{defer.DeferredList} 361 """ 362 def callError(error): 363 """ 364 errorback to handle individual call errors 365 366 Individual errors in a multicall are returned as 367 dictionaries. See U{http://www.xmlrpc.com/discuss/msgReader$1208}. 368 369 @param result: C{failure} 370 @type result: L{Failure} 371 372 @rtype: dict 373 @return: a dict with keys C{faultCode} and C{faultString} 374 """ 375 log.err(error.value) 376 return {'faultCode': self.FAILURE, 377 'faultString': (error.value.faultString 378 if isinstance(error.value, Fault) 379 else getattr(error.value, 'message', ''))} 380 381 def prepareCallResponse(result): 382 """ 383 callback to convert a call C{response} to a list 384 385 The xmlrpc multicall spec expects a list wrapping 386 each call response. 387 See U{http://www.xmlrpc.com/discuss/msgReader$1208}. 388 389 @param result: C{response} 390 @type result: any python type 391 392 @rtype: list 393 @return: a list with response as element 0 394 """ 395 return [result] 396 397 def run(procedurePath, params): 398 """ 399 run an individual procedure from the L{procedureList} and 400 returns a C{deferred} 401 402 @param procedurePath: string naming a procedure 403 @type procedurePath: str 404 405 @param params: list of arguments to be passed to the procedure 406 @type params: list 407 408 @return: a C{deferred} object with prepareCallResponse and 409 callError attached. 410 @rtype: L{defer.Deferred} 411 """ 412 try: 413 procedure = self._xmlrpc_parent.lookupProcedure(procedurePath) 414 except NoSuchFunction, e: 415 return defer.fail(e).addErrback(callError) 416 else: 417 if getattr(procedure, 'withRequest', False): 418 call = defer.maybeDeferred(procedure, request, *params) 419 else: 420 call = defer.maybeDeferred(procedure, *params) 421 422 call.addCallback(prepareCallResponse) 423 call.addErrback(callError) 424 return call 425 426 results = [ 427 run(procedure['methodName'], procedure['params']) 428 for procedure in procedureList] 429 430 return (defer.DeferredList(results) 431 .addCallback(lambda results: [r[1] for r in results])) 432 433 xmlrpc_multicall.signature = [['array', 'array']] 434 435 436 437 class _DeferredMultiCallProcedure(object): 438 """ 439 A helper object to store calls made on the 440 MultiCall object for batch execution 441 """ 442 def __init__(self, call_list, name): 443 self.__call_list = call_list 444 self.__name = name 445 446 447 def __getattr__(self, name): 448 """ 449 magic to emulate x.y.name lookups for 450 a remote procedure 451 """ 452 return _DeferredMultiCallProcedure( 453 self.__call_list, 454 "%s.%s" % (self.__name, name) 455 ) 456 457 458 def __call__(self, *args): 459 """ 460 "calling" an RPC on the multicall queues a deferred, 461 the procedure name, and its calling args. 462 463 @return: a L{defer.Deferred} that will be fired when the 464 results for this RPC are processed 465 @rtype: L{defer.Deferred} 466 """ 467 d = defer.Deferred() 468 self.__call_list.append((d, self.__name, args)) 469 return d 470 471 472 473 class MultiCall(xmlrpclib.MultiCall): 474 """ 475 @param server: a object used to boxcar method calls 476 @type server should be a twisted xmlrpc L{Proxy} object. 477 478 @return: a L{defer.DeferredList} of all the deferreds for 479 each queued rpc call 480 @rtype: L{defer.DeferredList} 481 482 Methods can be added to the MultiCall using normal 483 method call syntax e.g.: 484 485 proxy = Proxy('http://advogato.org/XMLRPC') 486 487 multicall = MultiCall(proxy) 488 d1 = multicall.add(2,3) 489 d2 = multicall.add(5,6) 490 491 or 492 493 d3 = multicall.callRemote('add', 2, 3) 494 495 To execute the multicall, call the MultiCall object 496 and attach callbacks, errbacks to the returned 497 deferred e.g.: 498 499 def printResults(results): 500 for result in results: 501 print result[1] 502 503 d = multicall() 504 d.addCallback(printResults) 505 """ 506 def __getattr__(self, name): 507 """ get a ref to a helper object to emulate 508 RPC 'attributes lookup' 509 """ 510 return _DeferredMultiCallProcedure(self.__call_list, name) 511 512 513 def callRemote(self, method, *args): 514 """ 515 queue a call for c{method} on this multicall object 516 with given arguments. 517 518 @return: a L{defer.Deferred} that will fire with the method response, 519 or a failure if the method failed. 520 """ 521 return getattr(self, method)(*args) 522 523 524 def __call__(self): 525 """ 526 execute the multicall, processing the deferreds for each 527 procedure once the results are ready 528 529 @return: a L{defer.DeferredList} that will fire all the queued deferreds 530 """ 531 marshalled_list = [] 532 deferreds = [] 533 for deferred, name, args in self.__call_list: 534 marshalled_list.append({ 535 'methodName': name, 536 'params': args}) 537 deferreds.append(deferred) 538 539 def processResults(results, deferreds): 540 """ 541 callback to return an xmlrpclib 542 MultiCallIterator of the results 543 """ 544 for d, result in zip(deferreds, results): 545 if isinstance(result, dict): 546 d.errback(Fault(result['faultCode'], 547 result['faultString'])) 548 549 elif isinstance(result, list): 550 d.callback(result[0]) 551 552 else: 553 raise ValueError( 554 "unexpected type in multicall result") 555 556 self.__server.callRemote( 557 'system.multicall', marshalled_list 558 ).addCallback(processResults, deferreds) 559 560 return defer.DeferredList(deferreds) 561 562 322 563 def addIntrospection(xmlrpc): 323 564 """ 324 565 Add Introspection support to an XMLRPC server. -
twisted/web/test/test_xmlrpc.py
14 14 from twisted.web import xmlrpc 15 15 from twisted.web.xmlrpc import ( 16 16 XMLRPC, payloadTemplate, addIntrospection, _QueryFactory, Proxy, 17 withRequest )17 withRequest, MultiCall) 18 18 from twisted.web import server, static, client, error, http 19 19 from twisted.internet import reactor, defer 20 20 from twisted.internet.error import ConnectionDone … … 28 28 else: 29 29 sslSkip = None 30 30 31 32 31 class AsyncXMLRPCTests(unittest.TestCase): 33 32 """ 34 33 Tests for L{XMLRPC}'s support of Deferreds. … … 667 666 return d 668 667 669 668 669 670 670 class XMLRPCTestIntrospection(XMLRPCTestCase): 671 671 672 672 def setUp(self): … … 686 686 'deferFault', 'dict', 'echo', 'fail', 'fault', 687 687 'pair', 'system.listMethods', 688 688 'system.methodHelp', 689 'system.methodSignature', 'withRequest']) 689 'system.methodSignature', 'system.multicall', 690 'withRequest']) 690 691 691 692 d = self.proxy().callRemote("system.listMethods") 692 693 d.addCallback(cbMethods) … … 720 721 return defer.DeferredList(dl, fireOnOneErrback=True) 721 722 722 723 724 725 class FakeProxy(object): 726 """ 727 Fake twisted XMLRPC Proxy client to run tests without using 728 the network 729 """ 730 def __init__(self, resource): 731 self.resource = resource 732 733 734 def callRemote(self, methodName, *args): 735 """ 736 emulate twisted.web.xmlrpc.Proxy.callRemote 737 """ 738 # build request 739 request = DummyRequest(['']) 740 request.method = 'POST' 741 request.content = StringIO( 742 payloadTemplate % (methodName, xmlrpclib.dumps(args))) 743 744 def returnResponse( requestResponse ): 745 results = xmlrpclib.loads(requestResponse)[0] 746 if len(results) == 1: 747 results = results[0] 748 return results 749 750 # look mom no network! 751 self.resource.render(request) 752 753 return (defer.succeed("".join(request.written)) 754 .addCallback(returnResponse)) 755 756 757 758 class XMLRPCTestMultiCall(unittest.TestCase): 759 """ 760 Tests for xmlrpc multicalls 761 """ 762 def setUp(self): 763 self.resource = Test() 764 addIntrospection(self.resource) 765 self.proxy = FakeProxy(self.resource) 766 767 768 def test_multicall(self): 769 """ 770 test a suscessfull multicall 771 """ 772 inputs = range(5) 773 m = MultiCall(self.proxy) 774 for x in inputs: 775 m.echo(x) 776 777 def testResults(results): 778 self.assertEqual(inputs, [x[1] for x in results]) 779 780 resultsDeferred = m().addCallback(testResults) 781 self.assertTrue(resultsDeferred.called) 782 783 784 def test_multicall_callRemote(self): 785 """ 786 test a suscessfull multicall using 787 multicall.callRemote instead of attribute lookups 788 """ 789 inputs = range(5) 790 m = MultiCall(self.proxy) 791 for x in inputs: 792 m.callRemote('echo', x) 793 794 def testResults(results): 795 self.assertEqual(inputs, [x[1] for x in results]) 796 797 resultsDeferred = m().addCallback(testResults) 798 self.assertTrue(resultsDeferred.called) 799 800 801 def test_multicall_with_callbacks(self): 802 """ 803 test correct execution of callbacks added to the 804 multicall's returned deferreds for each individual queued 805 call 806 """ 807 inputs = range(5) 808 m = MultiCall(self.proxy) 809 for x in inputs: 810 d = m.echo(x) 811 d.addCallback( lambda x : x*x ) 812 813 def testResults(results): 814 self.assertEqual([ x*x for x in inputs], [x[1] for x in results]) 815 816 resultsDeferred = m().addCallback(testResults) 817 self.assertTrue(resultsDeferred.called) 818 819 820 def test_multicall_errorback(self): 821 """ 822 test that an error (an invalid - not found - method) 823 does not propagate if properly handled in the errorback 824 of an individual deferred 825 """ 826 def trapFoo(error): 827 error.trap(xmlrpclib.Fault) 828 self.assertEqual(error.value.faultString, 829 'procedure foo not found', 830 'check we have a failure message' 831 ) 832 self.flushLoggedErrors(xmlrpc.NoSuchFunction) 833 834 835 m = MultiCall(self.proxy) 836 m.echo(1) 837 # method not present on server 838 m.foo().addErrback(trapFoo) 839 m.echo(2) 840 841 def handleErrors(error): 842 error.trap(xmlrpclib.Fault) 843 self.assertEqual(error.value.faultString, 844 'xmlrpc_echo() takes exactly 2 arguments (4 given)') 845 self.flushLoggedErrors(TypeError) 846 847 m.echo(1,2,3).addErrback(handleErrors) 848 849 def testResults(results): 850 """ 851 the errorback should have trapped the error 852 """ 853 self.assertEqual(results[1], (True, None), 854 'failure trapped in errorback does not propagate to deferredList results') 855 856 resultsDeferred = m().addCallback(testResults) 857 self.assertTrue(resultsDeferred.called) 858 859 860 def test_multicall_withRequest(self): 861 """ 862 Test that methods decorated with @withRequest are handled correctly 863 """ 864 m = MultiCall(self.proxy) 865 m.echo(1) 866 # method decorated with withRequest 867 msg = 'hoho' 868 m.withRequest(msg) 869 m.echo(2) 870 871 def testResults(results): 872 """ 873 test that a withRequest decorated method was properly handled 874 """ 875 self.assertEqual(results[1][1], 876 'POST %s' % msg, 'check withRequest decorated result') 877 878 resultsDeferred = m().addCallback(testResults) 879 self.assertTrue(resultsDeferred.called) 880 881 882 def test_multicall_with_xmlrpclib(self): 883 """ 884 check that the sever's response is also compatible with xmlrpclib 885 MultiCall client 886 """ 887 class PatchedXmlrpclibProxy(object): 888 """ 889 A proxy that more closely resembles xmlrpclib.ServerProxy 890 """ 891 def __init__(self, resource): 892 self.resource = resource 893 894 def __request(self, methodName, params): 895 """ 896 Patched xmlrpclib.ServerProxy.__request to emulate 897 RPC call without using the network 898 """ 899 request = DummyRequest(['']) 900 request.method = 'POST' 901 request.content = StringIO( 902 payloadTemplate % (methodName, xmlrpclib.dumps(params))) 903 904 self.resource.render(request) 905 response = xmlrpclib.loads("".join(request.written))[0] 906 if len(response) == 1: 907 response = response[0] 908 return response 909 910 def __getattr__(self, name): 911 """ 912 magic method dispatcher 913 """ 914 return xmlrpclib._Method(self.__request, name) 915 916 inputs = range(5) 917 m = xmlrpclib.MultiCall( 918 PatchedXmlrpclibProxy(self.resource)) 919 for x in inputs: 920 m.echo(x) 921 922 self.assertEqual( 923 inputs, 924 list(m()), 925 'xmlrpclib multicall can talk to the twisted multicall') 926 927 928 723 929 class XMLRPCClientErrorHandling(unittest.TestCase): 724 930 """ 725 931 Test error handling on the xmlrpc client. -
doc/web/howto/xmlrpc.xhtml
234 234 no <code class="python">help</code> attribute is specified, the 235 235 method's documentation string is used instead.</p> 236 236 237 238 237 239 <h2>SOAP Support</h2> 238 240 239 241 <p>From the point of view of a Twisted developer, there is little difference … … 295 297 [8, 15] 296 298 </pre> 297 299 300 <h3>Using multicall Objects</h3> 301 <p>Another informal pattern commonly used along with xmlrpc 302 introspection is the use of a multicall object to boxcar several 303 rpc calls in a single http request. 304 305 An instance of a <code>MultiCall</code> object 306 is created on the client side and several RPC calls are queued by 307 calling them on this <code>MultiCall</code> instance. Every RPC called on the MultiCall returns a <code>deferred</code> that you can use to attach callbacks to each queued rpc response. 308 309 Executing the multicall instance itself triggers a request to 310 <code>system.multicall</code> with a list of dictionaries representing each of the procedures to call, returning a <code>DeferredList</code> of the deferreds corresponding to each individual call. 311 312 Let's see an example to talk to the server above using a MultiCall:</p> 313 314 <pre class="python"> 315 from twisted.web.xmlrpc import Proxy, MultiCall, NoSuchFunction 316 from twisted.internet import reactor 317 318 def printResults(results): 319 for result in results: 320 print result[1] 321 reactor.stop() 322 323 proxy = Proxy('http://127.0.0.1:7080') 324 325 multiRPC = MultiCall(proxy) 326 327 # queue a few echo calls, with a callback to square the results 328 for x in range(10): 329 multiRPC.echo(x).addCallback(lamdba x: x*x) 330 331 # you can of course use the <code>callRemote</code> method instead 332 # for a better twisted-like feeling 333 for x in range(10,20): 334 multiRPC.callRemote('echo', x).addCallback(lamdba x: x*x) 335 336 # now queue a few more echo calls with a callback that will 337 # indicate the echoed letter and their index in the original string 338 for index, x in enumerate('hello'): 339 multiRPC.echo(x).addCallback(lambda result, index: '%s found at %d' % (result, index)) 340 341 # individual deferreds ideally should also handle errors 342 def handleErrors(error): 343 error.trap(xmlrpclib.Fault) 344 print "yay! The server said %s" % error.value.faultString 345 multiRPC.foo().addErrback(handleErrors) 346 347 # finally execute the multiRPC instance, to send the request, 348 # then handle the returned DeferredList 349 # results with the printResults callback 350 multiRPC().addCallback(printResults) 351 reactor.run() 352 </pre> 353 354 The <code>system.multicall</code> method is also 100% compatible with the xmlrpclib MultiCall, so you can also use xmlrpclib as the client: 355 356 <pre class="python"> 357 from xmlrpclib import ServerProxy, Multicall 358 359 proxy = ServerProxy('http://127.0.0.1:7080') 360 361 multiRPC = MultiCall(proxy) 362 for x in range(10): 363 multiRPC.echo(x) 364 365 for result in multiRPC(): 366 print result * result 367 </pre> 368 369 298 370 <h2>Serving SOAP and XML-RPC simultaneously</h2> 299 371 300 372 <p><code class="API">twisted.web.xmlrpc.XMLRPC</code> and <code
