Sign in

Google Cookbook - Google App Engine

Match webapp URL's using Routes
Posted by rodrigo.moraes on Sat 22 Nov 2008 in Webapp Framework
User ratings:
This recipe is a implementation of Routes (http://routes.groovie.org/) for webapp. Routes adds some flexibility and tools to match and generate url's, and I think more people would want to use it with webapp.

1. Download and add the Routes library from http://routes.groovie.org/. We added it to a "routes" directory in our app root dir.

2. Somewhere in your main.py, import and start the Mapper:
# Start the routes mapper.
from routes.mapper import Mapper
map = Mapper(explicit = True)


The option 'explicit' is set to True to avoid Routes setting 'index' as default action name. We only want explicit action names in our defined routes, otherwise we'll use the request method as the action name (as in webapp). For other initialization options, see Routes documentation.

3. Add some routes. Notice that we use the form "my.module.name:MyControllerName" to tell Routes which module to load when a route matches:
map.connect('my-first-route', '', controller = 'app.handler:MyHandler')
map.connect('my-second-route', 'testing-route/:somepath', controller = 'app.handler:MyOtherHandler', somepath = None)


Refer to Routes documentation for details about connect() parameters. There are quite some tricks you can do in route definitions.

This route rules can also be stored somewhere else, for example, a routing.py file for your application. In this case, you'd call add_routes(map) from routing.py:

In main.py:
from routing import add_routes
add_routes(map)



In routing.py:
def add_routes(map):
map.connect('my-first-route', '', controller = 'app.handler:MyHandler')
map.connect('my-second-route', 'testing-route/:somepath', controller = 'app.handler:MyOtherHandler', somepath = None)



4. Almost done! Now we have to match our routes and for this, we will replace the default WSGIApplication from webapp. This is where all the magic happens. Here's wsgi.py:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# http://www.apache.org/licenses/LICENSE-2.0
#
import sys
from google.appengine.ext.webapp import Request
from google.appengine.ext.webapp import Response
class WSGIApplication(object):
"""Wraps a set of webapp RequestHandlers in a WSGI-compatible application.
This is based on webapp's WSGIApplication by Google, but uses Routes library
(http://routes.groovie.org/) to match url's.
"""
def __init__(self, mapper, debug = False):
"""Initializes this application with the given URL mapping.
Args:
mapper: a routes.mapper.Mapper instance
debug: if true, we send Python stack traces to the browser on errors
"""
self.mapper = mapper
self.__debug = debug
WSGIApplication.active_instance = self
self.current_request_args = ()
def __call__(self, environ, start_response):
"""Called by WSGI when a request comes in."""
request = Request(environ)
response = Response()
WSGIApplication.active_instance = self
# Match the path against registered routes.
kargs = self.mapper.match(request.path)
if kargs is None:
raise TypeError('No routes match. Provide a fallback to avoid this.')
# Extract the module and controller names from the route.
try:
module_name, class_name = kargs['controller'].split(':', 1)
del kargs['controller']
except:
raise TypeError('Controller is not set, or not formatted in the form "my.module.name:MyControllerName".')
# Initialize matched controller from given module.
try:
__import__(module_name)
module = sys.modules[module_name]
controller = getattr(module, class_name)()
controller.initialize(request, response)
except:
raise ImportError('Controller %s from module %s could not be initialized.' % (class_name, module_name))
# Use the action set in the route, or the HTTP method.
if 'action' in kargs:
action = kargs['action']
del kargs['action']
else:
action = environ['REQUEST_METHOD'].lower()
if action not in ['get', 'post', 'head', 'options', 'put', 'delete', 'trace']:
action = None
if controller and action:
try:
# Execute the requested action, passing the route dictionary as
# named parameters.
getattr(controller, action)(**kargs)
except Exception, e:
controller.handle_exception(e, self.__debug)
response.wsgi_write(start_response)
return ['']
else:
response.set_status(404)



5. Now, in main.py use this WSGIApplication instead of webapp's one, and pass the map we created to it:
from wsgi import WSGIApplication
app = WSGIApplication(map, debug = True)
...
def main():
run_wsgi_app(app)



Done! Routes is up and running.

Notice that now your handlers will receive named parameters - the dictionary values from the matched route.

PS: the cookbook removed all blank lines from my code, making it more difficult to follow than usual. :-P sorry about that.

Comments (0)

Sign in to add comment.