All pastes #611424 Raw Edit

Untitled

public text v1 · immutable
·published 2007-07-09 22:21 UTC
rendered paste body
""" 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())