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