""" 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())