Skip to content

Commit

Permalink
- New public interface: repoze.bfg.exceptions.IExceptionResponse.
Browse files Browse the repository at this point in the history
  This interface is provided by all internal exception classes (such
  as ``repoze.bfg.exceptions.NotFound`` and
  ``repoze.bfg.exceptions.Forbidden``), instances of which are both
  exception objects and can behave as WSGI response objects.  This
  interface is made public so that exception classes which are also
  valid WSGI response factories can be configured to implement them
  or exception instances which are also or response instances can be
  configured to provide them.

- New API class: ``repoze.bfg.view.AppendSlashNotFoundViewFactory`` (undoes 
  previous custom_notfound_view on request passsed to 
  append_slash_notfound_view).

- Previously, two default view functions were registered at
  Configurator setup (one for ``repoze.bfg.exceptions.NotFound`` named
  ``default_notfound_view`` and one for
  ``repoze.bfg.exceptions.Forbidden`` named
  ``default_forbidden_view``) to render internal exception responses.
  Those default view functions have been removed, replaced with a
  generic default view function which is registered at Configurator
  setup for the ``repoze.bfg.interfaces.IExceptionResponse`` interface
  that simply returns the exception instance; the ``NotFound` and
  ``Forbidden`` classes are now still exception factories but they are
  also response factories which generate instances that implement the
  new ``repoze.bfg.interfaces.IExceptionResponse`` interface.
  • Loading branch information
Chris McDonough committed Aug 8, 2010
1 parent 9192964 commit d96ff91
Show file tree
Hide file tree
Showing 12 changed files with 442 additions and 255 deletions.
78 changes: 50 additions & 28 deletions CHANGES.txt
Expand Up @@ -4,42 +4,48 @@ Next release
Features
--------

- There can only be one Not Found view in any ``repoze.bfg``
application. If you use
- New public interface: ``repoze.bfg.exceptions.IExceptionResponse``.
This interface is provided by all internal exception classes (such
as ``repoze.bfg.exceptions.NotFound`` and
``repoze.bfg.exceptions.Forbidden``), instances of which are both
exception objects and can behave as WSGI response objects. This
interface is made public so that exception classes which are also
valid WSGI response factories can be configured to implement them
or exception instances which are also or response instances can be
configured to provide them.

- New API class: ``repoze.bfg.view.AppendSlashNotFoundViewFactory``.

There can only be one Not Found view in any :mod:`repoze.bfg
application. Even if you use
``repoze.bfg.view.append_slash_notfound_view`` as the Not Found
view, it still must generate a NotFound response when it cannot
redirect to a slash-appended URL; this not found response will be
visible to site users.

As of this release, if you wish to use a custom notfound view
callable when ``append_slash_notfound_view`` does not redirect to a
slash-appended URL, use a wrapper function as the
``repoze.bfg.exceptions.NotFound`` view; have this wrapper attach a
view callable which returns a response to the request object named
``custom_notfound_view`` before calling
``append_slash_notfound_view``. For example::

from webob.exc import HTTPNotFound
view, ``repoze.bfg`` still must generate a ``404 Not Found``
response when it cannot redirect to a slash-appended URL; this not
found response will be visible to site users.

If you don't care what this 404 response looks like, and you only
need redirections to slash-appended route URLs, you may use the
``repoze.bfg.view.append_slash_notfound_view`` object as the Not
Found view. However, if you wish to use a *custom* notfound view
callable when a URL cannot be redirected to a slash-appended URL,
you may wish to use an instance of the
``repoze.bfg.view.AppendSlashNotFoundViewFactory`` class as the Not
Found view, supplying the notfound view callable as the first
argument to its constructor. For instance::

from repoze.bfg.exceptions import NotFound
from repoze.bfg.view import append_slash_notfound_view
from repoze.bfg.view import AppendSlashNotFoundViewFactory

