From 1dfd12a21edba88f19d3f9af3ba6d127a461512d Mon Sep 17 00:00:00 2001 From: John Anderson Date: Sun, 14 Dec 2014 20:14:08 -0800 Subject: Fix checks for @wsgiapp2, MultiView, and add request method --- pyramid/scripts/proutes.py | 288 +++++++++++++++++++++-------- pyramid/tests/test_scripts/test_proutes.py | 255 +++++++++++++++++++++++-- 2 files changed, 452 insertions(+), 91 deletions(-) diff --git a/pyramid/scripts/proutes.py b/pyramid/scripts/proutes.py index d0c1aa13e..b9a33f5bc 100644 --- a/pyramid/scripts/proutes.py +++ b/pyramid/scripts/proutes.py @@ -3,10 +3,22 @@ import sys import textwrap from pyramid.paster import bootstrap +from pyramid.compat import string_types +from pyramid.interfaces import ( + IRouteRequest, + IViewClassifier, + IView, +) + from pyramid.scripts.common import parse_vars +from pyramid.static import static_view +from zope.interface import Interface +from collections import OrderedDict PAD = 3 +ANY_KEY = '*' +UNKNOWN_KEY = '' def main(argv=sys.argv, quiet=False): @@ -14,6 +26,165 @@ def main(argv=sys.argv, quiet=False): return command.run() +def _get_pattern(route): + pattern = route.pattern + + if not pattern.startswith('/'): + pattern = '/%s' % pattern + return pattern + + +def _get_print_format(max_name, max_pattern, max_view, max_method): + fmt = '%-{0}s %-{1}s %-{2}s %-{3}s'.format( + max_name + PAD, + max_pattern + PAD, + max_view + PAD, + max_method + PAD, + ) + return fmt + + +def _get_request_methods(route_request_methods, view_request_methods): + has_route_methods = route_request_methods is not None + has_view_methods = len(view_request_methods) > 0 + has_methods = has_route_methods or has_view_methods + + if has_route_methods is False and has_view_methods is False: + request_methods = [ANY_KEY] + elif has_route_methods is False and has_view_methods is True: + request_methods = view_request_methods + elif has_route_methods is True and has_view_methods is False: + request_methods = route_request_methods + else: + request_methods = set(route_request_methods).intersection( + set(view_request_methods) + ) + + if has_methods and not request_methods: + request_methods = '' + elif request_methods: + request_methods = ','.join(request_methods) + + return request_methods + + +def _get_view_module(view_callable): + if view_callable is None: + return UNKNOWN_KEY + + if hasattr(view_callable, '__name__'): + if hasattr(view_callable, '__original_view__'): + original_view = view_callable.__original_view__ + else: + original_view = None + + if isinstance(original_view, static_view): + if original_view.package_name is not None: + return '%s:%s' % ( + original_view.package_name, + original_view.docroot + ) + else: + return original_view.docroot + else: + view_name = view_callable.__name__ + else: + # Currently only MultiView hits this, + # we could just not run _get_view_module + # for them and remove this logic + view_name = str(view_callable) + + view_module = '%s.%s' % ( + view_callable.__module__, + view_name, + ) + + # If pyramid wraps something in wsgiapp or wsgiapp2 decorators + # that is currently returned as pyramid.router.decorator, lets + # hack a nice name in: + if view_module == 'pyramid.router.decorator': + view_module = '' + + return view_module + + +def get_route_data(route, registry): + pattern = _get_pattern(route) + + request_iface = registry.queryUtility( + IRouteRequest, + name=route.name + ) + + route_request_methods = None + view_request_methods = OrderedDict() + view_callable = None + + route_intr = registry.introspector.get( + 'routes', route.name + ) + + if (request_iface is None) or (route.factory is not None): + return [ + (route.name, _get_pattern(route), UNKNOWN_KEY, ANY_KEY) + ] + + view_callable = registry.adapters.lookup( + (IViewClassifier, request_iface, Interface), + IView, + name='', + default=None + ) + view_module = _get_view_module(view_callable) + # Introspectables can be turned off, so there could be a chance + # that we have no `route_intr` but we do have a route + callable + if route_intr is None: + view_request_methods[view_module] = [] + else: + route_request_methods = route_intr['request_methods'] + + view_intr = registry.introspector.related(route_intr) + + if view_intr: + for view in view_intr: + request_method = view.get('request_methods') + + if request_method is not None: + view_callable = view['callable'] + view_module = _get_view_module(view_callable) + + if view_module not in view_request_methods: + view_request_methods[view_module] = [] + + if isinstance(request_method, string_types): + request_method = (request_method,) + + view_request_methods[view_module].extend(request_method) + else: + if view_module not in view_request_methods: + view_request_methods[view_module] = [] + + else: + view_request_methods[view_module] = [] + + final_routes = [] + + for view_module, methods in view_request_methods.items(): + request_methods = _get_request_methods( + route_request_methods, + methods + ) + + final_routes.append(( + route.name, + pattern, + view_module, + request_methods, + )) + + return final_routes + + class PRoutesCommand(object): description = """\ Print all URL dispatch routes used by a Pyramid application in the @@ -34,107 +205,74 @@ class PRoutesCommand(object): parser = optparse.OptionParser( usage, description=textwrap.dedent(description) - ) + ) def __init__(self, argv, quiet=False): self.options, self.args = self.parser.parse_args(argv[1:]) self.quiet = quiet + def out(self, msg): # pragma: no cover + if not self.quiet: + print(msg) + def _get_mapper(self, registry): from pyramid.config import Configurator - config = Configurator(registry = registry) + config = Configurator(registry=registry) return config.get_routes_mapper() - def out(self, msg): # pragma: no cover - if not self.quiet: - print(msg) - def run(self, quiet=False): if not self.args: self.out('requires a config file argument') return 2 - from pyramid.interfaces import IRouteRequest - from pyramid.interfaces import IViewClassifier - from pyramid.interfaces import IView - from pyramid.interfaces import IMultiView - - from zope.interface import Interface config_uri = self.args[0] - env = self.bootstrap[0](config_uri, options=parse_vars(self.args[1:])) registry = env['registry'] mapper = self._get_mapper(registry) - if mapper is not None: - mapped_routes = [('Name', 'Pattern', 'View')] - - max_name = len('Name') - max_pattern = len('Pattern') - max_view = len('View') - - routes = mapper.get_routes() - - if not routes: - return 0 - - mapped_routes.append(( - '-' * max_name, - '-' * max_pattern, - '-' * max_view, - )) - - for route in routes: - pattern = route.pattern - if not pattern.startswith('/'): - pattern = '/' + pattern - request_iface = registry.queryUtility(IRouteRequest, - name=route.name) - view_callable = None - - if (request_iface is None) or (route.factory is not None): - view_callable = '' - else: - view_callable = registry.adapters.lookup( - (IViewClassifier, request_iface, Interface), - IView, name='', default=None) - - if view_callable is not None: - if IMultiView.providedBy(view_callable): - view_callables = [ - x[1] for x in view_callable.views - ] - else: - view_callables = [view_callable] - - for view_func in view_callables: - view_callable = '%s.%s' % ( - view_func.__module__, - view_func.__name__, - ) - else: - view_callable = str(None) - - if len(route.name) > max_name: - max_name = len(route.name) + + if mapper is None: + return 0 + + max_name = len('Name') + max_pattern = len('Pattern') + max_view = len('View') + max_method = len('Method') + + routes = mapper.get_routes() + + if len(routes) == 0: + return 0 + + mapped_routes = [ + ('Name', 'Pattern', 'View', 'Method'), + ('----', '-------', '----', '------') + ] + + for route in routes: + route_data = get_route_data(route, registry) + + for name, pattern, view, method in route_data: + if len(name) > max_name: + max_name = len(name) if len(pattern) > max_pattern: max_pattern = len(pattern) - if len(view_callable) > max_view: - max_view = len(view_callable) + if len(view) > max_view: + max_view = len(view) - mapped_routes.append((route.name, pattern, view_callable)) + if len(method) > max_method: + max_method = len(method) - fmt = '%-{0}s %-{1}s %-{2}s'.format( - max_name + PAD, - max_pattern + PAD, - max_view + PAD, - ) + mapped_routes.append((name, pattern, view, method)) - for route_data in mapped_routes: - self.out(fmt % route_data) + fmt = _get_print_format(max_name, max_pattern, max_view, max_method) + + for route in mapped_routes: + self.out(fmt % route) return 0 -if __name__ == '__main__': # pragma: no cover + +if __name__ == '__main__': # pragma: no cover sys.exit(main() or 0) diff --git a/pyramid/tests/test_scripts/test_proutes.py b/pyramid/tests/test_scripts/test_proutes.py index 32202af4b..0713f4ac9 100644 --- a/pyramid/tests/test_scripts/test_proutes.py +++ b/pyramid/tests/test_scripts/test_proutes.py @@ -1,6 +1,16 @@ import unittest from pyramid.tests.test_scripts import dummy + +class DummyIntrospector(object): + def __init__(self): + self.relations = {} + self.introspectables = {} + + def get(self, name, discrim): + pass + + class TestPRoutesCommand(unittest.TestCase): def _getTargetClass(self): from pyramid.scripts.proutes import PRoutesCommand @@ -12,6 +22,17 @@ class TestPRoutesCommand(unittest.TestCase): cmd.args = ('/foo/bar/myapp.ini#myapp',) return cmd + def _makeRegistry(self): + from pyramid.registry import Registry + registry = Registry() + registry.introspector = DummyIntrospector() + return registry + + def _makeConfig(self, *arg, **kw): + from pyramid.config import Configurator + config = Configurator(*arg, **kw) + return config + def test_good_args(self): cmd = self._getTargetClass()([]) cmd.bootstrap = (dummy.DummyBootstrap(),) @@ -19,6 +40,8 @@ class TestPRoutesCommand(unittest.TestCase): route = dummy.DummyRoute('a', '/a') mapper = dummy.DummyMapper(route) cmd._get_mapper = lambda *arg: mapper + registry = self._makeRegistry() + cmd.bootstrap = (dummy.DummyBootstrap(registry=registry),) L = [] cmd.out = lambda msg: L.append(msg) cmd.run() @@ -58,12 +81,15 @@ class TestPRoutesCommand(unittest.TestCase): route = dummy.DummyRoute('a', '/a') mapper = dummy.DummyMapper(route) command._get_mapper = lambda *arg: mapper + registry = self._makeRegistry() + command.bootstrap = (dummy.DummyBootstrap(registry=registry),) + L = [] command.out = L.append result = command.run() self.assertEqual(result, 0) self.assertEqual(len(L), 3) - self.assertEqual(L[-1].split(), ['a', '/a', '']) + self.assertEqual(L[-1].split(), ['a', '/a', '', '*']) def test_route_with_no_slash_prefix(self): command = self._makeOne() @@ -72,16 +98,18 @@ class TestPRoutesCommand(unittest.TestCase): command._get_mapper = lambda *arg: mapper L = [] command.out = L.append + registry = self._makeRegistry() + command.bootstrap = (dummy.DummyBootstrap(registry=registry),) result = command.run() self.assertEqual(result, 0) self.assertEqual(len(L), 3) - self.assertEqual(L[-1].split(), ['a', '/a', '']) + self.assertEqual(L[-1].split(), ['a', '/a', '', '*']) def test_single_route_no_views_registered(self): from zope.interface import Interface - from pyramid.registry import Registry from pyramid.interfaces import IRouteRequest - registry = Registry() + registry = self._makeRegistry() + def view():pass class IMyRoute(Interface): pass @@ -96,15 +124,15 @@ class TestPRoutesCommand(unittest.TestCase): result = command.run() self.assertEqual(result, 0) self.assertEqual(len(L), 3) - self.assertEqual(L[-1].split()[:3], ['a', '/a', 'None']) + self.assertEqual(L[-1].split()[:3], ['a', '/a', '']) def test_single_route_one_view_registered(self): from zope.interface import Interface - from pyramid.registry import Registry from pyramid.interfaces import IRouteRequest from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IView - registry = Registry() + registry = self._makeRegistry() + def view():pass class IMyRoute(Interface): pass @@ -130,11 +158,11 @@ class TestPRoutesCommand(unittest.TestCase): def test_one_route_with_long_name_one_view_registered(self): from zope.interface import Interface - from pyramid.registry import Registry from pyramid.interfaces import IRouteRequest from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IView - registry = Registry() + registry = self._makeRegistry() + def view():pass class IMyRoute(Interface): @@ -172,11 +200,11 @@ class TestPRoutesCommand(unittest.TestCase): def test_single_route_one_view_registered_with_factory(self): from zope.interface import Interface - from pyramid.registry import Registry from pyramid.interfaces import IRouteRequest from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IView - registry = Registry() + registry = self._makeRegistry() + def view():pass class IMyRoot(Interface): pass @@ -201,12 +229,11 @@ class TestPRoutesCommand(unittest.TestCase): def test_single_route_multiview_registered(self): from zope.interface import Interface - from pyramid.registry import Registry from pyramid.interfaces import IRouteRequest from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IMultiView - registry = Registry() + registry = self._makeRegistry() def view(): pass @@ -235,19 +262,215 @@ class TestPRoutesCommand(unittest.TestCase): self.assertEqual(result, 0) self.assertEqual(len(L), 3) compare_to = L[-1].split()[:3] + view_module = 'pyramid.tests.test_scripts.dummy' + view_str = '' + ] + self.assertEqual(compare_to, expected) + + def test_route_static_views(self): + from pyramid.renderers import null_renderer as nr + config = self._makeConfig(autocommit=True) + config.add_static_view('static', 'static', cache_max_age=3600) + config.add_static_view(name='static2', path='/var/www/static') + config.add_static_view( + name='pyramid_scaffold', + path='pyramid:scaffolds/starter/+package+/static' + ) + + command = self._makeOne() + L = [] + command.out = L.append + command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),) + result = command.run() + self.assertEqual(result, 0) + self.assertEqual(len(L), 5) + + expected = [ + ['__static/', '/static/*subpath', + 'pyramid.tests.test_scripts:static/', '*'], + ['__static2/', '/static2/*subpath', '/var/www/static/', '*'], + ['__pyramid_scaffold/', '/pyramid_scaffold/*subpath', + 'pyramid:scaffolds/starter/+package+/static/', '*'], + ] + + for index, line in enumerate(L[2:]): + data = line.split() + self.assertEqual(data, expected[index]) + + def test_route_no_view(self): + from pyramid.renderers import null_renderer as nr + config = self._makeConfig(autocommit=True) + config.add_route('foo', '/a/b', request_method='POST') + + command = self._makeOne() + L = [] + command.out = L.append + command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),) + result = command.run() + self.assertEqual(result, 0) + self.assertEqual(len(L), 3) + compare_to = L[-1].split() + expected = [ + 'foo', '/a/b', + '', + 'POST', + ] + self.assertEqual(compare_to, expected) + + def test_route_as_wsgiapp(self): + from pyramid.wsgi import wsgiapp2 + + config1 = self._makeConfig(autocommit=True) + def view1(context, request): return 'view1' + config1.add_route('foo', '/a/b', request_method='POST') + config1.add_view(view=view1, route_name='foo') + + config2 = self._makeConfig(autocommit=True) + config2.add_route('foo', '/a/b', request_method='POST') + config2.add_view( + wsgiapp2(config1.make_wsgi_app()), + route_name='foo', + ) + + command = self._makeOne() + L = [] + command.out = L.append + command.bootstrap = (dummy.DummyBootstrap(registry=config2.registry),) + result = command.run() + self.assertEqual(result, 0) + self.assertEqual(len(L), 3) + compare_to = L[-1].split() + expected = [ + 'foo', '/a/b', + '', + 'POST', + ] + self.assertEqual(compare_to, expected) + class Test_main(unittest.TestCase): def _callFUT(self, argv): from pyramid.scripts.proutes import main -- cgit v1.2.3