From f0a9dfba27012bcf84fccd445b0fcaa1bca32382 Mon Sep 17 00:00:00 2001 From: Laurence Rowe Date: Fri, 5 Dec 2014 17:37:57 -0800 Subject: Support returning app_iter from renderer. Sets `response.app_iter = result` for renderer results which are iterable and not text/bytes. --- pyramid/renderers.py | 4 ++++ pyramid/tests/test_renderers.py | 25 +++++++++++++++++++++---- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/pyramid/renderers.py b/pyramid/renderers.py index 108255ee4..2addaa5fe 100644 --- a/pyramid/renderers.py +++ b/pyramid/renderers.py @@ -456,6 +456,10 @@ class RendererHelper(object): if result is not None: if isinstance(result, text_type): response.text = result + elif isinstance(result, bytes): + response.body = result + elif hasattr(result, '__iter__'): + response.app_iter = result else: response.body = result diff --git a/pyramid/tests/test_renderers.py b/pyramid/tests/test_renderers.py index 2bddd2318..bb5b0455a 100644 --- a/pyramid/tests/test_renderers.py +++ b/pyramid/tests/test_renderers.py @@ -191,8 +191,8 @@ class TestRendererHelper(unittest.TestCase): helper = self._makeOne('loo.foo') response = helper.render_to_response('values', {}, request=request) - self.assertEqual(response.body[0], 'values') - self.assertEqual(response.body[1], {}) + self.assertEqual(response.app_iter[0], 'values') + self.assertEqual(response.app_iter[1], {}) def test_get_renderer(self): factory = self._registerRendererFactory() @@ -209,8 +209,8 @@ class TestRendererHelper(unittest.TestCase): request = testing.DummyRequest() response = 'response' response = helper.render_view(request, response, view, context) - self.assertEqual(response.body[0], 'response') - self.assertEqual(response.body[1], + self.assertEqual(response.app_iter[0], 'response') + self.assertEqual(response.app_iter[1], {'renderer_info': helper, 'renderer_name': 'loo.foo', 'request': request, @@ -287,6 +287,23 @@ class TestRendererHelper(unittest.TestCase): response = helper._make_response(la.encode('utf-8'), request) self.assertEqual(response.body, la.encode('utf-8')) + def test__make_response_result_is_iterable(self): + from pyramid.response import Response + request = testing.DummyRequest() + request.response = Response() + helper = self._makeOne('loo.foo') + la = text_('/La Pe\xc3\xb1a', 'utf-8') + response = helper._make_response([la.encode('utf-8')], request) + self.assertEqual(response.body, la.encode('utf-8')) + + def test__make_response_result_is_other(self): + self._registerResponseFactory() + request = None + helper = self._makeOne('loo.foo') + result = object() + response = helper._make_response(result, request) + self.assertEqual(response.body, result) + def test__make_response_result_is_None_no_body(self): from pyramid.response import Response request = testing.DummyRequest() -- cgit v1.2.3 From 1dfd12a21edba88f19d3f9af3ba6d127a461512d Mon Sep 17 00:00:00 2001 From: John Anderson Date: Sun, 14 Dec 2014 20:14:08 -0800 Subject: Fix checks for @wsgiapp2, MultiView, and add request method --- pyramid/scripts/proutes.py | 288 +++++++++++++++++++++-------- pyramid/tests/test_scripts/test_proutes.py | 255 +++++++++++++++++++++++-- 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 = '' 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 = '' + 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 = '' + + 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 = '' - 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', '']) + self.assertEqual(L[-1].split(), ['a', '/a', '', '*']) 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', '']) + self.assertEqual(L[-1].split(), ['a', '/a', '', '*']) 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', '']) 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 = '' + ] + 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', + '', + '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', + '', + 'POST', + ] + self.assertEqual(compare_to, expected) + class Test_main(unittest.TestCase): def _callFUT(self, argv): from pyramid.scripts.proutes import main -- cgit v1.2.3 From e2274e5d1aa8e125c26a2e8d168a64e1b9e68db2 Mon Sep 17 00:00:00 2001 From: John Anderson Date: Sun, 14 Dec 2014 20:27:26 -0800 Subject: Fix py26 support --- pyramid/scripts/proutes.py | 14 ++++++++++---- tox.ini | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/pyramid/scripts/proutes.py b/pyramid/scripts/proutes.py index b9a33f5bc..d68f35cef 100644 --- a/pyramid/scripts/proutes.py +++ b/pyramid/scripts/proutes.py @@ -13,7 +13,6 @@ from pyramid.interfaces import ( from pyramid.scripts.common import parse_vars from pyramid.static import static_view from zope.interface import Interface -from collections import OrderedDict PAD = 3 @@ -117,14 +116,15 @@ def get_route_data(route, registry): ) route_request_methods = None - view_request_methods = OrderedDict() + view_request_methods_order = [] + view_request_methods = {} view_callable = None route_intr = registry.introspector.get( 'routes', route.name ) - if (request_iface is None) or (route.factory is not None): + if request_iface is None: return [ (route.name, _get_pattern(route), UNKNOWN_KEY, ANY_KEY) ] @@ -136,10 +136,12 @@ def get_route_data(route, registry): 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: route_request_methods = route_intr['request_methods'] @@ -155,6 +157,7 @@ def get_route_data(route, registry): 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,) @@ -163,13 +166,16 @@ def get_route_data(route, registry): 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, methods in view_request_methods.items(): + for view_module in view_request_methods_order: + methods = view_request_methods[view_module] request_methods = _get_request_methods( route_request_methods, methods diff --git a/tox.ini b/tox.ini index 714c5b6d3..0b77d588a 100644 --- a/tox.ini +++ b/tox.ini @@ -9,7 +9,7 @@ commands = [testenv:cover] basepython = - python2.6 + python2.7 commands = python setup.py dev python setup.py nosetests --with-xunit --with-xcoverage -- cgit v1.2.3 From 59ee9f5fe96f133ff3582dbf34ea7da43ef39029 Mon Sep 17 00:00:00 2001 From: John Anderson Date: Sun, 14 Dec 2014 20:35:03 -0800 Subject: Switch tox back to 2.6, was supposed to be local --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 0b77d588a..714c5b6d3 100644 --- a/tox.ini +++ b/tox.ini @@ -9,7 +9,7 @@ commands = [testenv:cover] basepython = - python2.7 + python2.6 commands = python setup.py dev python setup.py nosetests --with-xunit --with-xcoverage -- cgit v1.2.3 From 228a5e5b3806c07c4d568ec491c4f83be5facbb1 Mon Sep 17 00:00:00 2001 From: John Anderson Date: Sun, 14 Dec 2014 21:09:48 -0800 Subject: Added support for `not_` request_method checks --- pyramid/scripts/proutes.py | 28 ++++++++++++-- pyramid/tests/test_scripts/test_proutes.py | 60 ++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 3 deletions(-) diff --git a/pyramid/scripts/proutes.py b/pyramid/scripts/proutes.py index d68f35cef..5d860b47d 100644 --- a/pyramid/scripts/proutes.py +++ b/pyramid/scripts/proutes.py @@ -9,6 +9,7 @@ from pyramid.interfaces import ( IViewClassifier, IView, ) +from pyramid.config import not_ from pyramid.scripts.common import parse_vars from pyramid.static import static_view @@ -44,6 +45,19 @@ def _get_print_format(max_name, max_pattern, max_view, max_method): 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 @@ -55,14 +69,20 @@ def _get_request_methods(route_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) + 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 = '' elif request_methods: - request_methods = ','.join(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 @@ -161,6 +181,8 @@ def get_route_data(route, registry): 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: diff --git a/pyramid/tests/test_scripts/test_proutes.py b/pyramid/tests/test_scripts/test_proutes.py index 0713f4ac9..f7a85bd1c 100644 --- a/pyramid/tests/test_scripts/test_proutes.py +++ b/pyramid/tests/test_scripts/test_proutes.py @@ -471,6 +471,66 @@ class TestPRoutesCommand(unittest.TestCase): ] 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) + class Test_main(unittest.TestCase): def _callFUT(self, argv): from pyramid.scripts.proutes import main -- cgit v1.2.3 From 1264707de3f9fa2d15943da3bf321b1304556aa3 Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Tue, 23 Dec 2014 14:58:13 -0800 Subject: Add a --glob option to limit output E.g.: $ proutes development.ini --glob='user*' Name Pattern View Method ---- ------- ---- ------ user /profilesvc/v1/user/{user_id} pyramid.config.views. * user /profilesvc/v1/user/{user_id} cornice.service.wrapper GET,HEAD --- pyramid/scripts/proutes.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pyramid/scripts/proutes.py b/pyramid/scripts/proutes.py index 5d860b47d..fce90c0c1 100644 --- a/pyramid/scripts/proutes.py +++ b/pyramid/scripts/proutes.py @@ -1,3 +1,4 @@ +import fnmatch import optparse import sys import textwrap @@ -234,6 +235,9 @@ class PRoutesCommand(object): usage, description=textwrap.dedent(description) ) + parser.add_option('-g', '--glob', + action='store', type='string', dest='glob', + default='', help='Display routes matching glob pattern') def __init__(self, argv, quiet=False): self.options, self.args = self.parser.parse_args(argv[1:]) @@ -280,6 +284,12 @@ class PRoutesCommand(object): 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) -- cgit v1.2.3 From c966a9283c2c6515848e8a5f4f7c34e0fba733a8 Mon Sep 17 00:00:00 2001 From: John Anderson Date: Thu, 25 Dec 2014 19:36:18 -0800 Subject: Added test for the view glob --- pyramid/tests/test_scripts/test_proutes.py | 41 ++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/pyramid/tests/test_scripts/test_proutes.py b/pyramid/tests/test_scripts/test_proutes.py index f7a85bd1c..4622383be 100644 --- a/pyramid/tests/test_scripts/test_proutes.py +++ b/pyramid/tests/test_scripts/test_proutes.py @@ -531,6 +531,47 @@ class TestPRoutesCommand(unittest.TestCase): ] 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) + class Test_main(unittest.TestCase): def _callFUT(self, argv): from pyramid.scripts.proutes import main -- cgit v1.2.3 From a99d2d7e17f4c87d9307215a2ad8d583e74c3a50 Mon Sep 17 00:00:00 2001 From: John Anderson Date: Thu, 25 Dec 2014 20:49:00 -0800 Subject: Add the --format flag --- pyramid/scripts/proutes.py | 106 ++++++++++++++++++++++++----- pyramid/tests/test_scripts/test_proutes.py | 94 +++++++++++++++++++++++++ 2 files changed, 183 insertions(+), 17 deletions(-) diff --git a/pyramid/scripts/proutes.py b/pyramid/scripts/proutes.py index fce90c0c1..917d9af71 100644 --- a/pyramid/scripts/proutes.py +++ b/pyramid/scripts/proutes.py @@ -4,7 +4,7 @@ import sys import textwrap from pyramid.paster import bootstrap -from pyramid.compat import string_types +from pyramid.compat import (string_types, configparser) from pyramid.interfaces import ( IRouteRequest, IViewClassifier, @@ -35,14 +35,22 @@ def _get_pattern(route): 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_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): @@ -230,7 +238,7 @@ class PRoutesCommand(object): bootstrap = (bootstrap,) stdout = sys.stdout usage = '%prog config_uri' - + ConfigParser = configparser.ConfigParser # testing parser = optparse.OptionParser( usage, description=textwrap.dedent(description) @@ -239,9 +247,49 @@ class PRoutesCommand(object): 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: + self.column_format = [x.strip() for x in v.split('\n')] + + except configparser.NoSectionError: + return def out(self, msg): # pragma: no cover if not self.quiet: @@ -261,6 +309,16 @@ class PRoutesCommand(object): env = self.bootstrap[0](config_uri, options=parse_vars(self.args[1:])) registry = env['registry'] mapper = self._get_mapper(registry) + 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 @@ -275,10 +333,17 @@ class PRoutesCommand(object): if len(routes) == 0: return 0 - mapped_routes = [ - ('Name', 'Pattern', 'View', 'Method'), - ('----', '-------', '----', '------') - ] + 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) @@ -302,12 +367,19 @@ class PRoutesCommand(object): if len(method) > max_method: max_method = len(method) - mapped_routes.append((name, pattern, view, method)) + mapped_routes.append({ + 'name': name, + 'pattern': pattern, + 'view': view, + 'method': method + }) - fmt = _get_print_format(max_name, max_pattern, max_view, max_method) + fmt = _get_print_format( + self.column_format, max_name, max_pattern, max_view, max_method + ) for route in mapped_routes: - self.out(fmt % route) + self.out(fmt.format(**route)) return 0 diff --git a/pyramid/tests/test_scripts/test_proutes.py b/pyramid/tests/test_scripts/test_proutes.py index 4622383be..d51baaa01 100644 --- a/pyramid/tests/test_scripts/test_proutes.py +++ b/pyramid/tests/test_scripts/test_proutes.py @@ -20,6 +20,7 @@ class TestPRoutesCommand(unittest.TestCase): cmd = self._getTargetClass()([]) cmd.bootstrap = (dummy.DummyBootstrap(),) cmd.args = ('/foo/bar/myapp.ini#myapp',) + return cmd def _makeRegistry(self): @@ -572,6 +573,99 @@ class TestPRoutesCommand(unittest.TestCase): ] 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(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),) + 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']) + class Test_main(unittest.TestCase): def _callFUT(self, argv): from pyramid.scripts.proutes import main -- cgit v1.2.3 From 88c11a8f09d6e9749a705cb62caa945c496e84e9 Mon Sep 17 00:00:00 2001 From: John Anderson Date: Fri, 26 Dec 2014 00:30:25 -0800 Subject: Refactored how `ResponseClass` is used so it can be overridden --- pyramid/renderers.py | 7 +++---- pyramid/request.py | 10 +++++----- pyramid/util.py | 21 +++++++++++++++++++++ 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/pyramid/renderers.py b/pyramid/renderers.py index e647ebacf..3d5390840 100644 --- a/pyramid/renderers.py +++ b/pyramid/renderers.py @@ -10,7 +10,6 @@ from zope.interface.registry import Components from pyramid.interfaces import ( IJSONAdapter, IRendererFactory, - IResponseFactory, IRendererInfo, ) @@ -19,6 +18,8 @@ from pyramid.compat import ( text_type, ) +from pyramid.util import _get_response_factory + from pyramid.decorator import reify from pyramid.events import BeforeRender @@ -448,9 +449,7 @@ class RendererHelper(object): if response is None: # request is None or request is not a pyramid.response.Response registry = self.registry - response_factory = registry.queryUtility(IResponseFactory, - default=Response) - + response_factory = _get_response_factory(registry, request) response = response_factory() if result is not None: diff --git a/pyramid/request.py b/pyramid/request.py index bc2889310..b66b8926c 100644 --- a/pyramid/request.py +++ b/pyramid/request.py @@ -10,7 +10,6 @@ from pyramid.interfaces import ( IRequest, IResponse, ISessionFactory, - IResponseFactory, ) from pyramid.compat import ( @@ -27,7 +26,10 @@ from pyramid.security import ( AuthorizationAPIMixin, ) from pyramid.url import URLMethodsMixin -from pyramid.util import InstancePropertyMixin +from pyramid.util import ( + InstancePropertyMixin, + _get_response_factory, +) class TemplateContext(object): pass @@ -214,9 +216,7 @@ class Request( right" attributes (e.g. by calling ``request.response.set_cookie()``) within a view that uses a renderer. Mutations to this response object will be preserved in the response sent to the client.""" - registry = self.registry - response_factory = registry.queryUtility(IResponseFactory, - default=Response) + response_factory = _get_response_factory(self.registry, self) return response_factory() def is_response(self, ob): diff --git a/pyramid/util.py b/pyramid/util.py index 6de53d559..ee642164a 100644 --- a/pyramid/util.py +++ b/pyramid/util.py @@ -15,6 +15,10 @@ from pyramid.exceptions import ( CyclicDependencyError, ) +from pyramid.interfaces import ( + IResponseFactory, + ) + from pyramid.compat import ( iteritems_, is_nonstr_iter, @@ -25,6 +29,7 @@ from pyramid.compat import ( ) from pyramid.interfaces import IActionInfo +from pyramid.response import Response from pyramid.path import DottedNameResolver as _DottedNameResolver class DottedNameResolver(_DottedNameResolver): @@ -551,3 +556,19 @@ def action_method(wrapped): wrapper.__docobj__ = wrapped return wrapper +def _get_response_factory(registry, request=None): + """ Obtain a :class: `pyramid.response.Response` using the + ``request.ResponseClass`` property if available. + """ + # Request is `None` or does not have a `ResponseClass` + if hasattr(request, 'ResponseClass'): + response_class = request.ResponseClass + else: + response_class = Response + + response_factory = registry.queryUtility( + IResponseFactory, + default=response_class + ) + + return response_factory -- cgit v1.2.3 From 367ffec92bd502075e0f2f827f4b9d9b3caea748 Mon Sep 17 00:00:00 2001 From: John Anderson Date: Fri, 26 Dec 2014 00:44:44 -0800 Subject: Add test to show usage of custom response class --- pyramid/tests/test_config/test_init.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/pyramid/tests/test_config/test_init.py b/pyramid/tests/test_config/test_init.py index 1e58e4d0f..c9eaf7c27 100644 --- a/pyramid/tests/test_config/test_init.py +++ b/pyramid/tests/test_config/test_init.py @@ -546,6 +546,36 @@ class ConfiguratorTests(unittest.TestCase): utility = reg.getUtility(IRequestFactory) self.assertEqual(utility, factory) + def test_setup_registry_request_factory_custom_response_class(self): + from pyramid.registry import Registry + from pyramid.interfaces import IRequestFactory + from pyramid.request import Request + + class MyResponse(object): + pass + + class MyRequest(Request): + ResponseClass = MyResponse + + reg = Registry() + config = self._makeOne(reg) + factory = MyRequest({ + 'PATH_INFO': '/', + 'wsgi.url_scheme': 'http', + 'HTTP_HOST': 'localhost', + 'SERVER_PROTOCOL': '1.0', + }) + factory.registry = reg + + config.setup_registry(request_factory=factory) + self.assertEqual(reg.queryUtility(IRequestFactory), None) + config.commit() + utility = reg.getUtility(IRequestFactory) + self.assertEqual(utility, factory) + + new_response = factory.response + self.assertTrue(isinstance(new_response, MyResponse)) + def test_setup_registry_request_factory_dottedname(self): from pyramid.registry import Registry from pyramid.interfaces import IRequestFactory -- cgit v1.2.3 From c0a01d1a49c9a02b1aa7266301fd64fb679e1d53 Mon Sep 17 00:00:00 2001 From: John Anderson Date: Fri, 26 Dec 2014 04:17:42 -0800 Subject: Added 2 more tests directly to the util function --- pyramid/tests/test_util.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/pyramid/tests/test_util.py b/pyramid/tests/test_util.py index a18fa8d16..c9c48aede 100644 --- a/pyramid/tests/test_util.py +++ b/pyramid/tests/test_util.py @@ -619,6 +619,33 @@ class TestActionInfo(unittest.TestCase): "Line 0 of file filename:\n linerepr ") +class TestGetResponseFactory(unittest.TestCase): + def test_no_request(self): + from pyramid.util import _get_response_factory + from pyramid.registry import Registry + from pyramid.response import Response + + registry = Registry() + factory = _get_response_factory(registry) + self.assertEqual(factory, Response) + + def test_with_request(self): + from pyramid.util import _get_response_factory + from pyramid.registry import Registry + from pyramid.request import Request + + class MyResponse(object): + pass + + class MyRequest(Request): + ResponseClass = MyResponse + registry = Registry() + + request = MyRequest({}) + factory = _get_response_factory(registry, request) + self.assertEqual(factory, MyResponse) + + def dummyfunc(): pass class Dummy(object): -- cgit v1.2.3 From 582c2ed180120d07c825e4897350f5d1a6285afa Mon Sep 17 00:00:00 2001 From: John Anderson Date: Fri, 26 Dec 2014 05:06:33 -0800 Subject: Add support for static routes --- pyramid/config/routes.py | 6 ++++++ pyramid/scripts/proutes.py | 9 +++++++-- pyramid/tests/test_scripts/dummy.py | 2 +- pyramid/tests/test_scripts/test_proutes.py | 20 ++++++++++++++++++++ pyramid/urldispatch.py | 11 ++++++++++- 5 files changed, 44 insertions(+), 4 deletions(-) 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 917d9af71..2155b4983 100644 --- a/pyramid/scripts/proutes.py +++ b/pyramid/scripts/proutes.py @@ -172,8 +172,13 @@ def get_route_data(route, registry): view_request_methods[view_module] = [] view_request_methods_order.append(view_module) else: - route_request_methods = route_intr['request_methods'] + 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: @@ -328,7 +333,7 @@ class PRoutesCommand(object): max_view = len('View') max_method = len('Method') - routes = mapper.get_routes() + routes = mapper.get_routes(include_static=True) if len(routes) == 0: return 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 d51baaa01..446d772ff 100644 --- a/pyramid/tests/test_scripts/test_proutes.py +++ b/pyramid/tests/test_scripts/test_proutes.py @@ -666,6 +666,26 @@ class TestPRoutesCommand(unittest.TestCase): 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', + '', '*', + ] + 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 -- cgit v1.2.3 From e21662924a296f391fdbbf725fb79e409bca59f4 Mon Sep 17 00:00:00 2001 From: John Anderson Date: Fri, 26 Dec 2014 05:11:51 -0800 Subject: pass proper registry --- pyramid/tests/test_util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyramid/tests/test_util.py b/pyramid/tests/test_util.py index c9c48aede..0ebbd80a2 100644 --- a/pyramid/tests/test_util.py +++ b/pyramid/tests/test_util.py @@ -642,7 +642,7 @@ class TestGetResponseFactory(unittest.TestCase): registry = Registry() request = MyRequest({}) - factory = _get_response_factory(registry, request) + factory = _get_response_factory(request.registry, request) self.assertEqual(factory, MyResponse) -- cgit v1.2.3 From d579409e656df9f92a89000d66e60ec71b5857bc Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Fri, 26 Dec 2014 21:35:06 -0800 Subject: - update theme with new image --- docs/_themes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_themes b/docs/_themes index 3bec9280a..b14bf8c2a 160000 --- a/docs/_themes +++ b/docs/_themes @@ -1 +1 @@ -Subproject commit 3bec9280a6cedb15e97e5899021aa8d723c25388 +Subproject commit b14bf8c2a0d95ae8e3d38d07ad3721370ae6f3f8 -- cgit v1.2.3 From 1236dec0dcfd916bca4e233587f86baa8d2418a8 Mon Sep 17 00:00:00 2001 From: John Anderson Date: Sat, 27 Dec 2014 00:18:23 -0800 Subject: Add the `set_response_factory` API --- docs/narr/hooks.rst | 62 +++++++++++++++++++++++++++++ pyramid/config/__init__.py | 16 +++++++- pyramid/config/factories.py | 27 +++++++++++++ pyramid/tests/test_config/test_factories.py | 15 +++++++ pyramid/tests/test_config/test_init.py | 12 ++++++ 5 files changed, 130 insertions(+), 2 deletions(-) diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index 4da36e730..f557527bb 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -354,6 +354,68 @@ We attach and cache an object named ``extra`` to the ``request`` object. .. _beforerender_event: +.. index:: + single: response factory + +.. _changing_the_response_factory: + +Changing the Response Factory +---------------------------- + +Whenever :app:`Pyramid` returns a response from a view it creates a +:term:`response` object. By default, an instance of the +:class:`pyramid.response.Response` class is created to represent the response +object. + +The class (aka "factory") that :app:`Pyramid` uses to create a response object +instance can be changed by passing a ``response_factory`` argument to the +constructor of the :term:`configurator`. This argument can be either a +callable or a :term:`dotted Python name` representing a callable. + +.. code-block:: python + :linenos: + + from pyramid.response import Response + + class MyResponse(Response): + pass + + config = Configurator(response_factory=MyResponse) + +If you're doing imperative configuration, and you'd rather do it after you've +already constructed a :term:`configurator` it can also be registered via the +:meth:`pyramid.config.Configurator.set_response_factory` method: + +.. code-block:: python + :linenos: + + from pyramid.config import Configurator + from pyramid.response import Response + + class MyResponse(Response): + pass + + config = Configurator() + config.set_response_factory(MyRequest) + +If you are already using a custom ```request_factory`` you can also set the +``ResponseClass`` on your :class:`pyramid.request.Request`: + +.. code-block:: python + :linenos: + + from pyramid.config import Configurator + from pyramid.response import Response + from pyramid.request import Request + + class MyResponse(Response): + pass + + class MyRequest(Request): + ResponseClass = MyResponse + + config = Configurator() + Using The Before Render Event ----------------------------- diff --git a/pyramid/config/__init__.py b/pyramid/config/__init__.py index cfa35ec6c..2ab654b9a 100644 --- a/pyramid/config/__init__.py +++ b/pyramid/config/__init__.py @@ -179,6 +179,11 @@ class Configurator( See :ref:`changing_the_request_factory`. By default it is ``None``, which means use the default request factory. + If ``response_factory`` is passed, it should be a :term:`response + factory` implementation or a :term:`dotted Python name` to the same. + See :ref:`changing_the_response_factory`. By default it is ``None``, + which means use the default response factory. + If ``default_permission`` is passed, it should be a :term:`permission` string to be used as the default permission for all view configuration registrations performed against this @@ -190,7 +195,7 @@ class Configurator( configurations which do not explicitly declare a permission will always be executable by entirely anonymous users (any authorization policy in effect is ignored). - + .. seealso:: See also :ref:`setting_a_default_permission`. @@ -254,6 +259,7 @@ class Configurator( .. versionadded:: 1.6 The ``root_package`` argument. + The ``response_factory`` argument. """ manager = manager # for testing injection venusian = venusian # for testing injection @@ -276,6 +282,7 @@ class Configurator( debug_logger=None, locale_negotiator=None, request_factory=None, + response_factory=None, default_permission=None, session_factory=None, default_view_mapper=None, @@ -310,6 +317,7 @@ class Configurator( debug_logger=debug_logger, locale_negotiator=locale_negotiator, request_factory=request_factory, + response_factory=response_factory, default_permission=default_permission, session_factory=session_factory, default_view_mapper=default_view_mapper, @@ -325,6 +333,7 @@ class Configurator( debug_logger=None, locale_negotiator=None, request_factory=None, + response_factory=None, default_permission=None, session_factory=None, default_view_mapper=None, @@ -412,6 +421,9 @@ class Configurator( if request_factory: self.set_request_factory(request_factory) + if response_factory: + self.set_response_factory(response_factory) + if default_permission: self.set_default_permission(default_permission) @@ -469,7 +481,7 @@ class Configurator( _registry.registerSelfAdapter = registerSelfAdapter # API - + def _get_introspector(self): introspector = getattr(self.registry, 'introspector', _marker) if introspector is _marker: diff --git a/pyramid/config/factories.py b/pyramid/config/factories.py index 5ce1081c6..d7a48ba93 100644 --- a/pyramid/config/factories.py +++ b/pyramid/config/factories.py @@ -4,6 +4,7 @@ from zope.interface import implementer from pyramid.interfaces import ( IDefaultRootFactory, IRequestFactory, + IResponseFactory, IRequestExtensions, IRootFactory, ISessionFactory, @@ -96,6 +97,32 @@ class FactoriesConfiguratorMixin(object): intr['factory'] = factory self.action(IRequestFactory, register, introspectables=(intr,)) + @action_method + def set_response_factory(self, factory): + """ The object passed as ``factory`` should be an object (or a + :term:`dotted Python name` which refers to an object) which + will be used by the :app:`Pyramid` as the default response + objects. This factory object must have the same + methods and attributes as the + :class:`pyramid.request.Response` class. + + .. note:: + + Using the ``response_factory`` argument to the + :class:`pyramid.config.Configurator` constructor + can be used to achieve the same purpose. + """ + factory = self.maybe_dotted(factory) + + def register(): + self.registry.registerUtility(factory, IResponseFactory) + + intr = self.introspectable('response factory', None, + self.object_description(factory), + 'response factory') + intr['factory'] = factory + self.action(IResponseFactory, register, introspectables=(intr,)) + @action_method def add_request_method(self, callable=None, diff --git a/pyramid/tests/test_config/test_factories.py b/pyramid/tests/test_config/test_factories.py index 6e679397f..c6785d4a5 100644 --- a/pyramid/tests/test_config/test_factories.py +++ b/pyramid/tests/test_config/test_factories.py @@ -23,6 +23,21 @@ class TestFactoriesMixin(unittest.TestCase): self.assertEqual(config.registry.getUtility(IRequestFactory), dummyfactory) + def test_set_response_factory(self): + from pyramid.interfaces import IResponseFactory + config = self._makeOne(autocommit=True) + factory = object() + config.set_response_factory(factory) + self.assertEqual(config.registry.getUtility(IResponseFactory), factory) + + def test_set_response_factory_dottedname(self): + from pyramid.interfaces import IResponseFactory + config = self._makeOne(autocommit=True) + config.set_response_factory( + 'pyramid.tests.test_config.dummyfactory') + self.assertEqual(config.registry.getUtility(IResponseFactory), + dummyfactory) + def test_set_root_factory(self): from pyramid.interfaces import IRootFactory config = self._makeOne() diff --git a/pyramid/tests/test_config/test_init.py b/pyramid/tests/test_config/test_init.py index c9eaf7c27..8fa6be011 100644 --- a/pyramid/tests/test_config/test_init.py +++ b/pyramid/tests/test_config/test_init.py @@ -546,6 +546,18 @@ class ConfiguratorTests(unittest.TestCase): utility = reg.getUtility(IRequestFactory) self.assertEqual(utility, factory) + def test_setup_registry_response_factory(self): + from pyramid.registry import Registry + from pyramid.interfaces import IResponseFactory + reg = Registry() + config = self._makeOne(reg) + factory = object() + config.setup_registry(response_factory=factory) + self.assertEqual(reg.queryUtility(IResponseFactory), None) + config.commit() + utility = reg.getUtility(IResponseFactory) + self.assertEqual(utility, factory) + def test_setup_registry_request_factory_custom_response_class(self): from pyramid.registry import Registry from pyramid.interfaces import IRequestFactory -- cgit v1.2.3 From e8a666655b5365a0adde32f2bd387b0d42690384 Mon Sep 17 00:00:00 2001 From: John Anderson Date: Sat, 27 Dec 2014 00:23:18 -0800 Subject: basic docs cleanup --- docs/glossary.rst | 3 +++ docs/narr/hooks.rst | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/glossary.rst b/docs/glossary.rst index 01300a0be..05ff7c114 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -16,6 +16,9 @@ Glossary An object which, provided a :term:`WSGI` environment as a single positional argument, returns a Pyramid-compatible request. + response factory + An object which returns a Pyramid-compatible response. + response An object returned by a :term:`view callable` that represents response data returned to the requesting user agent. It must implement the diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index f557527bb..4702c09b0 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -360,7 +360,7 @@ We attach and cache an object named ``extra`` to the ``request`` object. .. _changing_the_response_factory: Changing the Response Factory ----------------------------- +------------------------------- Whenever :app:`Pyramid` returns a response from a view it creates a :term:`response` object. By default, an instance of the -- cgit v1.2.3 From 807e941787e157db882fcd95e13f5cafb7ebde7f Mon Sep 17 00:00:00 2001 From: John Anderson Date: Sat, 27 Dec 2014 13:33:39 -0800 Subject: Added a version added flag --- docs/narr/hooks.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index 4702c09b0..689ce9dc2 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -362,6 +362,8 @@ We attach and cache an object named ``extra`` to the ``request`` object. Changing the Response Factory ------------------------------- +.. versionadded:: 1.6 + Whenever :app:`Pyramid` returns a response from a view it creates a :term:`response` object. By default, an instance of the :class:`pyramid.response.Response` class is created to represent the response -- cgit v1.2.3 From 32cb805132e8149a276a8c65fdfa961384e8254e Mon Sep 17 00:00:00 2001 From: John Anderson Date: Thu, 1 Jan 2015 15:03:56 -0800 Subject: Mkae the response factory a factory that takes a request --- docs/glossary.rst | 3 ++- docs/narr/hooks.rst | 29 ++++++-------------------- pyramid/renderers.py | 4 ++-- pyramid/request.py | 4 ++-- pyramid/testing.py | 10 +++++---- pyramid/tests/test_config/test_factories.py | 2 +- pyramid/tests/test_config/test_init.py | 32 +---------------------------- pyramid/tests/test_renderers.py | 9 ++++++-- pyramid/tests/test_testing.py | 4 +++- pyramid/tests/test_util.py | 26 +++++------------------ pyramid/util.py | 13 ++++-------- 11 files changed, 39 insertions(+), 97 deletions(-) diff --git a/docs/glossary.rst b/docs/glossary.rst index 05ff7c114..38133f68f 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -17,7 +17,8 @@ Glossary positional argument, returns a Pyramid-compatible request. response factory - An object which returns a Pyramid-compatible response. + An object which, provied a :term:`request` as a single positional + argument, returns a Pyramid-compatible response. response An object returned by a :term:`view callable` that represents response diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index 689ce9dc2..e250c2d7e 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -369,10 +369,10 @@ Whenever :app:`Pyramid` returns a response from a view it creates a :class:`pyramid.response.Response` class is created to represent the response object. -The class (aka "factory") that :app:`Pyramid` uses to create a response object -instance can be changed by passing a ``response_factory`` argument to the -constructor of the :term:`configurator`. This argument can be either a -callable or a :term:`dotted Python name` representing a callable. +The factory that :app:`Pyramid` uses to create a response object instance can be +changed by passing a ``response_factory`` argument to the constructor of the +:term:`configurator`. This argument can be either a callable or a +:term:`dotted Python name` representing a callable. .. code-block:: python :linenos: @@ -382,7 +382,7 @@ callable or a :term:`dotted Python name` representing a callable. class MyResponse(Response): pass - config = Configurator(response_factory=MyResponse) + config = Configurator(response_factory=lambda r: MyResponse()) If you're doing imperative configuration, and you'd rather do it after you've already constructed a :term:`configurator` it can also be registered via the @@ -398,25 +398,8 @@ already constructed a :term:`configurator` it can also be registered via the pass config = Configurator() - config.set_response_factory(MyRequest) - -If you are already using a custom ```request_factory`` you can also set the -``ResponseClass`` on your :class:`pyramid.request.Request`: - -.. code-block:: python - :linenos: - - from pyramid.config import Configurator - from pyramid.response import Response - from pyramid.request import Request - - class MyResponse(Response): - pass - - class MyRequest(Request): - ResponseClass = MyResponse + config.set_response_factory(lambda r: MyResponse()) - config = Configurator() Using The Before Render Event ----------------------------- diff --git a/pyramid/renderers.py b/pyramid/renderers.py index 3d5390840..51dbd318b 100644 --- a/pyramid/renderers.py +++ b/pyramid/renderers.py @@ -449,8 +449,8 @@ class RendererHelper(object): if response is None: # request is None or request is not a pyramid.response.Response registry = self.registry - response_factory = _get_response_factory(registry, request) - response = response_factory() + response_factory = _get_response_factory(registry) + response = response_factory(request) if result is not None: if isinstance(result, text_type): diff --git a/pyramid/request.py b/pyramid/request.py index b66b8926c..fc957fae2 100644 --- a/pyramid/request.py +++ b/pyramid/request.py @@ -216,8 +216,8 @@ class Request( right" attributes (e.g. by calling ``request.response.set_cookie()``) within a view that uses a renderer. Mutations to this response object will be preserved in the response sent to the client.""" - response_factory = _get_response_factory(self.registry, self) - return response_factory() + response_factory = _get_response_factory(self.registry) + return response_factory(self) def is_response(self, ob): """ Return ``True`` if the object passed as ``ob`` is a valid diff --git a/pyramid/testing.py b/pyramid/testing.py index f77889e72..66c694b31 100644 --- a/pyramid/testing.py +++ b/pyramid/testing.py @@ -9,7 +9,6 @@ from zope.interface import ( from pyramid.interfaces import ( IRequest, - IResponseFactory, ISession, ) @@ -40,7 +39,10 @@ from pyramid.threadlocal import ( from pyramid.i18n import LocalizerRequestMixin from pyramid.request import CallbackMethodsMixin from pyramid.url import URLMethodsMixin -from pyramid.util import InstancePropertyMixin +from pyramid.util import ( + InstancePropertyMixin, + _get_response_factory + ) _marker = object() @@ -383,8 +385,8 @@ class DummyRequest( @reify def response(self): - f = self.registry.queryUtility(IResponseFactory, default=Response) - return f() + f = _get_response_factory(self.registry) + return f(self) have_zca = True diff --git a/pyramid/tests/test_config/test_factories.py b/pyramid/tests/test_config/test_factories.py index c6785d4a5..0bd5336ff 100644 --- a/pyramid/tests/test_config/test_factories.py +++ b/pyramid/tests/test_config/test_factories.py @@ -26,7 +26,7 @@ class TestFactoriesMixin(unittest.TestCase): def test_set_response_factory(self): from pyramid.interfaces import IResponseFactory config = self._makeOne(autocommit=True) - factory = object() + factory = lambda r: object() config.set_response_factory(factory) self.assertEqual(config.registry.getUtility(IResponseFactory), factory) diff --git a/pyramid/tests/test_config/test_init.py b/pyramid/tests/test_config/test_init.py index 8fa6be011..aeebe3c91 100644 --- a/pyramid/tests/test_config/test_init.py +++ b/pyramid/tests/test_config/test_init.py @@ -551,43 +551,13 @@ class ConfiguratorTests(unittest.TestCase): from pyramid.interfaces import IResponseFactory reg = Registry() config = self._makeOne(reg) - factory = object() + factory = lambda r: object() config.setup_registry(response_factory=factory) self.assertEqual(reg.queryUtility(IResponseFactory), None) config.commit() utility = reg.getUtility(IResponseFactory) self.assertEqual(utility, factory) - def test_setup_registry_request_factory_custom_response_class(self): - from pyramid.registry import Registry - from pyramid.interfaces import IRequestFactory - from pyramid.request import Request - - class MyResponse(object): - pass - - class MyRequest(Request): - ResponseClass = MyResponse - - reg = Registry() - config = self._makeOne(reg) - factory = MyRequest({ - 'PATH_INFO': '/', - 'wsgi.url_scheme': 'http', - 'HTTP_HOST': 'localhost', - 'SERVER_PROTOCOL': '1.0', - }) - factory.registry = reg - - config.setup_registry(request_factory=factory) - self.assertEqual(reg.queryUtility(IRequestFactory), None) - config.commit() - utility = reg.getUtility(IRequestFactory) - self.assertEqual(utility, factory) - - new_response = factory.response - self.assertTrue(isinstance(new_response, MyResponse)) - def test_setup_registry_request_factory_dottedname(self): from pyramid.registry import Registry from pyramid.interfaces import IRequestFactory diff --git a/pyramid/tests/test_renderers.py b/pyramid/tests/test_renderers.py index 2bddd2318..21878b41f 100644 --- a/pyramid/tests/test_renderers.py +++ b/pyramid/tests/test_renderers.py @@ -182,7 +182,10 @@ class TestRendererHelper(unittest.TestCase): from pyramid.interfaces import IResponseFactory class ResponseFactory(object): pass - self.config.registry.registerUtility(ResponseFactory, IResponseFactory) + + self.config.registry.registerUtility( + lambda r: ResponseFactory(), IResponseFactory + ) def test_render_to_response(self): self._registerRendererFactory() @@ -310,7 +313,9 @@ class TestRendererHelper(unittest.TestCase): class ResponseFactory(object): def __init__(self): pass - self.config.registry.registerUtility(ResponseFactory, IResponseFactory) + self.config.registry.registerUtility( + lambda r: ResponseFactory(), IResponseFactory + ) request = testing.DummyRequest() helper = self._makeOne('loo.foo') response = helper._make_response(b'abc', request) diff --git a/pyramid/tests/test_testing.py b/pyramid/tests/test_testing.py index dfcad2a0c..113f7e5f4 100644 --- a/pyramid/tests/test_testing.py +++ b/pyramid/tests/test_testing.py @@ -259,7 +259,9 @@ class TestDummyRequest(unittest.TestCase): registry = Registry('this_test') class ResponseFactory(object): pass - registry.registerUtility(ResponseFactory, IResponseFactory) + registry.registerUtility( + lambda r: ResponseFactory(), IResponseFactory + ) request = self._makeOne() request.registry = registry resp = request.response diff --git a/pyramid/tests/test_util.py b/pyramid/tests/test_util.py index 0ebbd80a2..ba128eede 100644 --- a/pyramid/tests/test_util.py +++ b/pyramid/tests/test_util.py @@ -324,7 +324,7 @@ class Test_object_description(unittest.TestCase): self.assertEqual( self._callFUT(inst), "object %s" % str(inst)) - + def test_shortened_repr(self): inst = ['1'] * 1000 self.assertEqual( @@ -592,7 +592,7 @@ class TestActionInfo(unittest.TestCase): def _getTargetClass(self): from pyramid.util import ActionInfo return ActionInfo - + def _makeOne(self, filename, lineno, function, linerepr): return self._getTargetClass()(filename, lineno, function, linerepr) @@ -620,30 +620,14 @@ class TestActionInfo(unittest.TestCase): class TestGetResponseFactory(unittest.TestCase): - def test_no_request(self): + def test_get_factory(self): from pyramid.util import _get_response_factory from pyramid.registry import Registry from pyramid.response import Response registry = Registry() - factory = _get_response_factory(registry) - self.assertEqual(factory, Response) - - def test_with_request(self): - from pyramid.util import _get_response_factory - from pyramid.registry import Registry - from pyramid.request import Request - - class MyResponse(object): - pass - - class MyRequest(Request): - ResponseClass = MyResponse - registry = Registry() - - request = MyRequest({}) - factory = _get_response_factory(request.registry, request) - self.assertEqual(factory, MyResponse) + response = _get_response_factory(registry)(None) + self.assertTrue(isinstance(response, Response)) def dummyfunc(): pass diff --git a/pyramid/util.py b/pyramid/util.py index ee642164a..7aecca19c 100644 --- a/pyramid/util.py +++ b/pyramid/util.py @@ -556,19 +556,14 @@ def action_method(wrapped): wrapper.__docobj__ = wrapped return wrapper -def _get_response_factory(registry, request=None): + +def _get_response_factory(registry): """ Obtain a :class: `pyramid.response.Response` using the - ``request.ResponseClass`` property if available. + `pyramid.interfaces.IResponseFactory`. """ - # Request is `None` or does not have a `ResponseClass` - if hasattr(request, 'ResponseClass'): - response_class = request.ResponseClass - else: - response_class = Response - response_factory = registry.queryUtility( IResponseFactory, - default=response_class + default=lambda r: Response() ) return response_factory -- cgit v1.2.3 From ff01cdf0e392eb4e7926970a0cdee75663edb431 Mon Sep 17 00:00:00 2001 From: John Anderson Date: Thu, 1 Jan 2015 15:10:55 -0800 Subject: Fix typo --- docs/glossary.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/glossary.rst b/docs/glossary.rst index 38133f68f..911c22075 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -17,7 +17,7 @@ Glossary positional argument, returns a Pyramid-compatible request. response factory - An object which, provied a :term:`request` as a single positional + An object which, provided a :term:`request` as a single positional argument, returns a Pyramid-compatible response. response -- cgit v1.2.3 From a62462606704081b37ae0cb4c9f07a4690480609 Mon Sep 17 00:00:00 2001 From: John Anderson Date: Thu, 1 Jan 2015 15:26:00 -0800 Subject: Added CHANGES entry for this PR --- CHANGES.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 46c331268..129ce4616 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -59,6 +59,11 @@ Features via ``request.static_url('myapp:static/foo.png')``. See https://github.com/Pylons/pyramid/issues/1252 +- Added ``pyramid.config.Configurator.set_response_factory`` and the + ``response_factory`` keyword argument to the ``Configurator`` for defining + a factory that will return a custom ``Response`` class. + See https://github.com/Pylons/pyramid/pull/1499 + Bug Fixes --------- -- cgit v1.2.3 From 0882f7f0f4648b37617543a196ec4d5bf6d65278 Mon Sep 17 00:00:00 2001 From: John Anderson Date: Thu, 1 Jan 2015 17:42:57 -0800 Subject: Setup logging with prequest --- pyramid/scripts/prequest.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyramid/scripts/prequest.py b/pyramid/scripts/prequest.py index 2ab3b8bb9..92faa146c 100644 --- a/pyramid/scripts/prequest.py +++ b/pyramid/scripts/prequest.py @@ -5,7 +5,7 @@ import textwrap from pyramid.compat import url_unquote from pyramid.request import Request -from pyramid.paster import get_app +from pyramid.paster import get_app, setup_logging from pyramid.scripts.common import parse_vars def main(argv=sys.argv, quiet=False): @@ -102,6 +102,7 @@ class PRequestCommand(object): self.out('You must provide at least two arguments') return 2 app_spec = self.args[0] + setup_logging(app_spec) path = self.args[1] if not path.startswith('/'): path = '/' + path -- cgit v1.2.3 From aa9aef58b5b2fe4c9135aadc5481d129f1300154 Mon Sep 17 00:00:00 2001 From: John Anderson Date: Thu, 1 Jan 2015 17:56:13 -0800 Subject: Added a test for configuring logging --- pyramid/scripts/prequest.py | 7 ++++++- pyramid/tests/test_scripts/test_prequest.py | 13 +++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/pyramid/scripts/prequest.py b/pyramid/scripts/prequest.py index 92faa146c..34eeadf32 100644 --- a/pyramid/scripts/prequest.py +++ b/pyramid/scripts/prequest.py @@ -97,13 +97,18 @@ class PRequestCommand(object): if not self.quiet: print(msg) + def configure_logging(self, app_spec): + setup_logging(app_spec) + def run(self): if not len(self.args) >= 2: self.out('You must provide at least two arguments') return 2 app_spec = self.args[0] - setup_logging(app_spec) path = self.args[1] + + self.configure_logging(app_spec) + if not path.startswith('/'): path = '/' + path diff --git a/pyramid/tests/test_scripts/test_prequest.py b/pyramid/tests/test_scripts/test_prequest.py index 37f1d3c0f..95cec0518 100644 --- a/pyramid/tests/test_scripts/test_prequest.py +++ b/pyramid/tests/test_scripts/test_prequest.py @@ -210,8 +210,21 @@ class TestPRequestCommand(unittest.TestCase): self.assertEqual(self._path_info, '/') self.assertEqual(self._spec, 'development.ini') self.assertEqual(self._app_name, None) + self.assertEqual(self._out, [b'abc']) + def test_command_method_configures_logging(self): + command = self._makeOne(['', 'development.ini', '/']) + called_args = [] + + def configure_logging(app_spec): + called_args.append(app_spec) + + command.configure_logging = configure_logging + command.run() + self.assertEqual(called_args, ['development.ini']) + + class Test_main(unittest.TestCase): def _callFUT(self, argv): from pyramid.scripts.prequest import main -- cgit v1.2.3 From 303abc8b585d97c75773b3cfa48b6e748c96fd64 Mon Sep 17 00:00:00 2001 From: John Anderson Date: Thu, 1 Jan 2015 20:15:12 -0800 Subject: Move _get_response_factory to pyramid.response --- pyramid/renderers.py | 4 +--- pyramid/request.py | 7 ++----- pyramid/response.py | 17 +++++++++++++++-- pyramid/testing.py | 8 +++----- pyramid/tests/test_response.py | 17 +++++++++++++---- pyramid/tests/test_util.py | 11 ----------- pyramid/util.py | 12 ------------ 7 files changed, 34 insertions(+), 42 deletions(-) diff --git a/pyramid/renderers.py b/pyramid/renderers.py index 51dbd318b..d57671865 100644 --- a/pyramid/renderers.py +++ b/pyramid/renderers.py @@ -18,15 +18,13 @@ from pyramid.compat import ( text_type, ) -from pyramid.util import _get_response_factory - from pyramid.decorator import reify from pyramid.events import BeforeRender from pyramid.path import caller_package -from pyramid.response import Response +from pyramid.response import Response, _get_response_factory from pyramid.threadlocal import get_current_registry # API diff --git a/pyramid/request.py b/pyramid/request.py index fc957fae2..b2e2efe05 100644 --- a/pyramid/request.py +++ b/pyramid/request.py @@ -20,16 +20,13 @@ from pyramid.compat import ( from pyramid.decorator import reify from pyramid.i18n import LocalizerRequestMixin -from pyramid.response import Response +from pyramid.response import Response, _get_response_factory from pyramid.security import ( AuthenticationAPIMixin, AuthorizationAPIMixin, ) from pyramid.url import URLMethodsMixin -from pyramid.util import ( - InstancePropertyMixin, - _get_response_factory, -) +from pyramid.util import InstancePropertyMixin class TemplateContext(object): pass diff --git a/pyramid/response.py b/pyramid/response.py index d11fd0123..892e5dfff 100644 --- a/pyramid/response.py +++ b/pyramid/response.py @@ -8,7 +8,8 @@ import venusian from webob import Response as _Response from zope.interface import implementer -from pyramid.interfaces import IResponse +from pyramid.interfaces import IResponse, IResponseFactory + def init_mimetypes(mimetypes): # this is a function so it can be unittested @@ -143,7 +144,7 @@ class response_adapter(object): @response_adapter(dict, list) def myadapter(ob): return Response(json.dumps(ob)) - + This method will have no effect until a :term:`scan` is performed agains the package or module which contains it, ala: @@ -167,3 +168,15 @@ class response_adapter(object): def __call__(self, wrapped): self.venusian.attach(wrapped, self.register, category='pyramid') return wrapped + + +def _get_response_factory(registry): + """ Obtain a :class: `pyramid.response.Response` using the + `pyramid.interfaces.IResponseFactory`. + """ + response_factory = registry.queryUtility( + IResponseFactory, + default=lambda r: Response() + ) + + return response_factory diff --git a/pyramid/testing.py b/pyramid/testing.py index 66c694b31..667e6af4e 100644 --- a/pyramid/testing.py +++ b/pyramid/testing.py @@ -21,7 +21,7 @@ from pyramid.compat import ( from pyramid.config import Configurator from pyramid.decorator import reify from pyramid.path import caller_package -from pyramid.response import Response +from pyramid.response import Response, _get_response_factory from pyramid.registry import Registry from pyramid.security import ( @@ -39,10 +39,8 @@ from pyramid.threadlocal import ( from pyramid.i18n import LocalizerRequestMixin from pyramid.request import CallbackMethodsMixin from pyramid.url import URLMethodsMixin -from pyramid.util import ( - InstancePropertyMixin, - _get_response_factory - ) +from pyramid.util import InstancePropertyMixin + _marker = object() diff --git a/pyramid/tests/test_response.py b/pyramid/tests/test_response.py index 84ec57757..ad55882c9 100644 --- a/pyramid/tests/test_response.py +++ b/pyramid/tests/test_response.py @@ -8,7 +8,7 @@ class TestResponse(unittest.TestCase): def _getTargetClass(self): from pyramid.response import Response return Response - + def test_implements_IResponse(self): from pyramid.interfaces import IResponse cls = self._getTargetClass() @@ -119,7 +119,7 @@ class Test_patch_mimetypes(unittest.TestCase): result = self._callFUT(module) self.assertEqual(result, True) self.assertEqual(module.initted, True) - + def test_missing_init(self): class DummyMimetypes(object): pass @@ -174,6 +174,17 @@ class TestResponseAdapter(unittest.TestCase): self.assertEqual(dummy_venusian.attached, [(foo, dec.register, 'pyramid')]) + +class TestGetResponseFactory(unittest.TestCase): + def test_get_factory(self): + from pyramid.registry import Registry + from pyramid.response import Response, _get_response_factory + + registry = Registry() + response = _get_response_factory(registry)(None) + self.assertTrue(isinstance(response, Response)) + + class Dummy(object): pass @@ -190,5 +201,3 @@ class DummyVenusian(object): def attach(self, wrapped, fn, category=None): self.attached.append((wrapped, fn, category)) - - diff --git a/pyramid/tests/test_util.py b/pyramid/tests/test_util.py index ba128eede..ac5ea0683 100644 --- a/pyramid/tests/test_util.py +++ b/pyramid/tests/test_util.py @@ -619,17 +619,6 @@ class TestActionInfo(unittest.TestCase): "Line 0 of file filename:\n linerepr ") -class TestGetResponseFactory(unittest.TestCase): - def test_get_factory(self): - from pyramid.util import _get_response_factory - from pyramid.registry import Registry - from pyramid.response import Response - - registry = Registry() - response = _get_response_factory(registry)(None) - self.assertTrue(isinstance(response, Response)) - - def dummyfunc(): pass class Dummy(object): diff --git a/pyramid/util.py b/pyramid/util.py index 7aecca19c..4ca2937a1 100644 --- a/pyramid/util.py +++ b/pyramid/util.py @@ -555,15 +555,3 @@ def action_method(wrapped): functools.update_wrapper(wrapper, wrapped) wrapper.__docobj__ = wrapped return wrapper - - -def _get_response_factory(registry): - """ Obtain a :class: `pyramid.response.Response` using the - `pyramid.interfaces.IResponseFactory`. - """ - response_factory = registry.queryUtility( - IResponseFactory, - default=lambda r: Response() - ) - - return response_factory -- cgit v1.2.3 From 22c836ecbc6f10c4851d88017243f91e469016aa Mon Sep 17 00:00:00 2001 From: John Anderson Date: Thu, 1 Jan 2015 22:17:13 -0800 Subject: Updated the docs to talk about `--format` --- docs/narr/commandline.rst | 45 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/docs/narr/commandline.rst b/docs/narr/commandline.rst index 4f16617c4..02bb6138e 100644 --- a/docs/narr/commandline.rst +++ b/docs/narr/commandline.rst @@ -312,24 +312,49 @@ For example: :linenos: $ $VENV/bin/proutes development.ini - Name Pattern View - ---- ------- ---- - home / - home2 / - another /another None - static/ static/*subpath - catchall /*subpath - -``proutes`` generates a table with three columns: *Name*, *Pattern*, + Name Pattern View + ---- ------- ---- + debugtoolbar /_debug_toolbar/*subpath * + __static/ /static/*subpath dummy_starter:static/ * + __static2/ /static2/*subpath /var/www/static/ * + __pdt_images/ /pdt_images/*subpath pyramid_debugtoolbar:static/img/ * + a / * + no_view_attached / * + route_and_view_attached / app1.standard_views.route_and_view_attached * + method_conflicts /conflicts app1.standard_conflicts + 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 ```` 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 + +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 -- cgit v1.2.3 From 149ea94f24479889554b863c40fe72912f7203a8 Mon Sep 17 00:00:00 2001 From: John Anderson Date: Thu, 1 Jan 2015 22:23:06 -0800 Subject: Updated the CHANGES to discuss these improvments. --- CHANGES.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 129ce4616..2e2637ef1 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -64,6 +64,12 @@ Features a factory that will return a custom ``Response`` class. See https://github.com/Pylons/pyramid/pull/1499 +- Overally 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 --------- -- cgit v1.2.3 From 3f8ac549fa352b54a9f151b5047cc4d13f942a4a Mon Sep 17 00:00:00 2001 From: John Anderson Date: Thu, 1 Jan 2015 22:24:09 -0800 Subject: Fix typo --- CHANGES.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 2e2637ef1..2079d5cff 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -64,7 +64,7 @@ Features a factory that will return a custom ``Response`` class. See https://github.com/Pylons/pyramid/pull/1499 -- Overally improvments for the ``proutes`` command. Added ``--format`` and +- 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__``. -- cgit v1.2.3 From 83a400a3cd121fe65d33e796c28a199b35ab67e5 Mon Sep 17 00:00:00 2001 From: John Anderson Date: Thu, 1 Jan 2015 22:25:25 -0800 Subject: Terminated the highlight on ``format`` --- docs/narr/commandline.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/narr/commandline.rst b/docs/narr/commandline.rst index 02bb6138e..aca0ff425 100644 --- a/docs/narr/commandline.rst +++ b/docs/narr/commandline.rst @@ -351,7 +351,7 @@ For example you may remove request method and place the view first: 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 +``--format`` which is a comma separated list of columns you want to include. The current available formats are ``name``, ``pattern``, ``view``, and ``method``. -- cgit v1.2.3 From 2d659e41d01926acaa8670c4d20be20300bcedb7 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 5 Jan 2015 23:25:58 -0600 Subject: update changes for #1417 --- CHANGES.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 129ce4616..150ca85b3 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -64,6 +64,10 @@ Features a factory that will return a custom ``Response`` class. See https://github.com/Pylons/pyramid/pull/1499 +- Allow an iterator to be returned from a renderer. Previously it was only + possible to return bytes or unicode. + See https://github.com/Pylons/pyramid/pull/1417 + Bug Fixes --------- -- cgit v1.2.3 From ef2a4abb2850af8d21995f04e9f30e6a8949ff9d Mon Sep 17 00:00:00 2001 From: Pavlo Kapyshin Date: Wed, 7 Jan 2015 14:42:27 +0200 Subject: Fix "pyramid" spelling --- HISTORY.txt | 2 +- docs/narr/hybrid.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/HISTORY.txt b/HISTORY.txt index 6aad221a8..242568e98 100644 --- a/HISTORY.txt +++ b/HISTORY.txt @@ -1327,7 +1327,7 @@ Bug Fixes - Make test suite pass on 32-bit systems; closes #286. closes #306. See also https://github.com/Pylons/pyramid/issues/286 -- The ``pryamid.view.view_config`` decorator did not accept a ``match_params`` +- The ``pyramid.view.view_config`` decorator did not accept a ``match_params`` predicate argument. See https://github.com/Pylons/pyramid/pull/308 - The AuthTktCookieHelper could potentially generate Unicode headers diff --git a/docs/narr/hybrid.rst b/docs/narr/hybrid.rst index 4a3258d35..1c324d22b 100644 --- a/docs/narr/hybrid.rst +++ b/docs/narr/hybrid.rst @@ -453,7 +453,7 @@ commonly in route declarations that look like this: .. code-block:: python :linenos: - from pryamid.static import static_view + from pyramid.static import static_view www = static_view('mypackage:static', use_subpath=True) -- cgit v1.2.3 From b6ad615549fb52c40f55760027bffbdd1a919aa1 Mon Sep 17 00:00:00 2001 From: Pavlo Kapyshin Date: Wed, 7 Jan 2015 14:44:27 +0200 Subject: Fix "add_subscriber" spelling --- docs/narr/introspector.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/narr/introspector.rst b/docs/narr/introspector.rst index a7bde4cf7..0ff1615d1 100644 --- a/docs/narr/introspector.rst +++ b/docs/narr/introspector.rst @@ -121,7 +121,7 @@ introspectables in categories not described here. ``subscriber`` The subscriber callable object (the resolution of the ``subscriber`` - argument passed to ``add_susbcriber``). + argument passed to ``add_subscriber``). ``interfaces`` @@ -137,12 +137,12 @@ introspectables in categories not described here. ``predicates`` The predicate objects created as the result of passing predicate arguments - to ``add_susbcriber`` + to ``add_subscriber`` ``derived_predicates`` Wrappers around the predicate objects created as the result of passing - predicate arguments to ``add_susbcriber`` (to be used when predicates take + predicate arguments to ``add_subscriber`` (to be used when predicates take only one value but must be passed more than one). ``response adapters`` -- cgit v1.2.3 From 99d7c44610ad56bac0e90ba119b003ef11b2eb5a Mon Sep 17 00:00:00 2001 From: Pavlo Kapyshin Date: Wed, 7 Jan 2015 14:51:45 +0200 Subject: Fix rendering --- docs/narr/sessions.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/narr/sessions.rst b/docs/narr/sessions.rst index 8da743a01..f20a36d81 100644 --- a/docs/narr/sessions.rst +++ b/docs/narr/sessions.rst @@ -44,7 +44,7 @@ It is digitally signed, however, and thus its data cannot easily be tampered with. You can configure this session factory in your :app:`Pyramid` application -by using the :meth:`pyramid.config.Configurator.set_session_factory`` method. +by using the :meth:`pyramid.config.Configurator.set_session_factory` method. .. code-block:: python :linenos: @@ -380,7 +380,7 @@ Checking CSRF Tokens Manually ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ In request handling code, you can check the presence and validity of a CSRF -token with :func:`pyramid.session.check_csrf_token(request)``. If the token is +token with ``pyramid.session.check_csrf_token(request)``. If the token is valid, it will return ``True``, otherwise it will raise ``HTTPBadRequest``. Optionally, you can specify ``raises=False`` to have the check return ``False`` instead of raising an exception. -- cgit v1.2.3 From 77db3c2c000d7209d1c486585d7227181e2a4286 Mon Sep 17 00:00:00 2001 From: Pavlo Kapyshin Date: Wed, 7 Jan 2015 15:00:06 +0200 Subject: Fix typos in configuration introspection documentation --- docs/narr/introspector.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/narr/introspector.rst b/docs/narr/introspector.rst index 0ff1615d1..8caba522c 100644 --- a/docs/narr/introspector.rst +++ b/docs/narr/introspector.rst @@ -450,9 +450,9 @@ introspectables in categories not described here. The :class:`pyramid.interfaces.IRendererInfo` object which represents this template's renderer. -``view mapper`` +``view mappers`` - Each introspectable in the ``permissions`` category represents a call to + Each introspectable in the ``view mappers`` category represents a call to :meth:`pyramid.config.Configurator.add_view` that has an explicit ``mapper`` argument to *or* a call to :meth:`pyramid.config.Configurator.set_view_mapper`; each will have @@ -481,8 +481,8 @@ introspectables in categories not described here. ``translation directories`` - Each introspectable in the ``asset overrides`` category represents an - individual element in a ``specs`` argument passed to + Each introspectable in the ``translation directories`` category represents + an individual element in a ``specs`` argument passed to :meth:`pyramid.config.Configurator.add_translation_dirs`; each will have the following data. @@ -511,7 +511,7 @@ introspectables in categories not described here. ``type`` - ``implict`` or ``explicit`` as a string. + ``implicit`` or ``explicit`` as a string. ``under`` -- cgit v1.2.3 From 3702ab07e835a06f30abf5ceb626f81114115062 Mon Sep 17 00:00:00 2001 From: Pavlo Kapyshin Date: Wed, 7 Jan 2015 15:09:26 +0200 Subject: Fix typo --- docs/narr/hooks.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index e250c2d7e..5bba0d143 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -777,7 +777,7 @@ If you want to implement your own Response object instead of using the :class:`pyramid.response.Response` object in any capacity at all, you'll have to make sure the object implements every attribute and method outlined in :class:`pyramid.interfaces.IResponse` and you'll have to ensure that it uses -``zope.interface.implementer(IResponse)`` as a class decoratoror. +``zope.interface.implementer(IResponse)`` as a class decorator. .. code-block:: python :linenos: -- cgit v1.2.3 From 47e85294779814f14e02327eb4d378197bbaeb29 Mon Sep 17 00:00:00 2001 From: Pavlo Kapyshin Date: Wed, 7 Jan 2015 19:10:31 +0200 Subject: Provide a ref to check_csrf_token --- docs/narr/sessions.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/narr/sessions.rst b/docs/narr/sessions.rst index f20a36d81..5c103405a 100644 --- a/docs/narr/sessions.rst +++ b/docs/narr/sessions.rst @@ -380,7 +380,7 @@ Checking CSRF Tokens Manually ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ In request handling code, you can check the presence and validity of a CSRF -token with ``pyramid.session.check_csrf_token(request)``. If the token is +token with :func:`pyramid.session.check_csrf_token`. If the token is valid, it will return ``True``, otherwise it will raise ``HTTPBadRequest``. Optionally, you can specify ``raises=False`` to have the check return ``False`` instead of raising an exception. -- cgit v1.2.3 From 8dd970873a58e1f017c8bce7be62409b313f74e1 Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Tue, 20 Jan 2015 10:44:37 -0800 Subject: Give pserve the ability top open server in browser E.g.: pserve app.ini --browser or: pserve app.ini -b --- CHANGES.txt | 3 +++ pyramid/scripts/pserve.py | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 150ca85b3..0a28dc248 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -68,6 +68,9 @@ Features possible to return bytes or unicode. See https://github.com/Pylons/pyramid/pull/1417 +- ``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 + Bug Fixes --------- diff --git a/pyramid/scripts/pserve.py b/pyramid/scripts/pserve.py index ea125a0dd..314efd839 100644 --- a/pyramid/scripts/pserve.py +++ b/pyramid/scripts/pserve.py @@ -21,9 +21,11 @@ import textwrap import threading import time import traceback +import webbrowser from paste.deploy import loadserver from paste.deploy import loadapp +from paste.deploy.loadwsgi import loadcontext, SERVER from pyramid.compat import PY3 from pyramid.compat import WIN @@ -121,6 +123,11 @@ class PServeCommand(object): dest='monitor_restart', action='store_true', help="Auto-restart server if it dies") + parser.add_option( + '-b', '--browser', + dest='browser', + action='store_true', + help="Open a web browser to server url") parser.add_option( '--status', action='store_true', @@ -334,6 +341,17 @@ class PServeCommand(object): msg = '' self.out('Exiting%s (-v to see traceback)' % msg) + if self.options.browser: + def open_browser(): + context = loadcontext(SERVER, app_spec, name=app_name, relative_to=base, + global_conf=vars) + url = 'http://{host}:{port}/'.format(**context.config()) + time.sleep(1) + webbrowser.open(url) + t = threading.Thread(target=open_browser) + t.setDaemon(True) + t.start() + serve() def loadapp(self, app_spec, name, relative_to, **kw): # pragma: no cover -- cgit v1.2.3 From 8b5000f44cddd24df111c8a1d2ff65ee6d37afbb Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Thu, 22 Jan 2015 02:46:09 -0800 Subject: move index and reference down to proper section so that docs will build on master again --- docs/narr/hooks.rst | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index 5bba0d143..17cae2c67 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -348,12 +348,6 @@ We attach and cache an object named ``extra`` to the ``request`` object. >>> request.extra.prop the property -.. index:: - single: before render event - single: adding renderer globals - -.. _beforerender_event: - .. index:: single: response factory @@ -400,6 +394,11 @@ already constructed a :term:`configurator` it can also be registered via the config = Configurator() config.set_response_factory(lambda r: MyResponse()) +.. index:: + single: before render event + single: adding renderer globals + +.. _beforerender_event: Using The Before Render Event ----------------------------- -- cgit v1.2.3 From 0e4dcf9f85babd94dcd9fc59513d257b4aba8d40 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Thu, 22 Jan 2015 03:46:27 -0800 Subject: apply changes from #1538 and #1539 --- docs/narr/logging.rst | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/narr/logging.rst b/docs/narr/logging.rst index c16673ae6..921883091 100644 --- a/docs/narr/logging.rst +++ b/docs/narr/logging.rst @@ -254,16 +254,15 @@ level unless they're explicitly set differently. Meaning the ``myapp.views``, ``myapp.models`` (and all your app's modules') loggers by default have an effective level of ``DEBUG`` too. -For more advanced filtering, the logging module provides a `Filter -`_ object; however it cannot be used -directly from the configuration file. +For more advanced filtering, the logging module provides a +:class:`logging.Filter` object; however it cannot be used directly from the +configuration file. -Advanced Configuration +Advanced Configuration ---------------------- -To capture log output to a separate file, use a `FileHandler -`_ (or a `RotatingFileHandler -`_): +To capture log output to a separate file, use :class:`logging.FileHandler` (or +:class:`logging.handlers.RotatingFileHandler`): .. code-block:: ini @@ -317,8 +316,9 @@ output, etc., but not web traffic. For web traffic logging Paste provides the :term:`middleware`. TransLogger produces logs in the `Apache Combined Log Format `_. But TransLogger does not write to files, the Python logging system must be -configured to do this. The Python FileHandler_ logging handler can be used -alongside TransLogger to create an ``access.log`` file similar to Apache's. +configured to do this. The Python :class:`logging.FileHandler` logging +handler can be used alongside TransLogger to create an ``access.log`` file +similar to Apache's. Like any standard :term:`middleware` with a Paste entry point, TransLogger can be configured to wrap your application using ``.ini`` file syntax. First, -- cgit v1.2.3 From b8ba0f1ed25b118aeb05accb23d872b3a72dc548 Mon Sep 17 00:00:00 2001 From: John Anderson Date: Thu, 22 Jan 2015 07:29:02 -0800 Subject: Make more ways to configure [proutes] section --- docs/narr/commandline.rst | 11 +++++ pyramid/scripts/proutes.py | 5 +- pyramid/tests/test_scripts/test_proutes.py | 73 ++++++++++++++++++++++++++++-- 3 files changed, 84 insertions(+), 5 deletions(-) diff --git a/docs/narr/commandline.rst b/docs/narr/commandline.rst index aca0ff425..3dcb092e2 100644 --- a/docs/narr/commandline.rst +++ b/docs/narr/commandline.rst @@ -350,6 +350,17 @@ For example you may remove request method and place the view first: 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``. diff --git a/pyramid/scripts/proutes.py b/pyramid/scripts/proutes.py index 2155b4983..544947724 100644 --- a/pyramid/scripts/proutes.py +++ b/pyramid/scripts/proutes.py @@ -2,6 +2,7 @@ import fnmatch import optparse import sys import textwrap +import re from pyramid.paster import bootstrap from pyramid.compat import (string_types, configparser) @@ -291,7 +292,8 @@ class PRoutesCommand(object): items = config.items('proutes') for k, v in items: if 'format' == k: - self.column_format = [x.strip() for x in v.split('\n')] + cols = re.split(r'[,|\s|\n]*', v) + self.column_format = [x.strip() for x in cols] except configparser.NoSectionError: return @@ -314,6 +316,7 @@ class PRoutesCommand(object): env = self.bootstrap[0](config_uri, options=parse_vars(self.args[1:])) registry = env['registry'] mapper = self._get_mapper(registry) + self.proutes_file_config(config_uri) if self.options.format: diff --git a/pyramid/tests/test_scripts/test_proutes.py b/pyramid/tests/test_scripts/test_proutes.py index 446d772ff..e426eee73 100644 --- a/pyramid/tests/test_scripts/test_proutes.py +++ b/pyramid/tests/test_scripts/test_proutes.py @@ -632,7 +632,7 @@ class TestPRoutesCommand(unittest.TestCase): self.assertEqual(result, 2) self.assertEqual(L[0], expected) - def test_config_format_ini(self): + def test_config_format_ini_newlines(self): from pyramid.renderers import null_renderer as nr from pyramid.config import not_ @@ -648,14 +648,79 @@ class TestPRoutesCommand(unittest.TestCase): ) command = self._makeOne() - command.options.glob = '*foo*' - command.options.format = 'method,name' + + 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')] + config_factory.items = [('format', 'method,name')] result = command.run() self.assertEqual(result, 0) -- cgit v1.2.3 From 4fe3efda811c9d328a1a3da4acda32ecf18dbf03 Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Sat, 24 Jan 2015 19:13:20 -0800 Subject: Tighten test_call_eventsends - Make it check context of events - Rename aftertraversal_events => context_found_events --- pyramid/tests/test_router.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pyramid/tests/test_router.py b/pyramid/tests/test_router.py index c6c6eea1c..30ebd5918 100644 --- a/pyramid/tests/test_router.py +++ b/pyramid/tests/test_router.py @@ -599,17 +599,19 @@ class TestRouter(unittest.TestCase): environ = self._makeEnviron() self._registerView(view, '', IViewClassifier, None, None) request_events = self._registerEventListener(INewRequest) - aftertraversal_events = self._registerEventListener(IContextFound) + context_found_events = self._registerEventListener(IContextFound) response_events = self._registerEventListener(INewResponse) router = self._makeOne() start_response = DummyStartResponse() result = router(environ, start_response) self.assertEqual(len(request_events), 1) self.assertEqual(request_events[0].request.environ, environ) - self.assertEqual(len(aftertraversal_events), 1) - self.assertEqual(aftertraversal_events[0].request.environ, environ) + self.assertEqual(len(context_found_events), 1) + self.assertEqual(context_found_events[0].request.environ, environ) + self.assertEqual(context_found_events[0].request.context, context) self.assertEqual(len(response_events), 1) self.assertEqual(response_events[0].response, response) + self.assertEqual(response_events[0].request.context, context) self.assertEqual(result, response.app_iter) def test_call_newrequest_evllist_exc_can_be_caught_by_exceptionview(self): -- cgit v1.2.3 From c7bf2744f332c0294d8d673d21b56f5edacb2eae Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Tue, 27 Jan 2015 14:51:58 -0600 Subject: fix count --- docs/narr/commandline.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/narr/commandline.rst b/docs/narr/commandline.rst index 3dcb092e2..1fe2d9278 100644 --- a/docs/narr/commandline.rst +++ b/docs/narr/commandline.rst @@ -325,7 +325,7 @@ For example: 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*, +``proutes`` generates a table with four 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 -- cgit v1.2.3 From 462af266b61e0b8aad87775926b49e8758f564e7 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Fri, 30 Jan 2015 11:36:34 -0500 Subject: Use 'functools.update_wrapper' to preserve attributes from wrapped. --- pyramid/decorator.py | 8 ++++---- pyramid/tests/test_decorator.py | 18 +++++++++++------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/pyramid/decorator.py b/pyramid/decorator.py index 0d17bc398..df30c5e10 100644 --- a/pyramid/decorator.py +++ b/pyramid/decorator.py @@ -1,3 +1,6 @@ +import functools + + class reify(object): """ Use as a class method decorator. It operates almost exactly like the Python ``@property`` decorator, but it puts the result of the method it @@ -26,10 +29,7 @@ class reify(object): """ def __init__(self, wrapped): self.wrapped = wrapped - try: - self.__doc__ = wrapped.__doc__ - except: # pragma: no cover - pass + functools.update_wrapper(self, wrapped) def __get__(self, inst, objtype=None): if inst is None: diff --git a/pyramid/tests/test_decorator.py b/pyramid/tests/test_decorator.py index 9ab1b7229..eb5266235 100644 --- a/pyramid/tests/test_decorator.py +++ b/pyramid/tests/test_decorator.py @@ -15,15 +15,19 @@ class TestReify(unittest.TestCase): self.assertEqual(inst.__dict__['wrapped'], 'a') def test___get__noinst(self): - decorator = self._makeOne(None) + def wrapped(inst): + return 'a' + decorator = self._makeOne(wrapped) result = decorator.__get__(None) self.assertEqual(result, decorator) - def test___doc__copied(self): - def wrapped(inst): - """My doc""" - decorator = self._makeOne(wrapped) - self.assertEqual(decorator.__doc__, "My doc") - + def test_dunder_attrs_copied(self): + from pyramid.util import viewdefaults + decorator = self._makeOne(viewdefaults) + self.assertEqual(decorator.__doc__, viewdefaults.__doc__) + self.assertEqual(decorator.__name__, viewdefaults.__name__) + self.assertEqual(decorator.__module__, viewdefaults.__module__) + + class Dummy(object): pass -- cgit v1.2.3 From 7c8c852b3de053e4e8e459f3fc74e4b7d05aab93 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Fri, 30 Jan 2015 11:42:01 -0500 Subject: Suppress setuptools chatter when testing under tox. --- tox.ini | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tox.ini b/tox.ini index 714c5b6d3..29bd48639 100644 --- a/tox.ini +++ b/tox.ini @@ -4,15 +4,15 @@ envlist = [testenv] commands = - python setup.py dev - python setup.py test -q + python setup.py -q dev + python setup.py -q test -q [testenv:cover] basepython = python2.6 commands = - python setup.py dev - python setup.py nosetests --with-xunit --with-xcoverage + python setup.py -q dev + nosetests --with-xunit --with-xcoverage deps = nosexcover -- cgit v1.2.3 From 3271c7748d43fd5ecc9928ea69a8c4f497a68af5 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Fri, 30 Jan 2015 12:04:44 -0500 Subject: Ignore never-called function. Used only to allow 'functools.update_wrapper' to do its thing. --- pyramid/tests/test_decorator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyramid/tests/test_decorator.py b/pyramid/tests/test_decorator.py index eb5266235..0a98a512d 100644 --- a/pyramid/tests/test_decorator.py +++ b/pyramid/tests/test_decorator.py @@ -16,7 +16,7 @@ class TestReify(unittest.TestCase): def test___get__noinst(self): def wrapped(inst): - return 'a' + return 'a' # pragma: no cover decorator = self._makeOne(wrapped) result = decorator.__get__(None) self.assertEqual(result, decorator) -- cgit v1.2.3 From 8da041fe67a8f8d35418681f831a57e949d605e3 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 2 Feb 2015 10:58:20 -0600 Subject: fix syntax of code-block --- pyramid/security.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyramid/security.py b/pyramid/security.py index cbb4b895f..01de80b22 100644 --- a/pyramid/security.py +++ b/pyramid/security.py @@ -128,7 +128,7 @@ def remember(request, userid=_marker, **kw): assumed to be a :term:`WebOb` -style :term:`response` object computed previously by the view code):: - .. code-block:: python + .. code-block: python from pyramid.security import remember headers = remember(request, 'chrism', password='123', max_age='86400') -- cgit v1.2.3 From 06ecaf50d7a66a624761043ce950ab32d3e883a9 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 2 Feb 2015 14:15:27 -0600 Subject: fix syntax of code-block again --- pyramid/security.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyramid/security.py b/pyramid/security.py index 01de80b22..011545939 100644 --- a/pyramid/security.py +++ b/pyramid/security.py @@ -126,9 +126,9 @@ def remember(request, userid=_marker, **kw): current :term:`authentication policy`. Common usage might look like so within the body of a view function (``response`` is assumed to be a :term:`WebOb` -style :term:`response` object - computed previously by the view code):: + computed previously by the view code): - .. code-block: python + .. code-block:: python from pyramid.security import remember headers = remember(request, 'chrism', password='123', max_age='86400') -- cgit v1.2.3 From 620bdefbff81792fd047f273a234264ce3d0a764 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 2 Feb 2015 14:17:31 -0600 Subject: add code-block to p.security.forget --- pyramid/security.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/pyramid/security.py b/pyramid/security.py index 011545939..f993ef353 100644 --- a/pyramid/security.py +++ b/pyramid/security.py @@ -170,12 +170,14 @@ def forget(request): possessed by the currently authenticated user. A common usage might look like so within the body of a view function (``response`` is assumed to be an :term:`WebOb` -style - :term:`response` object computed previously by the view code):: + :term:`response` object computed previously by the view code): - from pyramid.security import forget - headers = forget(request) - response.headerlist.extend(headers) - return response + .. code-block:: python + + from pyramid.security import forget + headers = forget(request) + response.headerlist.extend(headers) + return response If no :term:`authentication policy` is in use, this function will always return an empty sequence. -- cgit v1.2.3 From 561591252bd029981952c5c229f4cf27832d34a1 Mon Sep 17 00:00:00 2001 From: saarni Date: Thu, 5 Feb 2015 12:14:21 +0200 Subject: use getfullargspec in PY3, allowing annotations in subscribers --- CONTRIBUTORS.txt | 2 ++ pyramid/config/util.py | 8 +++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index e4132cda5..adf2224a5 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -238,3 +238,5 @@ Contributors - Hugo Branquinho, 2014/11/25 - Adrian Teng, 2014/12/17 + +- Ilja Everila, 2015/02/05 diff --git a/pyramid/config/util.py b/pyramid/config/util.py index 892592196..b91f3f7ab 100644 --- a/pyramid/config/util.py +++ b/pyramid/config/util.py @@ -22,6 +22,12 @@ ActionInfo = ActionInfo # support bw compat imports MAX_ORDER = 1 << 30 DEFAULT_PHASH = md5().hexdigest() +# support annotations and keyword-only arguments in PY3 +try: + getargspec = inspect.getfullargspec +except AttributeError: + getargspec = inspect.getargspec + def as_sorted_tuple(val): if not is_nonstr_iter(val): val = (val,) @@ -201,7 +207,7 @@ def takes_one_arg(callee, attr=None, argname=None): return False try: - argspec = inspect.getargspec(fn) + argspec = getargspec(fn) except TypeError: return False -- cgit v1.2.3 From 0ccb82204b8d04f8ffeb8b49a94fb77f981d1122 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilja=20Everil=C3=A4?= Date: Thu, 5 Feb 2015 19:58:54 +0200 Subject: PY3 only test for function annotations --- pyramid/tests/test_config/test_util.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pyramid/tests/test_config/test_util.py b/pyramid/tests/test_config/test_util.py index bb61714ae..c9b0e9e9b 100644 --- a/pyramid/tests/test_config/test_util.py +++ b/pyramid/tests/test_config/test_util.py @@ -1,5 +1,5 @@ import unittest -from pyramid.compat import text_ +from pyramid.compat import text_, PY3 class TestPredicateList(unittest.TestCase): @@ -568,6 +568,14 @@ class Test_takes_one_arg(unittest.TestCase): foo = Foo() self.assertTrue(self._callFUT(foo.method)) + if PY3: + def test_function_annotations(self): + def foo(bar): + """ """ + # avoid SyntaxErrors in python2 + foo.__annotations__.update({'bar': 'baz'}) + self.assertTrue(self._callFUT(foo)) + class TestNotted(unittest.TestCase): def _makeOne(self, predicate): from pyramid.config.util import Notted -- cgit v1.2.3 From d49949081da1669914ddebb487c87edba3f41000 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilja=20Everil=C3=A4?= Date: Thu, 5 Feb 2015 20:48:54 +0200 Subject: ugly nop dict update hack for PY2 and coverage --- pyramid/tests/test_config/test_util.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/pyramid/tests/test_config/test_util.py b/pyramid/tests/test_config/test_util.py index c9b0e9e9b..0d0de9579 100644 --- a/pyramid/tests/test_config/test_util.py +++ b/pyramid/tests/test_config/test_util.py @@ -568,13 +568,12 @@ class Test_takes_one_arg(unittest.TestCase): foo = Foo() self.assertTrue(self._callFUT(foo.method)) - if PY3: - def test_function_annotations(self): - def foo(bar): - """ """ - # avoid SyntaxErrors in python2 - foo.__annotations__.update({'bar': 'baz'}) - self.assertTrue(self._callFUT(foo)) + def test_function_annotations(self): + def foo(bar): + """ """ + # avoid SyntaxErrors in python2, this if effectively nop + getattr(foo, '__annotations__', {}).update({'bar': 'baz'}) + self.assertTrue(self._callFUT(foo)) class TestNotted(unittest.TestCase): def _makeOne(self, predicate): -- cgit v1.2.3 From d6ff994619d18981dbde6dce7e8a10140f063e06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilja=20Everil=C3=A4?= Date: Thu, 5 Feb 2015 21:01:24 +0200 Subject: remove unused import --- pyramid/tests/test_config/test_util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyramid/tests/test_config/test_util.py b/pyramid/tests/test_config/test_util.py index 0d0de9579..ccf7fa260 100644 --- a/pyramid/tests/test_config/test_util.py +++ b/pyramid/tests/test_config/test_util.py @@ -1,5 +1,5 @@ import unittest -from pyramid.compat import text_, PY3 +from pyramid.compat import text_ class TestPredicateList(unittest.TestCase): -- cgit v1.2.3 From 686f03bab3df9e1527dce1b02bcca1b05fc74bf5 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Thu, 5 Feb 2015 16:36:12 -0600 Subject: we like pep 440 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 629b77f3d..3233193e7 100644 --- a/setup.py +++ b/setup.py @@ -68,7 +68,7 @@ testing_extras = tests_require + [ ] setup(name='pyramid', - version='1.6dev', + version='1.6.dev0', description='The Pyramid Web Framework, a Pylons project', long_description=README + '\n\n' + CHANGES, classifiers=[ -- cgit v1.2.3 From 31e4924465304f8b01b8c04b22085bf24f40096e Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Thu, 5 Feb 2015 22:36:59 -0600 Subject: move getargspec import into pyramid.compat --- pyramid/compat.py | 5 +++++ pyramid/config/util.py | 7 +------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pyramid/compat.py b/pyramid/compat.py index bfa345b88..5909debf2 100644 --- a/pyramid/compat.py +++ b/pyramid/compat.py @@ -244,3 +244,8 @@ else: def is_bound_method(ob): return inspect.ismethod(ob) and getattr(ob, im_self, None) is not None +# support annotations and keyword-only arguments in PY3 +if PY3: + from inspect import getfullargspec as getargspec +else: + from inspect import getargspec diff --git a/pyramid/config/util.py b/pyramid/config/util.py index b91f3f7ab..23cdc6be8 100644 --- a/pyramid/config/util.py +++ b/pyramid/config/util.py @@ -3,6 +3,7 @@ import inspect from pyramid.compat import ( bytes_, + getargspec, is_nonstr_iter, ) @@ -22,12 +23,6 @@ ActionInfo = ActionInfo # support bw compat imports MAX_ORDER = 1 << 30 DEFAULT_PHASH = md5().hexdigest() -# support annotations and keyword-only arguments in PY3 -try: - getargspec = inspect.getfullargspec -except AttributeError: - getargspec = inspect.getargspec - def as_sorted_tuple(val): if not is_nonstr_iter(val): val = (val,) -- cgit v1.2.3 From 86f4d59b23f91f4a070417bebe1302661d761b2f Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Thu, 5 Feb 2015 22:38:29 -0600 Subject: update changelog --- CHANGES.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index b1bd36904..22c7a20c2 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -77,6 +77,9 @@ Features output by showing the module instead of just ``__repr__``. See https://github.com/Pylons/pyramid/pull/1488 +- Support keyword-only arguments and function annotations in views in + Python 3. See https://github.com/Pylons/pyramid/pull/1556 + Bug Fixes --------- -- cgit v1.2.3 From bbe0ad003c2f0fe5bf66953f12bd15043702838c Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Thu, 5 Feb 2015 23:58:40 -0600 Subject: fix coverage --- pyramid/compat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyramid/compat.py b/pyramid/compat.py index 5909debf2..c49ea1e73 100644 --- a/pyramid/compat.py +++ b/pyramid/compat.py @@ -245,7 +245,7 @@ def is_bound_method(ob): return inspect.ismethod(ob) and getattr(ob, im_self, None) is not None # support annotations and keyword-only arguments in PY3 -if PY3: +if PY3: # pragma: no cover from inspect import getfullargspec as getargspec else: from inspect import getargspec -- cgit v1.2.3 From d7c9f0a8190b5041f8efb7f83535d181f6499605 Mon Sep 17 00:00:00 2001 From: Bert JW Regeer Date: Sun, 14 Dec 2014 18:51:23 -0700 Subject: Move documentation for accept to non-predicate While accept is partially documented as a predicate, it is actually NOT a predicate in that the view machinery has all kinds of special cases for it. This also means that `not_` will not function on it correctly since it is not actually a predicate. --- pyramid/config/views.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/pyramid/config/views.py b/pyramid/config/views.py index c01b72e12..8ee7ca6c9 100644 --- a/pyramid/config/views.py +++ b/pyramid/config/views.py @@ -841,6 +841,17 @@ class ViewsConfiguratorMixin(object): very useful for 'civilians' who are just developing stock Pyramid applications. Pay no attention to the man behind the curtain. + accept + + The value of this argument represents a match query for one + or more mimetypes in the ``Accept`` HTTP request header. If + this value is specified, it must be in one of the following + forms: a mimetype match token in the form ``text/plain``, a + wildcard mimetype match token in the form ``text/*`` or a + match-all wildcard mimetype match token in the form ``*/*``. + If any of the forms matches the ``Accept`` header of the + request, this predicate will be true. + Predicate Arguments name @@ -941,17 +952,6 @@ class ViewsConfiguratorMixin(object): This is useful for detecting AJAX requests issued from jQuery, Prototype and other Javascript libraries. - accept - - The value of this argument represents a match query for one - or more mimetypes in the ``Accept`` HTTP request header. If - this value is specified, it must be in one of the following - forms: a mimetype match token in the form ``text/plain``, a - wildcard mimetype match token in the form ``text/*`` or a - match-all wildcard mimetype match token in the form ``*/*``. - If any of the forms matches the ``Accept`` header of the - request, this predicate will be true. - header This value represents an HTTP header name or a header -- cgit v1.2.3 From 63366c3ddf353bbd2a237875f0c42e8f470c31c7 Mon Sep 17 00:00:00 2001 From: Bert JW Regeer Date: Sun, 14 Dec 2014 18:56:33 -0700 Subject: Update CHANGES --- CHANGES.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 22c7a20c2..b2f56e02e 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -130,6 +130,12 @@ Deprecations Docs ---- +- Moved the documentation for ``accept`` on ``Configurator.add_view`` to no + longer be part of the decorator list. See + https://github.com/Pylons/pyramid/issues/1391 for a bug report stating + ``not_`` was failing on ``accept``. Discussion with @mcdonc led to the + conclusion that it should not be documented as a predicate. + - Removed logging configuration from Quick Tutorial ini files except for scaffolding- and logging-related chapters to avoid needing to explain it too early. -- cgit v1.2.3 From 958c4c7de27b3353dab6e2a7552a17321236c138 Mon Sep 17 00:00:00 2001 From: Bert JW Regeer Date: Sun, 14 Dec 2014 19:21:55 -0700 Subject: Update add_route accept to non-predicate --- pyramid/config/routes.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/pyramid/config/routes.py b/pyramid/config/routes.py index 509955cdd..c6e8fe563 100644 --- a/pyramid/config/routes.py +++ b/pyramid/config/routes.py @@ -138,6 +138,19 @@ class RoutesConfiguratorMixin(object): .. versionadded:: 1.1 + accept + + This value represents a match query for one or more + mimetypes in the ``Accept`` HTTP request header. If this + value is specified, it must be in one of the following + forms: a mimetype match token in the form ``text/plain``, a + wildcard mimetype match token in the form ``text/*`` or a + match-all wildcard mimetype match token in the form ``*/*``. + If any of the forms matches the ``Accept`` header of the + request, or if the ``Accept`` header isn't set at all in the + request, this predicate will be true. If this predicate + returns ``False``, route matching continues. + Predicate Arguments pattern @@ -220,19 +233,6 @@ class RoutesConfiguratorMixin(object): case of the header name is not significant. If this predicate returns ``False``, route matching continues. - accept - - This value represents a match query for one or more - mimetypes in the ``Accept`` HTTP request header. If this - value is specified, it must be in one of the following - forms: a mimetype match token in the form ``text/plain``, a - wildcard mimetype match token in the form ``text/*`` or a - match-all wildcard mimetype match token in the form ``*/*``. - If any of the forms matches the ``Accept`` header of the - request, or if the ``Accept`` header isn't set at all in the - request, this predicate will be true. If this predicate - returns ``False``, route matching continues. - effective_principals If specified, this value should be a :term:`principal` identifier or -- cgit v1.2.3 From c015da76f51304a5186909f01a7850de073eabdc Mon Sep 17 00:00:00 2001 From: Bert JW Regeer Date: Sun, 14 Dec 2014 19:56:25 -0700 Subject: Fix typo --- CHANGES.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index b2f56e02e..d3788afa5 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -131,7 +131,7 @@ Docs ---- - Moved the documentation for ``accept`` on ``Configurator.add_view`` to no - longer be part of the decorator list. See + longer be part of the predicate list. See https://github.com/Pylons/pyramid/issues/1391 for a bug report stating ``not_`` was failing on ``accept``. Discussion with @mcdonc led to the conclusion that it should not be documented as a predicate. -- cgit v1.2.3 From 42075618568881a36d9fb03812a0e651d1c084ce Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Fri, 6 Feb 2015 00:06:01 -0600 Subject: fix typo --- pyramid/config/adapters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyramid/config/adapters.py b/pyramid/config/adapters.py index f6a652e3d..3d11980da 100644 --- a/pyramid/config/adapters.py +++ b/pyramid/config/adapters.py @@ -143,7 +143,7 @@ class AdaptersConfiguratorMixin(object): Adds a subscriber predicate factory. The associated subscriber predicate can later be named as a keyword argument to :meth:`pyramid.config.Configurator.add_subscriber` in the - ``**predicates`` anonyous keyword argument dictionary. + ``**predicates`` anonymous keyword argument dictionary. ``name`` should be the name of the predicate. It must be a valid Python identifier (it will be used as a ``**predicates`` keyword -- cgit v1.2.3 From da1f208369607a755be4ad355df07fac0bc7719d Mon Sep 17 00:00:00 2001 From: Bert JW Regeer Date: Thu, 5 Feb 2015 23:12:36 -0700 Subject: Remove all signs of predicate While this is somewhat a predicate, it really isn't for all intents and purposes because it is treated special. Make sure we document it that way. --- pyramid/config/routes.py | 19 +++++++++---------- pyramid/config/views.py | 17 +++++++++-------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/pyramid/config/routes.py b/pyramid/config/routes.py index c6e8fe563..24f38a4fd 100644 --- a/pyramid/config/routes.py +++ b/pyramid/config/routes.py @@ -140,16 +140,15 @@ class RoutesConfiguratorMixin(object): accept - This value represents a match query for one or more - mimetypes in the ``Accept`` HTTP request header. If this - value is specified, it must be in one of the following - forms: a mimetype match token in the form ``text/plain``, a - wildcard mimetype match token in the form ``text/*`` or a - match-all wildcard mimetype match token in the form ``*/*``. - If any of the forms matches the ``Accept`` header of the - request, or if the ``Accept`` header isn't set at all in the - request, this predicate will be true. If this predicate - returns ``False``, route matching continues. + This value represents a match query for one or more mimetypes in the + ``Accept`` HTTP request header. If this value is specified, it must + be in one of the following forms: a mimetype match token in the form + ``text/plain``, a wildcard mimetype match token in the form + ``text/*`` or a match-all wildcard mimetype match token in the form + ``*/*``. If any of the forms matches the ``Accept`` header of the + request, or if the ``Accept`` header isn't set at all in the request, + this will match the current route. If this does not match the + ``Accept`` header of the request, route matching continues. Predicate Arguments diff --git a/pyramid/config/views.py b/pyramid/config/views.py index 8ee7ca6c9..1f69d7e0b 100644 --- a/pyramid/config/views.py +++ b/pyramid/config/views.py @@ -843,14 +843,15 @@ class ViewsConfiguratorMixin(object): accept - The value of this argument represents a match query for one - or more mimetypes in the ``Accept`` HTTP request header. If - this value is specified, it must be in one of the following - forms: a mimetype match token in the form ``text/plain``, a - wildcard mimetype match token in the form ``text/*`` or a - match-all wildcard mimetype match token in the form ``*/*``. - If any of the forms matches the ``Accept`` header of the - request, this predicate will be true. + This value represents a match query for one or more mimetypes in the + ``Accept`` HTTP request header. If this value is specified, it must + be in one of the following forms: a mimetype match token in the form + ``text/plain``, a wildcard mimetype match token in the form + ``text/*`` or a match-all wildcard mimetype match token in the form + ``*/*``. If any of the forms matches the ``Accept`` header of the + request, or if the ``Accept`` header isn't set at all in the request, + this will match the current view. If this does not match the + ``Accept`` header of the request, view matching continues. Predicate Arguments -- cgit v1.2.3 From f176630ebd9848173e6cc748f361b4ce9acf76f3 Mon Sep 17 00:00:00 2001 From: Bert JW Regeer Date: Thu, 5 Feb 2015 23:15:51 -0700 Subject: Add link to PR --- CHANGES.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.txt b/CHANGES.txt index d3788afa5..832a2c216 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -135,6 +135,7 @@ Docs https://github.com/Pylons/pyramid/issues/1391 for a bug report stating ``not_`` was failing on ``accept``. Discussion with @mcdonc led to the conclusion that it should not be documented as a predicate. + See https://github.com/Pylons/pyramid/pull/1487 for this PR - Removed logging configuration from Quick Tutorial ini files except for scaffolding- and logging-related chapters to avoid needing to explain it too -- cgit v1.2.3 From fcb6cc082ea537b046df4b958f885f6a50b18d72 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Fri, 6 Feb 2015 01:01:32 -0600 Subject: fix #1535 by avoiding the request if it's None --- pyramid/renderers.py | 24 ++++++++++++------------ pyramid/tests/test_renderers.py | 6 ++++++ 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/pyramid/renderers.py b/pyramid/renderers.py index d840cc317..3c35551ea 100644 --- a/pyramid/renderers.py +++ b/pyramid/renderers.py @@ -24,7 +24,7 @@ from pyramid.events import BeforeRender from pyramid.path import caller_package -from pyramid.response import Response, _get_response_factory +from pyramid.response import _get_response_factory from pyramid.threadlocal import get_current_registry # API @@ -355,19 +355,19 @@ class JSONP(JSON): ``self.param_name`` is present in request.GET; otherwise returns plain-JSON encoded string with content-type ``application/json``""" def _render(value, system): - request = system['request'] + request = system.get('request') default = self._make_default(request) val = self.serializer(value, default=default, **self.kw) - callback = request.GET.get(self.param_name) - if callback is None: - ct = 'application/json' - body = val - else: - ct = 'application/javascript' - body = '%s(%s);' % (callback, val) - response = request.response - if response.content_type == response.default_content_type: - response.content_type = ct + ct = 'application/json' + body = val + if request is not None: + callback = request.GET.get(self.param_name) + if callback is not None: + ct = 'application/javascript' + body = '%s(%s);' % (callback, val) + response = request.response + if response.content_type == response.default_content_type: + response.content_type = ct return body return _render diff --git a/pyramid/tests/test_renderers.py b/pyramid/tests/test_renderers.py index 30fdef051..6d79cc291 100644 --- a/pyramid/tests/test_renderers.py +++ b/pyramid/tests/test_renderers.py @@ -602,6 +602,12 @@ class TestJSONP(unittest.TestCase): self.assertEqual(request.response.content_type, 'application/json') + def test_render_without_request(self): + renderer_factory = self._makeOne() + renderer = renderer_factory(None) + result = renderer({'a':'1'}, {}) + self.assertEqual(result, '{"a": "1"}') + class Dummy: pass -- cgit v1.2.3 From 1ef35b7194ad744f23cf2881bbf881690d680c83 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Fri, 6 Feb 2015 01:04:17 -0600 Subject: update changelog --- CHANGES.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 22c7a20c2..9b0ee90e9 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -120,6 +120,11 @@ Bug Fixes - Fix route generation for static view asset specifications having no path. See https://github.com/Pylons/pyramid/pull/1377 +- Allow the ``pyramid.renderers.JSONP`` renderer to work even if there is no + valid request object. In this case it will not wrap the object in a + callback and thus behave just like the ``pyramid.renderers.JSON` renderer. + See https://github.com/Pylons/pyramid/pull/1561 + Deprecations ------------ -- cgit v1.2.3