def notfound_view(exc, request):
def fallback_notfound_view(exc, request):
return HTTPNotFound('It aint there, stop trying!')
request.fallback_notfound_view = fallback_notfound_view
return append_slash_notfound_view(exc, request)
def notfound_view(context, request):
return HTTPNotFound('It aint there, stop trying!')

config.add_view(notfound_view, context=NotFound)
custom_append_slash = AppendSlashNotFoundViewFactory(notfound_view)
config.add_view(custom_append_slash, context=NotFound)

``custom_notfound_view`` must adhere to the two-argument view
The ``notfound_view`` supplied must adhere to the two-argument view
callable calling convention of ``(context, request)`` (``context``
will be the exception object).

If ``custom_notfound_view`` is not found on the request object, a
default notfound response will be generated when the
``append_slash_notfound_view`` doesn't redirect to a slash-appended
URL.

Documentation
--------------

Expand All @@ -49,6 +55,22 @@ Documentation
- Expanded the "Redirecting to Slash-Appended Routes" section of the
URL Dispatch narrative chapter.

Internal
--------

- Previously, two default view functions were registered at
Configurator setup (one for ``repoze.bfg.exceptions.NotFound`` named
``default_notfound_view`` and one for
``repoze.bfg.exceptions.Forbidden`` named
``default_forbidden_view``) to render internal exception responses.
Those default view functions have been removed, replaced with a
generic default view function which is registered at Configurator
setup for the ``repoze.bfg.interfaces.IExceptionResponse`` interface
that simply returns the exception instance; the ``NotFound` and
``Forbidden`` classes are now still exception factories but they are
also response factories which generate instances that implement the
new ``repoze.bfg.interfaces.IExceptionResponse`` interface.

1.3a7 (2010-08-01)
==================

Expand Down
8 changes: 8 additions & 0 deletions docs/api/interfaces.rst
Expand Up @@ -5,6 +5,9 @@

.. automodule:: repoze.bfg.interfaces

Event-Related Interfaces
++++++++++++++++++++++++

.. autoclass:: IAfterTraversal

.. autoclass:: INewRequest
Expand All @@ -13,4 +16,9 @@

.. autoclass:: IWSGIApplicationCreatedEvent

Other Interfaces
++++++++++++++++

.. autoclass:: IExceptionResponse


3 changes: 2 additions & 1 deletion docs/api/view.rst
Expand Up @@ -19,6 +19,7 @@
.. autoclass:: static
:members:

.. autofunction:: append_slash_notfound_view
.. autofunction:: append_slash_notfound_view(context, request)

.. autoclass:: AppendSlashNotFoundViewFactory

39 changes: 39 additions & 0 deletions docs/narr/urldispatch.rst
Expand Up @@ -1132,6 +1132,45 @@ general description of how to configure a not found view.
.. note:: This feature is new as of :mod:`repoze.bfg` 1.1.
Custom Not Found View With Slash Appended Routes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
There can only be one :term:`Not Found view` in any :mod:`repoze.bfg
application. Even if you use
:func:`repoze.bfg.view.append_slash_notfound_view` as the Not Found
view, :mod:`repoze.bfg` still must generate a ``404 Not Found``
response when it cannot redirect to a slash-appended URL; this not
found response will be visible to site users.
If you don't care what this 404 response looks like, and only you need
redirections to slash-appended route URLs, you may use the
:func:`repoze.bfg.view.append_slash_notfound_view` object as the Not
Found view as described above. However, if you wish to use a *custom*
notfound view callable when a URL cannot be redirected to a
slash-appended URL, you may wish to use an instance of the
:class:`repoze.bfg.view.AppendSlashNotFoundViewFactory` class as the
Not Found view, supplying a :term:`view callable` to be used as the
custom notfound view as the first argument to its constructor. For
instance:
.. code-block:: python
from repoze.bfg.exceptions import NotFound
from repoze.bfg.view import AppendSlashNotFoundViewFactory
def notfound_view(context, request):
return HTTPNotFound('It aint there, stop trying!')
custom_append_slash = AppendSlashNotFoundViewFactory(notfound_view)
config.add_view(custom_append_slash, context=NotFound)
The ``notfound_view`` supplied must adhere to the two-argument view
callable calling convention of ``(context, request)`` (``context``
will be the exception object).
.. note:: The :class:`repoze.bfg.view.AppendSlashNotFoundViewFactory`
class is new as of BFG 1.3.
.. _cleaning_up_after_a_request:
Cleaning Up After a Request
Expand Down
48 changes: 34 additions & 14 deletions docs/whatsnew-1.3.rst
Expand Up @@ -235,21 +235,41 @@ Minor Feature Additions
- The :func:`repoze.bfg.configuration.Configurator.add_route` API now
returns the route object that was added.

