delagoya (owner)

Revisions

gist: 22440 Download_button fork
public
Public Clone URL: git://gist.github.com/22440.git
ResourceDispatcher.py
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
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