Recently I have been contemplating client-server applications, how these can eventually be tied into a web app and how best to write all of this in Python. This post will outline the usage of one of the more awesome XMLRPC libraries and also discuss why it’s better than what else is out there…

So, the first place that good ol’ Google too me was the XMLRPC library for Python, however – may I recommend to steer clear of this library as it suffers from the following most immediate problems:

  1. Complex python objects cannot be passed easily, not without writing your own picking
  2. It presents errors when handling types it can;t marshal – things such as lists and dictionaries, which, out of pure interest are possibly some of the more useful elements in python
  3. No traceback for errors – this is a biggie, debugging an RPC client/server app is a pain in a** when you can;t clearly identify which part of the server code is failing since all your errors are coming from the XMLRPC library.

So, enough about that. If you are interested in torturing yourself with XMLRPC, you can find some really nice tutorials from IBM here on building a simple client / server.

Having given up on XMLRPC, I swiftly moved to the rather wonderful PYRO library. This library has great documentation, a clear syntax and some very handy features. Particularly, I would really enjoy:

  1. When you pass objects from client to server, they are full-blown python objects that retain all properties, not some poxy proxy
  2. It can handle all those un-marshalable types, so passing a really complex custom class down the wire isn’t a problem
  3. Full blown exception handling – you can trap the traceback of the error to your server app with a really nice error handler

So, without further ado – here’s the simplest way to use the PYRO library (this is pretty much a copy of the main example – I’ve just expanded with some additional notes underneath to save everyone some frustration):

The server:

class aService(Pyro.core.ObjBase):
    """
        This is our server application
    """

    def __init__(self):
        #You can do what you like in here to inittialise your server
        # However, it's worth remembering to rais PYRO's own
        # init() method!
       
        Pyro.core.ObjBase.__init__(self)   

    def whatsMyName(self, name):
        # This is a service we are going to make public
        return "Your name is  %s, hi %s!" % (name, name)
       
def startServer(self):
        """
            We'll start the server with this function
        """

   
        # Get the server ready
        Pyro.core.initServer()
       
        # Create a daemon object - you can set host and port here too
        daemon=Pyro.core.Daemon()
       
        # This is the URI of the service once it launches
        uri=daemon.connect(aService(),"aservice")
       
        print "Server URI is:", uri
       
        # Start the server...
        daemon.requestLoop()
       
def main():                    
    startServer()

if __name__ == "__main__":
    main()

As you can see from the code above, it’s very similar to the XMLRPC implementation of a service – essentially, all you need to do is create a class that represents your server, add some methods that do fancy things and return some kind of object. Unlike XMLRPC, PYRO will not crash if your method returns None.

Once you have created the server, the startServer() function is only used to kick off the daemon serving loop, an interesting thing to note here is the URI that the daemon spits out, because it will look horrendous and changes each time the server boots up. Now this might seem like quite silly behaviour, but it only occurs because there are different ways of accessing PYRO objects from a client, and the difference in the access URI’s left me with quite a bit of confusion for a while. But all will become clear.

Part 2 – The client:

Create a new client.py file and stick this code into it:

import Pyro.core

proxy = Pyro.core.getProxyForURI("PYROLOC://localhost:7766/jobserver")

print "Calling remote function whatsMyName()..."
return_value = ""

# This is really handy for debugging your server code:
try:
    return_value = proxy.whatsMyName('Jively')
    print return_value
except Exception,x:
    print ''.join(Pyro.util.getPyroTraceback(x))

Simples! So, what’s happening in this piece of code?

After importing the library, we create a proxy object – you will never call an object directly on the server – all calls are handled through a proxy. In pythonic terms, this is essentially the same as creating a local object based on the aService() class with all calls being called remotely (thanks to inheriting the server class from the Pyro ObjBase class).

You then treat the methods of the server class as if they were local – in this case we call whatsMyName() and it will return the string “Your name is Jively, Hi Jively!” and print this out.

What is important to note and to include is the structure of the try/except exception handling – this wonderful bit of code will make it super simple to debug your server app.

If you’ve worked with Pyro or think there’s any really useful resources out there – let me know in the comments!

Resources:

  1. Pyro3
  2. IBM Developerworks XMLRPC with Python
  3. Why I ditched XMLRPC in favor of Pyro