Repository: Pylons/pyramid

Issue:
request.invoke_exception_view does not conform to its docs
Currently the docs say `request.invoke_exception_view` will return `None` but it will actually raise a `PredicateMismatch` if it attempts a view. This needs to be standardized before release. The two options:
1. Squash `PredicateMismatch` into `None` such that you always receive a valid response or `None`, or a legitimate exception raised by the invoked view itself.
2. Raise `HTTPNotFound` instead of returning `None` such that you either receive a valid response or an exception (could be `HTTPNotFound`, `PredicateMismatch` or anything raised by the exception view).



Discussion:
Number 2 would basically document the existing behaviour, no?

Existing behavior is that it raises `PredicateMismatch` if a view is registered for a matching `context_iface, request_iface` but does not match predicates. If no views match at all, then `None` is returned. The conversion of the latter case to `HTTPNotFound` is done in the router itself which is why this oddity slipped by in this code which is not using the router. I think the appropriate change is to pursue option 2 instead of squashing the exceptions.


Resolving patch:
diff --git a/pyramid/view.py b/pyramid/view.py
--- a/pyramid/view.py
+++ b/pyramid/view.py
@@ -21,6 +21,7 @@
 
 from pyramid.httpexceptions import (
     HTTPFound,
+    HTTPNotFound,
     default_exceptionresponse_view,
     )
 
@@ -589,8 +590,9 @@ def invoke_exception_view(
         object that this method is attached to as the ``request``, and
         ``True`` for ``secure``.
 
-        This method returns a :term:`response` object or ``None`` if no
-        matching exception view can be found."""
+        This method returns a :term:`response` object or raises
+        :class:`pyramid.httpexceptions.HTTPNotFound` if a matching view cannot
+        be found."""
 
         if request is None:
             request = self
@@ -623,4 +625,6 @@ def invoke_exception_view(
                 secure=secure,
                 request_iface=request_iface.combined,
                 )
+            if response is None:
+                raise HTTPNotFound
             return response


Test patch:
diff --git a/pyramid/tests/test_view.py b/pyramid/tests/test_view.py
--- a/pyramid/tests/test_view.py
+++ b/pyramid/tests/test_view.py
@@ -805,6 +805,31 @@ def exc_view(exc, request):
         else: # pragma: no cover
             self.fail()
 
+    def test_it_raises_if_not_found(self):
+        from pyramid.httpexceptions import HTTPNotFound
+        request = self._makeOne()
+        dummy_exc = RuntimeError()
+        try:
+            raise dummy_exc
+        except RuntimeError:
+            self.assertRaises(HTTPNotFound, request.invoke_exception_view)
+        else: # pragma: no cover
+            self.fail()
+
+    def test_it_raises_predicate_mismatch(self):
+        from pyramid.exceptions import PredicateMismatch
+        def exc_view(exc, request): pass
+        self.config.add_view(exc_view, context=Exception, request_method='POST')
+        request = self._makeOne()
+        request.method = 'GET'
+        dummy_exc = RuntimeError()
+        try:
+            raise dummy_exc
+        except RuntimeError:
+            self.assertRaises(PredicateMismatch, request.invoke_exception_view)
+        else: # pragma: no cover
+            self.fail()
+
 class ExceptionResponse(Exception):
     status = '404 Not Found'
     app_iter = ['Not Found']