Sorry, I forgot to attach patch.

On Thu, Apr 1, 2010 at 5:28 PM, Andrey Popp <8may...@gmail.com> wrote:
> Hello,
>
> I have a preview for exception views functionality.
>
> I've refactored Router to handle exceptions via IView lookups on
> IRequest, Exception class. That means all exception views should be
> global (not belongs to specific route) and exception views can be
> clashed with ordinary views.
>
> Forbidden and NotFound views are special case of exception views now.
> Default views for that exceptions are registered via
> repoze.bfg.includes/configure.zcml.
>
> All tests are pass, 5 tests cases was removed and 7 new was added.
>
> If you like it, I will make other changes related to exception views
> configuration (possibly add methods to Configurator API and new ZCML
> directive?).
>
> Thanks.
>
> On Fri, Mar 12, 2010 at 5:27 AM, Chris McDonough <chr...@plope.com> wrote:
>> On 3/10/10 9:59 AM, Andrey Popp wrote:
>>>>
>>>> The context of such an "exception view" will be the exception instance
>>>> itself.  If you want a particular exception to be able to use a "real"
>>>> context, you'll make it available as an attribute of the exception for
>>>> use
>>>> by the registered exception view (e.g. context.context or so).
>>>
>>> Thanks, this is that I was talking about. Will try to implement.
>>>
>>
>> Great.  This would be an excellent feature to have.
>>
>> --
>> Chris McDonough
>> Agendaless Consulting, Fredericksburg VA
>> The repoze.bfg Web Application Framework Book: http://bfg.repoze.org/book
>>
>
>
>
> --
> Andrey Popp
>
> phone: +7 911 740 24 91
> e-mail: 8may...@gmail.com
>



-- 
Andrey Popp

phone: +7 911 740 24 91
e-mail: 8may...@gmail.com
=== modified file 'repoze/bfg/includes/configure.zcml'
--- repoze/bfg/includes/configure.zcml	2009-11-25 17:54:40 +0000
+++ repoze/bfg/includes/configure.zcml	2010-04-01 13:13:27 +0000
@@ -2,4 +2,14 @@
 
   <include file="meta.zcml" />
 
+  <view
+    for="repoze.bfg.exceptions.NotFound"
+    view="repoze.bfg.view.default_notfound_view"
+    />
+
+  <view
+    for="repoze.bfg.exceptions.Forbidden"
+    view="repoze.bfg.view.default_forbidden_view"
+    />
+
 </configure>

=== modified file 'repoze/bfg/router.py'
--- repoze/bfg/router.py	2010-01-25 12:37:07 +0000
+++ repoze/bfg/router.py	2010-04-01 13:11:20 +0000
@@ -37,8 +37,6 @@
     def __init__(self, registry):
         q = registry.queryUtility
         self.logger = q(IDebugLogger)
-        self.notfound_view = q(INotFoundView, default=default_notfound_view)
-        self.forbidden_view = q(IForbiddenView, default=default_forbidden_view)
         self.root_factory = q(IRootFactory, default=DefaultRootFactory)
         self.routes_mapper = q(IRoutesMapper)
         self.root_policy = self.root_factory # b/w compat
