Repository: Pylons/pyramid

Issue:
exception views squash the original exception if an exception view predicate is tested and fails
If you register an exception view with a predicate such as `config.add_view(view, context=Exception, request_method='GET')`, this will cause any errors from a POST request (without a matching exception view) to be squashed into a `PredicateMismatch` exception which will be propagated up the wsgi stack instead of the original exception.



Discussion:

Resolving patch:
diff --git a/pyramid/tweens.py b/pyramid/tweens.py
--- a/pyramid/tweens.py
+++ b/pyramid/tweens.py
@@ -1,5 +1,7 @@
 import sys
 
+from pyramid.compat import reraise
+from pyramid.exceptions import PredicateMismatch
 from pyramid.interfaces import (
     IExceptionViewClassifier,
     IRequest,
@@ -38,17 +40,26 @@ def excview_tween(request):
             # https://github.com/Pylons/pyramid/issues/700
             request_iface = attrs.get('request_iface', IRequest)
             provides = providedBy(exc)
-            response = _call_view(
-                registry,
-                request,
-                exc,
-                provides,
-                '',
-                view_classifier=IExceptionViewClassifier,
-                request_iface=request_iface.combined
-                )
+            try:
+                response = _call_view(
+                    registry,
+                    request,
+                    exc,
+                    provides,
+                    '',
+                    view_classifier=IExceptionViewClassifier,
+                    request_iface=request_iface.combined
+                    )
+
+            # if views matched but did not pass predicates, squash the error
+            # and re-raise the original exception
+            except PredicateMismatch:
+                response = None
+
+            # re-raise the original exception as no exception views were
+            # able to handle the error
             if response is None:
-                raise
+                reraise(*attrs['exc_info'])
 
         return response
 


Test patch:
diff --git a/pyramid/tests/test_tweens.py b/pyramid/tests/test_tweens.py
new file mode 100644
--- /dev/null
+++ b/pyramid/tests/test_tweens.py
@@ -0,0 +1,73 @@
+import unittest
+from pyramid import testing
+
+class Test_excview_tween_factory(unittest.TestCase):
+    def setUp(self):
+        self.config = testing.setUp()
+
+    def tearDown(self):
+        testing.tearDown()
+
+    def _makeOne(self, handler, registry=None):
+        from pyramid.tweens import excview_tween_factory
+        if registry is None:
+            registry = self.config.registry
+        return excview_tween_factory(handler, registry)
+
+    def test_it_passthrough_no_exception(self):
+        dummy_response = DummyResponse()
+        def handler(request):
+            return dummy_response
+        tween = self._makeOne(handler)
+        request = DummyRequest()
+        result = tween(request)
+        self.assertTrue(result is dummy_response)
+
+    def test_it_catches_notfound(self):
+        from pyramid.request import Request
+        from pyramid.httpexceptions import HTTPNotFound
+        self.config.add_notfound_view(lambda exc, request: exc)
+        def handler(request):
+            raise HTTPNotFound
+        tween = self._makeOne(handler)
+        request = Request.blank('/')
+        result = tween(request)
+        self.assertEqual(result.status, '404 Not Found')
+
+    def test_it_catches_with_predicate(self):
+        from pyramid.request import Request
+        from pyramid.response import Response
+        def excview(request):
+            return Response('foo')
+        self.config.add_view(excview, context=ValueError, request_method='GET')
+        def handler(request):
+            raise ValueError
+        tween = self._makeOne(handler)
+        request = Request.blank('/')
+        result = tween(request)
+        self.assertTrue(b'foo' in result.body)
+
+    def test_it_reraises_on_mismatch(self):
+        from pyramid.request import Request
+        def excview(request): pass
+        self.config.add_view(excview, context=ValueError, request_method='GET')
+        def handler(request):
+            raise ValueError
+        tween = self._makeOne(handler)
+        request = Request.blank('/')
+        request.method = 'POST'
+        self.assertRaises(ValueError, lambda: tween(request))
+
+    def test_it_reraises_on_no_match(self):
+        from pyramid.request import Request
+        def handler(request):
+            raise ValueError
+        tween = self._makeOne(handler)
+        request = Request.blank('/')
+        self.assertRaises(ValueError, lambda: tween(request))
+
+class DummyRequest:
+    pass
+
+class DummyResponse:
+    pass