Skip to content

Instantly share code, notes, and snippets.

@delagoya
Created November 5, 2008 21:48
Show Gist options
  • Save delagoya/22440 to your computer and use it in GitHub Desktop.
Save delagoya/22440 to your computer and use it in GitHub Desktop.
import cherrypy
from cherrypy._cpdispatch import PageHandler, LateParamPageHandler
import routes
import logging
import yaml
REST_METHODS = ('GET','PUT','POST','DELETE')
def is_form_post(environ):
"""Determine whether the request is a POSTed html form"""
if environ['REQUEST_METHOD'] != 'POST':
return False
content_type = environ.get('CONTENT_TYPE', '').lower()
if ';' in content_type:
content_type = content_type.split(';', 1)[0]
return content_type in ('application/x-www-form-urlencoded','multipart/form-data')
class ResourceDispatcher(object):
"""
A Routes-based dispatcher for CherryPy that maps RESTful resources.
The dispatcher is meant to follow most of the conventions pioneers by the
Ruby on Rails framework's router. In particular, if you do not predefine the
keys for the controllers prior to configuring the routes, ResourceDispatcher.resource()
will try to add a CamelCase version of the given collection_name + the "Controller"
suffix. For example mapper.resource("item","items") will try to add
{"items": ItemsController()} to the controllers dict.
Otherwise the connect() and resource() methods are passed directly to routes.Mapper()
and work in the same manner.
"""
def __init__(self,controllers={}):
"""
Resource dispatcher
RESTful Routes-based dispatcher for CherryPy.
Provide a dict of {collection_name: Controller()} to
initialize the set of controllers available. Otherwise,
set the controllers attr to this hash after creation.
"""
import routes
self.controllers = controllers
self.mapper = routes.Mapper()
self.mapper.controller_scan = self.controllers.keys
def connect(self, *args, **kwargs):
"""Create and connect a new Route to the dispatcher."""
self.mapper.connect(*args, **kwargs)
def resource(self,member_name,collection_name):
"""Maps a resource, given the member_name and collection_name of that resource.
If the resource does not yet exist in the set of defined controllers, this method
will attempt the add it, following a standard naming convention pioneered by
Ruby on Rails, where the plural snake_cased collection name is turned to
CamelCase and suffixed with "Controller" for the class' name.
For example mapper.resource('blog_comment','blog_comments') would map to
the controller BlogCommentsController."""
if collection_name not in self.controllers.keys():
self.controllers[collection_name] = \
eval("%s()" % collection_name.title().replace("_",""))
self.mapper.resource(member_name,collection_name)
def redirect(self, url):
"""A wrapper for CherryPy's HTTPRedirect method"""
raise cherrypy.HTTPRedirect(url)
def __call__(self, path_info):
"""Set handler and config for the current request."""
func = self.find_handler(path_info)
if func:
cherrypy.response.headers['Allow'] = ", ".join(REST_METHODS)
cherrypy.request.handler = LateParamPageHandler(func)
else:
cherrypy.request.handler = cherrypy.NotFound()
def find_handler(self,path_info):
"""Find the right page handler, and set request.config."""
request = cherrypy.request
environ = cherrypy.request.wsgi_environ
# account for HTTP REQUEST_METHOD overrides
old_method = None
if '_method' in environ.get('QUERY_STRING', '') and \
request.params.get('_method','').upper() in REST_METHODS:
old_method = environ['REQUEST_METHOD']
environ['REQUEST_METHOD'] = request.params['_method'].upper()
logging.debug("_method found in QUERY_STRING, altering request"
" method to %s", environ['REQUEST_METHOD'])
elif is_form_post(environ):
# must parse the request body to get the method param
request.process_body()
m = request.params.get('_method',None)
if m is not None and m.upper() in REST_METHODS:
old_method = environ['REQUEST_METHOD']
environ['REQUEST_METHOD'] = m.upper()
logging.debug("_method found in POST data, altering request "
"method to %s", environ['REQUEST_METHOD'])
config = routes.request_config()
# Hook up the routes variables for this request
config.mapper = self.mapper
config.environ = environ
config.host = request.headers.get('Host',None)
config.protocol = request.scheme
config.redirect = self.redirect
result = self.mapper.match(path_info)
m = self.mapper.match(path_info)
config.mapper_dict = result
if old_method:
environ['REQUEST_METHOD'] = old_method
# also pop out the _method request param, if it exists
request.params.pop('_method', None)
params = {}
if result:
params = result.copy()
params.pop('controller', None)
params.pop('action', None)
request.params.update(params)
# Get config for the root object/path.
request.config = base = cherrypy.config.copy()
curpath = ""
def merge(nodeconf):
if 'tools.staticdir.dir' in nodeconf:
nodeconf['tools.staticdir.section'] = curpath or "/"
base.update(nodeconf)
app = request.app
root = app.root
if hasattr(root, "_cp_config"):
merge(root._cp_config)
if "/" in app.config:
merge(app.config["/"])
# Mix in values from app.config.
atoms = [x for x in path_info.split("/") if x]
if atoms:
last = atoms.pop()
else:
last = None
for atom in atoms:
curpath = "/".join((curpath, atom))
if curpath in app.config:
merge(app.config[curpath])
handler = None
if result:
controller = result.get('controller', None)
controller = self.controllers.get(controller)
if controller:
# Get config from the controller.
if hasattr(controller, "_cp_config"):
merge(controller._cp_config)
action = result.get('action', None)
if action is not None:
handler = getattr(controller, action, None)
# Get config from the handler
if hasattr(handler, "_cp_config"):
merge(handler._cp_config)
# Do the last path atom here so it can
# override the controller's _cp_config.
if last:
curpath = "/".join((curpath, last))
if curpath in app.config:
merge(app.config[curpath])
return handler
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment