PageRenderTime 68ms CodeModel.GetById 58ms app.highlight 7ms RepoModel.GetById 1ms app.codeStats 0ms

/circuits/web/dispatchers/routes.py

https://bitbucket.org/prologic/circuits/
Python | 176 lines | 125 code | 20 blank | 31 comment | 9 complexity | ff4186e3dc79e5b578c62a786785fc00 MD5 | raw file
  1# Module:   routes
  2# Date:     13th September 2007
  3# Author:   James Mills, prologic at shortcircuit dot net dot au
  4
  5"""Routes
  6
  7This module implements a routes based dispatcher.
  8"""
  9
 10from circuits.tools import tryimport
 11
 12routes = tryimport("routes")
 13
 14from circuits import handler, BaseComponent
 15
 16from cgi import FieldStorage
 17from circuits.web.errors import HTTPError
 18from circuits.web.utils import parseQueryString, dictform
 19
 20
 21class RoutesError(Exception):
 22    pass
 23
 24
 25class Routes(BaseComponent):
 26    """A Routes Dispatcher
 27
 28    A dispatcher that uses the routes module to perform controller
 29    lookups for a url using pattern matching rules (connections).
 30
 31    See: http://routes.groovie.org/docs/ for more information on Routes.
 32    """
 33
 34    channel = "web"
 35
 36    def __init__(self, add_default_connection=False, **kwargs):
 37        """
 38        If add_default_connection is True then a default connection
 39        of /{controller}/{action} will be added as part of
 40        the instantiation. Note that because Routes are order dependant
 41        this could obscure similar connections added at
 42        a later date, hence why it is disabled by default.
 43        """
 44
 45        super(Routes, self).__init__(**kwargs)
 46
 47        if routes is None:
 48            raise RuntimeError("No routes support available")
 49
 50        # An index of controllers used by the Routes Mapper when performing
 51        # matches
 52        self.controllers = {}
 53        self.mapper = routes.Mapper(controller_scan=self.controllers.keys)
 54        if add_default_connection:
 55            self.connect("default", "/{controller}/{action}")
 56
 57    def connect(self, name, route, **kwargs):
 58        """
 59        Connect a route to a controller
 60
 61        Supply a name for the route and then the route itself.
 62        Naming the route allows it to be referred to easily
 63        elsewhere in the system (e.g. for generating urls).
 64        The route is the url pattern you wish to map.
 65        """
 66
 67        controller = kwargs.pop('controller', None)
 68        if controller is not None:
 69
 70            if not isinstance(controller, basestring):
 71                try:
 72                    controller = getattr(controller, 'channel')
 73                except AttributeError:
 74                    raise RoutesError("Controller %s must be " + \
 75                            "either a string or have a 'channel' property " + \
 76                            "defined." % controller)
 77
 78            controller = controller.strip("/")
 79        self.mapper.connect(name, route, controller=controller, **kwargs)
 80
 81    def _parseBody(self, request, response, params):
 82        body = request.body
 83        headers = request.headers
 84
 85        if "Content-Type" not in headers:
 86            headers["Content-Type"] = ""
 87
 88        try:
 89            form = FieldStorage(fp=body,
 90                headers=headers,
 91                environ={"REQUEST_METHOD": "POST"},
 92                keep_blank_values=True)
 93        except Exception as e:
 94            if e.__class__.__name__ == 'MaxSizeExceeded':
 95                # Post data is too big
 96                return HTTPError(request, response, 413)
 97            else:
 98                raise
 99
100        if form.file:
101            request.body = form.file
102        else:
103            params.update(dictform(form))
104
105        return True
106
107    def _getChannel(self, request):
108        """Find and return an appropriate channel for the given request.
109
110        The channel is found by consulting the Routes mapper to return a
111        matching controller (target) and action (channel) along with a
112        dictionary of additional parameters to be added to the request.
113        """
114
115        path = request.path
116        request.index = path.endswith("/")
117
118        # setup routes singleton config instance
119        config = routes.request_config()
120        config.mapper = self.mapper
121        config.host = request.headers.get('Host', None)
122        config.protocol = request.scheme
123
124        # Redirect handler not supported at present
125        config.redirect = None
126
127        result = self.mapper.match(path)
128        config.mapper_dict = result
129        channel = None
130        target = None
131        vpath = []
132        if result:
133            target = self.controllers[result['controller']]
134            channel = result["action"]
135        return channel, target, vpath, result.copy()
136
137    @handler("request", filter=True)
138    def _on_request(self, event, request, response):
139        req = event
140
141        # retrieve a channel (handler) for this request
142        channel, target, vpath, params = self._getChannel(request)
143
144        if channel:
145            # add the params from the routes match
146            req.kwargs = params
147            # update with any query string params
148            req.kwargs.update(parseQueryString(request.qs))
149            v = self._parseBody(request, response, req.kwargs)
150            if not v:
151                return v  # MaxSizeExceeded (return the HTTPError)
152
153            if vpath:
154                req.args += tuple(vpath)
155
156            return self.fire(req, channel, target=target)
157
158    @handler("registered", target="*")
159    def _on_registered(self, event, component, manager):
160        """
161        Listen for controllers being added to the system and add them
162        to the controller index.
163        """
164
165        if component.channel and component.channel.startswith("/"):
166            self.controllers[component.channel.strip("/")] = component.channel
167
168    @handler("unregistered", target="*")
169    def _on_unregistered(self, event, component, manager):
170        """
171        Listen for controllers being removed from the system and remove them
172        from the controller index.
173        """
174
175        if component.channel and component.channel.startswith("/"):
176            self.controllers.pop(component.channel.strip("/"), None)