Created
November 5, 2008 21:48
-
-
Save delagoya/22440 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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