From b3643d8e587b48711f3224673d6c864d8ce1f93f Mon Sep 17 00:00:00 2001 From: Amos Latteier Date: Thu, 10 Jan 2013 14:55:07 -0500 Subject: Make predicate mismatches not hide other possible valid views. This is mostly an issue for REST style views that use request_method predicates and are registered for context interfaces. See Issue #409 --- pyramid/router.py | 22 ++++++++++++-- pyramid/tests/test_router.py | 72 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 2 deletions(-) diff --git a/pyramid/router.py b/pyramid/router.py index 9b6138ea9..bb2466ec3 100644 --- a/pyramid/router.py +++ b/pyramid/router.py @@ -1,4 +1,5 @@ from zope.interface import ( + Interface, implementer, providedBy, ) @@ -24,6 +25,7 @@ from pyramid.events import ( NewResponse, ) +from pyramid.exceptions import PredicateMismatch from pyramid.httpexceptions import HTTPNotFound from pyramid.request import Request from pyramid.threadlocal import manager @@ -158,8 +160,24 @@ class Router(object): msg = request.path_info raise HTTPNotFound(msg) else: - response = view_callable(context, request) - + try: + response = view_callable(context, request) + except PredicateMismatch: + # look for other views that meet the predicate + # criteria + for iface in [i for i in context_iface.flattened() + if i != Interface]: + view_callable = adapters.lookup( + (IViewClassifier, request.request_iface, iface), + IView, name=view_name, default=None) + if view_callable is not None: + try: + response = view_callable(context, request) + break + except PredicateMismatch: + pass + else: + raise return response def invoke_subrequest(self, request, use_tweens=False): diff --git a/pyramid/tests/test_router.py b/pyramid/tests/test_router.py index 65152ca05..32e74a3b3 100644 --- a/pyramid/tests/test_router.py +++ b/pyramid/tests/test_router.py @@ -1164,6 +1164,78 @@ class TestRouter(unittest.TestCase): start_response = DummyStartResponse() self.assertRaises(RuntimeError, router, environ, start_response) + def test_call_view_raises_predicate_mismatch(self): + from pyramid.exceptions import PredicateMismatch + from pyramid.interfaces import IViewClassifier + from pyramid.interfaces import IRequest + view = DummyView(DummyResponse(), raise_exception=PredicateMismatch) + self._registerView(view, '', IViewClassifier, IRequest, None) + environ = self._makeEnviron() + router = self._makeOne() + start_response = DummyStartResponse() + self.assertRaises(PredicateMismatch, router, environ, start_response) + + def test_call_view_predicate_mismatch_doesnt_hide_views(self): + from pyramid.exceptions import PredicateMismatch + from pyramid.interfaces import IViewClassifier + from pyramid.interfaces import IRequest, IResponse + from pyramid.response import Response + from zope.interface import Interface, implementer + class IContext(Interface): + pass + @implementer(IContext) + class DummyContext: + pass + context = DummyContext() + self._registerTraverserFactory(context) + view = DummyView(DummyResponse(), raise_exception=PredicateMismatch) + self._registerView(view, '', IViewClassifier, IRequest, + DummyContext) + good_view = DummyView('abc') + self._registerView(self.config.derive_view(good_view), + '', IViewClassifier, IRequest, IContext) + router = self._makeOne() + def make_response(s): + return Response(s) + router.registry.registerAdapter(make_response, (str,), IResponse) + environ = self._makeEnviron() + start_response = DummyStartResponse() + app_iter = router(environ, start_response) + self.assertEqual(app_iter, [b'abc']) + + def test_call_view_multiple_predicate_mismatches_dont_hide_views(self): + from pyramid.exceptions import PredicateMismatch + from pyramid.interfaces import IViewClassifier + from pyramid.interfaces import IRequest, IResponse + from pyramid.response import Response + from zope.interface import Interface, implementer + class IBaseContext(Interface): + pass + class IContext(IBaseContext): + pass + @implementer(IContext) + class DummyContext: + pass + context = DummyContext() + self._registerTraverserFactory(context) + view1 = DummyView(DummyResponse(), raise_exception=PredicateMismatch) + self._registerView(view1, '', IViewClassifier, IRequest, + DummyContext) + view2 = DummyView(DummyResponse(), raise_exception=PredicateMismatch) + self._registerView(view2, '', IViewClassifier, IRequest, + IContext) + good_view = DummyView('abc') + self._registerView(self.config.derive_view(good_view), + '', IViewClassifier, IRequest, IBaseContext) + router = self._makeOne() + def make_response(s): + return Response(s) + router.registry.registerAdapter(make_response, (str,), IResponse) + environ = self._makeEnviron() + start_response = DummyStartResponse() + app_iter = router(environ, start_response) + self.assertEqual(app_iter, [b'abc']) + class DummyPredicate(object): def __call__(self, info, request): return True -- cgit v1.2.3 From 9e1e6d3f6d8e131afe102f60af3ca8f597b32745 Mon Sep 17 00:00:00 2001 From: Amos Latteier Date: Thu, 10 Jan 2013 15:19:08 -0500 Subject: Change log note. --- CHANGES.txt | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index adf448945..54623ddd2 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,40 @@ +Bug Fixes +--------- + +- Now predicate mismatches don't hide valid views. This is mostly an + issue with views that use request_method predicates. Here's an + example that now works:: + + class IResource(Interface): + ... + + @view_config(context=IResource) + def get(context, request): + ... + + @view_config(context=IResource, request_method='POST') + def post(context, request): + ... + + @view_config(context=IResource, request_method='DELETE') + def delete(context, request): + ... + + @implementor(IResource) + class MyResource: + ... + + @view_config(context=MyResource, request_method='POST') + def override_post(context, request): + ... + + Previously the override_post view registration would hide the get + and delete views in the context of MyResource -- leading to a + predicate mismatch error when trying to use GET or DELETE + methods. Now the views are found and no predicate mismatch is + raised. + + 1.4 (2012-12-18) ================ -- cgit v1.2.3 From e100873f6422b20d6bfda78abd684d7908d8a825 Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Tue, 12 Feb 2013 17:08:10 -0800 Subject: Add some tests for how content types and HTTP "Accept" header handling behave. --- pyramid/tests/test_integration.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/pyramid/tests/test_integration.py b/pyramid/tests/test_integration.py index bf3960b2d..b880cd741 100644 --- a/pyramid/tests/test_integration.py +++ b/pyramid/tests/test_integration.py @@ -634,6 +634,32 @@ class RendererScanAppTest(IntegrationBase, unittest.TestCase): res = testapp.get('/two', status=200) self.assertTrue(b'Two!' in res.body) +class AcceptContentTypeTest(unittest.TestCase): + def setUp(self): + def hello_view(request): + return {'message': 'Hello!'} + from pyramid.config import Configurator + config = Configurator() + config.add_route('hello', '/hello') + config.add_view(hello_view, route_name='hello', accept='text/plain', renderer='string') + config.add_view(hello_view, route_name='hello', accept='application/json', renderer='json') + app = config.make_wsgi_app() + from webtest import TestApp + self.testapp = TestApp(app) + + def test_ordering(self): + res = self.testapp.get('/hello', headers={'Accept': 'application/json; q=1.0, text/plain; q=0.9'}, status=200) + self.assertEqual(res.content_type, 'application/json') + res = self.testapp.get('/hello', headers={'Accept': 'text/plain; q=0.9, application/json; q=1.0'}, status=200) + self.assertEqual(res.content_type, 'application/json') + + def test_wildcards(self): + res = self.testapp.get('/hello', headers={'Accept': 'application/*'}, status=200) + self.assertEqual(res.content_type, 'application/json') + res = self.testapp.get('/hello', headers={'Accept': 'text/*'}, status=200) + self.assertEqual(res.content_type, 'text/plain') + + class DummyContext(object): pass -- cgit v1.2.3 From 69c3ad9896ae3ef82fa458629bc2213fd7b8de84 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Tue, 19 Mar 2013 10:10:10 -0700 Subject: add test to ensure unrelated views are not invoked after PredicateMismatch --- pyramid/tests/test_router.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/pyramid/tests/test_router.py b/pyramid/tests/test_router.py index 32e74a3b3..432959147 100644 --- a/pyramid/tests/test_router.py +++ b/pyramid/tests/test_router.py @@ -1236,6 +1236,32 @@ class TestRouter(unittest.TestCase): app_iter = router(environ, start_response) self.assertEqual(app_iter, [b'abc']) + def test_call_view_predicate_mismatch_doesnt_find_unrelated_views(self): + from pyramid.exceptions import PredicateMismatch + from pyramid.interfaces import IViewClassifier + from pyramid.interfaces import IRequest + from zope.interface import Interface, implementer + class IContext(Interface): + pass + class IOtherContext(Interface): + pass + @implementer(IContext) + class DummyContext: + pass + context = DummyContext() + self._registerTraverserFactory(context) + view = DummyView(DummyResponse(), raise_exception=PredicateMismatch) + self._registerView(view, '', IViewClassifier, IRequest, + DummyContext) + please_dont_call_me_view = DummyView('abc') + self._registerView(self.config.derive_view(please_dont_call_me_view), + '', IViewClassifier, IRequest, IOtherContext) + router = self._makeOne() + environ = self._makeEnviron() + router = self._makeOne() + start_response = DummyStartResponse() + self.assertRaises(PredicateMismatch, router, environ, start_response) + class DummyPredicate(object): def __call__(self, info, request): return True -- cgit v1.2.3