sixtyPercent: Cochlear Implants, Aviation, Technlology, and Philosophy 2006/02/02
More on Integrating CherryPy and Routes
I wrote before about an experiment with integrating Routes with CherryPy. I've now updated that experiment, and am beginning to look at putting the code into production. First I'll quickly cover how, then talk about why.
The "how" part is pretty simple, thanks to the nice implementations on the part of the CherryPy and Routes authors. The mapRequest function does the work of taking the cherry request object and handing it off to Routes. Since I'm retrofitting Routes onto an existing cherrypy application, I emulate the "index", "default", and "exposed" semantics. Routes doesn't say what to do with the mapped route, only that it did find a match. I maintain a dictionary of controllers (registered with the mapConnect function). As in cherrypy, the controller can be a function, object, module, etc. Here's the code:
import urllib
import cherrypy
import routes
mapper = routes.Mapper()
controllers = {}
def redirect( url ):
raise cherrypy.HTTPRedirect( url )
def mapRequest():
# sets up the current cherrypy request object with the Routes URL map
# tell routes to use the cherrypy threadlocal object
config = routes.request_config()
if hasattr(config, 'using_request_local'):
config.request_local = lambda : cherrypy.request
config = routes.request_config()
# hook up the routes variables for this request
config.mapper = mapper
config.host = cherrypy.request.headerMap['Host']
config.protocol = cherrypy.request.scheme
config.redirect = redirect
config.mapper_dict = mapper.match( cherrypy.request.path )
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
return None
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
@cherrypy.expose
def home():
return "...."
# '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()
class RouteRoot:
@cherrypy.expose
def default( self, *args, **kwargs ):
handler = appserver.mapRequest()
if handler:
kwargs.update( cherrypy.request.mapper_dict )
return handler( **kwargs )
raise cherrypy.NotFound( cherrypy.request.path )
cherrypy.root = RouteRoot()
cherrypy.server.start()
***
highlight file error
***
Now for the 'why'. I had three pain points with the CherryPy object traversal approach to URLs. First, I'm tired of restructuring my application every time I want to adjust the URL-space. Worse, I find myself avoiding that task because it's a pain, and dealing with the existing URLs. I don't design my application all at once -- it evolves over time, and sometimes the implementation decisions have to be revisited.
Second, in CherryPy it's very difficult to have URLs like /archive/2005/12/1/blog.post.title/edit or /archive/2005/12/1/blog.post.title/preview. I find myself doing this a lot, and with CherryPy I pretty much have to write my own URL dispatching functions after the "/archive" part.
Third, CherryPy -- and for that matter most other web frameworks -- don't help me out with URL generation. In my templates and code, I don't want to write literal URLs -- I want to refer to item symbolically. Routes takes care of this nicely, and that's what the URL function above is for.
Update, Feb 26, 2006: I had a chance to talk to Robert Brewer from the CherryPy team at the PyCon 2006 conference in Dallas. He's taken up the Routes integration, and suggested a slightly different approach using a custom request class. Thanks Robert!
by David Creemer : 2006/02/02 : Categories python (permalink)