pyfacebook

14 Nov. 2009

API implementations can be frustrating


The pyfacebook module by Samuel Cormier-Iijima is awesome. I've worked with multiple web API's now and found that their logic is rarely too hard to understand, but if you want some error checking before you query the remote source you need to use real functions.

I have come across many implementations where you're given a tool that can communicate to a URL and pass some arguments, one of which is a function name, and from there the URL, representing the remote function is called and the arguments are sent as POST data. But it's up to the programmer to know all the functions available because they're not in these API implementations. Amazon's current Python implementation of an API for handling mechanical turk is exactly one of these incomplete implementations. The programmer must also be sure they're not attempting to call functions that don't exist. They won't know this, however, until the server tells them so. You usually end up with code that looks like below when you're working with one of these abstract implementations.

# query server for hit count
query_mt('get_hit_count', server_key, secret_key)

The issue, simply, is that the programmer did not have time to do a proper implementation and instead gave a barebones framework. If they had time, they'd have done the right thing. Right?!

Pyfacebook is different


Pyfacebook solves this in a superb way. They use an IDL to describe the interface and then dynamically generate some objects encapsulating the behavior found in the language. The language is then expanded upon to have access to functions that handle the data transport and response processing. This technique is often found in RPC to leverage it's ability to open up system communication to an abstract and easily adapted interface. Basically, the systems agree to some function names having certain types of arguments and the rest is just communicating the values of the query.

If you check the pyfacebook code itself, you'll see the IDL start with the declaration of METHODS (line 116 at the time of this writing). We see a dictionary of dictionary's. The outermost dictionary's represent a namespace, like photos-related functions or admin-related functions. The next layer of dictionary's, inside a namespace, represent the functions offered by that namespace and inside each of those is a list of tuple's representing the argument list.

The tuple's consist of the argument name, the type, and any flags for describing the variable. It's common to see ('pid', int, []) or ('page_ids','list',['optional']). This entire list is iterated upon in __generate_proxies() where the transformation from IDL to actual functions takes place. This function reads the language definition and generates Python code from the list contents, calls eval() on the code and instantiates the objects from the generated code. Dynamic languages for the win!

The actual generated code looks something like what's below. Notice that every function essentially returns their name and a dictionary or arguments that have been processed.

def addLike(self, uid=None, post_id=None):
    """
    Facebook API call. See http://developers.facebook.com/documentation.php?v=1.0&method=stream.addLike
    """
    args = {}
    if uid is not None: args['uid'] = uid
    if post_id is not None: args['post_id'] = post_id
    return self('addLike', args)

def removeLike(self, uid=None, post_id=None):
    """
    Facebook API call. See http://developers.facebook.com/documentation.php?v=1.0&method=stream.removeLike
    """
    args = {}
    if uid is not None: args['uid'] = uid
    if post_id is not None: args['post_id'] = post_id
    return self('removeLike', args)

def remove(self, post_id, uid=None):
    """
    Facebook API call. See http://developers.facebook.com/documentation.php?v=1.0&method=stream.remove
    """
    args = {}
    args['post_id'] = post_id
    if uid is not None: args['uid'] = uid
    return self('remove', args)

def addComment(self, post_id, comment, uid=None):
    """
    Facebook API call. See http://developers.facebook.com/documentation.php?v=1.0&method=stream.addComment
    """
    args = {}
    args['post_id'] = post_id
    args['comment'] = comment
    if uid is not None: args['uid'] = uid
    return self('addComment', args)

Python offers programmers the chance to implement a function (__call__()) which gets called when the object itself is called as a function. If you look at the definition of the Proxy class you see an __init__() function and the __call__() function. For those who don't know Python, __init__ is the closest thing you have to a constructor. The proxy is instantiated such that it points to a _client object and this object is an instance of the Facebook class. __call__() calls _clent as a function with some arguments, one is the remote method we're calling. The Facebook class is defined lower than the IDL and lower than the proxy objects. It consists mainly of some communication and session oriented functions. When the Facebook class is called as a function (first the proxy, then the facebook class) we see Facebook's __call__() turn the arguments into post variables and query the API's URL for an answer. The facebook URL itself is http://api.facebook.com/restserver.php and the method we're calling is one of the POST arguments ('method', specifically).

The IDL functions default to basically being a list of arguments with certain types mapped to certain functions, but sometimes more code is needed than simply sending some data to a URL. Pyfacebook stores session data that comes back from some authentication based calls, as is done in AuthProxy. Another example is how PhotosProxy has an upload function defined which handles encoding the binary data for transmission. The proxy objects are first generated dynamically according to the IDL's first layer of dictionary keys (eg. 'photos'). Then the proxy object is created with the name PhotosProxy and added to the global namespace. If there are overrides you see a redeclaration of the object and it inherits from (itself?!) an object of the same name. I really dig this logic as it shows how you can use an IDL to make assumptions about typical behavior and then override the atypical cases with ease.

The Facebook class itself is largely of just an auth implementation which includes session handling for the user.

Neat stuff for sure!