Michael Merickel
2018-10-15 bda1306749c62ef4f11cfe567ed7d56c8ad94240
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)