summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Merickel <michael@merickel.org>2015-01-22 15:53:04 -0600
committerMichael Merickel <michael@merickel.org>2015-01-22 15:53:04 -0600
commitf5a78b1f3e21de2f77dff11d7c28a9c214e8fb28 (patch)
tree257c992f05e38c9c13253ef97444a22d496d2ab1
parent11c6a51ad35da3e57a6434e16b94e66ccd109817 (diff)
parentb8ba0f1ed25b118aeb05accb23d872b3a72dc548 (diff)
downloadpyramid-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.txt6
-rw-r--r--docs/narr/commandline.rst56
-rw-r--r--pyramid/config/routes.py6
-rw-r--r--pyramid/scripts/proutes.py408
-rw-r--r--pyramid/tests/test_scripts/dummy.py2
-rw-r--r--pyramid/tests/test_scripts/test_proutes.py535
-rw-r--r--pyramid/urldispatch.py11
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