In part 2, I hooked up the API to CherryPy in a very crude fashion, and this time we’ll look at how we can add handlers for resources in a less clumsy way. I decided to keep handlers on one ’level’ only – that is, /sketch/parrot and /sketch will both be handled by the /sketch handler. This is because I find that the same sub-resource often is present in several places (what about /props/parrot?) and having handlers like this simplifies stuff and makes the magic more readable.
That magic looks like this – it is passed a package, find all modules that has at least one of get/post/put/delete implemented, and stores them in a name->module dict.
def `get`_handlers(package): handlers = {} for `member`_name, `member` in [module for module in inspect.getmembers(package) if inspect.ismodule(module[1])]: if [fn for name, fn in inspect.getmembers(member) if name in ('get', 'post', 'put', 'delete')]: print("Adding handler %s" % `member`_name) handlers[`member`_name] = `member` return handlers
Later, when we get a request, we interpret the first part of the path as resource name (although I mounted it at /api, so it becomed /api/<resource>), and then use that string to get the correct module, check for a handler for the specific method, and call it if it exists.
def requesthandler(handlers, method, resource, *pathargs, **kwargs): """Main dispatch for calls to PyRest; no framework specific code to be present after this point""" if not resource in handlers: return Response('404 Not Found', 'No such resource') if not hasattr(handlers[resource], method): return Response('405 Method Not Allowed', 'Unsupported method for resource') `return`_data = getattr(handlers[resource], method)(*pathargs, **kwargs) `return` Response('200 OK', json.dumps(`return`_data))
Right now, there’s nothing exciting going on in the API, so the routing logic just calls hgapi and assumes everything will be in order:
def get(ref=None): rev = hgapi.Repo('.')[ref] return { 'node': rev.node, 'desc': rev.desc }
So, when we GET /api/changeset/1, the requesthandler will be passed this: ({’changeset’: <module>}, ’get’, ’changeset’, (’1’,)). It will lookup ’changeset’ to get the module, and then retrieve and call ’get’ using getattr and pass in the ’1’. changeset.get() will then call hgapi, stick it into a map, and requesthandler encodes it as json and returns it. Since none of the parts involved actually cares what the arguments are, you can just as well use /api/changeset/tip or /api/changeset/default.
As it looks now, the next part should probably be adding some tests, but since I’m not totally decided on how I want to write my tests, I’ll push ahead with separating the code instead – the current PyRest class and everything that has to do with CherryPy should go into a pyrest.cherrypy package or something similar, the requesthandler and get_handler functions should stay as part of pyRest proper, and the backend package should probably end up in an example package.
Code is as always available at Bitbucket.
0 kommentarer