I'm making a RESTful WebService using CherryPy 3 but I encounter a problem : I want to be able to answer requests like : /customers/1/products/386 meaning I want all the product with ID 386 of the client with ID 1.
So I try to make it with the CherryPy's MethodDispatcher like this :
class UserController(object):
exposed = True
def __init__(self):
self.product = ProductController()
@log_io
def GET(self, *args):
return "GET Users :" + str(args)
class ProductController(object):
exposed = True
@log_io
def GET(self, *args):
return "GET Product :" + str(args)
But when I request /customers/1/products/386, instead of redirecting me to ProductController.GET with the right parameters, it redirects me to UserController.GET with the parameters 1, "products", 386.
To be redirected to ProductController.GET I have to query /customers/products/386 which is incorrect because I miss the user ID parameter.
I've seen on this presentation : RESTful Web Applications with CherryPy that the path style I want to use seems to be a good choice. But is there an easy way to implement it with Cherry Py ?
I've heard about the _cp_dispatch method of CherryPy 3 but I don't get exactly what it is and how to use it. Does it replace the MethodDispatcher ?
CherryPy uses a tree-based mapper which does not accommodate well with segments that have no physical reality as a Python object, here your /1/ segment.
With that said, CherryPy does provide functionalities to reach your goal.
Let's focus on the last two.
_cp_dispatch is a special method you declare in any of your controller to massage the remaining segments before CherryPy gets to process them. This offers you the capacity to remove, add or otherwise handle any segment you wish and, even, entirely change the remaining parts.
import cherrypy
class Band(object):
def __init__(self):
self.albums = Album()
def _cp_dispatch(self, vpath):
if len(vpath) == 1:
cherrypy.request.params['name'] = vpath.pop()
return self
if len(vpath) == 3:
cherrypy.request.params['artist'] = vpath.pop(0) # /band name/
vpath.pop(0) # /albums/
cherrypy.request.params['title'] = vpath.pop(0) # /album title/
return self.albums
return vpath
@cherrypy.expose
def index(self, name):
return 'About %s...' % name
class Album(object):
@cherrypy.expose
def index(self, artist, title):
return 'About %s by %s...' % (title, artist)
if __name__ == '__main__':
cherrypy.quickstart(Band())
cherrypy.popargs is more straightforward as it gives a name to any segment that CherryPy wouldn't be able to interpret otherwise. This makes the matching of segments with page handler signatures easier and help CherryPy understand the structure of your URL.
import cherrypy
@cherrypy.popargs('name')
class Band(object):
def __init__(self):
self.albums = Album()
@cherrypy.expose
def index(self, name):
return 'About %s...' % name
@cherrypy.popargs('title')
class Album(object):
@cherrypy.expose
def index(self, name, title):
return 'About %s by %s...' % (title, name)
if __name__ == '__main__':
cherrypy.quickstart(Band())
In both cases go to http://whatevertomakesohappy.com:8080/nirvana/ and then http://whatevertomakesohappy.com:8080/nirvana/albums/nevermind/
Both are powerful but which one you want to use is up to you. For simple URLs, popargs is likely to be much easier in my book. Obviously both can be used concurrently.