@@ -56,6 +54,7 @@
         return an iterable.
         """
         registry = self.registry
+        adapters = registry.adapters
         has_listeners = registry.has_listeners
         logger = self.logger
         manager = self.threadlocal_manager
@@ -72,7 +71,7 @@
             has_listeners and registry.notify(NewRequest(request))
 
             request_iface = IRequest
-            
+
             try:
                 # find the root
                 root_factory = self.root_factory
@@ -94,7 +93,6 @@
                 attrs['root'] = root
 
                 # find a view callable
-                adapters = registry.adapters
                 traverser = adapters.queryAdapter(root, ITraverser)
                 if traverser is None:
                     traverser = ModelGraphTraverser(root)
@@ -125,25 +123,27 @@
                     else:
                         msg = request.path_info
                     environ['repoze.bfg.message'] = msg
-                    response = self.notfound_view(context, request)
+                    raise NotFound(msg)
                 else:
                     response = view_callable(context, request)
 
             # handle exceptions raised during root finding and view lookup
-            except Forbidden, why:
-                try:
-                    msg = why[0]
-                except (IndexError, TypeError):
-                    msg = ''
-                environ['repoze.bfg.message'] = msg
-                response = self.forbidden_view(context, request)
-            except NotFound, why:
-                try:
-                    msg = why[0]
-                except (IndexError, TypeError):
-                    msg = ''
-                environ['repoze.bfg.message'] = msg
-                response = self.notfound_view(context, request)
+            except Exception, why:
+                # lookup exception view
+                # XXX: exception views should NOT be registered against
+                # specific routes, they are global, so we need separate ZCML
+                # directive and Configurator method to register them.
+                view_callable = adapters.lookup(
+                    (IRequest, providedBy(why)),
+                    IView, default=None)
+                if view_callable is None:
+                    raise
+                try:
+                    msg = why[0]
+                except Exception:
+                    msg = ''
+                environ['repoze.bfg.message'] = msg
+                response = view_callable(why, request)
 
             # process the response
             has_listeners and registry.notify(NewResponse(response))
@@ -159,7 +159,7 @@
             if 'global_response_headers' in attrs:
                 headers = list(headers)
                 headers.extend(attrs['global_response_headers'])
-            
+
             start_response(status, headers)
             return app_iter
 

=== modified file 'repoze/bfg/tests/test_router.py'
--- repoze/bfg/tests/test_router.py	2010-01-24 08:45:35 +0000
+++ repoze/bfg/tests/test_router.py	2010-04-01 13:10:44 +0000
@@ -11,6 +11,18 @@
     def tearDown(self):
         testing.tearDown()
 
+    def _registerDefaultNotFoundView(self):
+        from repoze.bfg.interfaces import IRequest
+        from repoze.bfg.exceptions import NotFound
+        from repoze.bfg.view import default_notfound_view
+        self._registerView(default_notfound_view, '', IRequest, NotFound)
+
+    def _registerDefaultForbiddenView(self):
+        from repoze.bfg.interfaces import IRequest
+        from repoze.bfg.exceptions import Forbidden
+        from repoze.bfg.view import default_forbidden_view
+        self._registerView(default_forbidden_view, '', IRequest, Forbidden)
+
     def _registerRouteRequest(self, name):
         from repoze.bfg.interfaces import IRouteRequest
         from zope.interface import Interface
@@ -118,33 +130,8 @@
         router = self._makeOne()
         self.assertEqual(router.root_policy, rootfactory)
 
-    def test_iforbiddenview_override(self):
-        from repoze.bfg.interfaces import IForbiddenView
-        def app():
-            """ """
-        self.registry.registerUtility(app, IForbiddenView)
-        router = self._makeOne()
-        self.assertEqual(router.forbidden_view, app)
-
-    def test_iforbiddenview_nooverride(self):
-        router = self._makeOne()
-        from repoze.bfg.view import default_forbidden_view
-        self.assertEqual(router.forbidden_view, default_forbidden_view)
-
-    def test_inotfoundview_override(self):
-        from repoze.bfg.interfaces import INotFoundView
-        def app():
-            """ """
-        self.registry.registerUtility(app, INotFoundView)
-        router = self._makeOne()
-        self.assertEqual(router.notfound_view, app)
-
-    def test_inotfoundview_nooverride(self):
-        router = self._makeOne()
-        from repoze.bfg.view import default_notfound_view
-        self.assertEqual(router.notfound_view, default_notfound_view)
-
     def test_call_traverser_default(self):
+        self._registerDefaultNotFoundView()
         environ = self._makeEnviron()
         logger = self._registerLogger()
         router = self._makeOne()
@@ -158,8 +145,10 @@
         self.failIf('debug_notfound' in result[0])
         self.assertEqual(len(logger.messages), 0)
 
+    # TODO: Move this test case out of router tests
     def test_traverser_raises_notfound_class(self):
         from repoze.bfg.exceptions import NotFound
+        self._registerDefaultNotFoundView()
         environ = self._makeEnviron()
         context = DummyContext()
         self._registerTraverserFactory(context, raise_error=NotFound)
@@ -172,8 +161,10 @@
         self.assertEqual(status, '404 Not Found')
         self.failUnless('<code></code>' in result[0], result)
 
+    # TODO: Move this test case out of router tests
     def test_traverser_raises_notfound_instance(self):
         from repoze.bfg.exceptions import NotFound
+        self._registerDefaultNotFoundView()
         environ = self._makeEnviron()
         context = DummyContext()
         self._registerTraverserFactory(context, raise_error=NotFound('foo'))
@@ -188,6 +179,7 @@
 
     def test_traverser_raises_forbidden_class(self):
         from repoze.bfg.exceptions import Forbidden
+        self._registerDefaultForbiddenView()
         environ = self._makeEnviron()
         context = DummyContext()
         self._registerTraverserFactory(context, raise_error=Forbidden)
@@ -202,6 +194,7 @@
 
     def test_traverser_raises_forbidden_instance(self):
         from repoze.bfg.exceptions import Forbidden
+        self._registerDefaultForbiddenView()
         environ = self._makeEnviron()
         context = DummyContext()
         self._registerTraverserFactory(context, raise_error=Forbidden('foo'))
@@ -215,6 +208,7 @@
         self.failUnless('<code>foo</code>' in result[0], result)
 
     def test_call_no_view_registered_no_isettings(self):
+        self._registerDefaultNotFoundView()
         environ = self._makeEnviron()
         context = DummyContext()
         self._registerTraverserFactory(context)
@@ -231,6 +225,7 @@
         self.assertEqual(len(logger.messages), 0)
 
     def test_call_no_view_registered_debug_notfound_false(self):
+        self._registerDefaultNotFoundView()
         environ = self._makeEnviron()
         context = DummyContext()
         self._registerTraverserFactory(context)
@@ -248,6 +243,7 @@
         self.assertEqual(len(logger.messages), 0)
 
     def test_call_no_view_registered_debug_notfound_true(self):
+        self._registerDefaultNotFoundView()
         environ = self._makeEnviron()
         context = DummyContext()
         self._registerTraverserFactory(context)
@@ -284,18 +280,6 @@
         start_response = DummyStartResponse()
         self.assertRaises(ValueError, router, environ, start_response)
 
-    def test_inotfoundview_returns_nonresponse(self):
-        from repoze.bfg.interfaces import INotFoundView
-        context = DummyContext()
-        environ = self._makeEnviron()
-        self._registerTraverserFactory(context)
-        def app(context, request):
-            """ """
-        self.registry.registerUtility(app, INotFoundView)
-        router = self._makeOne()
-        start_response = DummyStartResponse()
-        self.assertRaises(ValueError, router, environ, start_response)
-
     def test_call_view_registered_nonspecific_default_path(self):
         context = DummyContext()
         self._registerTraverserFactory(context)
@@ -375,6 +359,7 @@
         class INotContext(Interface):
             pass
         from repoze.bfg.interfaces import IRequest
+        self._registerDefaultNotFoundView()
         context = DummyContext()
         directlyProvides(context, INotContext)
         self._registerTraverserFactory(context, subpath=[''])
@@ -394,11 +379,13 @@
         class IContext(Interface):
             pass
         from repoze.bfg.interfaces import IRequest
+        self._registerDefaultForbiddenView()
         context = DummyContext()
         directlyProvides(context, IContext)
         self._registerTraverserFactory(context, subpath=[''])
         response = DummyResponse()
-        view = DummyView(response, raise_unauthorized=True)
+        from repoze.bfg.exceptions import Forbidden
+        view = DummyView(response, raise_exception=Forbidden("unauthorized"))
         environ = self._makeEnviron()
         self._registerView(view, '', IRequest, IContext)
         router = self._makeOne()
@@ -407,17 +394,20 @@
         self.assertEqual(start_response.status, '401 Unauthorized')
         self.assertEqual(environ['repoze.bfg.message'], 'unauthorized')
 
+    # TODO: Move this test case out of router tests
     def test_call_view_raises_notfound(self):
         from zope.interface import Interface
         from zope.interface import directlyProvides
         class IContext(Interface):
             pass
         from repoze.bfg.interfaces import IRequest
+        from repoze.bfg.exceptions import NotFound
+        self._registerDefaultNotFoundView()
         context = DummyContext()
         directlyProvides(context, IContext)
         self._registerTraverserFactory(context, subpath=[''])
         response = DummyResponse()
-        view = DummyView(response, raise_notfound=True)
+        view = DummyView(response, raise_exception=NotFound("notfound"))
         environ = self._makeEnviron()
         self._registerView(view, '', IRequest, IContext)
         router = self._makeOne()
@@ -571,6 +561,7 @@
         self.registry.registerUtility(rootfactory, IRootFactory)
         class IContext(Interface):
             pass
+        self._registerDefaultNotFoundView()
         context = DummyContext()
         directlyProvides(context, IContext)
         environ = self._makeEnviron()
@@ -590,6 +581,7 @@
         self.registry.registerUtility(rootfactory, IRootFactory)
         class IContext(Interface):
             pass
+        self._registerDefaultForbiddenView()
         context = DummyContext()
         directlyProvides(context, IContext)
         environ = self._makeEnviron()
@@ -599,25 +591,142 @@
         self.assertEqual(start_response.status, '401 Unauthorized')
         self.failUnless('from root factory' in app_iter[0])
 
+    def test_root_factory_exception_propagating(self):
+        from repoze.bfg.interfaces import IRootFactory
+        from repoze.bfg.interfaces import IRequest
+        from repoze.bfg.exceptions import Forbidden
+        from zope.interface import Interface
+        from zope.interface import directlyProvides
+        def rootfactory(request):
+            raise RuntimeError()
+        self.registry.registerUtility(rootfactory, IRootFactory)
+        class IContext(Interface):
+            pass
+        context = DummyContext()
+        directlyProvides(context, IContext)
+        environ = self._makeEnviron()
+        router = self._makeOne()
+        start_response = DummyStartResponse()
+        self.assertRaises(RuntimeError, router, environ, start_response)
+
+    def test_traverser_exception_propagating(self):
+        from repoze.bfg.interfaces import IRequest
+        environ = self._makeEnviron()
+        context = DummyContext()
+        self._registerTraverserFactory(context, raise_error=RuntimeError())
+        router = self._makeOne()
+        start_response = DummyStartResponse()
+        self.assertRaises(RuntimeError, router, environ, start_response)
+
+    def test_call_view_exception_propagating(self):
+        from zope.interface import Interface
+        from zope.interface import directlyProvides
+        class IContext(Interface):
+            pass
+        from repoze.bfg.interfaces import IRequest
+        context = DummyContext()
+        directlyProvides(context, IContext)
+        self._registerTraverserFactory(context, subpath=[''])
+        response = DummyResponse()
+        view = DummyView(response, raise_exception=RuntimeError)
+        environ = self._makeEnviron()
+        self._registerView(view, '', IRequest, IContext)
+        router = self._makeOne()
+        start_response = DummyStartResponse()
+        self.assertRaises(RuntimeError, router, environ, start_response)
+
+    def test_call_view_raises_exception_view(self):
+        from zope.interface import Interface
+        from zope.interface import directlyProvides
+        class IContext(Interface):
+            pass
+        from repoze.bfg.interfaces import IRequest
+        context = DummyContext()
+        directlyProvides(context, IContext)
+        self._registerTraverserFactory(context, subpath=[''])
+        response = DummyResponse()
+        exception_response = DummyResponse()
+        exception_response.app_iter = ["Hello, world"]
+        view = DummyView(response, raise_exception=RuntimeError)
+        exception_view = DummyView(exception_response)
+        environ = self._makeEnviron()
+        self._registerView(view, '', IRequest, IContext)
+        self._registerView(exception_view, '', IRequest, RuntimeError)
+        router = self._makeOne()
+        start_response = DummyStartResponse()
+        result = router(environ, start_response)
+        self.assertEqual(result, ["Hello, world"])
+
+    def test_root_factory_raises_exception_view(self):
+        from repoze.bfg.interfaces import IRootFactory
+        from repoze.bfg.interfaces import IRequest
+        from repoze.bfg.exceptions import Forbidden
+        from zope.interface import Interface
+        from zope.interface import directlyProvides
+        def rootfactory(request):
+            raise RuntimeError()
+        self.registry.registerUtility(rootfactory, IRootFactory)
+        class IContext(Interface):
+            pass
+        context = DummyContext()
+        directlyProvides(context, IContext)
+        exception_response = DummyResponse()
+        exception_response.app_iter = ["Hello, world"]
+        exception_view = DummyView(exception_response)
+        self._registerView(exception_view, '', IRequest, RuntimeError)
+        environ = self._makeEnviron()
+        router = self._makeOne()
+        start_response = DummyStartResponse()
+        app_iter = router(environ, start_response)
+        self.assertEqual(app_iter, ["Hello, world"])
+
+    def test_traverser_raises_exception_view(self):
+        from repoze.bfg.interfaces import IRequest
+        environ = self._makeEnviron()
+        context = DummyContext()
+        self._registerTraverserFactory(context, raise_error=RuntimeError())
+        exception_response = DummyResponse()
+        exception_response.app_iter = ["Hello, world"]
+        exception_view = DummyView(exception_response)
+        self._registerView(exception_view, '', IRequest, RuntimeError)
+        router = self._makeOne()
+        start_response = DummyStartResponse()
+        result = router(environ, start_response)
+        self.assertEqual(result, ["Hello, world"])
+
+    def test_exception_view_returns_non_response(self):
+        from zope.interface import directlyProvides
+        from zope.interface import Interface
+        from repoze.bfg.interfaces import IRequest
+        class IContext(Interface):
+            pass
+        context = DummyContext()
+        directlyProvides(context, IContext)
+        environ = self._makeEnviron()
+        self._registerTraverserFactory(context)
+        response = DummyResponse()
+        view = DummyView(response, raise_exception=RuntimeError)
+        self._registerView(view, '', IRequest, IContext)
+        exception_view = DummyView(None)
+        self._registerView(exception_view, '', IRequest, RuntimeError)
+        router = self._makeOne()
+        start_response = DummyStartResponse()
+        self.assertRaises(ValueError, router, environ, start_response)
+
+
 class DummyContext:
     pass
 
 class DummyView:
-    def __init__(self, response, raise_unauthorized=False,
-                 raise_notfound=False):
+    def __init__(self, response, raise_exception=None):
         self.response = response
-        self.raise_unauthorized = raise_unauthorized
-        self.raise_notfound = raise_notfound
+        self.raise_exception = raise_exception
 
     def __call__(self, context, request):
         self.context = context
         self.request = request
-        if self.raise_unauthorized:
-            from repoze.bfg.exceptions import Forbidden
-            raise Forbidden('unauthorized')
-        if self.raise_notfound:
-            from repoze.bfg.exceptions import NotFound
-            raise NotFound('notfound')
+        if not self.raise_exception is None:
+            raise self.raise_exception
         return self.response
 
 class DummyRootFactory:

_______________________________________________
Repoze-dev mailing list
Repoze-dev@lists.repoze.org
http://lists.repoze.org/listinfo/repoze-dev

Reply via email to