diff options
| author | Michael Merickel <michael@merickel.org> | 2015-01-22 15:53:04 -0600 |
|---|---|---|
| committer | Michael Merickel <michael@merickel.org> | 2015-01-22 15:53:04 -0600 |
| commit | f5a78b1f3e21de2f77dff11d7c28a9c214e8fb28 (patch) | |
| tree | 257c992f05e38c9c13253ef97444a22d496d2ab1 | |
| parent | 11c6a51ad35da3e57a6434e16b94e66ccd109817 (diff) | |
| parent | b8ba0f1ed25b118aeb05accb23d872b3a72dc548 (diff) | |
| download | pyramid-f5a78b1f3e21de2f77dff11d7c28a9c214e8fb28.tar.gz pyramid-f5a78b1f3e21de2f77dff11d7c28a9c214e8fb28.tar.bz2 pyramid-f5a78b1f3e21de2f77dff11d7c28a9c214e8fb28.zip | |
Merge pull request #1488 from sontek/support_more_features_in_routes
Make Proutes *AMAZING*: Fix checks for @wsgiapp2, MultiView, and add request method
| -rw-r--r-- | CHANGES.txt | 6 | ||||
| -rw-r--r-- | docs/narr/commandline.rst | 56 | ||||
| -rw-r--r-- | pyramid/config/routes.py | 6 | ||||
| -rw-r--r-- | pyramid/scripts/proutes.py | 408 | ||||
| -rw-r--r-- | pyramid/tests/test_scripts/dummy.py | 2 | ||||
| -rw-r--r-- | pyramid/tests/test_scripts/test_proutes.py | 535 | ||||
| -rw-r--r-- | pyramid/urldispatch.py | 11 |
7 files changed, 920 insertions, 104 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index 0a28dc248..b1bd36904 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -71,6 +71,12 @@ Features - ``pserve`` can now take a ``-b`` or ``--browser`` option to open the server URL in a web browser. See https://github.com/Pylons/pyramid/pull/1533 +- 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__``. + See https://github.com/Pylons/pyramid/pull/1488 + Bug Fixes --------- diff --git a/docs/narr/commandline.rst b/docs/narr/commandline.rst index 4f16617c4..3dcb092e2 100644 --- a/docs/narr/commandline.rst +++ b/docs/narr/commandline.rst @@ -312,24 +312,60 @@ For example: :linenos: $ $VENV/bin/proutes development.ini - Name Pattern View - ---- ------- ---- - home / <function my_view> - home2 / <function my_view> - another /another None - static/ static/*subpath <static_view object> - catchall /*subpath <function static_view> - -``proutes`` generates a table with three columns: *Name*, *Pattern*, + Name Pattern View + ---- ------- ---- + debugtoolbar /_debug_toolbar/*subpath <wsgiapp> * + __static/ /static/*subpath dummy_starter:static/ * + __static2/ /static2/*subpath /var/www/static/ * + __pdt_images/ /pdt_images/*subpath pyramid_debugtoolbar:static/img/ * + a / <unknown> * + no_view_attached / <unknown> * + route_and_view_attached / app1.standard_views.route_and_view_attached * + method_conflicts /conflicts app1.standard_conflicts <route mismatch> + 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 ``<unknown>`` 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 + +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``. + + .. index:: pair: tweens; printing single: ptweens 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 d0c1aa13e..544947724 100644 --- a/pyramid/scripts/proutes.py +++ b/pyramid/scripts/proutes.py @@ -1,12 +1,26 @@ +import fnmatch import optparse import sys import textwrap +import re from pyramid.paster import bootstrap +from pyramid.compat import (string_types, configparser) +from pyramid.interfaces import ( + IRouteRequest, + IViewClassifier, + IView, +) +from pyramid.config import not_ + from pyramid.scripts.common import parse_vars +from pyramid.static import static_view +from zope.interface import Interface PAD = 3 +ANY_KEY = '*' +UNKNOWN_KEY = '<unknown>' def main(argv=sys.argv, quiet=False): @@ -14,6 +28,206 @@ 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(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): + 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 + + 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 = route_request_methods.intersection( + view_request_methods + ) + + request_methods = set(request_methods).difference(excludes) + + if has_methods and not request_methods: + request_methods = '<route mismatch>' + elif 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 + + +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 = '<wsgiapp>' + + 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_order = [] + view_request_methods = {} + view_callable = None + + route_intr = registry.introspector.get( + 'routes', route.name + ) + + if request_iface is 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] = [] + view_request_methods_order.append(view_module) + else: + 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: + 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] = [] + view_request_methods_order.append(view_module) + + 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: + 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 in view_request_methods_order: + methods = view_request_methods[view_module] + 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 @@ -30,111 +244,153 @@ class PRoutesCommand(object): bootstrap = (bootstrap,) stdout = sys.stdout usage = '%prog config_uri' - + ConfigParser = configparser.ConfigParser # testing parser = optparse.OptionParser( usage, description=textwrap.dedent(description) - ) + ) + parser.add_option('-g', '--glob', + 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' + ) - def _get_mapper(self, registry): - from pyramid.config import Configurator - config = Configurator(registry = registry) - return config.get_routes_mapper() + 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: + cols = re.split(r'[,|\s|\n]*', v) + self.column_format = [x.strip() for x in cols] - def out(self, msg): # pragma: no cover + except configparser.NoSectionError: + return + + 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) + return config.get_routes_mapper() + 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 = '<unknown>' - 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) + + 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 + + max_name = len('Name') + max_pattern = len('Pattern') + max_view = len('View') + max_method = len('Method') + + routes = mapper.get_routes(include_static=True) + + if len(routes) == 0: + return 0 + + 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) + + 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) 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': name, + 'pattern': pattern, + 'view': view, + 'method': method + }) - for route_data in mapped_routes: - self.out(fmt % route_data) + fmt = _get_print_format( + self.column_format, max_name, max_pattern, max_view, max_method + ) + + for route in mapped_routes: + self.out(fmt.format(**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/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 32202af4b..e426eee73 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 @@ -10,8 +20,20 @@ class TestPRoutesCommand(unittest.TestCase): cmd = self._getTargetClass()([]) cmd.bootstrap = (dummy.DummyBootstrap(),) 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 +41,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 +82,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', '<unknown>']) + self.assertEqual(L[-1].split(), ['a', '/a', '<unknown>', '*']) def test_route_with_no_slash_prefix(self): command = self._makeOne() @@ -72,16 +99,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', '<unknown>']) + self.assertEqual(L[-1].split(), ['a', '/a', '<unknown>', '*']) 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 +125,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', '<unknown>']) 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 +159,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 +201,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 +230,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 +263,494 @@ 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 = '<pyramid.tests.test_scripts.dummy.DummyMultiView' + final = '%s.%s' % (view_module, view_str) + self.assertEqual( compare_to, - ['a', '/a', 'pyramid.tests.test_scripts.test_proutes.view'] + ['a', '/a', final] ) def test__get_mapper(self): - from pyramid.registry import Registry from pyramid.urldispatch import RoutesMapper command = self._makeOne() - registry = Registry() + registry = self._makeRegistry() + result = command._get_mapper(registry) self.assertEqual(result.__class__, RoutesMapper) + def test_one_route_all_methods_view_only_post(self): + from pyramid.renderers import null_renderer as nr + + 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='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) + + def test_one_route_only_post_view_all_methods(self): + from pyramid.renderers import null_renderer as nr + + def view1(context, request): return 'view1' + + config = self._makeConfig(autocommit=True) + config.add_route('foo', '/a/b', request_method='POST') + config.add_view( + route_name='foo', + view=view1, + renderer=nr, + ) + + 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) + + def test_one_route_only_post_view_post_and_get(self): + from pyramid.renderers import null_renderer as nr + + def view1(context, request): return 'view1' + + config = self._makeConfig(autocommit=True) + config.add_route('foo', '/a/b', request_method='POST') + config.add_view( + route_name='foo', + view=view1, + renderer=nr, + request_method=('POST', 'GET') + ) + + 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) + + def test_route_request_method_mismatch(self): + from pyramid.renderers import null_renderer as nr + + def view1(context, request): return 'view1' + + config = self._makeConfig(autocommit=True) + config.add_route('foo', '/a/b', request_method='POST') + config.add_view( + route_name='foo', + view=view1, + renderer=nr, + request_method='GET' + ) + + 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', + '<route', 'mismatch>' + ] + 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', + '<unknown>', + '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', + '<wsgiapp>', + 'POST', + ] + 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) + + 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) + + 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_newlines(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\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')] + + 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_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', + '<unknown>', '*', + ] + 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 |
