Package wtf :: Package app :: Module resolver
[hide private]
[frames] | no frames]

Source Code for Module wtf.app.resolver

  1  # -*- coding: ascii -*- 
  2  # 
  3  # Copyright 2006-2012 
  4  # Andr\xe9 Malo or his licensors, as applicable 
  5  # 
  6  # Licensed under the Apache License, Version 2.0 (the "License"); 
  7  # you may not use this file except in compliance with the License. 
  8  # You may obtain a copy of the License at 
  9  # 
 10  #     http://www.apache.org/licenses/LICENSE-2.0 
 11  # 
 12  # Unless required by applicable law or agreed to in writing, software 
 13  # distributed under the License is distributed on an "AS IS" BASIS, 
 14  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 15  # See the License for the specific language governing permissions and 
 16  # limitations under the License. 
 17  """ 
 18  Simple URL resolver 
 19  =================== 
 20   
 21  This package contains a simple URL resolver. 
 22  """ 
 23  __author__ = u"Andr\xe9 Malo" 
 24  __docformat__ = "restructuredtext en" 
 25   
 26  import re as _re 
 27   
 28  from wtf import util as _util 
 29  from wtf.app import response as _response 
 30   
 31   
32 -class ResolverInterface(object):
33 """ 34 URL resolver interface 35 36 The resoling methods return callables, which take two parameters: request 37 and response, which in turn are instances of 38 `wtf.app.request.Request` and `wtf.app.response.Response`. 39 """ 40
41 - def error(self, status, default=None):
42 """ 43 Resolve an HTTP status code to a handler callable 44 45 :Parameters: 46 - `status`: The HTTP status code 47 - `default`: The default, if no callable if assigned 48 49 :Types: 50 - `status`: ``int`` 51 - `default`: any 52 53 :return: The resolved callable or `default` 54 :rtype: any 55 """
56
57 - def resolve(self, request):
58 """ 59 Resolve the request url to a python callable 60 61 :Parameters: 62 - `request`: The request object 63 64 :Types: 65 - `request`: `wtf.app.request.Request` 66 67 :return: The request/response handle 68 :rtype: ``callable`` 69 70 :Exceptions: 71 - `response.http.MovedPermanently`: a missing trailing slash was 72 detected 73 - `response.http.NotFound`: The url could not be resolved 74 """
75 76
77 -class MapResolver(object):
78 """ 79 Map based URL resolver class 80 81 The class takes a python package, which will be inspected and searched 82 in public modules (not starting with ``_``) for the following variables: 83 84 - ``__staticmap__`` - dict (``{'URL': callable, ...}``) 85 - ``__dynamicmap__`` - list of tuples (``[('regex', callable), ...]``) 86 - ``__errormap__`` - dict (``{int(HTTP-Code): callable, ...}``) 87 88 All of these variables are optional. Conflict resolution rules are as 89 follows: 90 91 - The modules/packages in a directory are ordered alphabetically 92 - recursing packages are collected immediately 93 - The latest one wins (z over a) 94 95 The actual resolver (`resolve`) works as follows: 96 97 - First the URL path is looked up in the static map. If found, the 98 callable is returned and we're done 99 - Otherwise the path is fed to every regex in the dynamic map. If a match 100 is found, the match object is attached to the request and the callable is 101 returned 102 - A 404 error is raised 103 104 :IVariables: 105 - `_staticmap`: final static URL map 106 - `_dynamicmap`: final dynamic map 107 - `_errormap`: final error map 108 109 :Types: 110 - `_staticmap`: ``dict`` 111 - `_dynamicmap`: ``list`` 112 - `_errormap`: ``dict`` 113 """ 114 __implements__ = [ResolverInterface] 115
116 - def __init__(self, config, opts, args):
117 """ 118 Initialization 119 120 :Parameters: 121 - `config`: Configuration 122 - `opts`: Command line arguments 123 - `args`: Positioned command line arguments 124 125 :Types: 126 - `config`: `wtf.config.Config` 127 - `opts`: ``optparse.OptionContainer`` 128 - `args`: ``list`` 129 """ 130 self._staticmap = {} 131 self._errormap = {} 132 self._dynamicmap = [] 133 for mod in _util.walk_package(config.app.package, 'error'): 134 modname = mod.__name__ 135 if '.' in modname: 136 modname = modname[modname.rfind('.') + 1:] 137 if modname.startswith('_'): 138 continue 139 static, error, dynamic = [ 140 getattr(mod, '__%smap__' % name, default) for name, default in 141 zip(('static', 'error', 'dynamic'), ({}, {}, [])) 142 ] 143 self._staticmap.update(static) 144 self._errormap.update(error) 145 self._dynamicmap = [(_re.compile( 146 isinstance(regex, basestring) and unicode(regex) or regex 147 ).match, func) for regex, func in dynamic] + self._dynamicmap 148 self.error = self._errormap.get
149
150 - def error(self, status, default=None):
151 """ Resolve error code """ 152 # pylint: disable = E0202, W0613 153 154 raise AssertionError( 155 "This method should have been replaced by __init__" 156 )
157
158 - def resolve(self, request):
159 """ Resolve this request """ 160 url = request.url.path 161 staticmap = self._staticmap 162 try: 163 return staticmap[url] 164 except KeyError: 165 if not url.endswith('/'): 166 candidate = "%s/" % url 167 if candidate in staticmap: 168 location = request.abs_uri(request.url) 169 location.path = candidate 170 raise _response.http.MovedPermanently( 171 request, location=str(location) 172 ) 173 174 for matcher, func in self._dynamicmap: 175 match = matcher(url) 176 if match: 177 request.match = match 178 return func 179 raise _response.http.NotFound(request)
180