summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Anderson <sontek@gmail.com>2014-12-14 20:14:08 -0800
committerJohn Anderson <sontek@gmail.com>2014-12-14 20:14:08 -0800
commit1dfd12a21edba88f19d3f9af3ba6d127a461512d (patch)
treee755c17de39e47896f4d999b5616253128bdeac6
parentcc15bbf7de74f4cdfc676e34fa429d2658d1ddf6 (diff)
downloadpyramid-1dfd12a21edba88f19d3f9af3ba6d127a461512d.tar.gz
pyramid-1dfd12a21edba88f19d3f9af3ba6d127a461512d.tar.bz2
pyramid-1dfd12a21edba88f19d3f9af3ba6d127a461512d.zip
Fix checks for @wsgiapp2, MultiView, and add request method
-rw-r--r--pyramid/scripts/proutes.py288
-rw-r--r--pyramid/tests/test_scripts/test_proutes.py255
2 files changed, 452 insertions, 91 deletions
diff --git a/pyramid/scripts/proutes.py b/pyramid/scripts/proutes.py
index d0c1aa13e..b9a33f5bc 100644
--- a/pyramid/scripts/proutes.py
+++ b/pyramid/scripts/proutes.py
@@ -3,10 +3,22 @@ import sys
import textwrap
from pyramid.paster import bootstrap
+from pyramid.compat import string_types
+from pyramid.interfaces import (
+ IRouteRequest,
+ IViewClassifier,
+ IView,
+)
+
from pyramid.scripts.common import parse_vars
+from pyramid.static import static_view
+from zope.interface import Interface
+from collections import OrderedDict
PAD = 3
+ANY_KEY = '*'
+UNKNOWN_KEY = '<unknown>'
def main(argv=sys.argv, quiet=False):
@@ -14,6 +26,165 @@ def main(argv=sys.argv, quiet=False):
return command.run()
+def _get_pattern(route):
+ pattern = route.pattern
+
+ if not pattern.startswith('/'):
+ pattern = '/%s' % pattern
+ return pattern
+
+
+def _get_print_format(max_name, max_pattern, max_view, max_method):
+ fmt = '%-{0}s %-{1}s %-{2}s %-{3}s'.format(
+ max_name + PAD,
+ max_pattern + PAD,
+ max_view + PAD,
+ max_method + PAD,
+ )
+ return fmt
+
+
+def _get_request_methods(route_request_methods, view_request_methods):
+ has_route_methods = route_request_methods is not None
+ has_view_methods = len(view_request_methods) > 0
+ has_methods = has_route_methods or has_view_methods
+
+ if has_route_methods is False and has_view_methods is False:
+ request_methods = [ANY_KEY]
+ elif has_route_methods is False and has_view_methods is True:
+ request_methods = view_request_methods
+ elif has_route_methods is True and has_view_methods is False:
+ request_methods = route_request_methods
+ else:
+ request_methods = set(route_request_methods).intersection(
+ set(view_request_methods)
+ )
+
+ if has_methods and not request_methods:
+ request_methods = '<route mismatch>'
+ elif request_methods:
+ request_methods = ','.join(request_methods)
+
+ return request_methods
+
+
+def _get_view_module(view_callable):
+ if view_callable is None:
+ return UNKNOWN_KEY
+
+ if hasattr(view_callable, '__name__'):
+ if hasattr(view_callable, '__original_view__'):
+ original_view = view_callable.__original_view__
+ else:
+ original_view = None
+
+ if isinstance(original_view, static_view):
+ if original_view.package_name is not None:
+ return '%s:%s' % (
+ original_view.package_name,
+ original_view.docroot
+ )
+ else:
+ return original_view.docroot
+ else:
+ view_name = view_callable.__name__
+ else:
+ # Currently only MultiView hits this,
+ # we could just not run _get_view_module
+ # for them and remove this logic
+ view_name = str(view_callable)
+
+ view_module = '%s.%s' % (
+ view_callable.__module__,
+ view_name,
+ )
+
+ # If pyramid wraps something in wsgiapp or wsgiapp2 decorators
+ # that is currently returned as pyramid.router.decorator, lets
+ # hack a nice name in:
+ if view_module == 'pyramid.router.decorator':
+ view_module = '<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 = OrderedDict()
+ view_callable = None
+
+ route_intr = registry.introspector.get(
+ 'routes', route.name
+ )
+
+ if (request_iface is None) or (route.factory is not None):
+ return [
+ (route.name, _get_pattern(route), UNKNOWN_KEY, ANY_KEY)
+ ]
+
+ view_callable = registry.adapters.lookup(
+ (IViewClassifier, request_iface, Interface),
+ IView,
+ name='',
+ default=None
+ )
+ view_module = _get_view_module(view_callable)
+ # Introspectables can be turned off, so there could be a chance
+ # that we have no `route_intr` but we do have a route + callable
+ if route_intr is None:
+ view_request_methods[view_module] = []
+ else:
+ route_request_methods = route_intr['request_methods']
+
+ view_intr = registry.introspector.related(route_intr)
+
+ if view_intr:
+ for view in view_intr:
+ request_method = view.get('request_methods')
+
+ if request_method is not None:
+ view_callable = view['callable']
+ view_module = _get_view_module(view_callable)
+
+ if view_module not in view_request_methods:
+ view_request_methods[view_module] = []
+
+ if isinstance(request_method, string_types):
+ request_method = (request_method,)
+
+ view_request_methods[view_module].extend(request_method)
+ else:
+ if view_module not in view_request_methods:
+ view_request_methods[view_module] = []
+
+ else:
+ view_request_methods[view_module] = []
+
+ final_routes = []
+
+ for view_module, methods in view_request_methods.items():
+ request_methods = _get_request_methods(
+ route_request_methods,
+ methods
+ )
+
+ final_routes.append((
+ route.name,
+ pattern,
+ view_module,
+ request_methods,
+ ))
+
+ return final_routes
+
+
class PRoutesCommand(object):
description = """\
Print all URL dispatch routes used by a Pyramid application in the
@@ -34,107 +205,74 @@ class PRoutesCommand(object):
parser = optparse.OptionParser(
usage,
description=textwrap.dedent(description)
- )
+ )
def __init__(self, argv, quiet=False):
self.options, self.args = self.parser.parse_args(argv[1:])
self.quiet = quiet
+ def out(self, msg): # pragma: no cover
+ if not self.quiet:
+ print(msg)
+
def _get_mapper(self, registry):
from pyramid.config import Configurator
- config = Configurator(registry = registry)
+ config = Configurator(registry=registry)
return config.get_routes_mapper()
- def out(self, msg): # pragma: no cover
- if not self.quiet:
- print(msg)
-
def run(self, quiet=False):
if not self.args:
self.out('requires a config file argument')
return 2
- from pyramid.interfaces import IRouteRequest
- from pyramid.interfaces import IViewClassifier
- from pyramid.interfaces import IView
- from pyramid.interfaces import IMultiView
-
- from zope.interface import Interface
config_uri = self.args[0]
-
env = self.bootstrap[0](config_uri, options=parse_vars(self.args[1:]))
registry = env['registry']
mapper = self._get_mapper(registry)
- if mapper is not None:
- mapped_routes = [('Name', 'Pattern', 'View')]
-
- max_name = len('Name')
- max_pattern = len('Pattern')
- max_view = len('View')
-
- routes = mapper.get_routes()
-
- if not routes:
- return 0
-
- mapped_routes.append((
- '-' * max_name,
- '-' * max_pattern,
- '-' * max_view,
- ))
-
- for route in routes:
- pattern = route.pattern
- if not pattern.startswith('/'):
- pattern = '/' + pattern
- request_iface = registry.queryUtility(IRouteRequest,
- name=route.name)
- view_callable = None
-
- if (request_iface is None) or (route.factory is not None):
- view_callable = '<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)
+
+ if mapper is None:
+ return 0
+
+ max_name = len('Name')
+ max_pattern = len('Pattern')
+ max_view = len('View')
+ max_method = len('Method')
+
+ routes = mapper.get_routes()
+
+ if len(routes) == 0:
+ return 0
+
+ mapped_routes = [
+ ('Name', 'Pattern', 'View', 'Method'),
+ ('----', '-------', '----', '------')
+ ]
+
+ for route in routes:
+ route_data = get_route_data(route, registry)
+
+ for name, pattern, view, method in route_data:
+ if len(name) > max_name:
+ max_name = len(name)
if len(pattern) > max_pattern:
max_pattern = len(pattern)
- if len(view_callable) > max_view:
- max_view = len(view_callable)
+ if len(view) > max_view:
+ max_view = len(view)
- mapped_routes.append((route.name, pattern, view_callable))
+ if len(method) > max_method:
+ max_method = len(method)
- fmt = '%-{0}s %-{1}s %-{2}s'.format(
- max_name + PAD,
- max_pattern + PAD,
- max_view + PAD,
- )
+ mapped_routes.append((name, pattern, view, method))
- for route_data in mapped_routes:
- self.out(fmt % route_data)
+ fmt = _get_print_format(max_name, max_pattern, max_view, max_method)
+
+ for route in mapped_routes:
+ self.out(fmt % route)
return 0
-if __name__ == '__main__': # pragma: no cover
+
+if __name__ == '__main__': # pragma: no cover
sys.exit(main() or 0)
diff --git a/pyramid/tests/test_scripts/test_proutes.py b/pyramid/tests/test_scripts/test_proutes.py
index 32202af4b..0713f4ac9 100644
--- a/pyramid/tests/test_scripts/test_proutes.py
+++ b/pyramid/tests/test_scripts/test_proutes.py
@@ -1,6 +1,16 @@
import unittest
from pyramid.tests.test_scripts import dummy
+
+class DummyIntrospector(object):
+ def __init__(self):
+ self.relations = {}
+ self.introspectables = {}
+
+ def get(self, name, discrim):
+ pass
+
+
class TestPRoutesCommand(unittest.TestCase):
def _getTargetClass(self):
from pyramid.scripts.proutes import PRoutesCommand
@@ -12,6 +22,17 @@ class TestPRoutesCommand(unittest.TestCase):
cmd.args = ('/foo/bar/myapp.ini#myapp',)
return cmd
+ def _makeRegistry(self):
+ from pyramid.registry import Registry
+ registry = Registry()
+ registry.introspector = DummyIntrospector()
+ return registry
+
+ def _makeConfig(self, *arg, **kw):
+ from pyramid.config import Configurator
+ config = Configurator(*arg, **kw)
+ return config
+
def test_good_args(self):
cmd = self._getTargetClass()([])
cmd.bootstrap = (dummy.DummyBootstrap(),)
@@ -19,6 +40,8 @@ class TestPRoutesCommand(unittest.TestCase):
route = dummy.DummyRoute('a', '/a')
mapper = dummy.DummyMapper(route)
cmd._get_mapper = lambda *arg: mapper
+ registry = self._makeRegistry()
+ cmd.bootstrap = (dummy.DummyBootstrap(registry=registry),)
L = []
cmd.out = lambda msg: L.append(msg)
cmd.run()
@@ -58,12 +81,15 @@ class TestPRoutesCommand(unittest.TestCase):
route = dummy.DummyRoute('a', '/a')
mapper = dummy.DummyMapper(route)
command._get_mapper = lambda *arg: mapper
+ registry = self._makeRegistry()
+ command.bootstrap = (dummy.DummyBootstrap(registry=registry),)
+
L = []
command.out = L.append
result = command.run()
self.assertEqual(result, 0)
self.assertEqual(len(L), 3)
- self.assertEqual(L[-1].split(), ['a', '/a', '<unknown>'])
+ self.assertEqual(L[-1].split(), ['a', '/a', '<unknown>', '*'])
def test_route_with_no_slash_prefix(self):
command = self._makeOne()
@@ -72,16 +98,18 @@ class TestPRoutesCommand(unittest.TestCase):
command._get_mapper = lambda *arg: mapper
L = []
command.out = L.append
+ registry = self._makeRegistry()
+ command.bootstrap = (dummy.DummyBootstrap(registry=registry),)
result = command.run()
self.assertEqual(result, 0)
self.assertEqual(len(L), 3)
- self.assertEqual(L[-1].split(), ['a', '/a', '<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 +124,15 @@ class TestPRoutesCommand(unittest.TestCase):
result = command.run()
self.assertEqual(result, 0)
self.assertEqual(len(L), 3)
- self.assertEqual(L[-1].split()[:3], ['a', '/a', 'None'])
+ self.assertEqual(L[-1].split()[:3], ['a', '/a', '<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 +158,11 @@ class TestPRoutesCommand(unittest.TestCase):
def test_one_route_with_long_name_one_view_registered(self):
from zope.interface import Interface
- from pyramid.registry import Registry
from pyramid.interfaces import IRouteRequest
from pyramid.interfaces import IViewClassifier
from pyramid.interfaces import IView
- registry = Registry()
+ registry = self._makeRegistry()
+
def view():pass
class IMyRoute(Interface):
@@ -172,11 +200,11 @@ class TestPRoutesCommand(unittest.TestCase):
def test_single_route_one_view_registered_with_factory(self):
from zope.interface import Interface
- from pyramid.registry import Registry
from pyramid.interfaces import IRouteRequest
from pyramid.interfaces import IViewClassifier
from pyramid.interfaces import IView
- registry = Registry()
+ registry = self._makeRegistry()
+
def view():pass
class IMyRoot(Interface):
pass
@@ -201,12 +229,11 @@ class TestPRoutesCommand(unittest.TestCase):
def test_single_route_multiview_registered(self):
from zope.interface import Interface
- from pyramid.registry import Registry
from pyramid.interfaces import IRouteRequest
from pyramid.interfaces import IViewClassifier
from pyramid.interfaces import IMultiView
- registry = Registry()
+ registry = self._makeRegistry()
def view(): pass
@@ -235,19 +262,215 @@ class TestPRoutesCommand(unittest.TestCase):
self.assertEqual(result, 0)
self.assertEqual(len(L), 3)
compare_to = L[-1].split()[:3]
+ view_module = 'pyramid.tests.test_scripts.dummy'
+ view_str = '<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)
+
class Test_main(unittest.TestCase):
def _callFUT(self, argv):
from pyramid.scripts.proutes import main