I need an asynchronous implementation of XML-RPC for a front-end GUI where some of the back-end's operations are slow. Here's what I came up with: a specialized xmlrpclib.Transport that returns an instance of an object called XMLRPCDeferred, and registers
The example below is a modified version of the helloworld2.py demo from the tutorial. You'll need to find an XML-RPC server and method that takes an appreciable time to run; the back-end I'm working with isn't publicly available. Anyone have suggestions on the code? --amk #!/usr/bin/env python # Example: async xml-rpc with pygtk import pygtk pygtk.require('2.0') import gtk, gobject import xmlrpclib class XMLRPCDeferred: """Object representing the delayed result of an XML-RPC request. .is_ready: bool True when the result is received; False before then. .value : any Once is_ready=True, this attribute contains the result of the request. If this value is an instance of the xmlrpclib.Fault class, then some exception occurred during the request's processing. """ def __init__ (self, transport, http): self.transport = transport self.http = http self.value = None self.is_ready = False sock = self.http._conn.sock self.src_id = gobject.io_add_watch(sock, gobject.IO_IN | gobject.IO_HUP, self.handle_io) def handle_io (self, source, condition): # Triggered when there's input available on the socket. # The assumption is that all the input will be available # relatively quickly. self.read() # Returning false prevents this callback from being triggered # again. We also remove the monitoring of this file # descriptor. gobject.source_remove(self.src_id) return False def read (self): errcode, errmsg, headers = self.http.getreply() if errcode != 200: raise ProtocolError( host + handler, errcode, errmsg, headers ) try: result = xmlrpclib.Transport._parse_response(self.transport, self.http.getfile(), None) except xmlrpclib.Fault, exc: result = exc self.value = result self.is_ready = True def __len__ (self): # XXX egregious hack!!! # The code in xmlrpclib.ServerProxy calls len() on the object # returned by the transport, and if it's of length 1 returns # the contained object. Therefore, this __len__ method # returns a completely fake length of 2. return 2 class GtkTransport (xmlrpclib.Transport): def request(self, host, handler, request_body, verbose=0): # issue XML-RPC request h = self.make_connection(host) if verbose: h.set_debuglevel(1) self.send_request(h, handler, request_body) self.send_host(h, host) self.send_user_agent(h) self.send_content(h, request_body) self.verbose = verbose return XMLRPCDeferred(self, h) class HelloWorld2: # Our new improved callback. The data passed to this method # is printed to stdout. def callback(self, widget, data): print "Hello again - %s was pressed" % data # Fire off an XML-RPC request for a model load; this # is pretty time-consuming. t = GtkTransport() s = xmlrpclib.ServerProxy('http://xmlrpc.example.com', t) result = s.long_slow_method() # You get back an XMLRPCDeferred object; is_ready will be True # once the results are back. import time while not result.is_ready: print 'waiting', time.time() gtk.main_iteration(block=False) time.sleep(1) # result.value now holds the result. print 'result:', result.value # another callback def delete_event(self, widget, event, data=None): gtk.main_quit() return False def __init__(self): # Create a new window self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) # This is a new call, which just sets the title of our # new window to "Hello Buttons!" self.window.set_title("Hello Buttons!") # Here we just set a handler for delete_event that immediately # exits GTK. self.window.connect("delete_event", self.delete_event) # Sets the border width of the window. self.window.set_border_width(10) # We create a box to pack widgets into. This is described in detail # in the "packing" section. The box is not really visible, it # is just used as a tool to arrange widgets. self.box1 = gtk.HBox(False, 0) # Put the box into the main window. self.window.add(self.box1) # Creates a new button with the label "Button 1". self.button1 = gtk.Button("Button 1") # Now when the button is clicked, we call the "callback" method # with a pointer to "button 1" as its argument self.button1.connect("clicked", self.callback, "button 1") # Instead of add(), we pack this button into the invisible # box, which has been packed into the window. self.box1.pack_start(self.button1, True, True, 0) # Always remember this step, this tells GTK that our preparation for # this button is complete, and it can now be displayed. self.button1.show() # Do these same steps again to create a second button self.button2 = gtk.Button("Button 2") # Call the same callback method with a different argument, # passing a pointer to "button 2" instead. self.button2.connect("clicked", self.callback, "button 2") self.box1.pack_start(self.button2, True, True, 0) # The order in which we show the buttons is not really important, but I # recommend showing the window last, so it all pops up at once. self.button2.show() self.box1.show() self.window.show() def main(): gtk.main() if __name__ == "__main__": hello = HelloWorld2() main() _______________________________________________ pygtk mailing list pygtk@daa.com.au http://www.daa.com.au/mailman/listinfo/pygtk Read the PyGTK FAQ: http://www.async.com.br/faq/pygtk/