« A radical act of obedienceDejavu 1.4.0 (Python ORM) release »

Making a custom CherryPy Request class for Routes

02/26/06

Permalink 03:58:33 am, by fumanchu Email , 552 words   English (US)
Categories: CherryPy

Making a custom CherryPy Request class for Routes

While at PyCon in Dallas this weekend, I got a chance to hear David Creemer talk about how he's using CherryPy (among lots of other tools) to deliver a site that's getting 250,000 hits a day before it's even been officially launched. He mentioned during the "lightning" (5 minute) talk that, of his entire toolkit, CherryPy and SQLObject were two of the tools that "mostly worked, except..." I spoke with him after the talk about his concerns, and the big CherryPy issue was dispatching: he prefers a Routes-style dispatch mechanism, which makes changes to the design easier.

He had previously posted his cherrypy+routes script, which I read but hadn't done anything about. I mentioned to him yesterday that it might be better, when overriding the dispatch mechanism, to do so in a custom Request class, rather than in a single exposed default method on the cherrypy tree. Here's a first crack at what that would look like; I haven't tested it but it's more to get the idea of custom Request classes out there than to be a working patch ;)

import urllib
import cherrypy
from cherrypy import _cphttptools
import routes


mapper = routes.Mapper()
controllers = {}

def redirect( url ):
    raise cherrypy.HTTPRedirect( url )

def mapConnect( name, route, controller, **kwargs ):
    controllers[ name ] = controller
    mapper.connect( name, route, controller=name, **kwargs )

def mapFinalize():
    mapper.create_regs( controllers.keys() )

def URL( name, query = None, doseq = None, **kwargs ):
    uri = routes.url_for( name, **kwargs )

    if not uri:
        return "/UNKNOWN-%s" % name

    if query:
        uri += '?' + urllib.urlencode(query, doseq)

    return uri


class RoutesRequest(_cphttptools.Request):

    def main(self, path=None):
        """Obtain and set cherrypy.response.body from a page handler."""
        if path is None:
            path = self.object_path

        page_handler = self.mapPathToObject(path)

        virtual_path = path.split("/")
        # Decode any leftover %2F in the virtual_path atoms.
        virtual_path = [x.replace("%2F", "/") for x in virtual_path if x]

        kwargs = self.params.copy()
        kwargs.update( cherrypy.request.mapper_dict )

        try:
            body = page_handler(*virtual_path, **kwargs)
        except Exception, x:
            x.args = x.args + (page_handler,)
            raise
        cherrypy.response.body = body

    def mapPathToObject(self, objectpath):
        """For path, return the corresponding exposed callable (or raise NotFound).

        path should be a "relative" URL path, like "/app/a/b/c". Leading and
        trailing slashes are ignored.
        """

        # tell routes to use the cherrypy threadlocal object
        config = routes.request_config()
        if hasattr(config, 'using_request_local'):
            config.request_local = lambda: self
            config = routes.request_config()

        # hook up the routes variables for this request
        config.mapper = mapper
        config.host = self.headerMap['Host']
        config.protocol = self.scheme
        config.redirect = redirect
        config.mapper_dict = mapper.match( objectpath )

        if config.mapper_dict:
            c = config.mapper_dict.pop( 'controller', None )
            if c:
                controller = controllers[c]

                # we have a controller, now emulate cherrypy's index/default/callable semantics:
                action = config.mapper_dict.pop( 'action', 'index' )

                meth = getattr( controller, action, None )
                if not meth:
                    meth = getattr( controller, 'default', None )

                if not meth and callable( controller ) and action == 'index' :
                    meth = controller

                if meth and getattr( meth, 'exposed', False ):
                    return meth

        raise cherrypy.NotFound( objectpath )


# 'authui' is a module with login/logout functions

mapConnect( name = 'home', route = '', controller = home )
mapConnect( name = 'auth', route = 'auth/:action', controller = authui,
            requirements = dict( action='(login|logout)' ) )
mapFinalize()

cherrypy.server.request_class = RoutesRequest
cherrypy.server.start()

Look, Ma, no root!

2 comments

Comment from: TaylorBoon [Member] Email

This is a good improvement on David Creamer's original code. One change needs to be made to make it work past the first use. The line in the mapPathToObject method:


config.request_local = lambda: self


should be changed to:


config.request_local = lambda: cherrypy.request


Otherwise, the second request (works the first time) will try to access an object with no mapper_dict attribute and an AttributeError is raised.

Still and all, this is excellent!

03/05/06 @ 08:53
Comment from: TaylorBoon [Member] Email

And I can't believe I mispelled David's last name in my last post - my apologies to David; that should be Creemer.

03/05/06 @ 08:55

Leave a comment


Your email address will not be revealed on this site.

Your URL will be displayed.

Please enter the phrase "I am a real human." in the textbox above.
(Line breaks become <br />)
(Name, email & website)
(Allow users to contact you through a message form (your email will not be revealed.)
April 2014
Sun Mon Tue Wed Thu Fri Sat
 << <   > >>
    1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30      

Search

The requested Blog doesn't exist any more!

XML Feeds

powered by b2evolution