1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
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
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
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
153
154 raise AssertionError(
155 "This method should have been replaced by __init__"
156 )
157
180