commit | author | age
|
126470
|
1 |
import fnmatch |
616f40
|
2 |
import argparse |
337960
|
3 |
import sys |
d58614
|
4 |
import textwrap |
b8ba0f
|
5 |
import re |
337960
|
6 |
|
e028e0
|
7 |
from zope.interface import Interface |
CM |
8 |
|
337960
|
9 |
from pyramid.paster import bootstrap |
678790
|
10 |
from pyramid.compat import string_types |
e028e0
|
11 |
from pyramid.interfaces import IRouteRequest |
228a5e
|
12 |
from pyramid.config import not_ |
1dfd12
|
13 |
|
678790
|
14 |
from pyramid.scripts.common import get_config_loader |
49fb77
|
15 |
from pyramid.scripts.common import parse_vars |
1dfd12
|
16 |
from pyramid.static import static_view |
e028e0
|
17 |
from pyramid.view import _find_views |
e51dd0
|
18 |
|
JA |
19 |
|
|
20 |
PAD = 3 |
1dfd12
|
21 |
ANY_KEY = '*' |
JA |
22 |
UNKNOWN_KEY = '<unknown>' |
e51dd0
|
23 |
|
337960
|
24 |
|
d29151
|
25 |
def main(argv=sys.argv, quiet=False): |
CM |
26 |
command = PRoutesCommand(argv, quiet) |
d58614
|
27 |
return command.run() |
1dfd12
|
28 |
|
JA |
29 |
|
|
30 |
def _get_pattern(route): |
|
31 |
pattern = route.pattern |
|
32 |
|
|
33 |
if not pattern.startswith('/'): |
|
34 |
pattern = '/%s' % pattern |
|
35 |
return pattern |
|
36 |
|
|
37 |
|
a99d2d
|
38 |
def _get_print_format(fmt, max_name, max_pattern, max_view, max_method): |
JA |
39 |
print_fmt = '' |
|
40 |
max_map = { |
|
41 |
'name': max_name, |
|
42 |
'pattern': max_pattern, |
|
43 |
'view': max_view, |
|
44 |
'method': max_method, |
|
45 |
} |
|
46 |
sizes = [] |
|
47 |
|
|
48 |
for index, col in enumerate(fmt): |
|
49 |
size = max_map[col] + PAD |
|
50 |
print_fmt += '{{%s: <{%s}}} ' % (col, index) |
|
51 |
sizes.append(size) |
|
52 |
|
|
53 |
return print_fmt.format(*sizes) |
1dfd12
|
54 |
|
JA |
55 |
|
|
56 |
def _get_request_methods(route_request_methods, view_request_methods): |
228a5e
|
57 |
excludes = set() |
JA |
58 |
|
|
59 |
if route_request_methods: |
|
60 |
route_request_methods = set(route_request_methods) |
|
61 |
|
|
62 |
if view_request_methods: |
|
63 |
view_request_methods = set(view_request_methods) |
|
64 |
|
|
65 |
for method in view_request_methods.copy(): |
|
66 |
if method.startswith('!'): |
|
67 |
view_request_methods.remove(method) |
|
68 |
excludes.add(method[1:]) |
|
69 |
|
1dfd12
|
70 |
has_route_methods = route_request_methods is not None |
JA |
71 |
has_view_methods = len(view_request_methods) > 0 |
|
72 |
has_methods = has_route_methods or has_view_methods |
|
73 |
|
|
74 |
if has_route_methods is False and has_view_methods is False: |
|
75 |
request_methods = [ANY_KEY] |
|
76 |
elif has_route_methods is False and has_view_methods is True: |
|
77 |
request_methods = view_request_methods |
|
78 |
elif has_route_methods is True and has_view_methods is False: |
|
79 |
request_methods = route_request_methods |
|
80 |
else: |
228a5e
|
81 |
request_methods = route_request_methods.intersection( |
JA |
82 |
view_request_methods |
1dfd12
|
83 |
) |
228a5e
|
84 |
|
JA |
85 |
request_methods = set(request_methods).difference(excludes) |
1dfd12
|
86 |
|
JA |
87 |
if has_methods and not request_methods: |
|
88 |
request_methods = '<route mismatch>' |
|
89 |
elif request_methods: |
228a5e
|
90 |
if excludes and request_methods == set([ANY_KEY]): |
JA |
91 |
for exclude in excludes: |
|
92 |
request_methods.add('!%s' % exclude) |
|
93 |
|
|
94 |
request_methods = ','.join(sorted(request_methods)) |
1dfd12
|
95 |
|
JA |
96 |
return request_methods |
|
97 |
|
|
98 |
|
|
99 |
def _get_view_module(view_callable): |
|
100 |
if view_callable is None: |
|
101 |
return UNKNOWN_KEY |
|
102 |
|
|
103 |
if hasattr(view_callable, '__name__'): |
|
104 |
if hasattr(view_callable, '__original_view__'): |
|
105 |
original_view = view_callable.__original_view__ |
|
106 |
else: |
|
107 |
original_view = None |
|
108 |
|
|
109 |
if isinstance(original_view, static_view): |
|
110 |
if original_view.package_name is not None: |
|
111 |
return '%s:%s' % ( |
|
112 |
original_view.package_name, |
0c29cf
|
113 |
original_view.docroot, |
1dfd12
|
114 |
) |
JA |
115 |
else: |
|
116 |
return original_view.docroot |
|
117 |
else: |
|
118 |
view_name = view_callable.__name__ |
|
119 |
else: |
|
120 |
# Currently only MultiView hits this, |
|
121 |
# we could just not run _get_view_module |
|
122 |
# for them and remove this logic |
|
123 |
view_name = str(view_callable) |
|
124 |
|
0c29cf
|
125 |
view_module = '%s.%s' % (view_callable.__module__, view_name) |
1dfd12
|
126 |
|
JA |
127 |
# If pyramid wraps something in wsgiapp or wsgiapp2 decorators |
|
128 |
# that is currently returned as pyramid.router.decorator, lets |
|
129 |
# hack a nice name in: |
|
130 |
if view_module == 'pyramid.router.decorator': |
|
131 |
view_module = '<wsgiapp>' |
|
132 |
|
|
133 |
return view_module |
|
134 |
|
|
135 |
|
|
136 |
def get_route_data(route, registry): |
|
137 |
pattern = _get_pattern(route) |
|
138 |
|
0c29cf
|
139 |
request_iface = registry.queryUtility(IRouteRequest, name=route.name) |
1dfd12
|
140 |
|
JA |
141 |
route_request_methods = None |
e2274e
|
142 |
view_request_methods_order = [] |
JA |
143 |
view_request_methods = {} |
1dfd12
|
144 |
view_callable = None |
JA |
145 |
|
0c29cf
|
146 |
route_intr = registry.introspector.get('routes', route.name) |
1dfd12
|
147 |
|
e2274e
|
148 |
if request_iface is None: |
0c29cf
|
149 |
return [(route.name, _get_pattern(route), UNKNOWN_KEY, ANY_KEY)] |
1dfd12
|
150 |
|
e028e0
|
151 |
view_callables = _find_views(registry, request_iface, Interface, '') |
CM |
152 |
if view_callables: |
|
153 |
view_callable = view_callables[0] |
|
154 |
else: |
|
155 |
view_callable = None |
1dfd12
|
156 |
view_module = _get_view_module(view_callable) |
e2274e
|
157 |
|
1dfd12
|
158 |
# Introspectables can be turned off, so there could be a chance |
JA |
159 |
# that we have no `route_intr` but we do have a route + callable |
|
160 |
if route_intr is None: |
|
161 |
view_request_methods[view_module] = [] |
e2274e
|
162 |
view_request_methods_order.append(view_module) |
1dfd12
|
163 |
else: |
582c2e
|
164 |
if route_intr.get('static', False) is True: |
JA |
165 |
return [ |
|
166 |
(route.name, route_intr['external_url'], UNKNOWN_KEY, ANY_KEY) |
|
167 |
] |
1dfd12
|
168 |
|
582c2e
|
169 |
route_request_methods = route_intr['request_methods'] |
1dfd12
|
170 |
view_intr = registry.introspector.related(route_intr) |
JA |
171 |
|
|
172 |
if view_intr: |
|
173 |
for view in view_intr: |
|
174 |
request_method = view.get('request_methods') |
|
175 |
|
|
176 |
if request_method is not None: |
1bdb55
|
177 |
if view.get('attr') is not None: |
JA |
178 |
view_callable = getattr(view['callable'], view['attr']) |
|
179 |
view_module = '%s.%s' % ( |
|
180 |
_get_view_module(view['callable']), |
0c29cf
|
181 |
view['attr'], |
1bdb55
|
182 |
) |
JA |
183 |
else: |
|
184 |
view_callable = view['callable'] |
|
185 |
view_module = _get_view_module(view_callable) |
1dfd12
|
186 |
|
JA |
187 |
if view_module not in view_request_methods: |
|
188 |
view_request_methods[view_module] = [] |
e2274e
|
189 |
view_request_methods_order.append(view_module) |
1dfd12
|
190 |
|
JA |
191 |
if isinstance(request_method, string_types): |
|
192 |
request_method = (request_method,) |
228a5e
|
193 |
elif isinstance(request_method, not_): |
JA |
194 |
request_method = ('!%s' % request_method.value,) |
1dfd12
|
195 |
|
JA |
196 |
view_request_methods[view_module].extend(request_method) |
|
197 |
else: |
|
198 |
if view_module not in view_request_methods: |
|
199 |
view_request_methods[view_module] = [] |
e2274e
|
200 |
view_request_methods_order.append(view_module) |
1dfd12
|
201 |
|
JA |
202 |
else: |
|
203 |
view_request_methods[view_module] = [] |
e2274e
|
204 |
view_request_methods_order.append(view_module) |
1dfd12
|
205 |
|
JA |
206 |
final_routes = [] |
|
207 |
|
e2274e
|
208 |
for view_module in view_request_methods_order: |
JA |
209 |
methods = view_request_methods[view_module] |
0c29cf
|
210 |
request_methods = _get_request_methods(route_request_methods, methods) |
1dfd12
|
211 |
|
0c29cf
|
212 |
final_routes.append( |
MM |
213 |
(route.name, pattern, view_module, request_methods) |
|
214 |
) |
1dfd12
|
215 |
|
JA |
216 |
return final_routes |
e51dd0
|
217 |
|
337960
|
218 |
|
CM |
219 |
class PRoutesCommand(object): |
d58614
|
220 |
description = """\ |
CM |
221 |
Print all URL dispatch routes used by a Pyramid application in the |
337960
|
222 |
order in which they are evaluated. Each route includes the name of the |
CM |
223 |
route, the pattern of the route, and the view callable which will be |
|
224 |
invoked when the route is matched. |
|
225 |
|
364280
|
226 |
This command accepts one positional argument named 'config_uri'. It |
d58614
|
227 |
specifies the PasteDeploy config file to use for the interactive |
364280
|
228 |
shell. The format is 'inifile#name'. If the name is left off, 'main' |
CM |
229 |
will be assumed. Example: 'proutes myapp.ini'. |
337960
|
230 |
|
CM |
231 |
""" |
678790
|
232 |
bootstrap = staticmethod(bootstrap) # testing |
MM |
233 |
get_config_loader = staticmethod(get_config_loader) # testing |
337960
|
234 |
stdout = sys.stdout |
616f40
|
235 |
parser = argparse.ArgumentParser( |
df57ec
|
236 |
description=textwrap.dedent(description), |
c9b2fa
|
237 |
formatter_class=argparse.RawDescriptionHelpFormatter, |
0c29cf
|
238 |
) |
MM |
239 |
parser.add_argument( |
|
240 |
'-g', |
|
241 |
'--glob', |
|
242 |
action='store', |
|
243 |
dest='glob', |
|
244 |
default='', |
|
245 |
help='Display routes matching glob pattern', |
|
246 |
) |
337960
|
247 |
|
0c29cf
|
248 |
parser.add_argument( |
MM |
249 |
'-f', |
|
250 |
'--format', |
|
251 |
action='store', |
|
252 |
dest='format', |
|
253 |
default='', |
|
254 |
help=( |
|
255 |
'Choose which columns to display, this will ' |
|
256 |
'override the format key in the [proutes] ini ' |
|
257 |
'section' |
|
258 |
), |
|
259 |
) |
616f40
|
260 |
|
SP |
261 |
parser.add_argument( |
|
262 |
'config_uri', |
307ee4
|
263 |
nargs='?', |
SP |
264 |
default=None, |
616f40
|
265 |
help='The URI to the configuration file.', |
0c29cf
|
266 |
) |
a99d2d
|
267 |
|
e96b2b
|
268 |
parser.add_argument( |
a86675
|
269 |
'config_vars', |
e96b2b
|
270 |
nargs='*', |
SP |
271 |
default=(), |
6721ff
|
272 |
help="Variables required by the config file. For example, " |
0c29cf
|
273 |
"`http_port=%%(http_port)s` would expect `http_port=8080` to be " |
MM |
274 |
"passed here.", |
|
275 |
) |
e96b2b
|
276 |
|
d29151
|
277 |
def __init__(self, argv, quiet=False): |
616f40
|
278 |
self.args = self.parser.parse_args(argv[1:]) |
d29151
|
279 |
self.quiet = quiet |
0c29cf
|
280 |
self.available_formats = ['name', 'pattern', 'view', 'method'] |
a99d2d
|
281 |
self.column_format = self.available_formats |
JA |
282 |
|
|
283 |
def validate_formats(self, formats): |
|
284 |
invalid_formats = [] |
|
285 |
for fmt in formats: |
|
286 |
if fmt not in self.available_formats: |
|
287 |
invalid_formats.append(fmt) |
|
288 |
|
0c29cf
|
289 |
msg = 'You provided invalid formats %s, ' 'Available formats are %s' |
a99d2d
|
290 |
|
JA |
291 |
if invalid_formats: |
|
292 |
msg = msg % (invalid_formats, self.available_formats) |
|
293 |
self.out(msg) |
|
294 |
return False |
|
295 |
|
|
296 |
return True |
|
297 |
|
678790
|
298 |
def proutes_file_config(self, loader, global_conf=None): |
MM |
299 |
settings = loader.get_settings('proutes', global_conf) |
|
300 |
format = settings.get('format') |
|
301 |
if format: |
|
302 |
cols = re.split(r'[,|\s\n]+', format) |
|
303 |
self.column_format = [x.strip() for x in cols] |
337960
|
304 |
|
1dfd12
|
305 |
def out(self, msg): # pragma: no cover |
d29151
|
306 |
if not self.quiet: |
5cf9fc
|
307 |
print(msg) |
1dfd12
|
308 |
|
JA |
309 |
def _get_mapper(self, registry): |
|
310 |
from pyramid.config import Configurator |
0c29cf
|
311 |
|
1dfd12
|
312 |
config = Configurator(registry=registry) |
JA |
313 |
return config.get_routes_mapper() |
e51dd0
|
314 |
|
d29151
|
315 |
def run(self, quiet=False): |
83fb10
|
316 |
if not self.args.config_uri: |
d29151
|
317 |
self.out('requires a config file argument') |
d58614
|
318 |
return 2 |
49fb77
|
319 |
|
a4f18f
|
320 |
config_uri = self.args.config_uri |
678790
|
321 |
config_vars = parse_vars(self.args.config_vars) |
MM |
322 |
loader = self.get_config_loader(config_uri) |
|
323 |
loader.setup_logging(config_vars) |
|
324 |
self.proutes_file_config(loader, config_vars) |
|
325 |
|
|
326 |
env = self.bootstrap(config_uri, options=config_vars) |
337960
|
327 |
registry = env['registry'] |
CM |
328 |
mapper = self._get_mapper(registry) |
a99d2d
|
329 |
|
616f40
|
330 |
if self.args.format: |
SP |
331 |
columns = self.args.format.split(',') |
a99d2d
|
332 |
self.column_format = [x.strip() for x in columns] |
JA |
333 |
|
|
334 |
is_valid = self.validate_formats(self.column_format) |
|
335 |
|
|
336 |
if is_valid is False: |
|
337 |
return 2 |
e51dd0
|
338 |
|
1dfd12
|
339 |
if mapper is None: |
JA |
340 |
return 0 |
e51dd0
|
341 |
|
1dfd12
|
342 |
max_name = len('Name') |
JA |
343 |
max_pattern = len('Pattern') |
|
344 |
max_view = len('View') |
|
345 |
max_method = len('Method') |
360463
|
346 |
|
582c2e
|
347 |
routes = mapper.get_routes(include_static=True) |
e51dd0
|
348 |
|
1dfd12
|
349 |
if len(routes) == 0: |
JA |
350 |
return 0 |
e51dd0
|
351 |
|
0c29cf
|
352 |
mapped_routes = [ |
MM |
353 |
{ |
|
354 |
'name': 'Name', |
|
355 |
'pattern': 'Pattern', |
|
356 |
'view': 'View', |
|
357 |
'method': 'Method', |
|
358 |
}, |
|
359 |
{ |
|
360 |
'name': '----', |
|
361 |
'pattern': '-------', |
|
362 |
'view': '----', |
|
363 |
'method': '------', |
|
364 |
}, |
|
365 |
] |
e51dd0
|
366 |
|
1dfd12
|
367 |
for route in routes: |
JA |
368 |
route_data = get_route_data(route, registry) |
e51dd0
|
369 |
|
1dfd12
|
370 |
for name, pattern, view, method in route_data: |
616f40
|
371 |
if self.args.glob: |
0c29cf
|
372 |
match = fnmatch.fnmatch( |
MM |
373 |
name, self.args.glob |
|
374 |
) or fnmatch.fnmatch(pattern, self.args.glob) |
126470
|
375 |
if not match: |
MA |
376 |
continue |
|
377 |
|
1dfd12
|
378 |
if len(name) > max_name: |
JA |
379 |
max_name = len(name) |
e51dd0
|
380 |
|
JA |
381 |
if len(pattern) > max_pattern: |
|
382 |
max_pattern = len(pattern) |
|
383 |
|
1dfd12
|
384 |
if len(view) > max_view: |
JA |
385 |
max_view = len(view) |
e51dd0
|
386 |
|
1dfd12
|
387 |
if len(method) > max_method: |
JA |
388 |
max_method = len(method) |
e51dd0
|
389 |
|
0c29cf
|
390 |
mapped_routes.append( |
MM |
391 |
{ |
|
392 |
'name': name, |
|
393 |
'pattern': pattern, |
|
394 |
'view': view, |
|
395 |
'method': method, |
|
396 |
} |
|
397 |
) |
e51dd0
|
398 |
|
a99d2d
|
399 |
fmt = _get_print_format( |
JA |
400 |
self.column_format, max_name, max_pattern, max_view, max_method |
|
401 |
) |
1dfd12
|
402 |
|
JA |
403 |
for route in mapped_routes: |
a99d2d
|
404 |
self.out(fmt.format(**route)) |
e51dd0
|
405 |
|
d58614
|
406 |
return 0 |
337960
|
407 |
|
1dfd12
|
408 |
|
JA |
409 |
if __name__ == '__main__': # pragma: no cover |
40d54e
|
410 |
sys.exit(main() or 0) |