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 From e2274e5d1aa8e125c26a2e8d168a64e1b9e68db2 Mon Sep 17 00:00:00 2001 From: John Anderson Date: Sun, 14 Dec 2014 20:27:26 -0800 Subject: Fix py26 support --- pyramid/scripts/proutes.py | 14 ++++++++++---- tox.ini | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/pyramid/scripts/proutes.py b/pyramid/scripts/proutes.py index b9a33f5bc..d68f35cef 100644 --- a/pyramid/scripts/proutes.py +++ b/pyramid/scripts/proutes.py @@ -13,7 +13,6 @@ from pyramid.interfaces import ( from pyramid.scripts.common import parse_vars from pyramid.static import static_view from zope.interface import Interface -from collections import OrderedDict PAD = 3 @@ -117,14 +116,15 @@ def get_route_data(route, registry): ) route_request_methods = None - view_request_methods = OrderedDict() + view_request_methods_order = [] + view_request_methods = {} view_callable = None route_intr = registry.introspector.get( 'routes', route.name ) - if (request_iface is None) or (route.factory is not None): + if request_iface is None: return [ (route.name, _get_pattern(route), UNKNOWN_KEY, ANY_KEY) ] @@ -136,10 +136,12 @@ def get_route_data(route, registry): 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] = [] + view_request_methods_order.append(view_module) else: route_request_methods = route_intr['request_methods'] @@ -155,6 +157,7 @@ def get_route_data(route, registry): if view_module not in view_request_methods: view_request_methods[view_module] = [] + view_request_methods_order.append(view_module) if isinstance(request_method, string_types): request_method = (request_method,) @@ -163,13 +166,16 @@ def get_route_data(route, registry): else: if view_module not in view_request_methods: view_request_methods[view_module] = [] + view_request_methods_order.append(view_module) else: view_request_methods[view_module] = [] + view_request_methods_order.append(view_module) final_routes = [] - for view_module, methods in view_request_methods.items(): + for view_module in view_request_methods_order: + methods = view_request_methods[view_module] request_methods = _get_request_methods( route_request_methods, methods diff --git a/tox.ini b/tox.ini index 714c5b6d3..0b77d588a 100644 --- a/tox.ini +++ b/tox.ini @@ -9,7 +9,7 @@ commands = [testenv:cover] basepython = - python2.6 + python2.7 commands = python setup.py dev python setup.py nosetests --with-xunit --with-xcoverage -- cgit v1.2.3 From 59ee9f5fe96f133ff3582dbf34ea7da43ef39029 Mon Sep 17 00:00:00 2001 From: John Anderson Date: Sun, 14 Dec 2014 20:35:03 -0800 Subject: Switch tox back to 2.6, was supposed to be local --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 0b77d588a..714c5b6d3 100644 --- a/tox.ini +++ b/tox.ini @@ -9,7 +9,7 @@ commands = [testenv:cover] basepython = - python2.7 + python2.6 commands = python setup.py dev python setup.py nosetests --with-xunit --with-xcoverage -- cgit v1.2.3 From 228a5e5b3806c07c4d568ec491c4f83be5facbb1 Mon Sep 17 00:00:00 2001 From: John Anderson Date: Sun, 14 Dec 2014 21:09:48 -0800 Subject: Added support for `not_` request_method checks --- pyramid/scripts/proutes.py | 28 ++++++++++++-- pyramid/tests/test_scripts/test_proutes.py | 60 ++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 3 deletions(-) diff --git a/pyramid/scripts/proutes.py b/pyramid/scripts/proutes.py index d68f35cef..5d860b47d 100644 --- a/pyramid/scripts/proutes.py +++ b/pyramid/scripts/proutes.py @@ -9,6 +9,7 @@ from pyramid.interfaces import ( IViewClassifier, IView, ) +from pyramid.config import not_ from pyramid.scripts.common import parse_vars from pyramid.static import static_view @@ -44,6 +45,19 @@ def _get_print_format(max_name, max_pattern, max_view, max_method): def _get_request_methods(route_request_methods, view_request_methods): + excludes = set() + + if route_request_methods: + route_request_methods = set(route_request_methods) + + if view_request_methods: + view_request_methods = set(view_request_methods) + + for method in view_request_methods.copy(): + if method.startswith('!'): + view_request_methods.remove(method) + excludes.add(method[1:]) + 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 @@ -55,14 +69,20 @@ def _get_request_methods(route_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) + request_methods = route_request_methods.intersection( + view_request_methods ) + request_methods = set(request_methods).difference(excludes) + if has_methods and not request_methods: request_methods = '' elif request_methods: - request_methods = ','.join(request_methods) + if excludes and request_methods == set([ANY_KEY]): + for exclude in excludes: + request_methods.add('!%s' % exclude) + + request_methods = ','.join(sorted(request_methods)) return request_methods @@ -161,6 +181,8 @@ def get_route_data(route, registry): if isinstance(request_method, string_types): request_method = (request_method,) + elif isinstance(request_method, not_): + request_method = ('!%s' % request_method.value,) view_request_methods[view_module].extend(request_method) else: diff --git a/pyramid/tests/test_scripts/test_proutes.py b/pyramid/tests/test_scripts/test_proutes.py index 0713f4ac9..f7a85bd1c 100644 --- a/pyramid/tests/test_scripts/test_proutes.py +++ b/pyramid/tests/test_scripts/test_proutes.py @@ -471,6 +471,66 @@ class TestPRoutesCommand(unittest.TestCase): ] self.assertEqual(compare_to, expected) + def test_route_is_get_view_request_method_not_post(self): + from pyramid.renderers import null_renderer as nr + from pyramid.config import not_ + + def view1(context, request): return 'view1' + + config = self._makeConfig(autocommit=True) + config.add_route('foo', '/a/b', request_method='GET') + config.add_view( + route_name='foo', + view=view1, + renderer=nr, + request_method=not_('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', + 'pyramid.tests.test_scripts.test_proutes.view1', + 'GET' + ] + self.assertEqual(compare_to, expected) + + def test_view_request_method_not_post(self): + from pyramid.renderers import null_renderer as nr + from pyramid.config import not_ + + def view1(context, request): return 'view1' + + config = self._makeConfig(autocommit=True) + config.add_route('foo', '/a/b') + config.add_view( + route_name='foo', + view=view1, + renderer=nr, + request_method=not_('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', + 'pyramid.tests.test_scripts.test_proutes.view1', + '!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 From 1264707de3f9fa2d15943da3bf321b1304556aa3 Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Tue, 23 Dec 2014 14:58:13 -0800 Subject: Add a --glob option to limit output E.g.: $ proutes development.ini --glob='user*' Name Pattern View Method ---- ------- ---- ------ user /profilesvc/v1/user/{user_id} pyramid.config.views. * user /profilesvc/v1/user/{user_id} cornice.service.wrapper GET,HEAD --- pyramid/scripts/proutes.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pyramid/scripts/proutes.py b/pyramid/scripts/proutes.py index 5d860b47d..fce90c0c1 100644 --- a/pyramid/scripts/proutes.py +++ b/pyramid/scripts/proutes.py @@ -1,3 +1,4 @@ +import fnmatch import optparse import sys import textwrap @@ -234,6 +235,9 @@ class PRoutesCommand(object): usage, description=textwrap.dedent(description) ) + parser.add_option('-g', '--glob', + action='store', type='string', dest='glob', + default='', help='Display routes matching glob pattern') def __init__(self, argv, quiet=False): self.options, self.args = self.parser.parse_args(argv[1:]) @@ -280,6 +284,12 @@ class PRoutesCommand(object): route_data = get_route_data(route, registry) for name, pattern, view, method in route_data: + if self.options.glob: + match = (fnmatch.fnmatch(name, self.options.glob) or + fnmatch.fnmatch(pattern, self.options.glob)) + if not match: + continue + if len(name) > max_name: max_name = len(name) -- cgit v1.2.3 From c966a9283c2c6515848e8a5f4f7c34e0fba733a8 Mon Sep 17 00:00:00 2001 From: John Anderson Date: Thu, 25 Dec 2014 19:36:18 -0800 Subject: Added test for the view glob --- pyramid/tests/test_scripts/test_proutes.py | 41 ++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/pyramid/tests/test_scripts/test_proutes.py b/pyramid/tests/test_scripts/test_proutes.py index f7a85bd1c..4622383be 100644 --- a/pyramid/tests/test_scripts/test_proutes.py +++ b/pyramid/tests/test_scripts/test_proutes.py @@ -531,6 +531,47 @@ class TestPRoutesCommand(unittest.TestCase): ] self.assertEqual(compare_to, expected) + def test_view_glob(self): + from pyramid.renderers import null_renderer as nr + from pyramid.config import not_ + + def view1(context, request): return 'view1' + def view2(context, request): return 'view2' + + config = self._makeConfig(autocommit=True) + config.add_route('foo', '/a/b') + config.add_view( + route_name='foo', + view=view1, + renderer=nr, + request_method=not_('POST') + ) + + config.add_route('bar', '/b/a') + config.add_view( + route_name='bar', + view=view2, + renderer=nr, + request_method=not_('POST') + ) + + command = self._makeOne() + command.options.glob = '*foo*' + + 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', + 'pyramid.tests.test_scripts.test_proutes.view1', + '!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 From a99d2d7e17f4c87d9307215a2ad8d583e74c3a50 Mon Sep 17 00:00:00 2001 From: John Anderson Date: Thu, 25 Dec 2014 20:49:00 -0800 Subject: Add the --format flag --- pyramid/scripts/proutes.py | 106 ++++++++++++++++++++++++----- pyramid/tests/test_scripts/test_proutes.py | 94 +++++++++++++++++++++++++ 2 files changed, 183 insertions(+), 17 deletions(-) diff --git a/pyramid/scripts/proutes.py b/pyramid/scripts/proutes.py index fce90c0c1..917d9af71 100644 --- a/pyramid/scripts/proutes.py +++ b/pyramid/scripts/proutes.py @@ -4,7 +4,7 @@ import sys import textwrap from pyramid.paster import bootstrap -from pyramid.compat import string_types +from pyramid.compat import (string_types, configparser) from pyramid.interfaces import ( IRouteRequest, IViewClassifier, @@ -35,14 +35,22 @@ def _get_pattern(route): 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_print_format(fmt, max_name, max_pattern, max_view, max_method): + print_fmt = '' + max_map = { + 'name': max_name, + 'pattern': max_pattern, + 'view': max_view, + 'method': max_method, + } + sizes = [] + + for index, col in enumerate(fmt): + size = max_map[col] + PAD + print_fmt += '{{%s: <{%s}}} ' % (col, index) + sizes.append(size) + + return print_fmt.format(*sizes) def _get_request_methods(route_request_methods, view_request_methods): @@ -230,7 +238,7 @@ class PRoutesCommand(object): bootstrap = (bootstrap,) stdout = sys.stdout usage = '%prog config_uri' - + ConfigParser = configparser.ConfigParser # testing parser = optparse.OptionParser( usage, description=textwrap.dedent(description) @@ -239,9 +247,49 @@ class PRoutesCommand(object): action='store', type='string', dest='glob', default='', help='Display routes matching glob pattern') + parser.add_option('-f', '--format', + action='store', type='string', dest='format', + default='', help=('Choose which columns to display, this ' + 'will override the format key in the ' + '[proutes] ini section')) + def __init__(self, argv, quiet=False): self.options, self.args = self.parser.parse_args(argv[1:]) self.quiet = quiet + self.available_formats = [ + 'name', 'pattern', 'view', 'method' + ] + self.column_format = self.available_formats + + def validate_formats(self, formats): + invalid_formats = [] + for fmt in formats: + if fmt not in self.available_formats: + invalid_formats.append(fmt) + + msg = ( + 'You provided invalid formats %s, ' + 'Available formats are %s' + ) + + if invalid_formats: + msg = msg % (invalid_formats, self.available_formats) + self.out(msg) + return False + + return True + + def proutes_file_config(self, filename): + config = self.ConfigParser() + config.read(filename) + try: + items = config.items('proutes') + for k, v in items: + if 'format' == k: + self.column_format = [x.strip() for x in v.split('\n')] + + except configparser.NoSectionError: + return def out(self, msg): # pragma: no cover if not self.quiet: @@ -261,6 +309,16 @@ class PRoutesCommand(object): env = self.bootstrap[0](config_uri, options=parse_vars(self.args[1:])) registry = env['registry'] mapper = self._get_mapper(registry) + self.proutes_file_config(config_uri) + + if self.options.format: + columns = self.options.format.split(',') + self.column_format = [x.strip() for x in columns] + + is_valid = self.validate_formats(self.column_format) + + if is_valid is False: + return 2 if mapper is None: return 0 @@ -275,10 +333,17 @@ class PRoutesCommand(object): if len(routes) == 0: return 0 - mapped_routes = [ - ('Name', 'Pattern', 'View', 'Method'), - ('----', '-------', '----', '------') - ] + mapped_routes = [{ + 'name': 'Name', + 'pattern': 'Pattern', + 'view': 'View', + 'method': 'Method' + },{ + 'name': '----', + 'pattern': '-------', + 'view': '----', + 'method': '------' + }] for route in routes: route_data = get_route_data(route, registry) @@ -302,12 +367,19 @@ class PRoutesCommand(object): if len(method) > max_method: max_method = len(method) - mapped_routes.append((name, pattern, view, method)) + mapped_routes.append({ + 'name': name, + 'pattern': pattern, + 'view': view, + 'method': method + }) - fmt = _get_print_format(max_name, max_pattern, max_view, max_method) + fmt = _get_print_format( + self.column_format, max_name, max_pattern, max_view, max_method + ) for route in mapped_routes: - self.out(fmt % route) + self.out(fmt.format(**route)) return 0 diff --git a/pyramid/tests/test_scripts/test_proutes.py b/pyramid/tests/test_scripts/test_proutes.py index 4622383be..d51baaa01 100644 --- a/pyramid/tests/test_scripts/test_proutes.py +++ b/pyramid/tests/test_scripts/test_proutes.py @@ -20,6 +20,7 @@ class TestPRoutesCommand(unittest.TestCase): cmd = self._getTargetClass()([]) cmd.bootstrap = (dummy.DummyBootstrap(),) cmd.args = ('/foo/bar/myapp.ini#myapp',) + return cmd def _makeRegistry(self): @@ -572,6 +573,99 @@ class TestPRoutesCommand(unittest.TestCase): ] self.assertEqual(compare_to, expected) + def test_good_format(self): + from pyramid.renderers import null_renderer as nr + from pyramid.config import not_ + + def view1(context, request): return 'view1' + + config = self._makeConfig(autocommit=True) + config.add_route('foo', '/a/b') + config.add_view( + route_name='foo', + view=view1, + renderer=nr, + request_method=not_('POST') + ) + + command = self._makeOne() + command.options.glob = '*foo*' + command.options.format = 'method,name' + 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 = ['!POST,*', 'foo'] + + self.assertEqual(compare_to, expected) + self.assertEqual(L[0].split(), ['Method', 'Name']) + + def test_bad_format(self): + from pyramid.renderers import null_renderer as nr + from pyramid.config import not_ + + def view1(context, request): return 'view1' + + config = self._makeConfig(autocommit=True) + config.add_route('foo', '/a/b') + config.add_view( + route_name='foo', + view=view1, + renderer=nr, + request_method=not_('POST') + ) + + command = self._makeOne() + command.options.glob = '*foo*' + command.options.format = 'predicates,name,pattern' + L = [] + command.out = L.append + command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),) + expected = ( + "You provided invalid formats ['predicates'], " + "Available formats are ['name', 'pattern', 'view', 'method']" + ) + result = command.run() + self.assertEqual(result, 2) + self.assertEqual(L[0], expected) + + def test_config_format_ini(self): + from pyramid.renderers import null_renderer as nr + from pyramid.config import not_ + + def view1(context, request): return 'view1' + + config = self._makeConfig(autocommit=True) + config.add_route('foo', '/a/b') + config.add_view( + route_name='foo', + view=view1, + renderer=nr, + request_method=not_('POST') + ) + + command = self._makeOne() + command.options.glob = '*foo*' + command.options.format = 'method,name' + L = [] + command.out = L.append + command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),) + config_factory = dummy.DummyConfigParserFactory() + command.ConfigParser = config_factory + config_factory.items = [('format', 'method\name')] + + result = command.run() + self.assertEqual(result, 0) + self.assertEqual(len(L), 3) + compare_to = L[-1].split() + expected = ['!POST,*', 'foo'] + + self.assertEqual(compare_to, expected) + self.assertEqual(L[0].split(), ['Method', 'Name']) + class Test_main(unittest.TestCase): def _callFUT(self, argv): from pyramid.scripts.proutes import main -- cgit v1.2.3 From 582c2ed180120d07c825e4897350f5d1a6285afa Mon Sep 17 00:00:00 2001 From: John Anderson Date: Fri, 26 Dec 2014 05:06:33 -0800 Subject: Add support for static routes --- pyramid/config/routes.py | 6 ++++++ pyramid/scripts/proutes.py | 9 +++++++-- pyramid/tests/test_scripts/dummy.py | 2 +- pyramid/tests/test_scripts/test_proutes.py | 20 ++++++++++++++++++++ pyramid/urldispatch.py | 11 ++++++++++- 5 files changed, 44 insertions(+), 4 deletions(-) diff --git a/pyramid/config/routes.py b/pyramid/config/routes.py index f1463b50b..509955cdd 100644 --- a/pyramid/config/routes.py +++ b/pyramid/config/routes.py @@ -303,6 +303,8 @@ class RoutesConfiguratorMixin(object): # check for an external route; an external route is one which is # is a full url (e.g. 'http://example.com/{id}') parsed = urlparse.urlparse(pattern) + external_url = pattern + if parsed.hostname: pattern = parsed.path @@ -357,6 +359,10 @@ class RoutesConfiguratorMixin(object): intr['pregenerator'] = pregenerator intr['static'] = static intr['use_global_views'] = use_global_views + + if static is True: + intr['external_url'] = external_url + introspectables.append(intr) if factory: diff --git a/pyramid/scripts/proutes.py b/pyramid/scripts/proutes.py index 917d9af71..2155b4983 100644 --- a/pyramid/scripts/proutes.py +++ b/pyramid/scripts/proutes.py @@ -172,8 +172,13 @@ def get_route_data(route, registry): view_request_methods[view_module] = [] view_request_methods_order.append(view_module) else: - route_request_methods = route_intr['request_methods'] + if route_intr.get('static', False) is True: + return [ + (route.name, route_intr['external_url'], UNKNOWN_KEY, ANY_KEY) + ] + + route_request_methods = route_intr['request_methods'] view_intr = registry.introspector.related(route_intr) if view_intr: @@ -328,7 +333,7 @@ class PRoutesCommand(object): max_view = len('View') max_method = len('Method') - routes = mapper.get_routes() + routes = mapper.get_routes(include_static=True) if len(routes) == 0: return 0 diff --git a/pyramid/tests/test_scripts/dummy.py b/pyramid/tests/test_scripts/dummy.py index 366aa00b5..930b9ed64 100644 --- a/pyramid/tests/test_scripts/dummy.py +++ b/pyramid/tests/test_scripts/dummy.py @@ -60,7 +60,7 @@ class DummyMapper(object): def __init__(self, *routes): self.routes = routes - def get_routes(self): + def get_routes(self, include_static=False): return self.routes class DummyRoute(object): diff --git a/pyramid/tests/test_scripts/test_proutes.py b/pyramid/tests/test_scripts/test_proutes.py index d51baaa01..446d772ff 100644 --- a/pyramid/tests/test_scripts/test_proutes.py +++ b/pyramid/tests/test_scripts/test_proutes.py @@ -666,6 +666,26 @@ class TestPRoutesCommand(unittest.TestCase): self.assertEqual(compare_to, expected) self.assertEqual(L[0].split(), ['Method', 'Name']) + def test_static_routes_included_in_list(self): + from pyramid.renderers import null_renderer as nr + + config = self._makeConfig(autocommit=True) + config.add_route('foo', 'http://example.com/bar.aspx', static=True) + + 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', 'http://example.com/bar.aspx', + '', '*', + ] + self.assertEqual(compare_to, expected) + class Test_main(unittest.TestCase): def _callFUT(self, argv): from pyramid.scripts.proutes import main diff --git a/pyramid/urldispatch.py b/pyramid/urldispatch.py index fe4d433c3..349742c4a 100644 --- a/pyramid/urldispatch.py +++ b/pyramid/urldispatch.py @@ -42,12 +42,17 @@ class Route(object): class RoutesMapper(object): def __init__(self): self.routelist = [] + self.static_routes = [] + self.routes = {} def has_routes(self): return bool(self.routelist) - def get_routes(self): + def get_routes(self, include_static=False): + if include_static is True: + return self.routelist + self.static_routes + return self.routelist def get_route(self, name): @@ -59,9 +64,13 @@ class RoutesMapper(object): oldroute = self.routes[name] if oldroute in self.routelist: self.routelist.remove(oldroute) + route = Route(name, pattern, factory, predicates, pregenerator) if not static: self.routelist.append(route) + else: + self.static_routes.append(route) + self.routes[name] = route return route -- cgit v1.2.3 From 22c836ecbc6f10c4851d88017243f91e469016aa Mon Sep 17 00:00:00 2001 From: John Anderson Date: Thu, 1 Jan 2015 22:17:13 -0800 Subject: Updated the docs to talk about `--format` --- docs/narr/commandline.rst | 45 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/docs/narr/commandline.rst b/docs/narr/commandline.rst index 4f16617c4..02bb6138e 100644 --- a/docs/narr/commandline.rst +++ b/docs/narr/commandline.rst @@ -312,24 +312,49 @@ For example: :linenos: $ $VENV/bin/proutes development.ini - Name Pattern View - ---- ------- ---- - home / - home2 / - another /another None - static/ static/*subpath - catchall /*subpath - -``proutes`` generates a table with three columns: *Name*, *Pattern*, + Name Pattern View + ---- ------- ---- + debugtoolbar /_debug_toolbar/*subpath * + __static/ /static/*subpath dummy_starter:static/ * + __static2/ /static2/*subpath /var/www/static/ * + __pdt_images/ /pdt_images/*subpath pyramid_debugtoolbar:static/img/ * + a / * + no_view_attached / * + route_and_view_attached / app1.standard_views.route_and_view_attached * + method_conflicts /conflicts app1.standard_conflicts + multiview /multiview app1.standard_views.multiview GET,PATCH + not_post /not_post app1.standard_views.multview !POST,* + +``proutes`` generates a table with three columns: *Name*, *Pattern*, *Method*, and *View*. The items listed in the Name column are route names, the items listed in the Pattern column are route patterns, and the items listed in the View column are representations of the view callable that will be invoked when a request matches the associated -route pattern. The view column may show ``None`` if no associated view +route pattern. The view column may show ```` if no associated view callable could be found. If no routes are configured within your application, nothing will be printed to the console when ``proutes`` is executed. +It is convenient when using the ``proutes`` often to configure which columns +and the order you would like to view them. To facilitate this, ``proutes`` will +look for a special ``[proutes]`` section in your INI file and use those as +defaults. + +For example you may remove request method and place the view first: + +.. code-block:: text + :linenos: + + [proutes] + format = view + name + pattern + +If you want to temporarily configure the columns and order there is the +``--format` which is a comma separated list of columns you want to include. The +current available formats are ``name``, ``pattern``, ``view``, and ``method``. + + .. index:: pair: tweens; printing single: ptweens -- cgit v1.2.3 From 149ea94f24479889554b863c40fe72912f7203a8 Mon Sep 17 00:00:00 2001 From: John Anderson Date: Thu, 1 Jan 2015 22:23:06 -0800 Subject: Updated the CHANGES to discuss these improvments. --- CHANGES.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 129ce4616..2e2637ef1 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -64,6 +64,12 @@ Features a factory that will return a custom ``Response`` class. See https://github.com/Pylons/pyramid/pull/1499 +- Overally improvments for the ``proutes`` command. Added ``--format`` and + ``--glob`` arguments to the command, introduced the ``method`` + column for displaying available request methods, and improved the ``view`` + output by showing the module instead of just ``__repr__``. + See https://github.com/Pylons/pyramid/pull/1488 + Bug Fixes --------- -- cgit v1.2.3 From 3f8ac549fa352b54a9f151b5047cc4d13f942a4a Mon Sep 17 00:00:00 2001 From: John Anderson Date: Thu, 1 Jan 2015 22:24:09 -0800 Subject: Fix typo --- CHANGES.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 2e2637ef1..2079d5cff 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -64,7 +64,7 @@ Features a factory that will return a custom ``Response`` class. See https://github.com/Pylons/pyramid/pull/1499 -- Overally improvments for the ``proutes`` command. Added ``--format`` and +- Overall improvments for the ``proutes`` command. Added ``--format`` and ``--glob`` arguments to the command, introduced the ``method`` column for displaying available request methods, and improved the ``view`` output by showing the module instead of just ``__repr__``. -- cgit v1.2.3 From 83a400a3cd121fe65d33e796c28a199b35ab67e5 Mon Sep 17 00:00:00 2001 From: John Anderson Date: Thu, 1 Jan 2015 22:25:25 -0800 Subject: Terminated the highlight on ``format`` --- docs/narr/commandline.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/narr/commandline.rst b/docs/narr/commandline.rst index 02bb6138e..aca0ff425 100644 --- a/docs/narr/commandline.rst +++ b/docs/narr/commandline.rst @@ -351,7 +351,7 @@ For example you may remove request method and place the view first: pattern If you want to temporarily configure the columns and order there is the -``--format` which is a comma separated list of columns you want to include. The +``--format`` which is a comma separated list of columns you want to include. The current available formats are ``name``, ``pattern``, ``view``, and ``method``. -- cgit v1.2.3 From b8ba0f1ed25b118aeb05accb23d872b3a72dc548 Mon Sep 17 00:00:00 2001 From: John Anderson Date: Thu, 22 Jan 2015 07:29:02 -0800 Subject: Make more ways to configure [proutes] section --- docs/narr/commandline.rst | 11 +++++ pyramid/scripts/proutes.py | 5 +- pyramid/tests/test_scripts/test_proutes.py | 73 ++++++++++++++++++++++++++++-- 3 files changed, 84 insertions(+), 5 deletions(-) diff --git a/docs/narr/commandline.rst b/docs/narr/commandline.rst index aca0ff425..3dcb092e2 100644 --- a/docs/narr/commandline.rst +++ b/docs/narr/commandline.rst @@ -350,6 +350,17 @@ For example you may remove request method and place the view first: name pattern +You can also separate the formats with commas or spaces: + +.. code-block:: text + :linenos: + + [proutes] + format = view name pattern + + [proutes] + format = view, name, pattern + If you want to temporarily configure the columns and order there is the ``--format`` which is a comma separated list of columns you want to include. The current available formats are ``name``, ``pattern``, ``view``, and ``method``. diff --git a/pyramid/scripts/proutes.py b/pyramid/scripts/proutes.py index 2155b4983..544947724 100644 --- a/pyramid/scripts/proutes.py +++ b/pyramid/scripts/proutes.py @@ -2,6 +2,7 @@ import fnmatch import optparse import sys import textwrap +import re from pyramid.paster import bootstrap from pyramid.compat import (string_types, configparser) @@ -291,7 +292,8 @@ class PRoutesCommand(object): items = config.items('proutes') for k, v in items: if 'format' == k: - self.column_format = [x.strip() for x in v.split('\n')] + cols = re.split(r'[,|\s|\n]*', v) + self.column_format = [x.strip() for x in cols] except configparser.NoSectionError: return @@ -314,6 +316,7 @@ class PRoutesCommand(object): env = self.bootstrap[0](config_uri, options=parse_vars(self.args[1:])) registry = env['registry'] mapper = self._get_mapper(registry) + self.proutes_file_config(config_uri) if self.options.format: diff --git a/pyramid/tests/test_scripts/test_proutes.py b/pyramid/tests/test_scripts/test_proutes.py index 446d772ff..e426eee73 100644 --- a/pyramid/tests/test_scripts/test_proutes.py +++ b/pyramid/tests/test_scripts/test_proutes.py @@ -632,7 +632,7 @@ class TestPRoutesCommand(unittest.TestCase): self.assertEqual(result, 2) self.assertEqual(L[0], expected) - def test_config_format_ini(self): + def test_config_format_ini_newlines(self): from pyramid.renderers import null_renderer as nr from pyramid.config import not_ @@ -648,14 +648,79 @@ class TestPRoutesCommand(unittest.TestCase): ) command = self._makeOne() - command.options.glob = '*foo*' - command.options.format = 'method,name' + + L = [] + command.out = L.append + command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),) + config_factory = dummy.DummyConfigParserFactory() + command.ConfigParser = config_factory + config_factory.items = [('format', 'method\nname')] + + result = command.run() + self.assertEqual(result, 0) + self.assertEqual(len(L), 3) + compare_to = L[-1].split() + expected = ['!POST,*', 'foo'] + + self.assertEqual(compare_to, expected) + self.assertEqual(L[0].split(), ['Method', 'Name']) + + def test_config_format_ini_spaces(self): + from pyramid.renderers import null_renderer as nr + from pyramid.config import not_ + + def view1(context, request): return 'view1' + + config = self._makeConfig(autocommit=True) + config.add_route('foo', '/a/b') + config.add_view( + route_name='foo', + view=view1, + renderer=nr, + request_method=not_('POST') + ) + + command = self._makeOne() + + L = [] + command.out = L.append + command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),) + config_factory = dummy.DummyConfigParserFactory() + command.ConfigParser = config_factory + config_factory.items = [('format', 'method name')] + + result = command.run() + self.assertEqual(result, 0) + self.assertEqual(len(L), 3) + compare_to = L[-1].split() + expected = ['!POST,*', 'foo'] + + self.assertEqual(compare_to, expected) + self.assertEqual(L[0].split(), ['Method', 'Name']) + + def test_config_format_ini_commas(self): + from pyramid.renderers import null_renderer as nr + from pyramid.config import not_ + + def view1(context, request): return 'view1' + + config = self._makeConfig(autocommit=True) + config.add_route('foo', '/a/b') + config.add_view( + route_name='foo', + view=view1, + renderer=nr, + request_method=not_('POST') + ) + + command = self._makeOne() + L = [] command.out = L.append command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),) config_factory = dummy.DummyConfigParserFactory() command.ConfigParser = config_factory - config_factory.items = [('format', 'method\name')] + config_factory.items = [('format', 'method,name')] result = command.run() self.assertEqual(result, 0) -- cgit v1.2.3