diff options
| author | Chris McDonough <chrism@agendaless.com> | 2009-05-31 03:13:13 +0000 |
|---|---|---|
| committer | Chris McDonough <chrism@agendaless.com> | 2009-05-31 03:13:13 +0000 |
| commit | 4936f862588240c51e619a65aa5d574ba29d784b (patch) | |
| tree | f73f81fa6f2603773948598218d32d74c6475ee2 | |
| parent | 964b7852d997f6c4aa4b04d54f2847095e4461e8 (diff) | |
| download | pyramid-4936f862588240c51e619a65aa5d574ba29d784b.tar.gz pyramid-4936f862588240c51e619a65aa5d574ba29d784b.tar.bz2 pyramid-4936f862588240c51e619a65aa5d574ba29d784b.zip | |
Features
--------
- It is now possible to register a custom
``repoze.bfg.interfaces.INotFoundView`` for a given application.
This feature replaces the
``repoze.bfg.interfaces.INotFoundAppFactory`` feature previously
described in the Hooks chapter. The INotFoundView will be called
when the framework detects that a view lookup done as a result of a
reqest fails; it should accept a context object and a request
object; it should return an IResponse object (a webob response,
basically). See the Hooks narrative chapter of the BFG docs for
more info.
Deprecations
------------
- The ``repoze.bfg.interfaces.IUnauthorizedAppFactory`` interface has
been deprecated in favor of using the new
``repoze.bfg.interfaces.IForbiddenResponseFactory`` mechanism.
| -rw-r--r-- | CHANGES.txt | 25 | ||||
| -rw-r--r-- | docs/narr/hooks.rst | 42 | ||||
| -rw-r--r-- | repoze/bfg/interfaces.py | 19 | ||||
| -rw-r--r-- | repoze/bfg/router.py | 50 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_router.py | 30 |
5 files changed, 112 insertions, 54 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index 1547eeb97..04ea3e0aa 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,9 +1,34 @@ Next release ============ +Features +-------- + +- It is now possible to register a custom + ``repoze.bfg.interfaces.INotFoundView`` for a given application. + This feature replaces the + ``repoze.bfg.interfaces.INotFoundAppFactory`` feature previously + described in the Hooks chapter. The INotFoundView will be called + when the framework detects that a view lookup done as a result of a + reqest fails; it should accept a context object and a request + object; it should return an IResponse object (a webob response, + basically). See the Hooks narrative chapter of the BFG docs for + more info. + +Deprecations +------------ + +- The ``repoze.bfg.interfaces.IUnauthorizedAppFactory`` interface has + been deprecated in favor of using the new + ``repoze.bfg.interfaces.IForbiddenResponseFactory`` mechanism. + +Renames +------- + - Renamed ``repoze.bfg.interfaces.IForbiddenResponseFactory`` to ``repoze.bfg.interfaces.IForbiddenView``. + 0.9a7 (2009-05-30) ================== diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index bc00b28a7..ace2c847b 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -88,41 +88,37 @@ an object that implements any particular interface; it simply needs have a ``status`` attribute, a ``headerlist`` attribute, and and ``app_iter`` attribute. -Changing the NotFound Application ---------------------------------- +Changing the Not Found View +--------------------------- -When :mod:`repoze.bfg` can't map a URL to code, it creates and invokes -a NotFound WSGI application. The application it invokes can be -customized by placing something like the following ZCML in your -``configure.zcml`` file. +When :mod:`repoze.bfg` can't map a URL to view code, it invokes a +notfound :term:`view`. The view it invokes can be customized by +placing something like the following ZCML in your ``configure.zcml`` +file. .. code-block:: xml :linenos: - <utility provides="repoze.bfg.interfaces.INotFoundAppFactory" - component="helloworld.factories.notfound_app_factory"/> + <utility provides="repoze.bfg.interfaces.INotFoundView" + component="helloworld.views.notfound_view"/> -Replace ``helloworld.factories.notfound_app_factory`` with the Python -dotted name to the request factory you want to use. Here's some -sample code that implements a minimal NotFound application factory: +Replace ``helloworld.views.notfound_view`` with the Python dotted name +to the notfound view you want to use. Here's some sample code that +implements a minimal NotFound view: .. code-block:: python from webob.exc import HTTPNotFound - class MyNotFound(HTTPNotFound): - pass + def notfound_view(context, request): + return HTTPNotFound() - def notfound_app_factory(): - return MyNotFound - -.. note:: When a NotFound application factory is invoked, it is passed - the WSGI environ and the WSGI ``start_response`` handler by - :mod:`repoze.bfg`. Within the WSGI environ will be a key named - ``repoze.bfg.message`` that has a value explaining why the not - found error was raised. This error will be different when the - ``debug_notfound`` environment setting is true than it is when it - is false. +.. note:: When a NotFound view is invoked, it is passed a request. + The ``environ`` attribute of the request is the WSGI environment. + Within the WSGI environ will be a key named ``repoze.bfg.message`` + that has a value explaining why the not found error was raised. + This error will be different when the ``debug_notfound`` + environment setting is true than it is when it is false. Changing the Forbidden View --------------------------- diff --git a/repoze/bfg/interfaces.py b/repoze/bfg/interfaces.py index 8316c58bc..79ef25f09 100644 --- a/repoze/bfg/interfaces.py +++ b/repoze/bfg/interfaces.py @@ -184,18 +184,21 @@ class IForbiddenView(Interface): repoze.bfg router during traversal or url dispatch. The ``request`` will be the request object which caused the deny.""" -class INotFoundAppFactory(Interface): - """ A utility which returns a NotFound WSGI application factory """ - def __call__(): - """ Return a callable which returns a notfound WSGI - application. When the WSGI application is invoked, +class INotFoundView(Interface): + """ A utility which returns a NotFound response (an IResponse) + when a view cannot be located for a particular URL""" + def __call__(context, request): + """ Return a NotFound response. When the view is rendered, a``message`` key in the WSGI environ provides information - pertaining to the reason for the notfound.""" + pertaining to the reason for the notfound error.""" + +class INotFoundAppFactory(Interface): + """ A utility which returns a NotFound WSGI application factory. + Deprecated in repoze.bfg 0.9 in favor of INotFoundView""" class IUnauthorizedAppFactory(Interface): """ A utility which returns an Unauthorized WSGI application - factory (deprecated in repoze.bfg 0.8.2) in favor of - IForbiddenResponseFactory """ + factory. Deprecated in repoze.bfg 0.9 in favor of IForbiddenView""" class IContextURL(Interface): """ An adapter which deals with URLs related to a context. diff --git a/repoze/bfg/router.py b/repoze/bfg/router.py index 6e6477be1..32b8c5b72 100644 --- a/repoze/bfg/router.py +++ b/repoze/bfg/router.py @@ -50,8 +50,6 @@ from repoze.bfg.traversal import _traverse from repoze.bfg.urldispatch import RoutesRootFactory -from repoze.bfg.wsgi import NotFound - _marker = object() class Router(object): @@ -68,17 +66,17 @@ class Router(object): self.request_factory = registry.queryUtility(IRequestFactory) + forbidden = None + unauthorized_app_factory = registry.queryUtility( IUnauthorizedAppFactory) - forbidden = None - if unauthorized_app_factory is not None: warning = ( 'Instead of registering a utility against the ' 'repoze.bfg.interfaces.IUnauthorizedAppFactory interface ' 'to return a custom forbidden response, you should now ' - 'register a "repoze.interfaces.IForbiddenView". ' + 'register a repoze.interfaces.IForbiddenView.' 'The IUnauthorizedAppFactory interface was deprecated in ' 'repoze.bfg 0.9 and will be removed in a subsequent version ' 'of repoze.bfg. See the "Hooks" chapter of the repoze.bfg ' @@ -92,10 +90,30 @@ class Router(object): forbidden = registry.queryUtility(IForbiddenView, default=forbidden) - self.forbidden_resp_factory = forbidden or default_forbidden_view + self.forbidden_view = forbidden or default_forbidden_view + + notfound = None - self.notfound_app_factory = registry.queryUtility(INotFoundAppFactory, - default=NotFound) + notfound_app_factory = registry.queryUtility(INotFoundAppFactory) + + if notfound_app_factory is not None: + warning = ( + 'Instead of registering a utility against the ' + 'repoze.bfg.interfaces.INotFoundAppFactory interface ' + 'to return a custom notfound response, you should register ' + 'a repoze.bfg.interfaces.INotFoundView. The ' + 'INotFoundAppFactory interface was deprecated in' + 'repoze.bfg 0.9 and will be removed in a subsequent version ' + 'of repoze.bfg. See the "Hooks" chapter of the repoze.bfg ' + 'documentation for more information about ' + 'INotFoundView.') + self.logger and self.logger.warn(warning) + def notfound(context, request): + app = notfound_app_factory() + response = request.get_response(app) + return response + + self.notfound_view = notfound or default_notfound_view settings = registry.queryUtility(ISettings) if settings is not None: @@ -203,7 +221,7 @@ class Router(object): environ['repoze.bfg.message'] = msg - response = self.forbidden_resp_factory(context, request) + response = self.forbidden_view(context, request) start_response(response.status, response.headerlist) return response.app_iter @@ -223,8 +241,9 @@ class Router(object): else: msg = request.url environ['repoze.bfg.message'] = msg - notfound_app = self.notfound_app_factory() - return notfound_app(environ, start_response) + response = self.notfound_view(context, request) + start_response(response.status, response.headerlist) + return response.app_iter registry.has_listeners and registry.notify(NewResponse(response)) @@ -238,8 +257,7 @@ class Router(object): finally: self.threadlocal_manager.pop() -def default_forbidden_view(context, request): - status = '401 Unauthorized' +def default_view(context, request, status): try: msg = escape(request.environ['repoze.bfg.message']) except KeyError: @@ -260,6 +278,12 @@ def default_forbidden_view(context, request): headerlist = headers, app_iter = [html]) +def default_forbidden_view(context, request): + return default_view(context, request, '401 Unauthorized') + +def default_notfound_view(context, request): + return default_view(context, request, '404 Not Found') + def make_app(root_factory, package=None, filename='configure.zcml', authentication_policy=None, authorization_policy=None, options=None, registry=None, debug_logger=None): diff --git a/repoze/bfg/tests/test_router.py b/repoze/bfg/tests/test_router.py index 86d7d12cf..3009e65dd 100644 --- a/repoze/bfg/tests/test_router.py +++ b/repoze/bfg/tests/test_router.py @@ -127,14 +127,24 @@ class RouterTests(unittest.TestCase): router = self._makeOne() self.assertEqual(router.root_policy, rootfactory) - def test_inotfound_appfactory_override(self): + def test_secpol_with_inotfound_appfactory_BBB(self): from repoze.bfg.interfaces import INotFoundAppFactory - def app(): - """ """ - self.registry.registerUtility(app, INotFoundAppFactory) - self._registerRootFactory(None) + environ = self._makeEnviron() + context = DummyContext() + self._registerTraverserFactory(context) + rootfactory = self._registerRootFactory(None) + logger = self._registerLogger() + def factory(): + return 'yo' + self.registry.registerUtility(factory, INotFoundAppFactory) router = self._makeOne() - self.assertEqual(router.notfound_app_factory, app) + self.assertEqual(len(logger.messages), 1) + self.failUnless('INotFoundView' in logger.messages[0]) + class DummyRequest: + def get_response(self, app): + return app + req = DummyRequest() + self.assertEqual(router.notfound_view(None, req), 'yo') def test_iforbidden_responsefactory_override(self): from repoze.bfg.interfaces import IForbiddenView @@ -143,16 +153,16 @@ class RouterTests(unittest.TestCase): self.registry.registerUtility(app, IForbiddenView) self._registerRootFactory(None) router = self._makeOne() - self.assertEqual(router.forbidden_resp_factory, app) + self.assertEqual(router.forbidden_view, app) def test_iforbidden_responsefactory_nooverride(self): context = DummyContext() self._registerRootFactory(None) router = self._makeOne() from repoze.bfg.router import default_forbidden_view - self.assertEqual(router.forbidden_resp_factory, default_forbidden_view) + self.assertEqual(router.forbidden_view, default_forbidden_view) - def test_secpol_with_iunauthorized_appfactory(self): + def test_secpol_with_iunauthorized_appfactory_BBB(self): from repoze.bfg.interfaces import IUnauthorizedAppFactory environ = self._makeEnviron() context = DummyContext() @@ -169,7 +179,7 @@ class RouterTests(unittest.TestCase): def get_response(self, app): return app req = DummyRequest() - self.assertEqual(router.forbidden_resp_factory(None, req), 'yo') + self.assertEqual(router.forbidden_view(None, req), 'yo') def test_call_no_view_registered_no_isettings(self): environ = self._makeEnviron() |
