Michael Merickel
2015-01-23 bc1f2e9b7bb453707c56e0b94fdacb42e3571f6a
Merge pull request #1542 from sontek/backport_proutes

Backport proutes improvements from 1.6 to 1.5
7 files modified
1075 ■■■■■ changed files
CHANGES.txt 6 ●●●●● patch | view | raw | blame | history
docs/narr/commandline.rst 52 ●●●● patch | view | raw | blame | history
pyramid/config/routes.py 6 ●●●●● patch | view | raw | blame | history
pyramid/scripts/proutes.py 380 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_scripts/dummy.py 2 ●●● patch | view | raw | blame | history
pyramid/tests/test_scripts/test_proutes.py 620 ●●●●● patch | view | raw | blame | history
pyramid/urldispatch.py 9 ●●●● patch | view | raw | blame | history
CHANGES.txt
@@ -4,6 +4,12 @@
- Fixed a failing unittest caused by differing mimetypes on various
  OS platforms. See https://github.com/Pylons/pyramid/issues/1405
- 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/1542
.. _changes_1.5.2:
1.5.2 (2014-11-09)
docs/narr/commandline.rst
@@ -312,24 +312,60 @@
   :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>
   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*,
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
pyramid/config/routes.py
@@ -303,6 +303,8 @@
        # 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 @@
        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:
pyramid/scripts/proutes.py
@@ -1,13 +1,232 @@
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):
    command = PRoutesCommand(argv, quiet)
    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 = """\
@@ -25,62 +244,153 @@
    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'
        )
        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]
        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)
        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 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:
            routes = mapper.get_routes()
            fmt = '%-15s %-30s %-25s'
            if not routes:
                return 0
            self.out(fmt % ('Name', 'Pattern', 'View'))
            self.out(
                fmt % ('-'*len('Name'), '-'*len('Pattern'), '-'*len('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):
                    self.out(fmt % (route.name, pattern, '<unknown>'))
                else:
                    view_callable = registry.adapters.lookup(
                        (IViewClassifier, request_iface, Interface),
                        IView, name='', default=None)
                    self.out(fmt % (route.name, pattern, view_callable))
        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) > max_view:
                    max_view = len(view)
                if len(method) > max_method:
                    max_method = len(method)
                mapped_routes.append({
                    'name': name,
                    'pattern': pattern,
                    'view': view,
                    'method': method
                })
        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)
pyramid/tests/test_scripts/dummy.py
@@ -60,7 +60,7 @@
    def __init__(self, *routes):
        self.routes = routes
    def get_routes(self):
    def get_routes(self, include_static=False):
        return self.routes
class DummyRoute(object):
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,7 +20,19 @@
        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()([])
@@ -19,6 +41,8 @@
        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 @@
        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 @@
        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 @@
        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
@@ -123,15 +152,60 @@
        self.assertEqual(result, 0)
        self.assertEqual(len(L), 3)
        compare_to = L[-1].split()[:3]
        self.assertEqual(compare_to, ['a', '/a', '<function'])
    def test_single_route_one_view_registered_with_factory(self):
        self.assertEqual(
            compare_to,
            ['a', '/a', 'pyramid.tests.test_scripts.test_proutes.view']
        )
    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):
            pass
        registry.registerAdapter(
            view,
            (IViewClassifier, IMyRoute, Interface),
            IView, ''
        )
        registry.registerUtility(IMyRoute, IRouteRequest,
                                 name='very_long_name_123')
        command = self._makeOne()
        route = dummy.DummyRoute(
            'very_long_name_123',
            '/and_very_long_pattern_as_well'
        )
        mapper = dummy.DummyMapper(route)
        command._get_mapper = lambda *arg: mapper
        L = []
        command.out = L.append
        command.bootstrap = (dummy.DummyBootstrap(registry=registry),)
        result = command.run()
        self.assertEqual(result, 0)
        self.assertEqual(len(L), 3)
        compare_to = L[-1].split()[:3]
        self.assertEqual(
            compare_to,
            ['very_long_name_123',
             '/and_very_long_pattern_as_well',
             'pyramid.tests.test_scripts.test_proutes.view']
        )
    def test_single_route_one_view_registered_with_factory(self):
        from zope.interface import Interface
        from pyramid.interfaces import IRouteRequest
        from pyramid.interfaces import IViewClassifier
        from pyramid.interfaces import IView
        registry = self._makeRegistry()
        def view():pass
        class IMyRoot(Interface):
            pass
@@ -154,14 +228,529 @@
        self.assertEqual(len(L), 3)
        self.assertEqual(L[-1].split()[:3], ['a', '/a', '<unknown>'])
    def test_single_route_multiview_registered(self):
        from zope.interface import Interface
        from pyramid.interfaces import IRouteRequest
        from pyramid.interfaces import IViewClassifier
        from pyramid.interfaces import IMultiView
        registry = self._makeRegistry()
        def view(): pass
        class IMyRoute(Interface):
            pass
        multiview1 = dummy.DummyMultiView(
            view, context='context',
            view_name='a1'
        )
        registry.registerAdapter(
            multiview1,
            (IViewClassifier, IMyRoute, Interface),
            IMultiView, ''
        )
        registry.registerUtility(IMyRoute, IRouteRequest, name='a')
        command = self._makeOne()
        route = dummy.DummyRoute('a', '/a')
        mapper = dummy.DummyMapper(route)
        command._get_mapper = lambda *arg: mapper
        L = []
        command.out = L.append
        command.bootstrap = (dummy.DummyBootstrap(registry=registry),)
        result = command.run()
        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', 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
@@ -170,4 +759,3 @@
    def test_it(self):
        result = self._callFUT(['proutes'])
        self.assertEqual(result, 2)
pyramid/urldispatch.py
@@ -42,12 +42,16 @@
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):
@@ -62,6 +66,9 @@
        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