- There can only be one Not Found view in any :mod:`repoze.bfg`
application. If you use
- New API class:
:class:`repoze.bfg.view.AppendSlashNotFoundViewFactory`.

There can only be one :term:`Not Found view` in any :mod:`repoze.bfg
application. Even if you use
:func:`repoze.bfg.view.append_slash_notfound_view` as the Not Found
view, it still must generate a 404 response when it cannot redirect
to a slash-appended URL; this not found response will be visible to
site users. As of this release, if you wish to use a custom
notfound view callable when
:func:`repoze.bfg.view.append_slash_notfound_view` does not redirect
to a slash-appended URL, use a wrapper function as the
:exc:`repoze.bfg.exceptions.NotFound` exception view; have this
wrapper attach a view callable which returns a response to the
request object named ``custom_notfound_view`` before calling
:func:`repoze.bfg.view.append_slash_notfound_view`. See
:func:`repoze.bfg.view.append_slash_notfound_view` for more
information.
view, :mod:`repoze.bfg` still must generate a ``404 Not Found``
response when it cannot redirect to a slash-appended URL; this not
found response will be visible to site users.

If you don't care what this 404 response looks like, and only you
need redirections to slash-appended route URLs, you may use the
:func:`repoze.bfg.view.append_slash_notfound_view` object as the Not
Found view. However, if you wish to use a *custom* notfound view
callable when a URL cannot be redirected to a slash-appended URL,
you may wish to use an instance of the
:class:`repoze.bfg.view.AppendSlashNotFoundViewFactory` class as the
Not Found view, supplying a :term:`view callable` to be used as the
custom notfound view as the first argument to its constructor. For
instance:

.. code-block:: python
from repoze.bfg.exceptions import NotFound
from repoze.bfg.view import AppendSlashNotFoundViewFactory
def notfound_view(context, request):
return HTTPNotFound('It aint there, stop trying!')
custom_append_slash = AppendSlashNotFoundViewFactory(notfound_view)
config.add_view(custom_append_slash, context=NotFound)
The ``notfound_view`` supplied must adhere to the two-argument view
callable calling convention of ``(context, request)`` (``context``
will be the exception object).

Backwards Incompatibilities
---------------------------
Expand Down
17 changes: 11 additions & 6 deletions repoze/bfg/configuration.py
Expand Up @@ -38,6 +38,8 @@
from repoze.bfg.interfaces import ITraverser
from repoze.bfg.interfaces import IView
from repoze.bfg.interfaces import IViewClassifier
from repoze.bfg.interfaces import IExceptionResponse
from repoze.bfg.interfaces import IException

from repoze.bfg import chameleon_text
from repoze.bfg import chameleon_zpt
Expand Down Expand Up @@ -69,8 +71,7 @@
from repoze.bfg.traversal import find_interface
from repoze.bfg.urldispatch import RoutesMapper
from repoze.bfg.view import render_view_to_response
from repoze.bfg.view import default_notfound_view
from repoze.bfg.view import default_forbidden_view
from repoze.bfg.view import default_exceptionresponse_view

MAX_ORDER = 1 << 30
DEFAULT_PHASH = md5().hexdigest()
Expand Down Expand Up @@ -408,8 +409,8 @@ def setup_registry(self, settings=None, root_factory=None,
authorization_policy)
for name, renderer in renderers:
self.add_renderer(name, renderer)
self.add_view(default_notfound_view, context=NotFound)
self.add_view(default_forbidden_view, context=Forbidden)
self.add_view(default_exceptionresponse_view,
context=IExceptionResponse)
if locale_negotiator:
registry.registerUtility(locale_negotiator, ILocaleNegotiator)
if request_factory:
Expand Down Expand Up @@ -2312,8 +2313,12 @@ def attr_view(context, request):
return attr_view

def isexception(o):
return isinstance(o, Exception) or (
inspect.isclass(o) and issubclass(o, Exception)
if IInterface.providedBy(o):
if IException.isEqualOrExtendedBy(o):
return True
return (
isinstance(o, Exception) or
(inspect.isclass(o) and (issubclass(o, Exception)))
)

# note that ``options`` is a b/w compat alias for ``settings`` and
Expand Down
68 changes: 55 additions & 13 deletions repoze/bfg/exceptions.py
@@ -1,7 +1,43 @@
from zope.configuration.exceptions import ConfigurationError as ZCE
from zope.interface import implements

class Forbidden(Exception):
"""\
from repoze.bfg.decorator import reify
from repoze.bfg.interfaces import IExceptionResponse
import cgi

class ExceptionResponse(Exception):
""" Abstract class to support behaving as a WSGI response object """
implements(IExceptionResponse)
status = None

def __init__(self, message=''):
Exception.__init__(self, message) # B / C
self.message = message

@reify # defer execution until asked explicitly
def app_iter(self):
return [
"""
<html>
<title>%s</title>
<body>
<h1>%s</h1>
<code>%s</code>
</body>
</html>
""" % (self.status, self.status, cgi.escape(self.message))
]

@reify # defer execution until asked explicitly
def headerlist(self):
return [
('Content-Length', str(len(self.app_iter[0]))),
('Content-Type', 'text/html')
]


class Forbidden(ExceptionResponse):
"""
Raise this exception within :term:`view` code to immediately
return the :term:`forbidden view` to the invoking user. Usually
this is a basic ``401`` page, but the forbidden view can be
Expand All @@ -11,10 +47,12 @@ class Forbidden(Exception):
which should be a string. The value of this string will be placed
into the WSGI environment by the router under the
``repoze.bfg.message`` key, for availability to the
:term:`Forbidden View`."""
:term:`Forbidden View`.
"""
status = '401 Unauthorized'

class NotFound(Exception):
"""\
class NotFound(ExceptionResponse):
"""
Raise this exception within :term:`view` code to immediately
return the :term:`Not Found view` to the invoking user. Usually
this is a basic ``404`` page, but the Not Found view can be
Expand All @@ -24,7 +62,18 @@ class NotFound(Exception):
which should be a string. The value of this string will be placed
into the WSGI environment by the router under the
``repoze.bfg.message`` key, for availability to the :term:`Not Found
View`."""
View`.
"""
status = '404 Not Found'

class PredicateMismatch(NotFound):
"""
Internal exception (not an API) raised by multiviews when no
view matches. This exception subclasses the ``NotFound``
exception only one reason: if it reaches the main exception
handler, it should be treated like a ``NotFound`` by any exception
view registrations.
"""

class URLDecodeError(UnicodeDecodeError):
"""
Expand All @@ -41,10 +90,3 @@ class ConfigurationError(ZCE):
""" Raised when inappropriate input values are supplied to an API
method of a :term:`Configurator`"""

class PredicateMismatch(NotFound):
""" Internal exception (not an API) raised by multiviews when no
view matches. This exception subclasses the ``NotFound``
exception only one reason: if it reaches the main exception
handler, it should be treated like a ``NotFound`` by any exception
view registrations."""

0 comments on commit d96ff91

Please sign in to comment.