XMLRPC in Django
Greg Abbas

In setting up a blog server, one of my requirements was that I wanted to use a desktop client to write articles, instead of doing it in an HTML form in a web browser. I accomplished that by purchasing Ecto (which I'm using to type this article) and adding a MetaWeblog interface to the server. Here's the first of two articles on how I did it. Today's article talks about the XMLRPC layer, and the second in this series will conclude with the MetaWeblog implementation itself.

MetaWeblog is a network API for posting blog articles, and it's one of the ways that Ecto can talk to a Blog server. There are some other common blog APIs, including Typepad, Moveable Type, Blogger, and Atom, and I could have chosen any of them. As I mentioned in a previous post, I strongly considered Atom, because it seems to be the newest contender and claims to address some of MetaWeblog's shortcomings. And it's RESTful, which is elegant. But when I tested Ecto with the Atom API, it didn't seem to support images. I don't know whether that's Ecto's fault or Atom's fault, but it would be a problem for me in any case. So I decided to be pragmatic and go with MetaWeblog for now.

MetaWeblog is based on XMLRPC, which (like the name suggests) is a protocol for making remote procedure calls over HTTP. So the first order of business was to add XMLRPC support to Django. I'm not the first person to do that; Amit Upadhyay and Georg "hugo" Bauer have done some good work in this area, and have some excellent ideas about how it ought to fit into Django as you can see from their comments on Django ticket #115. Amit's implementation looked similar to what I had in mind, so I started with his code. Here's what I came up with: xmlrpc.py.

The basic idea of XMLRPC is that a client (like Ecto) makes a procedure call (like "publish new post") by POSTing an XML document over HTTP. The web server (running Django, in this case) parses the XML into a method name and an unmarshalled set of arguments. It then locates the method that the client requested, executes it, and marshalls the returned value which is packaged inside an XML document and returned to the client.

How does that fit into the Django world-view? It seems to correspond to one entry (per API) in urls.py, because XMLRPC requests aren't sent to different URLs depending on which method you're calling or what object you're manipulating. But it makes sense for methods in an API (such as metaWeblog.newPost or metaWeblog.getCategories) to map to methods in a Python module (like views do) so we need an "XMLRPC dispatch view" to take care of this mapping. You specify the module containing your API methods by passing the name of the module as an argument to the view. For instance, here's a line from my urls.py:

(r'^metaweblog/', 'ayp.xmlrpc.view', {'module':'ayp.blog.metaweblog'}),

"ayp.xmlrpc.view" is the name of the dispatcher, and when it receives a request, it looks in the "ayp.blog.metaweblog" module for a corresponding method. This makes it slightly different from Amit's version, in a few ways:

  • The API methods are implemented as functions in a module, not methods in a class.
  • If a client requests a method whose name has a period in it (as many XMLRPC method names seem to) then it'll try translating the period into an underscore. This is a convenience feature that lets you call your methods things like "metaWeblog_newPost".
  • I made ayp.xmlrpc provide a callable view instead of a class, so that you don't have to explicitly instantiate a class or explicitly register an instance in your API module.

I did keep his "public" attribute which lets you declare which functions should be web-accessible, and I added a decorator (Python 2.4 style) for setting it. This is a safety feature so that you don't accidentally expose private methods.

Here's an example. Let's say we have an XMLRPC API that contains one method, "arithmetic.add". To implement it, we'd write an arithmetic.py that looks something like

from ayp.xmlrpc import public
@public
def arithmetic_add(a, b):
    return a+b

and then expose it in our urls.py with something like

urlpatterns = patterns('',
    (r'^arithmetic/', 'ayp.xmlrpc.view', {'module':'arithmetic'}),
)

If you put that into a running Django server, you can test it from a Python shell:

>>> from xmlrpclib import ServerProxy
>>> server = ServerProxy("http://localhost:8000/")
>>> print server.add(3,4)
7

Good times! Read "MetaWeblog & Django" to see how we apply this to blogging.