commit | author | age
|
812bc6
|
1 |
import argparse |
337960
|
2 |
import sys |
d58614
|
3 |
import textwrap |
337960
|
4 |
|
CM |
5 |
from pyramid.interfaces import IMultiView |
|
6 |
from pyramid.paster import bootstrap |
678790
|
7 |
from pyramid.paster import setup_logging |
cefcf8
|
8 |
from pyramid.request import Request |
49fb77
|
9 |
from pyramid.scripts.common import parse_vars |
13f594
|
10 |
from pyramid.view import _find_views |
337960
|
11 |
|
0c29cf
|
12 |
|
d29151
|
13 |
def main(argv=sys.argv, quiet=False): |
CM |
14 |
command = PViewsCommand(argv, quiet) |
d58614
|
15 |
return command.run() |
0c29cf
|
16 |
|
337960
|
17 |
|
CM |
18 |
class PViewsCommand(object): |
d58614
|
19 |
description = """\ |
CM |
20 |
Print, for a given URL, the views that might match. Underneath each |
337960
|
21 |
potentially matching route, list the predicates required. Underneath |
CM |
22 |
each route+predicate set, print each view that might match and its |
|
23 |
predicates. |
|
24 |
|
4d2602
|
25 |
This command accepts two positional arguments: 'config_uri' specifies the |
d58614
|
26 |
PasteDeploy config file to use for the interactive shell. The format is |
4d2602
|
27 |
'inifile#name'. If the name is left off, 'main' will be assumed. 'url' |
d58614
|
28 |
specifies the path info portion of a URL that will be used to find |
4d2602
|
29 |
matching views. Example: 'proutes myapp.ini#main /url' |
337960
|
30 |
""" |
CM |
31 |
stdout = sys.stdout |
|
32 |
|
812bc6
|
33 |
parser = argparse.ArgumentParser( |
df57ec
|
34 |
description=textwrap.dedent(description), |
c9b2fa
|
35 |
formatter_class=argparse.RawDescriptionHelpFormatter, |
0c29cf
|
36 |
) |
812bc6
|
37 |
|
0c29cf
|
38 |
parser.add_argument( |
MM |
39 |
'config_uri', |
|
40 |
nargs='?', |
|
41 |
default=None, |
|
42 |
help='The URI to the configuration file.', |
|
43 |
) |
337960
|
44 |
|
0c29cf
|
45 |
parser.add_argument( |
MM |
46 |
'url', |
|
47 |
nargs='?', |
|
48 |
default=None, |
|
49 |
help='The path info portion of the URL.', |
|
50 |
) |
5b40cd
|
51 |
parser.add_argument( |
SP |
52 |
'config_vars', |
|
53 |
nargs='*', |
|
54 |
default=(), |
|
55 |
help="Variables required by the config file. For example, " |
0c29cf
|
56 |
"`http_port=%%(http_port)s` would expect `http_port=8080` to be " |
MM |
57 |
"passed here.", |
|
58 |
) |
5b40cd
|
59 |
|
0c29cf
|
60 |
bootstrap = staticmethod(bootstrap) # testing |
MM |
61 |
setup_logging = staticmethod(setup_logging) # testing |
337960
|
62 |
|
d29151
|
63 |
def __init__(self, argv, quiet=False): |
CM |
64 |
self.quiet = quiet |
812bc6
|
65 |
self.args = self.parser.parse_args(argv[1:]) |
337960
|
66 |
|
0c29cf
|
67 |
def out(self, msg): # pragma: no cover |
d29151
|
68 |
if not self.quiet: |
5cf9fc
|
69 |
print(msg) |
0c29cf
|
70 |
|
337960
|
71 |
def _find_multi_routes(self, mapper, request): |
CM |
72 |
infos = [] |
|
73 |
path = request.environ['PATH_INFO'] |
|
74 |
# find all routes that match path, regardless of predicates |
|
75 |
for route in mapper.get_routes(): |
|
76 |
match = route.match(path) |
|
77 |
if match is not None: |
0c29cf
|
78 |
info = {'match': match, 'route': route} |
337960
|
79 |
infos.append(info) |
CM |
80 |
return infos |
|
81 |
|
cefcf8
|
82 |
def _find_view(self, request): |
337960
|
83 |
""" |
CM |
84 |
Accept ``url`` and ``registry``; create a :term:`request` and |
|
85 |
find a :app:`Pyramid` view based on introspection of :term:`view |
|
86 |
configuration` within the application registry; return the view. |
|
87 |
""" |
|
88 |
from zope.interface import providedBy |
|
89 |
from zope.interface import implementer |
|
90 |
from pyramid.interfaces import IRequest |
|
91 |
from pyramid.interfaces import IRootFactory |
|
92 |
from pyramid.interfaces import IRouteRequest |
|
93 |
from pyramid.interfaces import IRoutesMapper |
|
94 |
from pyramid.interfaces import ITraverser |
|
95 |
from pyramid.traversal import DefaultRootFactory |
|
96 |
from pyramid.traversal import ResourceTreeTraverser |
|
97 |
|
cefcf8
|
98 |
registry = request.registry |
337960
|
99 |
q = registry.queryUtility |
CM |
100 |
root_factory = q(IRootFactory, default=DefaultRootFactory) |
|
101 |
routes_mapper = q(IRoutesMapper) |
|
102 |
|
|
103 |
adapters = registry.adapters |
|
104 |
|
|
105 |
@implementer(IMultiView) |
|
106 |
class RoutesMultiView(object): |
|
107 |
def __init__(self, infos, context_iface, root_factory, request): |
|
108 |
self.views = [] |
|
109 |
for info in infos: |
|
110 |
match, route = info['match'], info['route'] |
|
111 |
if route is not None: |
|
112 |
request_iface = registry.queryUtility( |
0c29cf
|
113 |
IRouteRequest, name=route.name, default=IRequest |
MM |
114 |
) |
13f594
|
115 |
views = _find_views( |
0c29cf
|
116 |
request.registry, request_iface, context_iface, '' |
MM |
117 |
) |
13f594
|
118 |
if not views: |
337960
|
119 |
continue |
13f594
|
120 |
view = views[0] |
337960
|
121 |
view.__request_attrs__ = {} |
CM |
122 |
view.__request_attrs__['matchdict'] = match |
|
123 |
view.__request_attrs__['matched_route'] = route |
|
124 |
root_factory = route.factory or root_factory |
|
125 |
root = root_factory(request) |
|
126 |
traverser = adapters.queryAdapter(root, ITraverser) |
|
127 |
if traverser is None: |
|
128 |
traverser = ResourceTreeTraverser(root) |
|
129 |
tdict = traverser(request) |
|
130 |
view.__request_attrs__.update(tdict) |
|
131 |
if not hasattr(view, '__view_attr__'): |
|
132 |
view.__view_attr__ = '' |
|
133 |
self.views.append((None, view, None)) |
|
134 |
|
|
135 |
context = None |
|
136 |
routes_multiview = None |
|
137 |
attrs = request.__dict__ |
|
138 |
request_iface = IRequest |
|
139 |
|
|
140 |
# find the root object |
|
141 |
if routes_mapper is not None: |
|
142 |
infos = self._find_multi_routes(routes_mapper, request) |
|
143 |
if len(infos) == 1: |
|
144 |
info = infos[0] |
|
145 |
match, route = info['match'], info['route'] |
|
146 |
if route is not None: |
|
147 |
attrs['matchdict'] = match |
|
148 |
attrs['matched_route'] = route |
|
149 |
request.environ['bfg.routes.matchdict'] = match |
|
150 |
request_iface = registry.queryUtility( |
0c29cf
|
151 |
IRouteRequest, name=route.name, default=IRequest |
MM |
152 |
) |
337960
|
153 |
root_factory = route.factory or root_factory |
CM |
154 |
if len(infos) > 1: |
|
155 |
routes_multiview = infos |
|
156 |
|
|
157 |
root = root_factory(request) |
|
158 |
attrs['root'] = root |
|
159 |
|
|
160 |
# find a context |
|
161 |
traverser = adapters.queryAdapter(root, ITraverser) |
|
162 |
if traverser is None: |
|
163 |
traverser = ResourceTreeTraverser(root) |
|
164 |
tdict = traverser(request) |
25c64c
|
165 |
context, view_name = (tdict['context'], tdict['view_name']) |
JA |
166 |
|
337960
|
167 |
attrs.update(tdict) |
CM |
168 |
|
|
169 |
# find a view callable |
|
170 |
context_iface = providedBy(context) |
|
171 |
if routes_multiview is None: |
13f594
|
172 |
views = _find_views( |
0c29cf
|
173 |
request.registry, request_iface, context_iface, view_name |
MM |
174 |
) |
13f594
|
175 |
if views: |
CM |
176 |
view = views[0] |
|
177 |
else: |
|
178 |
view = None |
337960
|
179 |
else: |
CM |
180 |
view = RoutesMultiView(infos, context_iface, root_factory, request) |
|
181 |
|
|
182 |
# routes are not registered with a view name |
|
183 |
if view is None: |
13f594
|
184 |
views = _find_views( |
0c29cf
|
185 |
request.registry, request_iface, context_iface, '' |
MM |
186 |
) |
13f594
|
187 |
if views: |
CM |
188 |
view = views[0] |
|
189 |
else: |
|
190 |
view = None |
337960
|
191 |
# we don't want a multiview here |
CM |
192 |
if IMultiView.providedBy(view): |
|
193 |
view = None |
|
194 |
|
|
195 |
if view is not None: |
|
196 |
view.__request_attrs__ = attrs |
|
197 |
|
|
198 |
return view |
|
199 |
|
|
200 |
def output_route_attrs(self, attrs, indent): |
|
201 |
route = attrs['matched_route'] |
|
202 |
self.out("%sroute name: %s" % (indent, route.name)) |
|
203 |
self.out("%sroute pattern: %s" % (indent, route.pattern)) |
|
204 |
self.out("%sroute path: %s" % (indent, route.path)) |
|
205 |
self.out("%ssubpath: %s" % (indent, '/'.join(attrs['subpath']))) |
0ccdc2
|
206 |
predicates = ', '.join([p.text() for p in route.predicates]) |
337960
|
207 |
if predicates != '': |
CM |
208 |
self.out("%sroute predicates (%s)" % (indent, predicates)) |
|
209 |
|
|
210 |
def output_view_info(self, view_wrapper, level=1): |
|
211 |
indent = " " * level |
|
212 |
name = getattr(view_wrapper, '__name__', '') |
|
213 |
module = getattr(view_wrapper, '__module__', '') |
|
214 |
attr = getattr(view_wrapper, '__view_attr__', None) |
|
215 |
request_attrs = getattr(view_wrapper, '__request_attrs__', {}) |
|
216 |
if attr is not None: |
|
217 |
view_callable = "%s.%s.%s" % (module, name, attr) |
|
218 |
else: |
|
219 |
attr = view_wrapper.__class__.__name__ |
|
220 |
if attr == 'function': |
|
221 |
attr = name |
|
222 |
view_callable = "%s.%s" % (module, attr) |
|
223 |
self.out('') |
|
224 |
if 'matched_route' in request_attrs: |
|
225 |
self.out("%sRoute:" % indent) |
|
226 |
self.out("%s------" % indent) |
|
227 |
self.output_route_attrs(request_attrs, indent) |
|
228 |
permission = getattr(view_wrapper, '__permission__', None) |
|
229 |
if not IMultiView.providedBy(view_wrapper): |
|
230 |
# single view for this route, so repeat call without route data |
|
231 |
del request_attrs['matched_route'] |
25c64c
|
232 |
self.output_view_info(view_wrapper, level + 1) |
337960
|
233 |
else: |
CM |
234 |
self.out("%sView:" % indent) |
|
235 |
self.out("%s-----" % indent) |
|
236 |
self.out("%s%s" % (indent, view_callable)) |
|
237 |
permission = getattr(view_wrapper, '__permission__', None) |
|
238 |
if permission is not None: |
|
239 |
self.out("%srequired permission = %s" % (indent, permission)) |
|
240 |
predicates = getattr(view_wrapper, '__predicates__', None) |
|
241 |
if predicates is not None: |
4d2602
|
242 |
predicate_text = ', '.join([p.text() for p in predicates]) |
337960
|
243 |
self.out("%sview predicates (%s)" % (indent, predicate_text)) |
CM |
244 |
|
|
245 |
def run(self): |
258f84
|
246 |
if not self.args.config_uri or not self.args.url: |
d29151
|
247 |
self.out('Command requires a config file arg and a url arg') |
d58614
|
248 |
return 2 |
5b40cd
|
249 |
config_uri = self.args.config_uri |
678790
|
250 |
config_vars = parse_vars(self.args.config_vars) |
5b40cd
|
251 |
url = self.args.url |
678790
|
252 |
|
MM |
253 |
self.setup_logging(config_uri, global_conf=config_vars) |
49fb77
|
254 |
|
337960
|
255 |
if not url.startswith('/'): |
CM |
256 |
url = '/%s' % url |
cefcf8
|
257 |
request = Request.blank(url) |
678790
|
258 |
env = self.bootstrap(config_uri, options=config_vars, request=request) |
cefcf8
|
259 |
view = self._find_view(request) |
337960
|
260 |
self.out('') |
CM |
261 |
self.out("URL = %s" % url) |
|
262 |
self.out('') |
|
263 |
if view is not None: |
|
264 |
self.out(" context: %s" % view.__request_attrs__['context']) |
|
265 |
self.out(" view name: %s" % view.__request_attrs__['view_name']) |
|
266 |
if IMultiView.providedBy(view): |
|
267 |
for dummy, view_wrapper, dummy in view.views: |
|
268 |
self.output_view_info(view_wrapper) |
|
269 |
if IMultiView.providedBy(view_wrapper): |
|
270 |
for dummy, mv_view_wrapper, dummy in view_wrapper.views: |
|
271 |
self.output_view_info(mv_view_wrapper, level=2) |
|
272 |
else: |
|
273 |
if view is not None: |
|
274 |
self.output_view_info(view) |
|
275 |
else: |
|
276 |
self.out(" Not found.") |
|
277 |
self.out('') |
cefcf8
|
278 |
env['closer']() |
d58614
|
279 |
return 0 |
CM |
280 |
|
0c29cf
|
281 |
|
MM |
282 |
if __name__ == '__main__': # pragma: no cover |
40d54e
|
283 |
sys.exit(main() or 0) |