From 85d801817e2137848a3ccb4303b88ace063a81d5 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Thu, 5 Feb 2015 16:39:16 -0600 Subject: provide a fix for view lookup fallbacks --- pyramid/router.py | 22 ++++++++++------------ pyramid/view.py | 12 ++++++++++++ 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/pyramid/router.py b/pyramid/router.py index ba4f85b18..5190311d2 100644 --- a/pyramid/router.py +++ b/pyramid/router.py @@ -27,6 +27,7 @@ from pyramid.events import ( from pyramid.exceptions import PredicateMismatch from pyramid.httpexceptions import HTTPNotFound from pyramid.request import Request +from pyramid.view import find_views from pyramid.threadlocal import manager from pyramid.traversal import ( @@ -138,9 +139,14 @@ class Router(object): # find a view callable context_iface = providedBy(context) - view_callable = adapters.lookup( - (IViewClassifier, request.request_iface, context_iface), - IView, name=view_name, default=None) + views_iter = find_views( + registry, + request.request_iface, + context_iface, + view_name, + ) + + view_callable = next(views_iter, None) # invoke the view callable if view_callable is None: @@ -164,14 +170,7 @@ class Router(object): except PredicateMismatch: # look for other views that meet the predicate # criteria - for iface in context_iface.__sro__[1:]: - previous_view_callable = view_callable - view_callable = adapters.lookup( - (IViewClassifier, request.request_iface, iface), - IView, name=view_name, default=None) - # intermediate bases may lookup same view_callable - if view_callable is previous_view_callable: - continue + for view_callable in views_iter: if view_callable is not None: try: response = view_callable(context, request) @@ -241,4 +240,3 @@ class Router(object): request = self.request_factory(environ) response = self.invoke_subrequest(request, use_tweens=True) return response(request.environ, start_response) - diff --git a/pyramid/view.py b/pyramid/view.py index 02ac8849f..9ee9503f7 100644 --- a/pyramid/view.py +++ b/pyramid/view.py @@ -4,6 +4,8 @@ from zope.interface import providedBy from pyramid.interfaces import ( IRoutesMapper, + IMultiView, + ISecuredView, IView, IViewClassifier, ) @@ -414,3 +416,13 @@ class forbidden_view_config(object): settings['_info'] = info.codeinfo # fbo "action_method" return wrapped +def find_views(registry, request_iface, context_iface, view_name): + adapters = registry.adapters + for req_type in request_iface.__sro__: + for iface in context_iface.__sro__: + for view_type in (IView, ISecuredView, IMultiView): + view_callable = adapters.registered( + (IViewClassifier, req_type, iface), + view_type, name=view_name) + if view_callable is not None: + yield view_callable -- cgit v1.2.3 From 0b03c334dfb219738929d39d7dda4a84a30c251d Mon Sep 17 00:00:00 2001 From: John Anderson Date: Thu, 5 Feb 2015 15:40:52 -0800 Subject: Add tests to verify view lookup with multiple inheritance --- pyramid/tests/test_config/test_views.py | 72 +++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/pyramid/tests/test_config/test_views.py b/pyramid/tests/test_config/test_views.py index b0d03fb72..a83a25e58 100644 --- a/pyramid/tests/test_config/test_views.py +++ b/pyramid/tests/test_config/test_views.py @@ -1007,6 +1007,78 @@ class TestViewsConfigurationMixin(unittest.TestCase): request.params = {'param':'1'} self.assertEqual(wrapper(ctx, request), 'view8') + def test_view_with_most_specific_predicate(self): + from pyramid.renderers import null_renderer as nr + from pyramid.router import Router + + class OtherBase(object): pass + class Int1(object): pass + class Int2(object): pass + + class Resource(OtherBase, Int1, Int2): + def __init__(self, request): pass + + def unknown(context, request): return 'unknown' + def view(context, request): return 'hello' + + config = self._makeOne(autocommit=True) + config.add_route('root', '/', factory=Resource) + config.add_view(unknown, route_name='root', renderer=nr) + config.add_view( + view, renderer=nr, route_name='root', + context=Int1, request_method='GET' + ) + config.add_view( + view=view, renderer=nr, route_name='root', + context=Int2, request_method='POST' + ) + request = self._makeRequest(config) + request.method = 'POST' + request.params = {} + router = Router(config.registry) + response = router.handle_request(request) + self.assertEqual(response, 'hello') + + def test_view_with_most_specific_predicate_with_mismatch(self): + from pyramid.renderers import null_renderer as nr + from pyramid.router import Router + + class OtherBase(object): pass + class Int1(object): pass + class Int2(object): pass + + class Resource(OtherBase, Int1, Int2): + def __init__(self, request): pass + + def unknown(context, request): return 'unknown' + def view(context, request): return 'hello' + + config = self._makeOne(autocommit=True) + config.add_route('root', '/', factory=Resource) + + config.add_view( + unknown, + route_name='root', + renderer=nr, + request_method=('POST',), + xhr=True, + ) + + config.add_view( + view, renderer=nr, route_name='root', + context=Int1, request_method='GET' + ) + config.add_view( + view=view, renderer=nr, route_name='root', + context=Int2, request_method='POST' + ) + request = self._makeRequest(config) + request.method = 'POST' + request.params = {} + router = Router(config.registry) + response = router.handle_request(request) + self.assertEqual(response, 'hello') + def test_add_view_multiview___discriminator__(self): from pyramid.renderers import null_renderer from zope.interface import Interface -- cgit v1.2.3 From fb2824846c6bcdf5a42a2d0adcfe404d4c9a194e Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Fri, 6 Feb 2015 01:32:28 -0600 Subject: less dot operators in loops --- pyramid/view.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/pyramid/view.py b/pyramid/view.py index 9ee9503f7..9f37b4436 100644 --- a/pyramid/view.py +++ b/pyramid/view.py @@ -1,3 +1,4 @@ +import itertools import venusian from zope.interface import providedBy @@ -417,12 +418,16 @@ class forbidden_view_config(object): return wrapped def find_views(registry, request_iface, context_iface, view_name): - adapters = registry.adapters - for req_type in request_iface.__sro__: - for iface in context_iface.__sro__: - for view_type in (IView, ISecuredView, IMultiView): - view_callable = adapters.registered( - (IViewClassifier, req_type, iface), - view_type, name=view_name) - if view_callable is not None: - yield view_callable + registered = registry.adapters.registered + for req_type, iface in itertools.product( + request_iface.__sro__, context_iface.__sro__ + ): + source_ifaces = (IViewClassifier, req_type, iface) + for view_type in (IView, ISecuredView, IMultiView): + view_callable = registered( + source_ifaces, + view_type, + name=view_name, + ) + if view_callable is not None: + yield view_callable -- cgit v1.2.3 From 44bfdb5e87c1e45a2c901dbd4895b7e6dcbaf8ae Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Fri, 6 Feb 2015 01:41:27 -0600 Subject: req_type and ctx_type --- pyramid/view.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyramid/view.py b/pyramid/view.py index 9f37b4436..5a02c6312 100644 --- a/pyramid/view.py +++ b/pyramid/view.py @@ -419,10 +419,10 @@ class forbidden_view_config(object): def find_views(registry, request_iface, context_iface, view_name): registered = registry.adapters.registered - for req_type, iface in itertools.product( + for req_type, ctx_type in itertools.product( request_iface.__sro__, context_iface.__sro__ ): - source_ifaces = (IViewClassifier, req_type, iface) + source_ifaces = (IViewClassifier, req_type, ctx_type) for view_type in (IView, ISecuredView, IMultiView): view_callable = registered( source_ifaces, -- cgit v1.2.3 From e218e1fb1b5996afdc64ee071de515557f516f5a Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Sat, 7 Feb 2015 02:26:23 -0600 Subject: privitize _find_views --- pyramid/router.py | 4 ++-- pyramid/view.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyramid/router.py b/pyramid/router.py index 5190311d2..908fdc46c 100644 --- a/pyramid/router.py +++ b/pyramid/router.py @@ -27,7 +27,7 @@ from pyramid.events import ( from pyramid.exceptions import PredicateMismatch from pyramid.httpexceptions import HTTPNotFound from pyramid.request import Request -from pyramid.view import find_views +from pyramid.view import _find_views from pyramid.threadlocal import manager from pyramid.traversal import ( @@ -139,7 +139,7 @@ class Router(object): # find a view callable context_iface = providedBy(context) - views_iter = find_views( + views_iter = _find_views( registry, request.request_iface, context_iface, diff --git a/pyramid/view.py b/pyramid/view.py index 5a02c6312..2df79455c 100644 --- a/pyramid/view.py +++ b/pyramid/view.py @@ -417,7 +417,7 @@ class forbidden_view_config(object): settings['_info'] = info.codeinfo # fbo "action_method" return wrapped -def find_views(registry, request_iface, context_iface, view_name): +def _find_views(registry, request_iface, context_iface, view_name): registered = registry.adapters.registered for req_type, ctx_type in itertools.product( request_iface.__sro__, context_iface.__sro__ -- cgit v1.2.3 From c0070c95eeb39186c7c2ea0dea6b972c5782f185 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Sun, 22 Feb 2015 13:17:27 -0600 Subject: remove tuple creation from inner loop --- pyramid/view.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyramid/view.py b/pyramid/view.py index 2df79455c..4bd036428 100644 --- a/pyramid/view.py +++ b/pyramid/view.py @@ -419,11 +419,12 @@ class forbidden_view_config(object): def _find_views(registry, request_iface, context_iface, view_name): registered = registry.adapters.registered + view_types = (IView, ISecuredView, IMultiView) for req_type, ctx_type in itertools.product( request_iface.__sro__, context_iface.__sro__ ): source_ifaces = (IViewClassifier, req_type, ctx_type) - for view_type in (IView, ISecuredView, IMultiView): + for view_type in view_types: view_callable = registered( source_ifaces, view_type, -- cgit v1.2.3 From c15cbce92826cdee1dcc78b2060100b59a6d4caa Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Fri, 3 Apr 2015 16:09:22 -0400 Subject: cache view lookups; see #1557 --- pyramid/config/__init__.py | 10 ++++++ pyramid/config/views.py | 2 ++ pyramid/registry.py | 12 +++++++ pyramid/router.py | 59 +++++++++++++++------------------- pyramid/tests/test_config/test_init.py | 15 +++++++++ pyramid/tests/test_registry.py | 10 ++++++ pyramid/view.py | 40 +++++++++++++++-------- 7 files changed, 102 insertions(+), 46 deletions(-) diff --git a/pyramid/config/__init__.py b/pyramid/config/__init__.py index 401def208..549144fab 100644 --- a/pyramid/config/__init__.py +++ b/pyramid/config/__init__.py @@ -4,6 +4,7 @@ import logging import operator import os import sys +import threading import venusian from webob.exc import WSGIHTTPException as WebobWSGIHTTPException @@ -485,6 +486,15 @@ class Configurator( info=info, event=event) _registry.registerSelfAdapter = registerSelfAdapter + if not hasattr(_registry, '_lock'): + _registry._lock = threading.Lock() + + if not hasattr(_registry, '_clear_view_lookup_cache'): + def _clear_view_lookup_cache(): + _registry._view_lookup_cache = {} + _registry._clear_view_lookup_cache = _clear_view_lookup_cache + + # API def _get_introspector(self): diff --git a/pyramid/config/views.py b/pyramid/config/views.py index aba28467d..4c927f5b5 100644 --- a/pyramid/config/views.py +++ b/pyramid/config/views.py @@ -1344,6 +1344,8 @@ class ViewsConfiguratorMixin(object): multiview, (IExceptionViewClassifier, request_iface, context), IMultiView, name=name) + + self.registry._clear_view_lookup_cache() renderer_type = getattr(renderer, 'type', None) # gard against None intrspc = self.introspector if ( diff --git a/pyramid/registry.py b/pyramid/registry.py index 8c05940b9..fb4ec52e2 100644 --- a/pyramid/registry.py +++ b/pyramid/registry.py @@ -1,4 +1,5 @@ import operator +import threading from zope.interface import implementer @@ -39,6 +40,17 @@ class Registry(Components, dict): _settings = None + def __init__(self, *arg, **kw): + # add a registry-instance-specific lock, which is used when the lookup + # cache is mutated + self._lock = threading.Lock() + # add a view lookup cache + self._clear_view_lookup_cache() + Components.__init__(self, *arg, **kw) + + def _clear_view_lookup_cache(self): + self._view_lookup_cache = {} + def __nonzero__(self): # defeat bool determination via dict.__len__ return True diff --git a/pyramid/router.py b/pyramid/router.py index eac7f7976..9ce5d2487 100644 --- a/pyramid/router.py +++ b/pyramid/router.py @@ -138,47 +138,40 @@ class Router(object): # find a view callable context_iface = providedBy(context) - views_iter = _find_views( + view_callables = _find_views( registry, request.request_iface, context_iface, view_name, ) - view_callable = next(views_iter, None) - - # invoke the view callable - if view_callable is None: - if self.debug_notfound: - msg = ( - 'debug_notfound of url %s; path_info: %r, ' - 'context: %r, view_name: %r, subpath: %r, ' - 'traversed: %r, root: %r, vroot: %r, ' - 'vroot_path: %r' % ( - request.url, request.path_info, context, - view_name, subpath, traversed, root, vroot, - vroot_path) - ) - logger and logger.debug(msg) - else: - msg = request.path_info - raise HTTPNotFound(msg) - else: + pme = None + + for view_callable in view_callables: + # look for views that meet the predicate criteria try: response = view_callable(context, request) - except PredicateMismatch: - # look for other views that meet the predicate - # criteria - for view_callable in views_iter: - if view_callable is not None: - try: - response = view_callable(context, request) - break - except PredicateMismatch: - pass - else: - raise - return response + return response + except PredicateMismatch as _pme: + pme = _pme + + if pme is not None: + raise pme + + if self.debug_notfound: + msg = ( + 'debug_notfound of url %s; path_info: %r, ' + 'context: %r, view_name: %r, subpath: %r, ' + 'traversed: %r, root: %r, vroot: %r, ' + 'vroot_path: %r' % ( + request.url, request.path_info, context, + view_name, subpath, traversed, root, vroot, + vroot_path) + ) + logger and logger.debug(msg) + else: + msg = request.path_info + raise HTTPNotFound(msg) def invoke_subrequest(self, request, use_tweens=False): """Obtain a response object from the Pyramid application based on diff --git a/pyramid/tests/test_config/test_init.py b/pyramid/tests/test_config/test_init.py index 0ed04eb06..ec58e2466 100644 --- a/pyramid/tests/test_config/test_init.py +++ b/pyramid/tests/test_config/test_init.py @@ -343,6 +343,21 @@ class ConfiguratorTests(unittest.TestCase): {'info': '', 'provided': 'provided', 'required': 'required', 'name': 'abc', 'event': True}) + def test__fix_registry_adds__lock(self): + reg = DummyRegistry() + config = self._makeOne(reg) + config._fix_registry() + self.assertTrue(hasattr(reg, '_lock')) + + def test__fix_registry_adds_clear_view_lookup_cache(self): + reg = DummyRegistry() + config = self._makeOne(reg) + self.assertFalse(hasattr(reg, '_clear_view_lookup_cache')) + config._fix_registry() + self.assertFalse(hasattr(reg, '_view_lookup_cache')) + reg._clear_view_lookup_cache() + self.assertEqual(reg._view_lookup_cache, {}) + def test_setup_registry_calls_fix_registry(self): reg = DummyRegistry() config = self._makeOne(reg) diff --git a/pyramid/tests/test_registry.py b/pyramid/tests/test_registry.py index 50f49f24d..c9dff5b22 100644 --- a/pyramid/tests/test_registry.py +++ b/pyramid/tests/test_registry.py @@ -12,6 +12,16 @@ class TestRegistry(unittest.TestCase): registry = self._makeOne() self.assertEqual(registry.__nonzero__(), True) + def test__lock(self): + registry = self._makeOne() + self.assertTrue(registry._lock) + + def test_clear_view_cache_lookup(self): + registry = self._makeOne() + registry._view_lookup_cache[1] = 2 + registry._clear_view_lookup_cache() + self.assertEqual(registry._view_lookup_cache, {}) + def test_package_name(self): package_name = 'testing' registry = self._getTargetClass()(package_name) diff --git a/pyramid/view.py b/pyramid/view.py index 4bd036428..8cde1d004 100644 --- a/pyramid/view.py +++ b/pyramid/view.py @@ -419,16 +419,30 @@ class forbidden_view_config(object): def _find_views(registry, request_iface, context_iface, view_name): registered = registry.adapters.registered - view_types = (IView, ISecuredView, IMultiView) - for req_type, ctx_type in itertools.product( - request_iface.__sro__, context_iface.__sro__ - ): - source_ifaces = (IViewClassifier, req_type, ctx_type) - for view_type in view_types: - view_callable = registered( - source_ifaces, - view_type, - name=view_name, - ) - if view_callable is not None: - yield view_callable + cache = registry._view_lookup_cache + views = cache.get((request_iface, context_iface, view_name)) + if views is None: + views = [] + view_types = (IView, ISecuredView, IMultiView) + for req_type, ctx_type in itertools.product( + request_iface.__sro__, context_iface.__sro__ + ): + source_ifaces = (IViewClassifier, req_type, ctx_type) + for view_type in view_types: + view_callable = registered( + source_ifaces, + view_type, + name=view_name, + ) + if view_callable is not None: + views.append(view_callable) + if views: + # do not cache view lookup misses. rationale: dont allow cache to + # grow without bound if somebody tries to hit the site with many + # missing URLs. we could use an LRU cache instead, but then + # purposeful misses by an attacker would just blow out the cache + # anyway. downside: misses will almost always consume more CPU than + # hits in steady state. + with registry._lock: + cache[(request_iface, context_iface, view_name)] = views + return iter(views) -- cgit v1.2.3 From 99bc0c2f8ef7615fb3b1e4d8e3c6253157cf4ac8 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Fri, 3 Apr 2015 16:30:44 -0400 Subject: no need to iter() the result --- pyramid/view.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyramid/view.py b/pyramid/view.py index 8cde1d004..127122a36 100644 --- a/pyramid/view.py +++ b/pyramid/view.py @@ -445,4 +445,4 @@ def _find_views(registry, request_iface, context_iface, view_name): # hits in steady state. with registry._lock: cache[(request_iface, context_iface, view_name)] = views - return iter(views) + return views -- cgit v1.2.3 From 13f5943a213bbf82c09d02a04cc610b77cfe1c79 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 6 Apr 2015 00:06:17 -0400 Subject: use _find_views in pviews --- pyramid/scripts/pviews.py | 39 +++++++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/pyramid/scripts/pviews.py b/pyramid/scripts/pviews.py index 5e92ec4e6..15431e9b2 100644 --- a/pyramid/scripts/pviews.py +++ b/pyramid/scripts/pviews.py @@ -6,6 +6,7 @@ from pyramid.interfaces import IMultiView from pyramid.paster import bootstrap from pyramid.request import Request from pyramid.scripts.common import parse_vars +from pyramid.view import _find_views def main(argv=sys.argv, quiet=False): command = PViewsCommand(argv, quiet) @@ -90,11 +91,15 @@ class PViewsCommand(object): IRouteRequest, name=route.name, default=IRequest) - view = adapters.lookup( - (IViewClassifier, request_iface, context_iface), - IView, name='', default=None) - if view is None: + views = _find_views( + request.registry, + request_iface, + context_iface, + '' + ) + if not views: continue + view = views[0] view.__request_attrs__ = {} view.__request_attrs__['matchdict'] = match view.__request_attrs__['matched_route'] = route @@ -149,17 +154,31 @@ class PViewsCommand(object): # find a view callable context_iface = providedBy(context) if routes_multiview is None: - view = adapters.lookup( - (IViewClassifier, request_iface, context_iface), - IView, name=view_name, default=None) + views = _find_views( + request.registry, + request_iface, + context_iface, + view_name, + ) + if views: + view = views[0] + else: + view = None else: view = RoutesMultiView(infos, context_iface, root_factory, request) # routes are not registered with a view name if view is None: - view = adapters.lookup( - (IViewClassifier, request_iface, context_iface), - IView, name='', default=None) + views = _find_views( + request.registry, + request_iface, + context_iface, + '', + ) + if views: + view = views[0] + else: + view = None # we don't want a multiview here if IMultiView.providedBy(view): view = None -- cgit v1.2.3 From c8f1ea7e7185e7ea22abcd5abb9a23738165d140 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 6 Apr 2015 05:02:23 -0400 Subject: unused imports --- pyramid/scripts/pviews.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pyramid/scripts/pviews.py b/pyramid/scripts/pviews.py index 15431e9b2..4922c3a70 100644 --- a/pyramid/scripts/pviews.py +++ b/pyramid/scripts/pviews.py @@ -66,8 +66,6 @@ class PViewsCommand(object): from pyramid.interfaces import IRootFactory from pyramid.interfaces import IRouteRequest from pyramid.interfaces import IRoutesMapper - from pyramid.interfaces import IView - from pyramid.interfaces import IViewClassifier from pyramid.interfaces import ITraverser from pyramid.traversal import DefaultRootFactory from pyramid.traversal import ResourceTreeTraverser -- cgit v1.2.3 From e028e0a6fbb0050a007236721277b2fa62a00e5c Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 6 Apr 2015 05:15:57 -0400 Subject: use _find_views instead of lookup in proutes --- pyramid/scripts/proutes.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/pyramid/scripts/proutes.py b/pyramid/scripts/proutes.py index 544947724..a389c303c 100644 --- a/pyramid/scripts/proutes.py +++ b/pyramid/scripts/proutes.py @@ -4,18 +4,16 @@ import sys import textwrap import re +from zope.interface import Interface + from pyramid.paster import bootstrap from pyramid.compat import (string_types, configparser) -from pyramid.interfaces import ( - IRouteRequest, - IViewClassifier, - IView, -) +from pyramid.interfaces import IRouteRequest from pyramid.config import not_ from pyramid.scripts.common import parse_vars from pyramid.static import static_view -from zope.interface import Interface +from pyramid.view import _find_views PAD = 3 @@ -159,12 +157,11 @@ def get_route_data(route, registry): (route.name, _get_pattern(route), UNKNOWN_KEY, ANY_KEY) ] - view_callable = registry.adapters.lookup( - (IViewClassifier, request_iface, Interface), - IView, - name='', - default=None - ) + view_callables = _find_views(registry, request_iface, Interface, '') + if view_callables: + view_callable = view_callables[0] + else: + view_callable = None view_module = _get_view_module(view_callable) # Introspectables can be turned off, so there could be a chance -- cgit v1.2.3 From eb3ac8e33b0fe516fb6cf8d27061eff6dec1afc2 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 6 Apr 2015 05:32:47 -0400 Subject: break out _call_view into separate importable callable for use in scripts --- pyramid/router.py | 52 +++++++++++++++++++++------------------------------- pyramid/view.py | 26 ++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 31 deletions(-) diff --git a/pyramid/router.py b/pyramid/router.py index 9ce5d2487..4054ef52e 100644 --- a/pyramid/router.py +++ b/pyramid/router.py @@ -22,10 +22,9 @@ from pyramid.events import ( NewResponse, ) -from pyramid.exceptions import PredicateMismatch from pyramid.httpexceptions import HTTPNotFound from pyramid.request import Request -from pyramid.view import _find_views +from pyramid.view import _call_view from pyramid.request import apply_request_extensions from pyramid.threadlocal import manager @@ -138,40 +137,31 @@ class Router(object): # find a view callable context_iface = providedBy(context) - view_callables = _find_views( + response = _call_view( registry, - request.request_iface, + request, + context, context_iface, - view_name, + view_name ) - pme = None + if response is None: + if self.debug_notfound: + msg = ( + 'debug_notfound of url %s; path_info: %r, ' + 'context: %r, view_name: %r, subpath: %r, ' + 'traversed: %r, root: %r, vroot: %r, ' + 'vroot_path: %r' % ( + request.url, request.path_info, context, + view_name, subpath, traversed, root, vroot, + vroot_path) + ) + logger and logger.debug(msg) + else: + msg = request.path_info + raise HTTPNotFound(msg) - for view_callable in view_callables: - # look for views that meet the predicate criteria - try: - response = view_callable(context, request) - return response - except PredicateMismatch as _pme: - pme = _pme - - if pme is not None: - raise pme - - if self.debug_notfound: - msg = ( - 'debug_notfound of url %s; path_info: %r, ' - 'context: %r, view_name: %r, subpath: %r, ' - 'traversed: %r, root: %r, vroot: %r, ' - 'vroot_path: %r' % ( - request.url, request.path_info, context, - view_name, subpath, traversed, root, vroot, - vroot_path) - ) - logger and logger.debug(msg) - else: - msg = request.path_info - raise HTTPNotFound(msg) + return response def invoke_subrequest(self, request, use_tweens=False): """Obtain a response object from the Pyramid application based on diff --git a/pyramid/view.py b/pyramid/view.py index 127122a36..8565cd875 100644 --- a/pyramid/view.py +++ b/pyramid/view.py @@ -16,6 +16,8 @@ from pyramid.compat import ( decode_path_info, ) +from pyramid.exceptions import PredicateMismatch + from pyramid.httpexceptions import ( HTTPFound, default_exceptionresponse_view, @@ -446,3 +448,27 @@ def _find_views(registry, request_iface, context_iface, view_name): with registry._lock: cache[(request_iface, context_iface, view_name)] = views return views + +def _call_view(registry, request, context, context_iface, view_name): + view_callables = _find_views( + registry, + request.request_iface, + context_iface, + view_name, + ) + + pme = None + response = None + + for view_callable in view_callables: + # look for views that meet the predicate criteria + try: + response = view_callable(context, request) + return response + except PredicateMismatch as _pme: + pme = _pme + + if pme is not None: + raise pme + + return response -- cgit v1.2.3 From 8491964f35f5a3327751eb9dc42c9d6f128c9c22 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 6 Apr 2015 06:08:43 -0400 Subject: convert render_view_to_response to use _call_view --- pyramid/request.py | 1 + pyramid/testing.py | 3 +- pyramid/tests/test_config/test_init.py | 4 ++- pyramid/tests/test_config/test_views.py | 7 +++- pyramid/tests/test_view.py | 9 +++-- pyramid/view.py | 59 +++++++++++++++++++++------------ 6 files changed, 54 insertions(+), 29 deletions(-) diff --git a/pyramid/request.py b/pyramid/request.py index 3cbe5d9e3..13b8cd339 100644 --- a/pyramid/request.py +++ b/pyramid/request.py @@ -182,6 +182,7 @@ class Request( exc_info = None matchdict = None matched_route = None + request_iface = IRequest ResponseClass = Response diff --git a/pyramid/testing.py b/pyramid/testing.py index 667e6af4e..772914f3b 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, _get_response_factory +from pyramid.response import _get_response_factory from pyramid.registry import Registry from pyramid.security import ( @@ -327,6 +327,7 @@ class DummyRequest( charset = 'UTF-8' script_name = '' _registry = None + request_iface = IRequest def __init__(self, params=None, environ=None, headers=None, path='/', cookies=None, post=None, **kw): diff --git a/pyramid/tests/test_config/test_init.py b/pyramid/tests/test_config/test_init.py index ec58e2466..de199d079 100644 --- a/pyramid/tests/test_config/test_init.py +++ b/pyramid/tests/test_config/test_init.py @@ -1,5 +1,4 @@ import unittest -import warnings import os @@ -16,6 +15,8 @@ from pyramid.tests.test_config import DummyContext from pyramid.exceptions import ConfigurationExecutionError from pyramid.exceptions import ConfigurationConflictError +from pyramid.interfaces import IRequest + class ConfiguratorTests(unittest.TestCase): def _makeOne(self, *arg, **kw): from pyramid.config import Configurator @@ -1845,6 +1846,7 @@ class TestGlobalRegistriesIntegration(unittest.TestCase): class DummyRequest: subpath = () matchdict = None + request_iface = IRequest def __init__(self, environ=None): if environ is None: environ = {} diff --git a/pyramid/tests/test_config/test_views.py b/pyramid/tests/test_config/test_views.py index 706d7ff77..b4ad357ff 100644 --- a/pyramid/tests/test_config/test_views.py +++ b/pyramid/tests/test_config/test_views.py @@ -4131,7 +4131,11 @@ class DummyRegistry: self.settings = {} from zope.interface import implementer -from pyramid.interfaces import IResponse +from pyramid.interfaces import ( + IResponse, + IRequest, + ) + @implementer(IResponse) class DummyResponse(object): content_type = None @@ -4141,6 +4145,7 @@ class DummyResponse(object): class DummyRequest: subpath = () matchdict = None + request_iface = IRequest def __init__(self, environ=None): if environ is None: diff --git a/pyramid/tests/test_view.py b/pyramid/tests/test_view.py index 309fd47e2..ff73a93ab 100644 --- a/pyramid/tests/test_view.py +++ b/pyramid/tests/test_view.py @@ -5,6 +5,8 @@ from zope.interface import implementer from pyramid import testing +from pyramid.interfaces import IRequest + class BaseTest(object): def setUp(self): self.config = testing.setUp() @@ -13,7 +15,6 @@ class BaseTest(object): testing.tearDown() def _registerView(self, reg, app, name): - from pyramid.interfaces import IRequest from pyramid.interfaces import IViewClassifier for_ = (IViewClassifier, IRequest, IContext) from pyramid.interfaces import IView @@ -32,14 +33,11 @@ class BaseTest(object): return environ def _makeRequest(self, **environ): - from pyramid.interfaces import IRequest - from zope.interface import directlyProvides - from webob import Request + from pyramid.request import Request from pyramid.registry import Registry environ = self._makeEnviron(**environ) request = Request(environ) request.registry = Registry() - directlyProvides(request, IRequest) return request def _makeContext(self): @@ -676,6 +674,7 @@ def make_view(response): class DummyRequest: exception = None + request_iface = IRequest def __init__(self, environ=None): if environ is None: diff --git a/pyramid/view.py b/pyramid/view.py index 8565cd875..1ea4e3b47 100644 --- a/pyramid/view.py +++ b/pyramid/view.py @@ -11,10 +11,7 @@ from pyramid.interfaces import ( IViewClassifier, ) -from pyramid.compat import ( - map_, - decode_path_info, - ) +from pyramid.compat import decode_path_info from pyramid.exceptions import PredicateMismatch @@ -45,24 +42,24 @@ def render_view_to_response(context, request, name='', secure=True): disallowed. If ``secure`` is ``False``, no permission checking is done.""" - provides = [IViewClassifier] + map_(providedBy, (request, context)) - try: - reg = request.registry - except AttributeError: - reg = get_current_registry() - view = reg.adapters.lookup(provides, IView, name=name) - if view is None: - return None - if not secure: - # the view will have a __call_permissive__ attribute if it's - # secured; otherwise it won't. - view = getattr(view, '__call_permissive__', view) + registry = getattr(request, 'registry', None) + if registry is None: + registry = get_current_registry() + + context_iface = providedBy(context) + + response = _call_view( + registry, + request, + context, + context_iface, + name, + secure = secure, + ) + + return response # NB: might be None - # if this view is secured, it will raise a Forbidden - # appropriately if the executing user does not have the proper - # permission - return view(context, request) def render_view_to_iterable(context, request, name='', secure=True): """ Call the :term:`view callable` configured with a :term:`view @@ -447,9 +444,17 @@ def _find_views(registry, request_iface, context_iface, view_name): # hits in steady state. with registry._lock: cache[(request_iface, context_iface, view_name)] = views + return views -def _call_view(registry, request, context, context_iface, view_name): +def _call_view( + registry, + request, + context, + context_iface, + view_name, + secure=True, + ): view_callables = _find_views( registry, request.request_iface, @@ -463,6 +468,18 @@ def _call_view(registry, request, context, context_iface, view_name): for view_callable in view_callables: # look for views that meet the predicate criteria try: + if not secure: + # the view will have a __call_permissive__ attribute if it's + # secured; otherwise it won't. + view_callable = getattr( + view_callable, + '__call_permissive__', + view_callable + ) + + # if this view is secured, it will raise a Forbidden + # appropriately if the executing user does not have the proper + # permission response = view_callable(context, request) return response except PredicateMismatch as _pme: -- cgit v1.2.3 From 2a842e615d5f36b4dfb26e843de7d73147e546f8 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 6 Apr 2015 06:18:29 -0400 Subject: add comment about not changing view_execution_permitted use of .lookup for view finding --- pyramid/security.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyramid/security.py b/pyramid/security.py index f993ef353..82e6b73a9 100644 --- a/pyramid/security.py +++ b/pyramid/security.py @@ -225,6 +225,8 @@ def view_execution_permitted(context, request, name=''): """ reg = _get_registry(request) provides = [IViewClassifier] + map_(providedBy, (request, context)) + # XXX not sure what to do here about using _find_views or analogue; + # for now let's just keep it as-is view = reg.adapters.lookup(provides, ISecuredView, name=name) if view is None: view = reg.adapters.lookup(provides, IView, name=name) -- cgit v1.2.3 From 17c7f4091a3e747fb215328803462ff14de556d2 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 6 Apr 2015 06:44:14 -0400 Subject: change excview_tween to use _call_view instead of adapters.lookup; parameterize _call_view and _find_views to cope --- pyramid/tweens.py | 17 +++++++++++------ pyramid/view.py | 26 ++++++++++++++++++++++---- 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/pyramid/tweens.py b/pyramid/tweens.py index 831de8481..d6044dcdc 100644 --- a/pyramid/tweens.py +++ b/pyramid/tweens.py @@ -3,17 +3,16 @@ import sys from pyramid.interfaces import ( IExceptionViewClassifier, IRequest, - IView, ) from zope.interface import providedBy +from pyramid.view import _call_view def excview_tween_factory(handler, registry): """ A :term:`tween` factory which produces a tween that catches an exception raised by downstream tweens (or the main Pyramid request handler) and, if possible, converts it into a Response using an :term:`exception view`.""" - adapters = registry.adapters def excview_tween(request): attrs = request.__dict__ @@ -39,11 +38,17 @@ def excview_tween_factory(handler, registry): # https://github.com/Pylons/pyramid/issues/700 request_iface = attrs.get('request_iface', IRequest) provides = providedBy(exc) - for_ = (IExceptionViewClassifier, request_iface.combined, provides) - view_callable = adapters.lookup(for_, IView, default=None) - if view_callable is None: + response = _call_view( + registry, + request, + exc, + provides, + '', + view_classifier=IExceptionViewClassifier, + request_iface=request_iface.combined + ) + if response is None: raise - response = view_callable(exc, request) return response diff --git a/pyramid/view.py b/pyramid/view.py index 1ea4e3b47..ffa3fa791 100644 --- a/pyramid/view.py +++ b/pyramid/view.py @@ -9,6 +9,7 @@ from pyramid.interfaces import ( ISecuredView, IView, IViewClassifier, + IRequest, ) from pyramid.compat import decode_path_info @@ -416,17 +417,27 @@ class forbidden_view_config(object): settings['_info'] = info.codeinfo # fbo "action_method" return wrapped -def _find_views(registry, request_iface, context_iface, view_name): +def _find_views( + registry, + request_iface, + context_iface, + view_name, + view_types=None, + view_classifier=None, + ): + if view_types is None: + view_types = (IView, ISecuredView, IMultiView) + if view_classifier is None: + view_classifier = IViewClassifier registered = registry.adapters.registered cache = registry._view_lookup_cache views = cache.get((request_iface, context_iface, view_name)) if views is None: views = [] - view_types = (IView, ISecuredView, IMultiView) for req_type, ctx_type in itertools.product( request_iface.__sro__, context_iface.__sro__ ): - source_ifaces = (IViewClassifier, req_type, ctx_type) + source_ifaces = (view_classifier, req_type, ctx_type) for view_type in view_types: view_callable = registered( source_ifaces, @@ -453,13 +464,20 @@ def _call_view( context, context_iface, view_name, + view_types=None, + view_classifier=None, secure=True, + request_iface=None, ): + if request_iface is None: + request_iface = getattr(request, 'request_iface', IRequest) view_callables = _find_views( registry, - request.request_iface, + request_iface, context_iface, view_name, + view_types=view_types, + view_classifier=view_classifier, ) pme = None -- cgit v1.2.3