""" callable.py - an experiment with __call__ I need to know a bit more about __call__, and also a bit more about cherrypy (a pythonic web server). I'd like to provide a RESTful interface for objects which is capable of providing resources over the web, as well as in the local machine address space. """ import cherrypy from cherrypy import expose class Callable(object): """Just an experiment, and for my own learning. If an object implements __call__, it is a function (because functions are objects too). This makes things pretty nifty :) For example, let's say we have an object, and want to provide a highly constrained verb space to provide an interface to that object. We can use __call__ to provide an opaque interface, provided we know how to choose that interface. For now, let's just try a naive approach. """ def __call__(self, method='GET'): # should this have args? # yes. #method = cherrypy.request.method # EAFP try: print getattr(self, method)() except AttributeError, e: print "%s not allowed" % method except NotImplementedError, e: print "%s not implemented" % e # next we have some methods, which corelate 1:1 with our verb space. def GET(self): return "I was GET!" def POST(self): return "I was POST!" def DELETE(self): raise NotImplementedError() def HEAD(self): return "I was HEAD!" class WebObject(Callable): """Now we subclass from our Callable experiment above. The idea is basically the same. We'll just override __call__, GET, and POST, to do things more suitable for the web. """ def __call__(self, *args, **kwds): """Same basic implementation as before. The difference is that cherrypy will pass us the remaining paths segments as positional parameters (unnamed), and will pass us the query parameters as keyword parameters (named). We can use python's syntactic sugar to collapse the list of parameters into *args, and collapse the dict parameters (the named ones) into **kwds. (The asterisks are the syntactic sugar that facilitate this feature.) """ method = cherrypy.request.method cherrypy.request.headers['content-type'] = 'text/plain' try: return getattr(self, method)(*args, **kwds) except AttributeError, e: return "%s not allowed" % method except NotImplementedError, e: return "%s not implemented" % e def GET(self, *args, **kwds): reveal = {'locals': locals(), 'args': args, 'kwds': kwds} reveal['type()'] = type(self) return "%s" % reveal def POST(self, *args, **kwds): return "method: POST.\nargs: %s\nkwds: %s" % (args, kwds) class HelloWorld: def __init__(self): """Set up some intial stuff. I'm going to change the state of the object on each request, so that I can tell what is passed to __call__. If state increments on requests, where WebObject is handling the request, we know that "self" for the WebObject instance is actually the instance of HelloWorld. However, the results indicate that "self" is a WebObject, and the locals from HelloWorld are not present. This means that "self" is actually the instance of WebObject. """ self.state = 0 # why doesn't @expose work? # because cherrypy doesn't implement it as a decorator. # this is the same as defining a function for index. index = WebObject() index.exposed = True test = WebObject() test.exposed = True def typical(self, *args, **kwds): self.state += 1 return "this is the typical way of doing things\nstate: %s" % self.state typical.exposed = True if __name__ == '__main__': # quick test of the simple stuff... myfunc = Callable() myfunc() myfunc('POST') myfunc('DELETE') myfunc('HEAD') myfunc('GET') myfunc('TRACE') # fire up the server, go to localhost:8080 and play around. cherrypy.quickstart(HelloWorld())