CherryPy and RESTful web api
Asked Answered
S

5

13

What's the best approach of creating a RESTful web api in CherryPy? I've been looking around for a few days now and nothing seems great. For Django it seems that are lots of tools to do this, but not for CherryPy or I am not aware of them.

Later edit: How should I use Cherrypy to transform a request like /getOrders?account=X&type=Y into something like /orders/account/type ?

Straddle answered 26/4, 2010 at 16:48 Comment(1)
try this pypi.org/project/cherrypyrestKurgan
H
11

I don't know if it's the "best" way, but here's how I do it:

import cherrypy

class RESTResource(object):
   """
   Base class for providing a RESTful interface to a resource.

   To use this class, simply derive a class from it and implement the methods
   you want to support.  The list of possible methods are:
   handle_GET
   handle_PUT
   handle_POST
   handle_DELETE
   """
   @cherrypy.expose
   def default(self, *vpath, **params):
      method = getattr(self, "handle_" + cherrypy.request.method, None)
      if not method:
         methods = [x.replace("handle_", "")
            for x in dir(self) if x.startswith("handle_")]
         cherrypy.response.headers["Allow"] = ",".join(methods)
         raise cherrypy.HTTPError(405, "Method not implemented.")
      return method(*vpath, **params);

class FooResource(RESTResource):
    def handle_GET(self, *vpath, **params):
        retval = "Path Elements:<br/>" + '<br/>'.join(vpath)
        query = ['%s=>%s' % (k,v) for k,v in params.items()]
        retval += "<br/>Query String Elements:<br/>" + \
            '<br/>'.join(query)
        return retval

class Root(object):
    foo = FooResource()

    @cherrypy.expose
    def index(self):
        return "REST example."

cherrypy.quickstart(Root())

You simply derive from the RESTResource class and handle whichever RESTful verbs you desire (GET, PUT, POST, DELETE) with a method of the same name prefixed with handle_. If you do not handle a particular verb (such as POST) the base class will raise a 405 Method Not Implemented error for you.

The path items are passed in vpaths and any query strings are passed in in params. Using the above sample code, if you were to request /foo/bar?woo=hoo, vpath[0] would be bar, and params would be {'woo': 'hoo'}.

Horney answered 14/5, 2010 at 2:18 Comment(0)
D
7

Because HTTP defines these invocation methods, the most direct way to implement REST using CherryPy is to utilize the MethodDispatcher instead of the default dispatcher.

More can be found in CherryPy docs: http://cherrypy.readthedocs.io/en/latest/tutorials.html#tutorial-7-give-us-a-rest

Here is also detailed description on how to send and receive JSON using CherryPy Tools: http://tools.cherrypy.org/wiki/JSON

Danzig answered 2/2, 2012 at 14:26 Comment(1)
Last link is gone now.Jabot
S
2

So you want to transform /getOrders?account=X&type=Y into something like /orders/account/type using Cherrypy.

I would try the approach used in http://cherrypy.readthedocs.org/en/latest/tutorial/REST.html as mentioned by @Tomasz Blachowicz, with some modifications.

Remember that you can handle something like /order/account/type with

@cherrypy.expose
def order(account=None, type=None):
    print account, type

class Root(object):
    pass

root = Root()
root.orders = orders


cherrypy.quickstart(root, '/')

So if you take the example given in http://cherrypy.readthedocs.org/en/latest/tutorial/REST.html, you can modify it to handle that type of URL.

class Orders(object):
    exposed = True
    def __init__(self):
        pass

    def GET(self, account=None, type=None):
        #return the order list for this account type
        return getOrders(account, type)

    def PUT(self, account=None, type=None, orders=None):
        #Set the orders associated with account or something
        setOrders(account, type, orders)


class Root(object):
    pass

root = Root()
root.orders = Orders()

conf = {
    'global': {
        'server.socket_host': '0.0.0.0',
        'server.socket_port': 8000,
    },
    '/': {
        'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
    },
}

cherrypy.quickstart(root, '/', conf)

Why you would want to set orders using that put method I don't know, but it does give an another example of how to do PUT methods. All you have to do is replace the method used by a request with PUT and it will use the PUT() method of Orders and use a regular GET on Orders and it will use the GET() method. Because a POST() method is not defined, POST can't be used with this example. If you try POST or DELETE you will get "405 Method Not Allowed".

I like this approach because it is easy to see what is going on and, I believe, it answers your question.

Segregationist answered 11/3, 2012 at 9:4 Comment(0)
S
1

I assume you've tried partial matches as talked about in the tutorial. I find that while not great, it does get the job done most of the time.

Beyond that, though I haven't tried it, Cherrypy apparently supports Routes (see http://www.cherrypy.org/wiki/PageHandlers), which gives you all kinds of RESTful options.

Sandarac answered 3/5, 2010 at 7:47 Comment(0)
D
1

To answer your second question, you want to define and expose a default method:

class getOrders(Object):
    def default(account, type):
        ...

    default.exposed = True

using this method, getOrders/x/y would map to default(account='x', type='y'). Like someone else said, it's not great, but it gets the job done.

As far as RESTful applications goes, I'm pretty sure the default page handler will work for such an application.

Dungdungan answered 20/5, 2010 at 21:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.