Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make proutes more readable and report more info. #1431

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 126 additions & 17 deletions pyramid/scripts/proutes.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,17 @@

from pyramid.paster import bootstrap
from pyramid.scripts.common import parse_vars
from pyramid.config.views import MultiView

UNKNOWN_KEY = '<unknown>'
ANY_KEY = '<ANY>'
PAD = 3

def main(argv=sys.argv, quiet=False):
command = PRoutesCommand(argv, quiet)
return command.run()


class PRoutesCommand(object):
description = """\
Print all URL dispatch routes used by a Pyramid application in the
Expand Down Expand Up @@ -37,13 +43,13 @@ def __init__(self, argv, quiet=False):

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
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')
Expand All @@ -58,29 +64,132 @@ def run(self, quiet=False):
env = self.bootstrap[0](config_uri, options=parse_vars(self.args[1:]))
registry = env['registry']
mapper = self._get_mapper(registry)

max_name = len('Name')
max_pattern = len('Pattern')
max_view = len('View')
max_method = len('Method')

mapped_routes = []

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')))

mapped_routes.append(('Name', 'Pattern', 'View', 'Method'))
mapped_routes.append((
'-' * max_name,
'-' * max_pattern,
'-' * max_view,
'-' * max_method,
))

for route in routes:
route_request_methods = []
view_request_methods = []

request_iface = registry.queryUtility(
IRouteRequest,
name=route.name
)

view_callable = None
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>'))

if len(pattern) > max_pattern:
max_pattern = len(pattern)

# if (request_iface is None) or (route.factory is not None):
# view_callable = UNKNOWN_KEY
# else:
route_intr = registry.introspector.get(
'routes', route.name
)

if route_intr is not None:
route_request_methods = route_intr['request_methods']
view_intr = registry.introspector.related(route_intr)

for view in view_intr:
request_method = view.get('request_methods')

if request_method is not None:
if len(request_method) > max_method:
max_method = len(request_method)

view_request_methods.append(request_method)

view_callable = registry.adapters.lookup(
(IViewClassifier, request_iface, Interface),
IView,
name='',
default=None
)

if view_callable is not None:
if isinstance(view_callable, MultiView):
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 = registry.adapters.lookup(
(IViewClassifier, request_iface, Interface),
IView, name='', default=None)
self.out(fmt % (route.name, pattern, view_callable))
view_callable = str(None)

if len(route.name) > max_name:
max_name = len(route.name)

if len(view_callable) > max_view:
max_view = len(view_callable)

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(
view_request_methods
)

if has_methods and not request_methods:
request_methods = '<route mismatch>'
elif request_methods:
request_methods = ','.join(request_methods)
else:
request_methods = UNKNOWN_KEY

mapped_routes.append(
(route.name, pattern, view_callable, request_methods)
)

fmt = '%-{0}s %-{1}s %-{2}s %-{3}s'.format(
max_name + PAD,
max_pattern + PAD,
max_view + PAD,
max_method + PAD
)

for route_data in mapped_routes:
self.out(fmt % route_data)

return 0

if __name__ == '__main__': # pragma: no cover
if __name__ == '__main__': # pragma: no cover
sys.exit(main() or 0)
72 changes: 63 additions & 9 deletions pyramid/tests/test_scripts/test_proutes.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,36 @@
import unittest
from pyramid.tests.test_scripts import dummy


class DummyIntrospector(object):
def __init__(self):
self.relations = {}
self.introspectables = {}

def add(self, introspectable):
cat = introspectable.category_name
if cat not in self.introspectables:
self.introspectables[cat] = []

self.introspectables[cat].append(introspectable)

def get(self, name, discrim):
intrs = self.introspectables[name]

for intr in intrs:
if intr.discriminator == discrim:
return intr

def relate(self, a, b):
if a not in self.relations:
self.retlations[a] = []

self.relations[a].append(b)

def related(self, a):
return self.relations.get(a, [])


class TestPRoutesCommand(unittest.TestCase):
def _getTargetClass(self):
from pyramid.scripts.proutes import PRoutesCommand
Expand All @@ -12,6 +42,21 @@ def _makeOne(self):
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 _makeIntrospectable(self, category, discrim, request_methods=None):
from pyramid.registry import Introspectable
intr = Introspectable(category, discrim, 'title', 'type')

intr['request_methods'] = request_methods

return intr

def test_good_args(self):
cmd = self._getTargetClass()([])
cmd.bootstrap = (dummy.DummyBootstrap(),)
Expand Down Expand Up @@ -60,28 +105,34 @@ def test_single_route_no_route_registered(self):
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>', '<unknown>'])

def test_route_with_no_slash_prefix(self):
command = self._makeOne()
route = dummy.DummyRoute('a', 'a')
mapper = dummy.DummyMapper(route)
registry = self._makeRegistry()
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)
self.assertEqual(L[-1].split(), ['a', '/a', '<unknown>'])
self.assertEqual(L[-1].split(), ['a', '/a', '<unknown>', '<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()
registry.introspector.add(self._makeIntrospectable('routes', 'a'))

def view():pass
class IMyRoute(Interface):
pass
Expand All @@ -97,21 +148,23 @@ class IMyRoute(Interface):
self.assertEqual(result, 0)
self.assertEqual(len(L), 3)
self.assertEqual(L[-1].split()[:3], ['a', '/a', 'None'])


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
registry.registerAdapter(view,
(IViewClassifier, IMyRoute, Interface),
IView, '')
registry.registerUtility(IMyRoute, IRouteRequest, name='a')
registry.introspector.add(self._makeIntrospectable('routes', 'a'))
command = self._makeOne()
route = dummy.DummyRoute('a', '/a')
mapper = dummy.DummyMapper(route)
Expand All @@ -123,15 +176,16 @@ class IMyRoute(Interface):
self.assertEqual(result, 0)
self.assertEqual(len(L), 3)
compare_to = L[-1].split()[:3]
self.assertEqual(compare_to, ['a', '/a', '<function'])
self.assertEqual(compare_to, ['a', '/a', 'pyramid.tests.test_scripts.test_proutes.view'])

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
Expand Down