summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/glossary.rst4
-rw-r--r--docs/narr/hooks.rst48
-rw-r--r--docs/narr/renderers.rst31
-rw-r--r--docs/narr/router.rst53
-rw-r--r--docs/narr/testing.rst13
-rw-r--r--docs/narr/urldispatch.rst6
-rw-r--r--docs/narr/views.rst82
-rw-r--r--docs/narr/webob.rst36
-rw-r--r--docs/tutorials/bfg/index.rst2
-rw-r--r--docs/tutorials/wiki/authorization.rst2
-rw-r--r--docs/tutorials/wiki/definingviews.rst8
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/login.py4
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/views.py2
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/views.py2
-rw-r--r--docs/tutorials/wiki2/definingviews.rst4
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/__init__.py2
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/login.py2
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/views.py2
-rw-r--r--docs/tutorials/wiki2/src/views/tutorial/views.py2
-rw-r--r--pyramid/config.py14
-rw-r--r--pyramid/exceptions.py1026
-rw-r--r--pyramid/httpexceptions.py116
-rw-r--r--pyramid/interfaces.py17
-rw-r--r--pyramid/response.py940
-rw-r--r--pyramid/router.py4
-rw-r--r--pyramid/testing.py4
-rw-r--r--pyramid/tests/fixtureapp/views.py4
-rw-r--r--pyramid/tests/forbiddenapp/__init__.py6
-rw-r--r--pyramid/tests/test_config.py69
-rw-r--r--pyramid/tests/test_exceptions.py299
-rw-r--r--pyramid/tests/test_httpexceptions.py2
-rw-r--r--pyramid/tests/test_response.py308
-rw-r--r--pyramid/tests/test_router.py56
-rw-r--r--pyramid/tests/test_testing.py4
-rw-r--r--pyramid/view.py19
35 files changed, 1618 insertions, 1575 deletions
diff --git a/docs/glossary.rst b/docs/glossary.rst
index 797343e5e..20b9bfd64 100644
--- a/docs/glossary.rst
+++ b/docs/glossary.rst
@@ -594,7 +594,7 @@ Glossary
Not Found view
An :term:`exception view` invoked by :app:`Pyramid` when the
- developer explicitly raises a ``pyramid.exceptions.NotFound``
+ developer explicitly raises a ``pyramid.response.HTTPNotFound``
exception from within :term:`view` code or :term:`root factory`
code, or when the current request doesn't match any :term:`view
configuration`. :app:`Pyramid` provides a default
@@ -604,7 +604,7 @@ Glossary
Forbidden view
An :term:`exception view` invoked by :app:`Pyramid` when the
developer explicitly raises a
- ``pyramid.exceptions.Forbidden`` exception from within
+ ``pyramid.response.HTTPForbidden`` exception from within
:term:`view` code or :term:`root factory` code, or when the
:term:`view configuration` and :term:`authorization policy`
found for a request disallows a particular view invocation.
diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst
index 7e3fe0a5c..d620b5672 100644
--- a/docs/narr/hooks.rst
+++ b/docs/narr/hooks.rst
@@ -21,7 +21,7 @@ configuration.
The :term:`not found view` callable is a view callable like any other. The
:term:`view configuration` which causes it to be a "not found" view consists
-only of naming the :exc:`pyramid.exceptions.NotFound` class as the
+only of naming the :exc:`pyramid.response.HTTPNotFound` class as the
``context`` of the view configuration.
If your application uses :term:`imperative configuration`, you can replace
@@ -31,9 +31,9 @@ method to register an "exception view":
.. code-block:: python
:linenos:
- from pyramid.exceptions import NotFound
+ from pyramid.response import HTTPNotFound
from helloworld.views import notfound_view
- config.add_view(notfound_view, context=NotFound)
+ config.add_view(notfound_view, context=HTTPNotFound)
Replace ``helloworld.views.notfound_view`` with a reference to the
:term:`view callable` you want to use to represent the Not Found view.
@@ -42,7 +42,7 @@ Like any other view, the notfound view must accept at least a ``request``
parameter, or both ``context`` and ``request``. The ``request`` is the
current :term:`request` representing the denied action. The ``context`` (if
used in the call signature) will be the instance of the
-:exc:`~pyramid.exceptions.NotFound` exception that caused the view to be
+:exc:`~pyramid.response.HTTPNotFound` exception that caused the view to be
called.
Here's some sample code that implements a minimal NotFound view callable:
@@ -50,25 +50,25 @@ Here's some sample code that implements a minimal NotFound view callable:
.. code-block:: python
:linenos:
- from pyramid.httpexceptions import HTTPNotFound
+ from pyramid.response import HTTPNotFound
def notfound_view(request):
return HTTPNotFound()
.. note:: When a NotFound view callable is invoked, it is passed a
- :term:`request`. The ``exception`` attribute of the request will
- be an instance of the :exc:`~pyramid.exceptions.NotFound`
- exception that caused the not found view to be called. The value
- of ``request.exception.args[0]`` will be a value explaining why the
- not found error was raised. This message will be different when
- the ``debug_notfound`` environment setting is true than it is when
- it is false.
+ :term:`request`. The ``exception`` attribute of the request will be an
+ instance of the :exc:`~pyramid.response.HTTPNotFound` exception that
+ caused the not found view to be called. The value of
+ ``request.exception.args[0]`` will be a value explaining why the not found
+ error was raised. This message will be different when the
+ ``debug_notfound`` environment setting is true than it is when it is
+ false.
.. warning:: When a NotFound view callable accepts an argument list as
described in :ref:`request_and_context_view_definitions`, the ``context``
passed as the first argument to the view callable will be the
- :exc:`~pyramid.exceptions.NotFound` exception instance. If available, the
- resource context will still be available as ``request.context``.
+ :exc:`~pyramid.response.HTTPNotFound` exception instance. If available,
+ the resource context will still be available as ``request.context``.
.. index::
single: forbidden view
@@ -85,7 +85,7 @@ the view which generates it can be overridden as necessary.
The :term:`forbidden view` callable is a view callable like any other. The
:term:`view configuration` which causes it to be a "not found" view consists
-only of naming the :exc:`pyramid.exceptions.Forbidden` class as the
+only of naming the :exc:`pyramid.response.HTTPForbidden` class as the
``context`` of the view configuration.
You can replace the forbidden view by using the
@@ -96,8 +96,8 @@ view":
:linenos:
from helloworld.views import forbidden_view
- from pyramid.exceptions import Forbidden
- config.add_view(forbidden_view, context=Forbidden)
+ from pyramid.response import HTTPForbidden
+ config.add_view(forbidden_view, context=HTTPForbidden)
Replace ``helloworld.views.forbidden_view`` with a reference to the Python
:term:`view callable` you want to use to represent the Forbidden view.
@@ -121,13 +121,13 @@ Here's some sample code that implements a minimal forbidden view:
return Response('forbidden')
.. note:: When a forbidden view callable is invoked, it is passed a
- :term:`request`. The ``exception`` attribute of the request will
- be an instance of the :exc:`~pyramid.exceptions.Forbidden`
- exception that caused the forbidden view to be called. The value
- of ``request.exception.args[0]`` will be a value explaining why the
- forbidden was raised. This message will be different when the
- ``debug_authorization`` environment setting is true than it is when
- it is false.
+ :term:`request`. The ``exception`` attribute of the request will be an
+ instance of the :exc:`~pyramid.response.HTTPForbidden` exception that
+ caused the forbidden view to be called. The value of
+ ``request.exception.args[0]`` will be a value explaining why the forbidden
+ was raised. This message will be different when the
+ ``debug_authorization`` environment setting is true than it is when it is
+ false.
.. index::
single: request factory
diff --git a/docs/narr/renderers.rst b/docs/narr/renderers.rst
index c3533648b..c7a3d7837 100644
--- a/docs/narr/renderers.rst
+++ b/docs/narr/renderers.rst
@@ -73,30 +73,43 @@ When this configuration is added to an application, the
which renders view return values to a :term:`JSON` response serialization.
Other built-in renderers include renderers which use the :term:`Chameleon`
-templating language to render a dictionary to a response.
+templating language to render a dictionary to a response. Additional
+renderers can be added by developers to the system as necessary (see
+:ref:`adding_and_overriding_renderers`).
+
+Views which use a renderer can vary non-body response attributes (such as
+headers and the HTTP status code) by attaching a property to the
+``request.response`` attribute See :ref:`request_response_attr`.
If the :term:`view callable` associated with a :term:`view configuration`
returns a Response object directly (an object with the attributes ``status``,
``headerlist`` and ``app_iter``), any renderer associated with the view
configuration is ignored, and the response is passed back to :app:`Pyramid`
unchanged. For example, if your view callable returns an instance of the
-:class:`pyramid.httpexceptions.HTTPFound` class as a response, no renderer
-will be employed.
+:class:`pyramid.response.HTTPFound` class as a response, no renderer will be
+employed.
.. code-block:: python
:linenos:
- from pyramid.httpexceptions import HTTPFound
+ from pyramid.response import HTTPFound
def view(request):
return HTTPFound(location='http://example.com') # any renderer avoided
-Views which use a renderer can vary non-body response attributes (such as
-headers and the HTTP status code) by attaching a property to the
-``request.response`` attribute See :ref:`request_response_attr`.
+Likewise for a "plain old response":
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.response import Response
+
+ def view(request):
+ return Response('OK') # any renderer avoided
-Additional renderers can be added by developers to the system as necessary
-(see :ref:`adding_and_overriding_renderers`).
+Mutations to ``request.response`` in views which return a Response object
+like this directly (unless that response *is* ``request.response``) will be
+ignored.
.. index::
single: renderers (built-in)
diff --git a/docs/narr/router.rst b/docs/narr/router.rst
index 11f84d4ea..44fa9835b 100644
--- a/docs/narr/router.rst
+++ b/docs/narr/router.rst
@@ -77,40 +77,37 @@ processing?
#. A :class:`~pyramid.events.ContextFound` :term:`event` is
sent to any subscribers.
-#. :app:`Pyramid` looks up a :term:`view` callable using the
- context, the request, and the view name. If a view callable
- doesn't exist for this combination of objects (based on the type of
- the context, the type of the request, and the value of the view
- name, and any :term:`predicate` attributes applied to the view
- configuration), :app:`Pyramid` raises a
- :class:`~pyramid.exceptions.NotFound` exception, which is meant
- to be caught by a surrounding exception handler.
+#. :app:`Pyramid` looks up a :term:`view` callable using the context, the
+ request, and the view name. If a view callable doesn't exist for this
+ combination of objects (based on the type of the context, the type of the
+ request, and the value of the view name, and any :term:`predicate`
+ attributes applied to the view configuration), :app:`Pyramid` raises a
+ :class:`~pyramid.response.HTTPNotFound` exception, which is meant to be
+ caught by a surrounding exception handler.
#. If a view callable was found, :app:`Pyramid` attempts to call
the view function.
-#. If an :term:`authorization policy` is in use, and the view was
- protected by a :term:`permission`, :app:`Pyramid` passes the
- context, the request, and the view_name to a function which
- determines whether the view being asked for can be executed by the
- requesting user, based on credential information in the request and
- security information attached to the context. If it returns
- ``True``, :app:`Pyramid` calls the view callable to obtain a
- response. If it returns ``False``, it raises a
- :class:`~pyramid.exceptions.Forbidden` exception, which is meant
- to be called by a surrounding exception handler.
+#. If an :term:`authorization policy` is in use, and the view was protected
+ by a :term:`permission`, :app:`Pyramid` passes the context, the request,
+ and the view_name to a function which determines whether the view being
+ asked for can be executed by the requesting user, based on credential
+ information in the request and security information attached to the
+ context. If it returns ``True``, :app:`Pyramid` calls the view callable
+ to obtain a response. If it returns ``False``, it raises a
+ :class:`~pyramid.response.HTTPForbidden` exception, which is meant to be
+ called by a surrounding exception handler.
#. If any exception was raised within a :term:`root factory`, by
- :term:`traversal`, by a :term:`view callable` or by
- :app:`Pyramid` itself (such as when it raises
- :class:`~pyramid.exceptions.NotFound` or
- :class:`~pyramid.exceptions.Forbidden`), the router catches the
- exception, and attaches it to the request as the ``exception``
- attribute. It then attempts to find a :term:`exception view` for
- the exception that was caught. If it finds an exception view
- callable, that callable is called, and is presumed to generate a
- response. If an :term:`exception view` that matches the exception
- cannot be found, the exception is reraised.
+ :term:`traversal`, by a :term:`view callable` or by :app:`Pyramid` itself
+ (such as when it raises :class:`~pyramid.response.HTTPNotFound` or
+ :class:`~pyramid.response.HTTPForbidden`), the router catches the
+ exception, and attaches it to the request as the ``exception`` attribute.
+ It then attempts to find a :term:`exception view` for the exception that
+ was caught. If it finds an exception view callable, that callable is
+ called, and is presumed to generate a response. If an :term:`exception
+ view` that matches the exception cannot be found, the exception is
+ reraised.
#. The following steps occur only when a :term:`response` could be
successfully generated by a normal :term:`view callable` or an
diff --git a/docs/narr/testing.rst b/docs/narr/testing.rst
index bd45388c2..862eda9f0 100644
--- a/docs/narr/testing.rst
+++ b/docs/narr/testing.rst
@@ -191,11 +191,11 @@ function.
:linenos:
from pyramid.security import has_permission
- from pyramid.exceptions import Forbidden
+ from pyramid.response import HTTPForbidden
def view_fn(request):
if not has_permission('edit', request.context, request):
- raise Forbidden
+ raise HTTPForbidden
return {'greeting':'hello'}
Without doing anything special during a unit test, the call to
@@ -207,7 +207,7 @@ application registry is not created and populated (e.g. by initializing the
configurator with an authorization policy), like when you invoke application
code via a unit test, :app:`Pyramid` API functions will tend to either fail
or return default results. So how do you test the branch of the code in this
-view function that raises :exc:`Forbidden`?
+view function that raises :exc:`HTTPForbidden`?
The testing API provided by :app:`Pyramid` allows you to simulate various
application registry registrations for use under a unit testing framework
@@ -230,16 +230,15 @@ without needing to invoke the actual application configuration implied by its
testing.tearDown()
def test_view_fn_forbidden(self):
- from pyramid.exceptions import Forbidden
+ from pyramid.response import HTTPForbidden
from my.package import view_fn
self.config.testing_securitypolicy(userid='hank',
permissive=False)
request = testing.DummyRequest()
request.context = testing.DummyResource()
- self.assertRaises(Forbidden, view_fn, request)
+ self.assertRaises(HTTPForbidden, view_fn, request)
def test_view_fn_allowed(self):
- from pyramid.exceptions import Forbidden
from my.package import view_fn
self.config.testing_securitypolicy(userid='hank',
permissive=True)
@@ -265,7 +264,7 @@ We call the function being tested with the manufactured request. When the
function is called, :func:`pyramid.security.has_permission` will call the
"dummy" authentication policy we've registered through
:meth:`~pyramid.config.Configuration.testing_securitypolicy`, which denies
-access. We check that the view function raises a :exc:`Forbidden` error.
+access. We check that the view function raises a :exc:`HTTPForbidden` error.
The second test method, named ``test_view_fn_allowed`` tests the alternate
case, where the authentication policy allows access. Notice that we pass
diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst
index 5df1eb3af..e5228b81e 100644
--- a/docs/narr/urldispatch.rst
+++ b/docs/narr/urldispatch.rst
@@ -917,7 +917,7 @@ the application's startup configuration, adding the following stanza:
:linenos:
config.add_view('pyramid.view.append_slash_notfound_view',
- context='pyramid.exceptions.NotFound')
+ context='pyramid.response.HTTPNotFound')
See :ref:`view_module` and :ref:`changing_the_notfound_view` for more
information about the slash-appending not found view and for a more general
@@ -945,14 +945,14 @@ view as the first argument to its constructor. For instance:
.. code-block:: python
:linenos:
- from pyramid.exceptions import NotFound
+ from pyramid.response import HTTPNotFound
from pyramid.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)
+ config.add_view(custom_append_slash, context=HTTPNotFound)
The ``notfound_view`` supplied must adhere to the two-argument view callable
calling convention of ``(context, request)`` (``context`` will be the
diff --git a/docs/narr/views.rst b/docs/narr/views.rst
index 66e9919e2..73a7c2e2a 100644
--- a/docs/narr/views.rst
+++ b/docs/narr/views.rst
@@ -233,7 +233,7 @@ implements the :term:`Response` interface is to return a
You don't need to always use :class:`~pyramid.response.Response` to represent
a response. :app:`Pyramid` provides a range of different "exception" classes
which can act as response objects too. For example, an instance of the class
-:class:`pyramid.httpexceptions.HTTPFound` is also a valid response object
+:class:`pyramid.response.HTTPFound` is also a valid response object
(see :ref:`http_exceptions` and ref:`http_redirect`). A view can actually
return any object that has the following attributes.
@@ -275,17 +275,18 @@ exist: internal exceptions and HTTP exceptions.
Internal Exceptions
~~~~~~~~~~~~~~~~~~~
-:exc:`pyramid.exceptions.NotFound` and :exc:`pyramid.exceptions.Forbidden`
-are exceptions often raised by Pyramid itself when it (respectively) cannot
-find a view to service a request or when authorization was forbidden by a
-security policy. However, they can also be raised by application developers.
+:exc:`pyramid.response.HTTPNotFound` and
+:exc:`pyramid.response.HTTPForbidden` are exceptions often raised by Pyramid
+itself when it (respectively) cannot find a view to service a request or when
+authorization was forbidden by a security policy. However, they can also be
+raised by application developers.
-If :exc:`~pyramid.exceptions.NotFound` is raised within view code, the result
-of the :term:`Not Found View` will be returned to the user agent which
+If :exc:`~pyramid.response.HTTPNotFound` is raised within view code, the
+result of the :term:`Not Found View` will be returned to the user agent which
performed the request.
-If :exc:`~pyramid.exceptions.Forbidden` is raised within view code, the result
-of the :term:`Forbidden View` will be returned to the user agent which
+If :exc:`~pyramid.response.HTTPForbidden` is raised within view code, the
+result of the :term:`Forbidden View` will be returned to the user agent which
performed the request.
Both are exception classes which accept a single positional constructor
@@ -298,13 +299,10 @@ An example:
.. code-block:: python
:linenos:
- from pyramid.exceptions import NotFound
+ from pyramid.response import HTTPNotFound
def aview(request):
- raise NotFound('not found!')
-
-Internal exceptions may not be *returned* in order to generate a response,
-they must always be *raised*.
+ raise HTTPNotFound('not found!')
.. index::
single: HTTP exceptions
@@ -314,32 +312,33 @@ they must always be *raised*.
HTTP Exceptions
~~~~~~~~~~~~~~~
-All exception classes documented in the :mod:`pyramid.httpexceptions` module
-implement the :term:`Response` interface; an instance of any of these classes
-can be returned or raised from within a view. The instance will be used as
-as the view's response.
+All classes documented in the :mod:`pyramid.response` module as inheriting
+from the :class:`pryamid.response.Response` object implement the
+:term:`Response` interface; an instance of any of these classes can be
+returned or raised from within a view. The instance will be used as as the
+view's response.
-For example, the :class:`pyramid.httpexceptions.HTTPUnauthorized` exception
+For example, the :class:`pyramid.response.HTTPUnauthorized` exception
can be raised. This will cause a response to be generated with a ``401
Unauthorized`` status:
.. code-block:: python
:linenos:
- from pyramid.httpexceptions import HTTPUnauthorized
+ from pyramid.response import HTTPUnauthorized
def aview(request):
raise HTTPUnauthorized()
A shortcut for importing and raising an HTTP exception is the
-:func:`pyramid.httpexceptions.abort` function. This function accepts an HTTP
+:func:`pyramid.response.abort` function. This function accepts an HTTP
status code and raises the corresponding HTTP exception. For example, to
raise HTTPUnauthorized, instead of the above, you could do:
.. code-block:: python
:linenos:
- from pyramid.httpexceptions import abort
+ from pyramid.response import abort
def aview(request):
abort(401)
@@ -347,8 +346,8 @@ raise HTTPUnauthorized, instead of the above, you could do:
This is the case because ``401`` is the HTTP status code for "HTTP
Unauthorized". Therefore, ``abort(401)`` is functionally equivalent to
``raise HTTPUnauthorized()``. Other exceptions in
-:mod:`pyramid.httpexceptions` can be raised via
-:func:`pyramid.httpexceptions.abort` as well, as long as the status code
+:mod:`pyramid.response` can be raised via
+:func:`pyramid.response.abort` as well, as long as the status code
associated with the exception is provided to the function.
An HTTP exception, instead of being raised, can alternately be *returned*
@@ -357,18 +356,11 @@ An HTTP exception, instead of being raised, can alternately be *returned*
.. code-block:: python
:linenos:
- from pyramid.httpexceptions import HTTPUnauthorized
+ from pyramid.response import HTTPUnauthorized
def aview(request):
return HTTPUnauthorized()
-Note that :class:`pyramid.exceptions.NotFound` is *not* the same as
-:class:`pyramid.httpexceptions.HTTPNotFound`. If the latter is raised, the
-:term:`Not Found view` will *not* be called automatically. Likewise,
-:class:`pyramid.exceptions.Forbidden` is not the same exception as
-:class:`pyramid.httpexceptions.HTTPForbidden`. If the latter is raised, the
-:term:`Forbidden view` will not be called automatically.
-
.. index::
single: exception views
@@ -377,11 +369,11 @@ Note that :class:`pyramid.exceptions.NotFound` is *not* the same as
Custom Exception Views
----------------------
-The machinery which allows :exc:`~pyramid.exceptions.NotFound`,
-:exc:`~pyramid.exceptions.Forbidden` and HTTP exceptions to be caught by
-specialized views as described in :ref:`special_exceptions_in_callables` can
-also be used by application developers to convert arbitrary exceptions to
-responses.
+The machinery which allows :exc:`~pyramid.response.HTTPNotFound`,
+:exc:`~pyramid.response.HTTPForbidden` and other responses to be used as
+exceptions and caught by specialized views as described in
+:ref:`special_exceptions_in_callables` can also be used by application
+developers to convert arbitrary exceptions to responses.
To register a view that should be called whenever a particular exception is
raised from with :app:`Pyramid` view code, use the exception class or one of
@@ -474,14 +466,14 @@ Short Form
~~~~~~~~~~
You can issue an HTTP redirect from within a view callable by using the
-:func:`pyramid.httpexceptions.redirect` function. This function raises an
-:class:`pyramid.httpexceptions.HTTPFound` exception (a "302"), which is
-caught by an exception handler and turned into a response.
+:func:`pyramid.response.redirect` function. This function raises an
+:class:`pyramid.response.HTTPFound` exception (a "302"), which is caught by
+the default exception response handler and turned into a response.
.. code-block:: python
:linenos:
- from pyramid.httpexceptions import redirect
+ from pyramid.response import redirect
def myview(request):
redirect('http://example.com')
@@ -490,16 +482,16 @@ Long Form
~~~~~~~~~
You can issue an HTTP redirect from within a view "by hand" instead of
-relying on the :func:`pyramid.httpexceptions.redirect` function to do it for
+relying on the :func:`pyramid.response.redirect` function to do it for
you.
-To do so, you can *return* a :class:`pyramid.httpexceptions.HTTPFound`
+To do so, you can *return* a :class:`pyramid.response.HTTPFound`
instance.
.. code-block:: python
:linenos:
- from pyramid.httpexceptions import HTTPFound
+ from pyramid.response import HTTPFound
def myview(request):
return HTTPFound(location='http://example.com')
@@ -510,7 +502,7 @@ one.
.. code-block:: python
:linenos:
- from pyramid.httpexceptions import HTTPFound
+ from pyramid.response import HTTPFound
def myview(request):
raise HTTPFound(location='http://example.com')
diff --git a/docs/narr/webob.rst b/docs/narr/webob.rst
index 072ca1c74..6cd9418ce 100644
--- a/docs/narr/webob.rst
+++ b/docs/narr/webob.rst
@@ -362,11 +362,11 @@ To facilitate error responses like ``404 Not Found``, the module
:mod:`webob.exc` contains classes for each kind of error response. These
include boring, but appropriate error bodies. The exceptions exposed by this
module, when used under :app:`Pyramid`, should be imported from the
-:mod:`pyramid.httpexceptions` "facade" module. This import location is merely
-a facade for the original location of these exceptions: ``webob.exc``.
+:mod:`pyramid.response` module. This import location contains subclasses and
+replacements that mirror those in the original ``webob.exc``.
-Each class is named ``pyramid.httpexceptions.HTTP*``, where ``*`` is the reason
-for the error. For instance, :class:`pyramid.httpexceptions.HTTPNotFound`. It
+Each class is named ``pyramid.response.HTTP*``, where ``*`` is the reason for
+the error. For instance, :class:`pyramid.response.HTTPNotFound`. It
subclasses :class:`pyramid.Response`, so you can manipulate the instances in
the same way. A typical example is:
@@ -374,40 +374,18 @@ the same way. A typical example is:
.. code-block:: python
:linenos:
- from pyramid.httpexceptions import HTTPNotFound
- from pyramid.httpexceptions import HTTPMovedPermanently
+ from pyramid.response import HTTPNotFound
+ from pyramid.response import HTTPMovedPermanently
response = HTTPNotFound('There is no such resource')
# or:
response = HTTPMovedPermanently(location=new_url)
-These are not exceptions unless you are using Python 2.5+, because
-they are new-style classes which are not allowed as exceptions until
-Python 2.5. To get an exception object use ``response.exception``.
-You can use this like:
-
-.. code-block:: python
- :linenos:
-
- from pyramid.httpexceptions import HTTPException
- from pyramid.httpexceptions import HTTPNotFound
-
- def aview(request):
- try:
- # ... stuff ...
- raise HTTPNotFound('No such resource').exception
- except HTTPException, e:
- return request.get_response(e)
-
-The exceptions are still WSGI applications, but you cannot set
-attributes like ``content_type``, ``charset``, etc. on these exception
-objects.
-
More Details
++++++++++++
More details about the response object API are available in the
:mod:`pyramid.response` documentation. More details about exception responses
-are in the :mod:`pyramid.httpexceptions` API documentation. The `WebOb
+are in the :mod:`pyramid.response` API documentation. The `WebOb
documentation <http://pythonpaste.org/webob>`_ is also useful.
diff --git a/docs/tutorials/bfg/index.rst b/docs/tutorials/bfg/index.rst
index e68e63b0b..e01345158 100644
--- a/docs/tutorials/bfg/index.rst
+++ b/docs/tutorials/bfg/index.rst
@@ -106,7 +106,7 @@ Here's how to convert a :mod:`repoze.bfg` application to a
- ZCML files which contain directives that have attributes which
name a ``repoze.bfg`` API module or attribute of an API module
- (e.g. ``context="repoze.bfg.exceptions.NotFound"``) will be
+ (e.g. ``context="repoze.bfg.exeptions.NotFound"``) will be
converted to :app:`Pyramid` compatible ZCML attributes
(e.g. ``context="pyramid.exceptions.NotFound``). Every ZCML file
beneath the top-level path (files ending with ``.zcml``) will be
diff --git a/docs/tutorials/wiki/authorization.rst b/docs/tutorials/wiki/authorization.rst
index e4480d6d9..3b102958e 100644
--- a/docs/tutorials/wiki/authorization.rst
+++ b/docs/tutorials/wiki/authorization.rst
@@ -131,7 +131,7 @@ callable.
The first view configuration decorator configures the ``login`` view callable
so it will be invoked when someone visits ``/login`` (when the context is a
Wiki and the view name is ``login``). The second decorator (with context of
-``pyramid.exceptions.Forbidden``) specifies a :term:`forbidden view`. This
+``pyramid.response.HTTPForbidden``) specifies a :term:`forbidden view`. This
configures our login view to be presented to the user when :app:`Pyramid`
detects that a view invocation can not be authorized. Because we've
configured a forbidden view, the ``login`` view callable will be invoked
diff --git a/docs/tutorials/wiki/definingviews.rst b/docs/tutorials/wiki/definingviews.rst
index b6c083bbf..ea8842294 100644
--- a/docs/tutorials/wiki/definingviews.rst
+++ b/docs/tutorials/wiki/definingviews.rst
@@ -83,10 +83,10 @@ No renderer is necessary when a view returns a response object.
The ``view_wiki`` view callable always redirects to the URL of a Page
resource named "FrontPage". To do so, it returns an instance of the
-:class:`pyramid.httpexceptions.HTTPFound` class (instances of which implement
-the WebOb :term:`response` interface). The :func:`pyramid.url.resource_url`
-API. :func:`pyramid.url.resource_url` constructs a URL to the ``FrontPage``
-page resource (e.g. ``http://localhost:6543/FrontPage``), and uses it as the
+:class:`pyramid.response.HTTPFound` class (instances of which implement the
+WebOb :term:`response` interface). The :func:`pyramid.url.resource_url` API.
+:func:`pyramid.url.resource_url` constructs a URL to the ``FrontPage`` page
+resource (e.g. ``http://localhost:6543/FrontPage``), and uses it as the
"location" of the HTTPFound response, forming an HTTP redirect.
The ``view_page`` view function
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/login.py b/docs/tutorials/wiki/src/authorization/tutorial/login.py
index 463db71a6..822b19b9e 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/login.py
+++ b/docs/tutorials/wiki/src/authorization/tutorial/login.py
@@ -1,4 +1,4 @@
-from pyramid.httpexceptions import HTTPFound
+from pyramid.response import HTTPFound
from pyramid.security import remember
from pyramid.security import forget
@@ -9,7 +9,7 @@ from tutorial.security import USERS
@view_config(context='tutorial.models.Wiki', name='login',
renderer='templates/login.pt')
-@view_config(context='pyramid.exceptions.Forbidden',
+@view_config(context='pyramid.response.HTTPForbidden',
renderer='templates/login.pt')
def login(request):
login_url = resource_url(request.context, request, 'login')
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/views.py b/docs/tutorials/wiki/src/authorization/tutorial/views.py
index a83e17de4..67550d58e 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/views.py
+++ b/docs/tutorials/wiki/src/authorization/tutorial/views.py
@@ -1,7 +1,7 @@
from docutils.core import publish_parts
import re
-from pyramid.httpexceptions import HTTPFound
+from pyramid.response import HTTPFound
from pyramid.url import resource_url
from pyramid.view import view_config
from pyramid.security import authenticated_userid
diff --git a/docs/tutorials/wiki/src/views/tutorial/views.py b/docs/tutorials/wiki/src/views/tutorial/views.py
index 42420f2fe..d72cbd3fd 100644
--- a/docs/tutorials/wiki/src/views/tutorial/views.py
+++ b/docs/tutorials/wiki/src/views/tutorial/views.py
@@ -1,7 +1,7 @@
from docutils.core import publish_parts
import re
-from pyramid.httpexceptions import HTTPFound
+from pyramid.response import HTTPFound
from pyramid.url import resource_url
from pyramid.view import view_config
diff --git a/docs/tutorials/wiki2/definingviews.rst b/docs/tutorials/wiki2/definingviews.rst
index 832f90b92..32e3c0b24 100644
--- a/docs/tutorials/wiki2/definingviews.rst
+++ b/docs/tutorials/wiki2/definingviews.rst
@@ -90,8 +90,8 @@ path to our "FrontPage".
:language: python
The ``view_wiki`` function returns an instance of the
-:class:`pyramid.httpexceptions.HTTPFound` class (instances of which implement
-the WebOb :term:`response` interface), It will use the
+:class:`pyramid.response.HTTPFound` class (instances of which implement the
+WebOb :term:`response` interface), It will use the
:func:`pyramid.url.route_url` API to construct a URL to the ``FrontPage``
page (e.g. ``http://localhost:6543/FrontPage``), and will use it as the
"location" of the HTTPFound response, forming an HTTP redirect.
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py b/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py
index 05183d3d4..42013622c 100644
--- a/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py
@@ -39,7 +39,7 @@ def main(global_config, **settings):
config.add_view('tutorial.views.edit_page', route_name='edit_page',
renderer='tutorial:templates/edit.pt', permission='edit')
config.add_view('tutorial.login.login',
- context='pyramid.exceptions.Forbidden',
+ context='pyramid.response.HTTPForbidden',
renderer='tutorial:templates/login.pt')
return config.make_wsgi_app()
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/login.py b/docs/tutorials/wiki2/src/authorization/tutorial/login.py
index 7a1d1f663..2bc8a7201 100644
--- a/docs/tutorials/wiki2/src/authorization/tutorial/login.py
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/login.py
@@ -1,4 +1,4 @@
-from pyramid.httpexceptions import HTTPFound
+from pyramid.response import HTTPFound
from pyramid.security import remember
from pyramid.security import forget
from pyramid.url import route_url
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/views.py b/docs/tutorials/wiki2/src/authorization/tutorial/views.py
index 5abd8391e..ed441295c 100644
--- a/docs/tutorials/wiki2/src/authorization/tutorial/views.py
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/views.py
@@ -2,7 +2,7 @@ import re
from docutils.core import publish_parts
-from pyramid.httpexceptions import HTTPFound
+from pyramid.response import HTTPFound
from pyramid.security import authenticated_userid
from pyramid.url import route_url
diff --git a/docs/tutorials/wiki2/src/views/tutorial/views.py b/docs/tutorials/wiki2/src/views/tutorial/views.py
index b8896abe7..80d817d99 100644
--- a/docs/tutorials/wiki2/src/views/tutorial/views.py
+++ b/docs/tutorials/wiki2/src/views/tutorial/views.py
@@ -2,7 +2,7 @@ import re
from docutils.core import publish_parts
-from pyramid.httpexceptions import HTTPFound
+from pyramid.response import HTTPFound
from pyramid.url import route_url
from tutorial.models import DBSession
diff --git a/pyramid/config.py b/pyramid/config.py
index ce5201ed3..ab1729c06 100644
--- a/pyramid/config.py
+++ b/pyramid/config.py
@@ -56,9 +56,9 @@ from pyramid.compat import md5
from pyramid.compat import any
from pyramid.events import ApplicationCreated
from pyramid.exceptions import ConfigurationError
-from pyramid.exceptions import default_exceptionresponse_view
-from pyramid.exceptions import Forbidden
-from pyramid.exceptions import NotFound
+from pyramid.response import default_exceptionresponse_view
+from pyramid.response import HTTPForbidden
+from pyramid.response import HTTPNotFound
from pyramid.exceptions import PredicateMismatch
from pyramid.i18n import get_localizer
from pyramid.log import make_stream_logger
@@ -1997,7 +1997,8 @@ class Configurator(object):
def bwcompat_view(context, request):
context = getattr(request, 'context', None)
return view(context, request)
- return self.add_view(bwcompat_view, context=Forbidden, wrapper=wrapper)
+ return self.add_view(bwcompat_view, context=HTTPForbidden,
+ wrapper=wrapper)
@action_method
def set_notfound_view(self, view=None, attr=None, renderer=None,
@@ -2037,7 +2038,8 @@ class Configurator(object):
def bwcompat_view(context, request):
context = getattr(request, 'context', None)
return view(context, request)
- return self.add_view(bwcompat_view, context=NotFound, wrapper=wrapper)
+ return self.add_view(bwcompat_view, context=HTTPNotFound,
+ wrapper=wrapper)
@action_method
def set_request_factory(self, factory):
@@ -2845,7 +2847,7 @@ class ViewDeriver(object):
return view(context, request)
msg = getattr(request, 'authdebug_message',
'Unauthorized: %s failed permission check' % view)
- raise Forbidden(msg, result=result)
+ raise HTTPForbidden(msg, result=result)
_secured_view.__call_permissive__ = view
_secured_view.__permitted__ = _permitted
_secured_view.__permission__ = permission
diff --git a/pyramid/exceptions.py b/pyramid/exceptions.py
index 678529c1e..2484f94a3 100644
--- a/pyramid/exceptions.py
+++ b/pyramid/exceptions.py
@@ -1,984 +1,7 @@
-"""
-HTTP Exceptions
----------------
-
-This module contains Pyramid HTTP exception classes. Each class relates to a
-single HTTP status code. Each class is a subclass of the
-:class:`~HTTPException`. Each exception class is also a :term:`response`
-object.
-
-Each exception class has a status code according to `RFC 2068
-<http://www.ietf.org/rfc/rfc2068.txt>`: codes with 100-300 are not really
-errors; 400's are client errors, and 500's are server errors.
-
-Exception
- HTTPException
- HTTPOk
- * 200 - HTTPOk
- * 201 - HTTPCreated
- * 202 - HTTPAccepted
- * 203 - HTTPNonAuthoritativeInformation
- * 204 - HTTPNoContent
- * 205 - HTTPResetContent
- * 206 - HTTPPartialContent
- HTTPRedirection
- * 300 - HTTPMultipleChoices
- * 301 - HTTPMovedPermanently
- * 302 - HTTPFound
- * 303 - HTTPSeeOther
- * 304 - HTTPNotModified
- * 305 - HTTPUseProxy
- * 306 - Unused (not implemented, obviously)
- * 307 - HTTPTemporaryRedirect
- HTTPError
- HTTPClientError
- * 400 - HTTPBadRequest
- * 401 - HTTPUnauthorized
- * 402 - HTTPPaymentRequired
- * 403 - HTTPForbidden
- * 404 - HTTPNotFound
- * 405 - HTTPMethodNotAllowed
- * 406 - HTTPNotAcceptable
- * 407 - HTTPProxyAuthenticationRequired
- * 408 - HTTPRequestTimeout
- * 409 - HTTPConflict
- * 410 - HTTPGone
- * 411 - HTTPLengthRequired
- * 412 - HTTPPreconditionFailed
- * 413 - HTTPRequestEntityTooLarge
- * 414 - HTTPRequestURITooLong
- * 415 - HTTPUnsupportedMediaType
- * 416 - HTTPRequestRangeNotSatisfiable
- * 417 - HTTPExpectationFailed
- HTTPServerError
- * 500 - HTTPInternalServerError
- * 501 - HTTPNotImplemented
- * 502 - HTTPBadGateway
- * 503 - HTTPServiceUnavailable
- * 504 - HTTPGatewayTimeout
- * 505 - HTTPVersionNotSupported
-
-Each HTTP exception has the following attributes:
-
- ``code``
- the HTTP status code for the exception
-
- ``title``
- remainder of the status line (stuff after the code)
-
- ``explanation``
- a plain-text explanation of the error message that is
- not subject to environment or header substitutions;
- it is accessible in the template via ${explanation}
-
- ``detail``
- a plain-text message customization that is not subject
- to environment or header substitutions; accessible in
- the template via ${detail}
-
- ``body_template``
- a ``String.template``-format content fragment used for environment
- and header substitution; the default template includes both
- the explanation and further detail provided in the
- message.
-
-Each HTTP exception accepts the following parameters:
-
- ``detail``
- a plain-text override of the default ``detail``
-
- ``headers``
- a list of (k,v) header pairs
-
- ``comment``
- a plain-text additional information which is
- usually stripped/hidden for end-users
-
- ``body_template``
- a ``string.Template`` object containing a content fragment in HTML
- that frames the explanation and further detail
-
-Substitution of response headers into template values is always performed.
-Substitution of WSGI environment values is performed if a ``request`` is
-passed to the exception's constructor.
-
-The subclasses of :class:`~_HTTPMove`
-(:class:`~HTTPMultipleChoices`, :class:`~HTTPMovedPermanently`,
-:class:`~HTTPFound`, :class:`~HTTPSeeOther`, :class:`~HTTPUseProxy` and
-:class:`~HTTPTemporaryRedirect`) are redirections that require a ``Location``
-field. Reflecting this, these subclasses have one additional keyword argument:
-``location``, which indicates the location to which to redirect.
-"""
-import types
-from string import Template
-from webob import html_escape as _html_escape
from zope.configuration.exceptions import ConfigurationError as ZCE
-from zope.interface import implements
-
-from pyramid.interfaces import IExceptionResponse
-from pyramid.response import Response
-
-def _no_escape(value):
- if value is None:
- return ''
- if not isinstance(value, basestring):
- if hasattr(value, '__unicode__'):
- value = unicode(value)
- else:
- value = str(value)
- return value
-
-
-class HTTPException(Exception): # bw compat
- pass
-
-class WSGIHTTPException(Response, HTTPException):
- implements(IExceptionResponse)
-
- ## You should set in subclasses:
- # code = 200
- # title = 'OK'
- # explanation = 'why this happens'
- # body_template_obj = Template('response template')
-
- # differences from webob.exc.WSGIHTTPException:
- # - not a WSGI application (just a response)
- #
- # as a result:
- #
- # - bases plaintext vs. html result on self.content_type rather than
- # on request environ
- #
- # - doesn't add request.environ keys to template substitutions unless
- # 'request' is passed as a constructor keyword argument.
- #
- # - doesn't use "strip_tags" (${br} placeholder for <br/>, no other html
- # in default body template)
- #
- # - sets a default app_iter if no body, app_iter, or unicode_body is
- # passed
- #
- # - explicitly sets self.message = detail to prevent whining by Python
- # 2.6.5+ access of Exception.message
- #
- # - its base class of HTTPException is no longer a Python 2.4 compatibility
- # shim; it's purely a base class that inherits from Exception. This
- # implies that this class' ``exception`` property always returns
- # ``self`` (only for bw compat at this point).
- code = None
- title = None
- explanation = ''
- body_template_obj = Template('''\
-${explanation}${br}${br}
-${detail}
-${html_comment}
-''')
-
- plain_template_obj = Template('''\
-${status}
-
-${body}''')
-
- html_template_obj = Template('''\
-<html>
- <head>
- <title>${status}</title>
- </head>
- <body>
- <h1>${status}</h1>
- ${body}
- </body>
-</html>''')
-
- ## Set this to True for responses that should have no request body
- empty_body = False
-
- def __init__(self, detail=None, headers=None, comment=None,
- body_template=None, **kw):
- status = '%s %s' % (self.code, self.title)
- Response.__init__(self, status=status, **kw)
- Exception.__init__(self, detail)
- self.detail = self.message = detail
- if headers:
- self.headers.extend(headers)
- self.comment = comment
- if body_template is not None:
- self.body_template = body_template
- self.body_template_obj = Template(body_template)
-
- if self.empty_body:
- del self.content_type
- del self.content_length
- elif not ('unicode_body' in kw or 'body' in kw or 'app_iter' in kw):
- self.app_iter = self._default_app_iter()
-
- def __str__(self):
- return self.detail or self.explanation
-
- def _default_app_iter(self):
- # This is a generator which defers the creation of the response page
- # body; we use a generator because we want to ensure that if
- # attributes of this response are changed after it is constructed, we
- # use the changed values rather than the values at time of construction
- # (e.g. self.content_type or self.charset).
- html_comment = ''
- comment = self.comment or ''
- content_type = self.content_type or ''
- if 'html' in content_type:
- escape = _html_escape
- page_template = self.html_template_obj
- br = '<br/>'
- if comment:
- html_comment = '<!-- %s -->' % escape(comment)
- else:
- escape = _no_escape
- page_template = self.plain_template_obj
- br = '\n'
- if comment:
- html_comment = escape(comment)
- args = {
- 'br':br,
- 'explanation': escape(self.explanation),
- 'detail': escape(self.detail or ''),
- 'comment': escape(comment),
- 'html_comment':html_comment,
- }
- body_tmpl = self.body_template_obj
- if WSGIHTTPException.body_template_obj is not body_tmpl:
- # Custom template; add headers to args
- environ = self.environ
- if environ is not None:
- for k, v in environ.items():
- args[k] = escape(v)
- for k, v in self.headers.items():
- args[k.lower()] = escape(v)
- body = body_tmpl.substitute(args)
- page = page_template.substitute(status=self.status, body=body)
- if isinstance(page, unicode):
- page = page.encode(self.charset)
- yield page
- raise StopIteration
-
- @property
- def exception(self):
- # bw compat only
- return self
- wsgi_response = exception # bw compat only
-
-class HTTPError(WSGIHTTPException):
- """
- base class for status codes in the 400's and 500's
-
- This is an exception which indicates that an error has occurred,
- and that any work in progress should not be committed. These are
- typically results in the 400's and 500's.
- """
-
-class HTTPRedirection(WSGIHTTPException):
- """
- base class for 300's status code (redirections)
-
- This is an abstract base class for 3xx redirection. It indicates
- that further action needs to be taken by the user agent in order
- to fulfill the request. It does not necessarly signal an error
- condition.
- """
-
-class HTTPOk(WSGIHTTPException):
- """
- Base class for the 200's status code (successful responses)
-
- code: 200, title: OK
- """
- code = 200
- title = 'OK'
-
-############################################################
-## 2xx success
-############################################################
-
-class HTTPCreated(HTTPOk):
- """
- subclass of :class:`~HTTPOk`
-
- This indicates that request has been fulfilled and resulted in a new
- resource being created.
-
- code: 201, title: Created
- """
- code = 201
- title = 'Created'
-
-class HTTPAccepted(HTTPOk):
- """
- subclass of :class:`~HTTPOk`
-
- This indicates that the request has been accepted for processing, but the
- processing has not been completed.
-
- code: 202, title: Accepted
- """
- code = 202
- title = 'Accepted'
- explanation = 'The request is accepted for processing.'
-
-class HTTPNonAuthoritativeInformation(HTTPOk):
- """
- subclass of :class:`~HTTPOk`
-
- This indicates that the returned metainformation in the entity-header is
- not the definitive set as available from the origin server, but is
- gathered from a local or a third-party copy.
-
- code: 203, title: Non-Authoritative Information
- """
- code = 203
- title = 'Non-Authoritative Information'
-
-class HTTPNoContent(HTTPOk):
- """
- subclass of :class:`~HTTPOk`
-
- This indicates that the server has fulfilled the request but does
- not need to return an entity-body, and might want to return updated
- metainformation.
-
- code: 204, title: No Content
- """
- code = 204
- title = 'No Content'
- empty_body = True
-
-class HTTPResetContent(HTTPOk):
- """
- subclass of :class:`~HTTPOk`
-
- This indicates that the the server has fulfilled the request and
- the user agent SHOULD reset the document view which caused the
- request to be sent.
-
- code: 205, title: Reset Content
- """
- code = 205
- title = 'Reset Content'
- empty_body = True
-
-class HTTPPartialContent(HTTPOk):
- """
- subclass of :class:`~HTTPOk`
-
- This indicates that the server has fulfilled the partial GET
- request for the resource.
-
- code: 206, title: Partial Content
- """
- code = 206
- title = 'Partial Content'
-
-## FIXME: add 207 Multi-Status (but it's complicated)
-
-############################################################
-## 3xx redirection
-############################################################
-
-class _HTTPMove(HTTPRedirection):
- """
- redirections which require a Location field
-
- Since a 'Location' header is a required attribute of 301, 302, 303,
- 305 and 307 (but not 304), this base class provides the mechanics to
- make this easy.
-
- You must provide a ``location`` keyword argument.
- """
- # differences from webob.exc._HTTPMove:
- #
- # - not a wsgi app
- #
- # - ${location} isn't wrapped in an <a> tag in body
- #
- # - location keyword arg defaults to ''
- #
- # - ``add_slash`` argument is no longer accepted: code that passes
- # add_slash argument to the constructor will receive an exception.
- explanation = 'The resource has been moved to'
- body_template_obj = Template('''\
-${explanation} ${location};
-you should be redirected automatically.
-${detail}
-${html_comment}''')
-
- def __init__(self, detail=None, headers=None, comment=None,
- body_template=None, location='', **kw):
- super(_HTTPMove, self).__init__(
- detail=detail, headers=headers, comment=comment,
- body_template=body_template, location=location, **kw)
-
-class HTTPMultipleChoices(_HTTPMove):
- """
- subclass of :class:`~_HTTPMove`
-
- This indicates that the requested resource corresponds to any one
- of a set of representations, each with its own specific location,
- and agent-driven negotiation information is being provided so that
- the user can select a preferred representation and redirect its
- request to that location.
-
- code: 300, title: Multiple Choices
- """
- code = 300
- title = 'Multiple Choices'
-
-class HTTPMovedPermanently(_HTTPMove):
- """
- subclass of :class:`~_HTTPMove`
-
- This indicates that the requested resource has been assigned a new
- permanent URI and any future references to this resource SHOULD use
- one of the returned URIs.
-
- code: 301, title: Moved Permanently
- """
- code = 301
- title = 'Moved Permanently'
-
-class HTTPFound(_HTTPMove):
- """
- subclass of :class:`~_HTTPMove`
-
- This indicates that the requested resource resides temporarily under
- a different URI.
-
- code: 302, title: Found
- """
- code = 302
- title = 'Found'
- explanation = 'The resource was found at'
-
-# This one is safe after a POST (the redirected location will be
-# retrieved with GET):
-class HTTPSeeOther(_HTTPMove):
- """
- subclass of :class:`~_HTTPMove`
-
- This indicates that the response to the request can be found under
- a different URI and SHOULD be retrieved using a GET method on that
- resource.
-
- code: 303, title: See Other
- """
- code = 303
- title = 'See Other'
-
-class HTTPNotModified(HTTPRedirection):
- """
- subclass of :class:`~HTTPRedirection`
-
- This indicates that if the client has performed a conditional GET
- request and access is allowed, but the document has not been
- modified, the server SHOULD respond with this status code.
-
- code: 304, title: Not Modified
- """
- # FIXME: this should include a date or etag header
- code = 304
- title = 'Not Modified'
- empty_body = True
-
-class HTTPUseProxy(_HTTPMove):
- """
- subclass of :class:`~_HTTPMove`
-
- This indicates that the requested resource MUST be accessed through
- the proxy given by the Location field.
-
- code: 305, title: Use Proxy
- """
- # Not a move, but looks a little like one
- code = 305
- title = 'Use Proxy'
- explanation = (
- 'The resource must be accessed through a proxy located at')
-
-class HTTPTemporaryRedirect(_HTTPMove):
- """
- subclass of :class:`~_HTTPMove`
-
- This indicates that the requested resource resides temporarily
- under a different URI.
-
- code: 307, title: Temporary Redirect
- """
- code = 307
- title = 'Temporary Redirect'
-
-############################################################
-## 4xx client error
-############################################################
-
-class HTTPClientError(HTTPError):
- """
- base class for the 400's, where the client is in error
-
- This is an error condition in which the client is presumed to be
- in-error. This is an expected problem, and thus is not considered
- a bug. A server-side traceback is not warranted. Unless specialized,
- this is a '400 Bad Request'
- """
- code = 400
- title = 'Bad Request'
- explanation = ('The server could not comply with the request since '
- 'it is either malformed or otherwise incorrect.')
-
-class HTTPBadRequest(HTTPClientError):
- pass
-
-class HTTPUnauthorized(HTTPClientError):
- """
- subclass of :class:`~HTTPClientError`
-
- This indicates that the request requires user authentication.
-
- code: 401, title: Unauthorized
- """
- code = 401
- title = 'Unauthorized'
- explanation = (
- 'This server could not verify that you are authorized to '
- 'access the document you requested. Either you supplied the '
- 'wrong credentials (e.g., bad password), or your browser '
- 'does not understand how to supply the credentials required.')
-
-class HTTPPaymentRequired(HTTPClientError):
- """
- subclass of :class:`~HTTPClientError`
-
- code: 402, title: Payment Required
- """
- code = 402
- title = 'Payment Required'
- explanation = ('Access was denied for financial reasons.')
-
-class HTTPForbidden(HTTPClientError):
- """
- subclass of :class:`~HTTPClientError`
- This indicates that the server understood the request, but is
- refusing to fulfill it.
-
- code: 403, title: Forbidden
-
- Raise this exception within :term:`view` code to immediately return the
- :term:`forbidden view` to the invoking user. Usually this is a basic
- ``403`` page, but the forbidden view can be customized as necessary. See
- :ref:`changing_the_forbidden_view`. A ``Forbidden`` exception will be
- the ``context`` of a :term:`Forbidden View`.
-
- This exception's constructor treats two arguments specially. The first
- argument, ``detail``, should be a string. The value of this string will
- be used as the ``message`` attribute of the exception object. The second
- special keyword argument, ``result`` is usually an instance of
- :class:`pyramid.security.Denied` or :class:`pyramid.security.ACLDenied`
- each of which indicates a reason for the forbidden error. However,
- ``result`` is also permitted to be just a plain boolean ``False`` object
- or ``None``. The ``result`` value will be used as the ``result``
- attribute of the exception object. It defaults to ``None``.
-
- The :term:`Forbidden View` can use the attributes of a Forbidden
- exception as necessary to provide extended information in an error
- report shown to a user.
- """
- # differences from webob.exc.HTTPForbidden:
- #
- # - accepts a ``result`` keyword argument
- #
- # - overrides constructor to set ``self.result``
- #
- # differences from older pyramid.exceptions.Forbidden:
- #
- # - ``result`` must be passed as a keyword argument.
- #
- code = 403
- title = 'Forbidden'
- explanation = ('Access was denied to this resource.')
- def __init__(self, detail=None, headers=None, comment=None,
- body_template=None, result=None, **kw):
- HTTPClientError.__init__(self, detail=detail, headers=headers,
- comment=comment, body_template=body_template,
- **kw)
- self.result = result
-
-class HTTPNotFound(HTTPClientError):
- """
- subclass of :class:`~HTTPClientError`
-
- This indicates that the server did not find anything matching the
- Request-URI.
-
- code: 404, title: Not Found
-
- 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
- customized as necessary. See :ref:`changing_the_notfound_view`.
-
- This exception's constructor accepts a ``detail`` argument
- (the first argument), which should be a string. The value of this
- string will be available as the ``message`` attribute of this exception,
- for availability to the :term:`Not Found View`.
- """
- code = 404
- title = 'Not Found'
- explanation = ('The resource could not be found.')
-
-class HTTPMethodNotAllowed(HTTPClientError):
- """
- subclass of :class:`~HTTPClientError`
-
- This indicates that the method specified in the Request-Line is
- not allowed for the resource identified by the Request-URI.
-
- code: 405, title: Method Not Allowed
- """
- # differences from webob.exc.HTTPMethodNotAllowed:
- #
- # - body_template_obj not overridden (it tried to use request environ's
- # REQUEST_METHOD)
- code = 405
- title = 'Method Not Allowed'
-
-class HTTPNotAcceptable(HTTPClientError):
- """
- subclass of :class:`~HTTPClientError`
-
- This indicates the resource identified by the request is only
- capable of generating response entities which have content
- characteristics not acceptable according to the accept headers
- sent in the request.
-
- code: 406, title: Not Acceptable
- """
- # differences from webob.exc.HTTPNotAcceptable:
- #
- # - body_template_obj not overridden (it tried to use request environ's
- # HTTP_ACCEPT)
- code = 406
- title = 'Not Acceptable'
-
-class HTTPProxyAuthenticationRequired(HTTPClientError):
- """
- subclass of :class:`~HTTPClientError`
-
- This is similar to 401, but indicates that the client must first
- authenticate itself with the proxy.
-
- code: 407, title: Proxy Authentication Required
- """
- code = 407
- title = 'Proxy Authentication Required'
- explanation = ('Authentication with a local proxy is needed.')
-
-class HTTPRequestTimeout(HTTPClientError):
- """
- subclass of :class:`~HTTPClientError`
-
- This indicates that the client did not produce a request within
- the time that the server was prepared to wait.
-
- code: 408, title: Request Timeout
- """
- code = 408
- title = 'Request Timeout'
- explanation = ('The server has waited too long for the request to '
- 'be sent by the client.')
-
-class HTTPConflict(HTTPClientError):
- """
- subclass of :class:`~HTTPClientError`
-
- This indicates that the request could not be completed due to a
- conflict with the current state of the resource.
-
- code: 409, title: Conflict
- """
- code = 409
- title = 'Conflict'
- explanation = ('There was a conflict when trying to complete '
- 'your request.')
-
-class HTTPGone(HTTPClientError):
- """
- subclass of :class:`~HTTPClientError`
-
- This indicates that the requested resource is no longer available
- at the server and no forwarding address is known.
-
- code: 410, title: Gone
- """
- code = 410
- title = 'Gone'
- explanation = ('This resource is no longer available. No forwarding '
- 'address is given.')
-
-class HTTPLengthRequired(HTTPClientError):
- """
- subclass of :class:`~HTTPClientError`
-
- This indicates that the the server refuses to accept the request
- without a defined Content-Length.
-
- code: 411, title: Length Required
- """
- code = 411
- title = 'Length Required'
- explanation = ('Content-Length header required.')
-
-class HTTPPreconditionFailed(HTTPClientError):
- """
- subclass of :class:`~HTTPClientError`
-
- This indicates that the precondition given in one or more of the
- request-header fields evaluated to false when it was tested on the
- server.
-
- code: 412, title: Precondition Failed
- """
- code = 412
- title = 'Precondition Failed'
- explanation = ('Request precondition failed.')
-
-class HTTPRequestEntityTooLarge(HTTPClientError):
- """
- subclass of :class:`~HTTPClientError`
-
- This indicates that the server is refusing to process a request
- because the request entity is larger than the server is willing or
- able to process.
-
- code: 413, title: Request Entity Too Large
- """
- code = 413
- title = 'Request Entity Too Large'
- explanation = ('The body of your request was too large for this server.')
-
-class HTTPRequestURITooLong(HTTPClientError):
- """
- subclass of :class:`~HTTPClientError`
-
- This indicates that the server is refusing to service the request
- because the Request-URI is longer than the server is willing to
- interpret.
-
- code: 414, title: Request-URI Too Long
- """
- code = 414
- title = 'Request-URI Too Long'
- explanation = ('The request URI was too long for this server.')
-
-class HTTPUnsupportedMediaType(HTTPClientError):
- """
- subclass of :class:`~HTTPClientError`
-
- This indicates that the server is refusing to service the request
- because the entity of the request is in a format not supported by
- the requested resource for the requested method.
-
- code: 415, title: Unsupported Media Type
- """
- # differences from webob.exc.HTTPUnsupportedMediaType:
- #
- # - body_template_obj not overridden (it tried to use request environ's
- # CONTENT_TYPE)
- code = 415
- title = 'Unsupported Media Type'
-
-class HTTPRequestRangeNotSatisfiable(HTTPClientError):
- """
- subclass of :class:`~HTTPClientError`
-
- The server SHOULD return a response with this status code if a
- request included a Range request-header field, and none of the
- range-specifier values in this field overlap the current extent
- of the selected resource, and the request did not include an
- If-Range request-header field.
-
- code: 416, title: Request Range Not Satisfiable
- """
- code = 416
- title = 'Request Range Not Satisfiable'
- explanation = ('The Range requested is not available.')
-
-class HTTPExpectationFailed(HTTPClientError):
- """
- subclass of :class:`~HTTPClientError`
-
- This indidcates that the expectation given in an Expect
- request-header field could not be met by this server.
-
- code: 417, title: Expectation Failed
- """
- code = 417
- title = 'Expectation Failed'
- explanation = ('Expectation failed.')
-
-class HTTPUnprocessableEntity(HTTPClientError):
- """
- subclass of :class:`~HTTPClientError`
-
- This indicates that the server is unable to process the contained
- instructions. Only for WebDAV.
-
- code: 422, title: Unprocessable Entity
- """
- ## Note: from WebDAV
- code = 422
- title = 'Unprocessable Entity'
- explanation = 'Unable to process the contained instructions'
-
-class HTTPLocked(HTTPClientError):
- """
- subclass of :class:`~HTTPClientError`
-
- This indicates that the resource is locked. Only for WebDAV
-
- code: 423, title: Locked
- """
- ## Note: from WebDAV
- code = 423
- title = 'Locked'
- explanation = ('The resource is locked')
-
-class HTTPFailedDependency(HTTPClientError):
- """
- subclass of :class:`~HTTPClientError`
-
- This indicates that the method could not be performed because the
- requested action depended on another action and that action failed.
- Only for WebDAV.
-
- code: 424, title: Failed Dependency
- """
- ## Note: from WebDAV
- code = 424
- title = 'Failed Dependency'
- explanation = (
- 'The method could not be performed because the requested '
- 'action dependended on another action and that action failed')
-
-############################################################
-## 5xx Server Error
-############################################################
-# Response status codes beginning with the digit "5" indicate cases in
-# which the server is aware that it has erred or is incapable of
-# performing the request. Except when responding to a HEAD request, the
-# server SHOULD include an entity containing an explanation of the error
-# situation, and whether it is a temporary or permanent condition. User
-# agents SHOULD display any included entity to the user. These response
-# codes are applicable to any request method.
-
-class HTTPServerError(HTTPError):
- """
- base class for the 500's, where the server is in-error
-
- This is an error condition in which the server is presumed to be
- in-error. This is usually unexpected, and thus requires a traceback;
- ideally, opening a support ticket for the customer. Unless specialized,
- this is a '500 Internal Server Error'
- """
- code = 500
- title = 'Internal Server Error'
- explanation = (
- 'The server has either erred or is incapable of performing '
- 'the requested operation.')
-
-class HTTPInternalServerError(HTTPServerError):
- pass
-
-class HTTPNotImplemented(HTTPServerError):
- """
- subclass of :class:`~HTTPServerError`
-
- This indicates that the server does not support the functionality
- required to fulfill the request.
-
- code: 501, title: Not Implemented
- """
- # differences from webob.exc.HTTPNotAcceptable:
- #
- # - body_template_obj not overridden (it tried to use request environ's
- # REQUEST_METHOD)
- code = 501
- title = 'Not Implemented'
-
-class HTTPBadGateway(HTTPServerError):
- """
- subclass of :class:`~HTTPServerError`
-
- This indicates that the server, while acting as a gateway or proxy,
- received an invalid response from the upstream server it accessed
- in attempting to fulfill the request.
-
- code: 502, title: Bad Gateway
- """
- code = 502
- title = 'Bad Gateway'
- explanation = ('Bad gateway.')
-
-class HTTPServiceUnavailable(HTTPServerError):
- """
- subclass of :class:`~HTTPServerError`
-
- This indicates that the server is currently unable to handle the
- request due to a temporary overloading or maintenance of the server.
-
- code: 503, title: Service Unavailable
- """
- code = 503
- title = 'Service Unavailable'
- explanation = ('The server is currently unavailable. '
- 'Please try again at a later time.')
-
-class HTTPGatewayTimeout(HTTPServerError):
- """
- subclass of :class:`~HTTPServerError`
-
- This indicates that the server, while acting as a gateway or proxy,
- did not receive a timely response from the upstream server specified
- by the URI (e.g. HTTP, FTP, LDAP) or some other auxiliary server
- (e.g. DNS) it needed to access in attempting to complete the request.
-
- code: 504, title: Gateway Timeout
- """
- code = 504
- title = 'Gateway Timeout'
- explanation = ('The gateway has timed out.')
-
-class HTTPVersionNotSupported(HTTPServerError):
- """
- subclass of :class:`~HTTPServerError`
-
- This indicates that the server does not support, or refuses to
- support, the HTTP protocol version that was used in the request
- message.
-
- code: 505, title: HTTP Version Not Supported
- """
- code = 505
- title = 'HTTP Version Not Supported'
- explanation = ('The HTTP version is not supported.')
-
-class HTTPInsufficientStorage(HTTPServerError):
- """
- subclass of :class:`~HTTPServerError`
-
- This indicates that the server does not have enough space to save
- the resource.
-
- code: 507, title: Insufficient Storage
- """
- code = 507
- title = 'Insufficient Storage'
- explanation = ('There was not enough space to save the resource')
+from pyramid.response import HTTPNotFound
+from pyramid.response import HTTPForbidden
NotFound = HTTPNotFound # bw compat
Forbidden = HTTPForbidden # bw compat
@@ -1008,48 +31,3 @@ class ConfigurationError(ZCE):
method of a :term:`Configurator`"""
-def abort(status_code, **kw):
- """Aborts the request immediately by raising an HTTP exception based on a
- status code. Example::
-
- abort(404) # raises an HTTPNotFound exception.
-
- The values passed as ``kw`` are provided to the exception's constructor.
- """
- exc = status_map[status_code](**kw)
- raise exc
-
-
-def redirect(url, code=302, **kw):
- """Raises an :class:`~HTTPFound` (302) redirect exception to the
- URL specified by ``url``.
-
- Optionally, a code variable may be passed with the status code of
- the redirect, ie::
-
- redirect(route_url('foo', request), code=303)
-
- The values passed as ``kw`` are provided to the exception constructor.
-
- """
- exc = status_map[code]
- raise exc(location=url, **kw)
-
-def default_exceptionresponse_view(context, request):
- if not isinstance(context, Exception):
- # backwards compat for an exception response view registered via
- # config.set_notfound_view or config.set_forbidden_view
- # instead of as a proper exception view
- context = request.exception or context
- return context
-
-status_map={}
-for name, value in globals().items():
- if (isinstance(value, (type, types.ClassType)) and
- issubclass(value, HTTPException)
- and not name.startswith('_')):
- code = getattr(value, 'code', None)
- if code:
- status_map[code] = value
-del name, value
-
diff --git a/pyramid/httpexceptions.py b/pyramid/httpexceptions.py
index 8b2a012cc..dbb530b4a 100644
--- a/pyramid/httpexceptions.py
+++ b/pyramid/httpexceptions.py
@@ -1,2 +1,116 @@
-from pyramid.exceptions import * # bw compat
+"""
+HTTP Exceptions
+---------------
+
+This module contains Pyramid HTTP exception classes. Each class relates to a
+single HTTP status code. Each class is a subclass of the
+:class:`~HTTPException`. Each exception class is also a :term:`response`
+object.
+
+Each exception class has a status code according to `RFC 2068
+<http://www.ietf.org/rfc/rfc2068.txt>`: codes with 100-300 are not really
+errors; 400's are client errors, and 500's are server errors.
+
+Exception
+ HTTPException
+ HTTPOk
+ * 200 - HTTPOk
+ * 201 - HTTPCreated
+ * 202 - HTTPAccepted
+ * 203 - HTTPNonAuthoritativeInformation
+ * 204 - HTTPNoContent
+ * 205 - HTTPResetContent
+ * 206 - HTTPPartialContent
+ HTTPRedirection
+ * 300 - HTTPMultipleChoices
+ * 301 - HTTPMovedPermanently
+ * 302 - HTTPFound
+ * 303 - HTTPSeeOther
+ * 304 - HTTPNotModified
+ * 305 - HTTPUseProxy
+ * 306 - Unused (not implemented, obviously)
+ * 307 - HTTPTemporaryRedirect
+ HTTPError
+ HTTPClientError
+ * 400 - HTTPBadRequest
+ * 401 - HTTPUnauthorized
+ * 402 - HTTPPaymentRequired
+ * 403 - HTTPForbidden
+ * 404 - HTTPNotFound
+ * 405 - HTTPMethodNotAllowed
+ * 406 - HTTPNotAcceptable
+ * 407 - HTTPProxyAuthenticationRequired
+ * 408 - HTTPRequestTimeout
+ * 409 - HTTPConflict
+ * 410 - HTTPGone
+ * 411 - HTTPLengthRequired
+ * 412 - HTTPPreconditionFailed
+ * 413 - HTTPRequestEntityTooLarge
+ * 414 - HTTPRequestURITooLong
+ * 415 - HTTPUnsupportedMediaType
+ * 416 - HTTPRequestRangeNotSatisfiable
+ * 417 - HTTPExpectationFailed
+ HTTPServerError
+ * 500 - HTTPInternalServerError
+ * 501 - HTTPNotImplemented
+ * 502 - HTTPBadGateway
+ * 503 - HTTPServiceUnavailable
+ * 504 - HTTPGatewayTimeout
+ * 505 - HTTPVersionNotSupported
+
+Each HTTP exception has the following attributes:
+
+ ``code``
+ the HTTP status code for the exception
+
+ ``title``
+ remainder of the status line (stuff after the code)
+
+ ``explanation``
+ a plain-text explanation of the error message that is
+ not subject to environment or header substitutions;
+ it is accessible in the template via ${explanation}
+
+ ``detail``
+ a plain-text message customization that is not subject
+ to environment or header substitutions; accessible in
+ the template via ${detail}
+
+ ``body_template``
+ a ``String.template``-format content fragment used for environment
+ and header substitution; the default template includes both
+ the explanation and further detail provided in the
+ message.
+
+Each HTTP exception accepts the following parameters:
+
+ ``detail``
+ a plain-text override of the default ``detail``
+
+ ``headers``
+ a list of (k,v) header pairs
+
+ ``comment``
+ a plain-text additional information which is
+ usually stripped/hidden for end-users
+
+ ``body_template``
+ a ``string.Template`` object containing a content fragment in HTML
+ that frames the explanation and further detail
+
+Substitution of response headers into template values is always performed.
+Substitution of WSGI environment values is performed if a ``request`` is
+passed to the exception's constructor.
+
+The subclasses of :class:`~_HTTPMove`
+(:class:`~HTTPMultipleChoices`, :class:`~HTTPMovedPermanently`,
+:class:`~HTTPFound`, :class:`~HTTPSeeOther`, :class:`~HTTPUseProxy` and
+:class:`~HTTPTemporaryRedirect`) are redirections that require a ``Location``
+field. Reflecting this, these subclasses have one additional keyword argument:
+``location``, which indicates the location to which to redirect.
+"""
+
+from pyramid.response import * # API
+
+
diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py
index d200d15cf..237727b41 100644
--- a/pyramid/interfaces.py
+++ b/pyramid/interfaces.py
@@ -55,12 +55,13 @@ class IException(Interface): # not an API
""" An interface representing a generic exception """
class IExceptionResponse(IException, IResponse):
- """ An interface representing a WSGI response which is also an
- exception object. Register an exception view using this interface
- as a ``context`` to apply the registered view for all exception
- types raised by :app:`Pyramid` internally
- (:class:`pyramid.exceptions.NotFound` and
- :class:`pyramid.exceptions.Forbidden`)."""
+ """ An interface representing a WSGI response which is also an exception
+ object. Register an exception view using this interface as a ``context``
+ to apply the registered view for all exception types raised by
+ :app:`Pyramid` internally (any exception that inherits from
+ :class:`pyramid.response.Response`, including
+ :class:`pyramid.response.HTTPNotFound` and
+ :class:`pyramid.response.HTTPForbidden`)."""
class IBeforeRender(Interface):
"""
@@ -274,9 +275,9 @@ class IExceptionViewClassifier(Interface):
class IView(Interface):
def __call__(context, request):
""" Must return an object that implements IResponse. May
- optionally raise ``pyramid.exceptions.Forbidden`` if an
+ optionally raise ``pyramid.response.HTTPForbidden`` if an
authorization failure is detected during view execution or
- ``pyramid.exceptions.NotFound`` if the not found page is
+ ``pyramid.response.HTTPNotFound`` if the not found page is
meant to be returned."""
class ISecuredView(IView):
diff --git a/pyramid/response.py b/pyramid/response.py
index e9f5528a5..41ac354f9 100644
--- a/pyramid/response.py
+++ b/pyramid/response.py
@@ -1,8 +1,948 @@
+import types
+from string import Template
+
from webob import Response as _Response
+from webob import html_escape as _html_escape
from zope.interface import implements
+from zope.configuration.exceptions import ConfigurationError as ZCE
from pyramid.interfaces import IExceptionResponse
class Response(_Response, Exception):
implements(IExceptionResponse)
+def _no_escape(value):
+ if value is None:
+ return ''
+ if not isinstance(value, basestring):
+ if hasattr(value, '__unicode__'):
+ value = unicode(value)
+ else:
+ value = str(value)
+ return value
+
+
+class HTTPException(Exception): # bw compat
+ pass
+
+class WSGIHTTPException(Response, HTTPException):
+ implements(IExceptionResponse)
+
+ ## You should set in subclasses:
+ # code = 200
+ # title = 'OK'
+ # explanation = 'why this happens'
+ # body_template_obj = Template('response template')
+
+ # differences from webob.exc.WSGIHTTPException:
+ # - not a WSGI application (just a response)
+ #
+ # as a result:
+ #
+ # - bases plaintext vs. html result on self.content_type rather than
+ # on request environ
+ #
+ # - doesn't add request.environ keys to template substitutions unless
+ # 'request' is passed as a constructor keyword argument.
+ #
+ # - doesn't use "strip_tags" (${br} placeholder for <br/>, no other html
+ # in default body template)
+ #
+ # - sets a default app_iter if no body, app_iter, or unicode_body is
+ # passed
+ #
+ # - explicitly sets self.message = detail to prevent whining by Python
+ # 2.6.5+ access of Exception.message
+ #
+ # - its base class of HTTPException is no longer a Python 2.4 compatibility
+ # shim; it's purely a base class that inherits from Exception. This
+ # implies that this class' ``exception`` property always returns
+ # ``self`` (only for bw compat at this point).
+ code = None
+ title = None
+ explanation = ''
+ body_template_obj = Template('''\
+${explanation}${br}${br}
+${detail}
+${html_comment}
+''')
+
+ plain_template_obj = Template('''\
+${status}
+
+${body}''')
+
+ html_template_obj = Template('''\
+<html>
+ <head>
+ <title>${status}</title>
+ </head>
+ <body>
+ <h1>${status}</h1>
+ ${body}
+ </body>
+</html>''')
+
+ ## Set this to True for responses that should have no request body
+ empty_body = False
+
+ def __init__(self, detail=None, headers=None, comment=None,
+ body_template=None, **kw):
+ status = '%s %s' % (self.code, self.title)
+ Response.__init__(self, status=status, **kw)
+ Exception.__init__(self, detail)
+ self.detail = self.message = detail
+ if headers:
+ self.headers.extend(headers)
+ self.comment = comment
+ if body_template is not None:
+ self.body_template = body_template
+ self.body_template_obj = Template(body_template)
+
+ if self.empty_body:
+ del self.content_type
+ del self.content_length
+ elif not ('unicode_body' in kw or 'body' in kw or 'app_iter' in kw):
+ self.app_iter = self._default_app_iter()
+
+ def __str__(self):
+ return self.detail or self.explanation
+
+ def _default_app_iter(self):
+ # This is a generator which defers the creation of the response page
+ # body; we use a generator because we want to ensure that if
+ # attributes of this response are changed after it is constructed, we
+ # use the changed values rather than the values at time of construction
+ # (e.g. self.content_type or self.charset).
+ html_comment = ''
+ comment = self.comment or ''
+ content_type = self.content_type or ''
+ if 'html' in content_type:
+ escape = _html_escape
+ page_template = self.html_template_obj
+ br = '<br/>'
+ if comment:
+ html_comment = '<!-- %s -->' % escape(comment)
+ else:
+ escape = _no_escape
+ page_template = self.plain_template_obj
+ br = '\n'
+ if comment:
+ html_comment = escape(comment)
+ args = {
+ 'br':br,
+ 'explanation': escape(self.explanation),
+ 'detail': escape(self.detail or ''),
+ 'comment': escape(comment),
+ 'html_comment':html_comment,
+ }
+ body_tmpl = self.body_template_obj
+ if WSGIHTTPException.body_template_obj is not body_tmpl:
+ # Custom template; add headers to args
+ environ = self.environ
+ if environ is not None:
+ for k, v in environ.items():
+ args[k] = escape(v)
+ for k, v in self.headers.items():
+ args[k.lower()] = escape(v)
+ body = body_tmpl.substitute(args)
+ page = page_template.substitute(status=self.status, body=body)
+ if isinstance(page, unicode):
+ page = page.encode(self.charset)
+ yield page
+ raise StopIteration
+
+ @property
+ def exception(self):
+ # bw compat only
+ return self
+ wsgi_response = exception # bw compat only
+
+class HTTPError(WSGIHTTPException):
+ """
+ base class for status codes in the 400's and 500's
+
+ This is an exception which indicates that an error has occurred,
+ and that any work in progress should not be committed. These are
+ typically results in the 400's and 500's.
+ """
+
+class HTTPRedirection(WSGIHTTPException):
+ """
+ base class for 300's status code (redirections)
+
+ This is an abstract base class for 3xx redirection. It indicates
+ that further action needs to be taken by the user agent in order
+ to fulfill the request. It does not necessarly signal an error
+ condition.
+ """
+
+class HTTPOk(WSGIHTTPException):
+ """
+ Base class for the 200's status code (successful responses)
+
+ code: 200, title: OK
+ """
+ code = 200
+ title = 'OK'
+
+############################################################
+## 2xx success
+############################################################
+
+class HTTPCreated(HTTPOk):
+ """
+ subclass of :class:`~HTTPOk`
+
+ This indicates that request has been fulfilled and resulted in a new
+ resource being created.
+
+ code: 201, title: Created
+ """
+ code = 201
+ title = 'Created'
+
+class HTTPAccepted(HTTPOk):
+ """
+ subclass of :class:`~HTTPOk`
+
+ This indicates that the request has been accepted for processing, but the
+ processing has not been completed.
+
+ code: 202, title: Accepted
+ """
+ code = 202
+ title = 'Accepted'
+ explanation = 'The request is accepted for processing.'
+
+class HTTPNonAuthoritativeInformation(HTTPOk):
+ """
+ subclass of :class:`~HTTPOk`
+
+ This indicates that the returned metainformation in the entity-header is
+ not the definitive set as available from the origin server, but is
+ gathered from a local or a third-party copy.
+
+ code: 203, title: Non-Authoritative Information
+ """
+ code = 203
+ title = 'Non-Authoritative Information'
+
+class HTTPNoContent(HTTPOk):
+ """
+ subclass of :class:`~HTTPOk`
+
+ This indicates that the server has fulfilled the request but does
+ not need to return an entity-body, and might want to return updated
+ metainformation.
+
+ code: 204, title: No Content
+ """
+ code = 204
+ title = 'No Content'
+ empty_body = True
+
+class HTTPResetContent(HTTPOk):
+ """
+ subclass of :class:`~HTTPOk`
+
+ This indicates that the the server has fulfilled the request and
+ the user agent SHOULD reset the document view which caused the
+ request to be sent.
+
+ code: 205, title: Reset Content
+ """
+ code = 205
+ title = 'Reset Content'
+ empty_body = True
+
+class HTTPPartialContent(HTTPOk):
+ """
+ subclass of :class:`~HTTPOk`
+
+ This indicates that the server has fulfilled the partial GET
+ request for the resource.
+
+ code: 206, title: Partial Content
+ """
+ code = 206
+ title = 'Partial Content'
+
+## FIXME: add 207 Multi-Status (but it's complicated)
+
+############################################################
+## 3xx redirection
+############################################################
+
+class _HTTPMove(HTTPRedirection):
+ """
+ redirections which require a Location field
+
+ Since a 'Location' header is a required attribute of 301, 302, 303,
+ 305 and 307 (but not 304), this base class provides the mechanics to
+ make this easy.
+
+ You must provide a ``location`` keyword argument.
+ """
+ # differences from webob.exc._HTTPMove:
+ #
+ # - not a wsgi app
+ #
+ # - ${location} isn't wrapped in an <a> tag in body
+ #
+ # - location keyword arg defaults to ''
+ #
+ # - ``add_slash`` argument is no longer accepted: code that passes
+ # add_slash argument to the constructor will receive an exception.
+ explanation = 'The resource has been moved to'
+ body_template_obj = Template('''\
+${explanation} ${location};
+you should be redirected automatically.
+${detail}
+${html_comment}''')
+
+ def __init__(self, detail=None, headers=None, comment=None,
+ body_template=None, location='', **kw):
+ super(_HTTPMove, self).__init__(
+ detail=detail, headers=headers, comment=comment,
+ body_template=body_template, location=location, **kw)
+
+class HTTPMultipleChoices(_HTTPMove):
+ """
+ subclass of :class:`~_HTTPMove`
+
+ This indicates that the requested resource corresponds to any one
+ of a set of representations, each with its own specific location,
+ and agent-driven negotiation information is being provided so that
+ the user can select a preferred representation and redirect its
+ request to that location.
+
+ code: 300, title: Multiple Choices
+ """
+ code = 300
+ title = 'Multiple Choices'
+
+class HTTPMovedPermanently(_HTTPMove):
+ """
+ subclass of :class:`~_HTTPMove`
+
+ This indicates that the requested resource has been assigned a new
+ permanent URI and any future references to this resource SHOULD use
+ one of the returned URIs.
+
+ code: 301, title: Moved Permanently
+ """
+ code = 301
+ title = 'Moved Permanently'
+
+class HTTPFound(_HTTPMove):
+ """
+ subclass of :class:`~_HTTPMove`
+
+ This indicates that the requested resource resides temporarily under
+ a different URI.
+
+ code: 302, title: Found
+ """
+ code = 302
+ title = 'Found'
+ explanation = 'The resource was found at'
+
+# This one is safe after a POST (the redirected location will be
+# retrieved with GET):
+class HTTPSeeOther(_HTTPMove):
+ """
+ subclass of :class:`~_HTTPMove`
+
+ This indicates that the response to the request can be found under
+ a different URI and SHOULD be retrieved using a GET method on that
+ resource.
+
+ code: 303, title: See Other
+ """
+ code = 303
+ title = 'See Other'
+
+class HTTPNotModified(HTTPRedirection):
+ """
+ subclass of :class:`~HTTPRedirection`
+
+ This indicates that if the client has performed a conditional GET
+ request and access is allowed, but the document has not been
+ modified, the server SHOULD respond with this status code.
+
+ code: 304, title: Not Modified
+ """
+ # FIXME: this should include a date or etag header
+ code = 304
+ title = 'Not Modified'
+ empty_body = True
+
+class HTTPUseProxy(_HTTPMove):
+ """
+ subclass of :class:`~_HTTPMove`
+
+ This indicates that the requested resource MUST be accessed through
+ the proxy given by the Location field.
+
+ code: 305, title: Use Proxy
+ """
+ # Not a move, but looks a little like one
+ code = 305
+ title = 'Use Proxy'
+ explanation = (
+ 'The resource must be accessed through a proxy located at')
+
+class HTTPTemporaryRedirect(_HTTPMove):
+ """
+ subclass of :class:`~_HTTPMove`
+
+ This indicates that the requested resource resides temporarily
+ under a different URI.
+
+ code: 307, title: Temporary Redirect
+ """
+ code = 307
+ title = 'Temporary Redirect'
+
+############################################################
+## 4xx client error
+############################################################
+
+class HTTPClientError(HTTPError):
+ """
+ base class for the 400's, where the client is in error
+
+ This is an error condition in which the client is presumed to be
+ in-error. This is an expected problem, and thus is not considered
+ a bug. A server-side traceback is not warranted. Unless specialized,
+ this is a '400 Bad Request'
+ """
+ code = 400
+ title = 'Bad Request'
+ explanation = ('The server could not comply with the request since '
+ 'it is either malformed or otherwise incorrect.')
+
+class HTTPBadRequest(HTTPClientError):
+ pass
+
+class HTTPUnauthorized(HTTPClientError):
+ """
+ subclass of :class:`~HTTPClientError`
+
+ This indicates that the request requires user authentication.
+
+ code: 401, title: Unauthorized
+ """
+ code = 401
+ title = 'Unauthorized'
+ explanation = (
+ 'This server could not verify that you are authorized to '
+ 'access the document you requested. Either you supplied the '
+ 'wrong credentials (e.g., bad password), or your browser '
+ 'does not understand how to supply the credentials required.')
+
+class HTTPPaymentRequired(HTTPClientError):
+ """
+ subclass of :class:`~HTTPClientError`
+
+ code: 402, title: Payment Required
+ """
+ code = 402
+ title = 'Payment Required'
+ explanation = ('Access was denied for financial reasons.')
+
+class HTTPForbidden(HTTPClientError):
+ """
+ subclass of :class:`~HTTPClientError`
+
+ This indicates that the server understood the request, but is
+ refusing to fulfill it.
+
+ code: 403, title: Forbidden
+
+ Raise this exception within :term:`view` code to immediately return the
+ :term:`forbidden view` to the invoking user. Usually this is a basic
+ ``403`` page, but the forbidden view can be customized as necessary. See
+ :ref:`changing_the_forbidden_view`. A ``Forbidden`` exception will be
+ the ``context`` of a :term:`Forbidden View`.
+
+ This exception's constructor treats two arguments specially. The first
+ argument, ``detail``, should be a string. The value of this string will
+ be used as the ``message`` attribute of the exception object. The second
+ special keyword argument, ``result`` is usually an instance of
+ :class:`pyramid.security.Denied` or :class:`pyramid.security.ACLDenied`
+ each of which indicates a reason for the forbidden error. However,
+ ``result`` is also permitted to be just a plain boolean ``False`` object
+ or ``None``. The ``result`` value will be used as the ``result``
+ attribute of the exception object. It defaults to ``None``.
+
+ The :term:`Forbidden View` can use the attributes of a Forbidden
+ exception as necessary to provide extended information in an error
+ report shown to a user.
+ """
+ # differences from webob.exc.HTTPForbidden:
+ #
+ # - accepts a ``result`` keyword argument
+ #
+ # - overrides constructor to set ``self.result``
+ #
+ # differences from older ``pyramid.exceptions.Forbidden``:
+ #
+ # - ``result`` must be passed as a keyword argument.
+ #
+ code = 403
+ title = 'Forbidden'
+ explanation = ('Access was denied to this resource.')
+ def __init__(self, detail=None, headers=None, comment=None,
+ body_template=None, result=None, **kw):
+ HTTPClientError.__init__(self, detail=detail, headers=headers,
+ comment=comment, body_template=body_template,
+ **kw)
+ self.result = result
+
+class HTTPNotFound(HTTPClientError):
+ """
+ subclass of :class:`~HTTPClientError`
+
+ This indicates that the server did not find anything matching the
+ Request-URI.
+
+ code: 404, title: Not Found
+
+ 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
+ customized as necessary. See :ref:`changing_the_notfound_view`.
+
+ This exception's constructor accepts a ``detail`` argument
+ (the first argument), which should be a string. The value of this
+ string will be available as the ``message`` attribute of this exception,
+ for availability to the :term:`Not Found View`.
+ """
+ code = 404
+ title = 'Not Found'
+ explanation = ('The resource could not be found.')
+
+class HTTPMethodNotAllowed(HTTPClientError):
+ """
+ subclass of :class:`~HTTPClientError`
+
+ This indicates that the method specified in the Request-Line is
+ not allowed for the resource identified by the Request-URI.
+
+ code: 405, title: Method Not Allowed
+ """
+ # differences from webob.exc.HTTPMethodNotAllowed:
+ #
+ # - body_template_obj not overridden (it tried to use request environ's
+ # REQUEST_METHOD)
+ code = 405
+ title = 'Method Not Allowed'
+
+class HTTPNotAcceptable(HTTPClientError):
+ """
+ subclass of :class:`~HTTPClientError`
+
+ This indicates the resource identified by the request is only
+ capable of generating response entities which have content
+ characteristics not acceptable according to the accept headers
+ sent in the request.
+
+ code: 406, title: Not Acceptable
+ """
+ # differences from webob.exc.HTTPNotAcceptable:
+ #
+ # - body_template_obj not overridden (it tried to use request environ's
+ # HTTP_ACCEPT)
+ code = 406
+ title = 'Not Acceptable'
+
+class HTTPProxyAuthenticationRequired(HTTPClientError):
+ """
+ subclass of :class:`~HTTPClientError`
+
+ This is similar to 401, but indicates that the client must first
+ authenticate itself with the proxy.
+
+ code: 407, title: Proxy Authentication Required
+ """
+ code = 407
+ title = 'Proxy Authentication Required'
+ explanation = ('Authentication with a local proxy is needed.')
+
+class HTTPRequestTimeout(HTTPClientError):
+ """
+ subclass of :class:`~HTTPClientError`
+
+ This indicates that the client did not produce a request within
+ the time that the server was prepared to wait.
+
+ code: 408, title: Request Timeout
+ """
+ code = 408
+ title = 'Request Timeout'
+ explanation = ('The server has waited too long for the request to '
+ 'be sent by the client.')
+
+class HTTPConflict(HTTPClientError):
+ """
+ subclass of :class:`~HTTPClientError`
+
+ This indicates that the request could not be completed due to a
+ conflict with the current state of the resource.
+
+ code: 409, title: Conflict
+ """
+ code = 409
+ title = 'Conflict'
+ explanation = ('There was a conflict when trying to complete '
+ 'your request.')
+
+class HTTPGone(HTTPClientError):
+ """
+ subclass of :class:`~HTTPClientError`
+
+ This indicates that the requested resource is no longer available
+ at the server and no forwarding address is known.
+
+ code: 410, title: Gone
+ """
+ code = 410
+ title = 'Gone'
+ explanation = ('This resource is no longer available. No forwarding '
+ 'address is given.')
+
+class HTTPLengthRequired(HTTPClientError):
+ """
+ subclass of :class:`~HTTPClientError`
+
+ This indicates that the the server refuses to accept the request
+ without a defined Content-Length.
+
+ code: 411, title: Length Required
+ """
+ code = 411
+ title = 'Length Required'
+ explanation = ('Content-Length header required.')
+
+class HTTPPreconditionFailed(HTTPClientError):
+ """
+ subclass of :class:`~HTTPClientError`
+
+ This indicates that the precondition given in one or more of the
+ request-header fields evaluated to false when it was tested on the
+ server.
+
+ code: 412, title: Precondition Failed
+ """
+ code = 412
+ title = 'Precondition Failed'
+ explanation = ('Request precondition failed.')
+
+class HTTPRequestEntityTooLarge(HTTPClientError):
+ """
+ subclass of :class:`~HTTPClientError`
+
+ This indicates that the server is refusing to process a request
+ because the request entity is larger than the server is willing or
+ able to process.
+
+ code: 413, title: Request Entity Too Large
+ """
+ code = 413
+ title = 'Request Entity Too Large'
+ explanation = ('The body of your request was too large for this server.')
+
+class HTTPRequestURITooLong(HTTPClientError):
+ """
+ subclass of :class:`~HTTPClientError`
+
+ This indicates that the server is refusing to service the request
+ because the Request-URI is longer than the server is willing to
+ interpret.
+
+ code: 414, title: Request-URI Too Long
+ """
+ code = 414
+ title = 'Request-URI Too Long'
+ explanation = ('The request URI was too long for this server.')
+
+class HTTPUnsupportedMediaType(HTTPClientError):
+ """
+ subclass of :class:`~HTTPClientError`
+
+ This indicates that the server is refusing to service the request
+ because the entity of the request is in a format not supported by
+ the requested resource for the requested method.
+
+ code: 415, title: Unsupported Media Type
+ """
+ # differences from webob.exc.HTTPUnsupportedMediaType:
+ #
+ # - body_template_obj not overridden (it tried to use request environ's
+ # CONTENT_TYPE)
+ code = 415
+ title = 'Unsupported Media Type'
+
+class HTTPRequestRangeNotSatisfiable(HTTPClientError):
+ """
+ subclass of :class:`~HTTPClientError`
+
+ The server SHOULD return a response with this status code if a
+ request included a Range request-header field, and none of the
+ range-specifier values in this field overlap the current extent
+ of the selected resource, and the request did not include an
+ If-Range request-header field.
+
+ code: 416, title: Request Range Not Satisfiable
+ """
+ code = 416
+ title = 'Request Range Not Satisfiable'
+ explanation = ('The Range requested is not available.')
+
+class HTTPExpectationFailed(HTTPClientError):
+ """
+ subclass of :class:`~HTTPClientError`
+
+ This indidcates that the expectation given in an Expect
+ request-header field could not be met by this server.
+
+ code: 417, title: Expectation Failed
+ """
+ code = 417
+ title = 'Expectation Failed'
+ explanation = ('Expectation failed.')
+
+class HTTPUnprocessableEntity(HTTPClientError):
+ """
+ subclass of :class:`~HTTPClientError`
+
+ This indicates that the server is unable to process the contained
+ instructions. Only for WebDAV.
+
+ code: 422, title: Unprocessable Entity
+ """
+ ## Note: from WebDAV
+ code = 422
+ title = 'Unprocessable Entity'
+ explanation = 'Unable to process the contained instructions'
+
+class HTTPLocked(HTTPClientError):
+ """
+ subclass of :class:`~HTTPClientError`
+
+ This indicates that the resource is locked. Only for WebDAV
+
+ code: 423, title: Locked
+ """
+ ## Note: from WebDAV
+ code = 423
+ title = 'Locked'
+ explanation = ('The resource is locked')
+
+class HTTPFailedDependency(HTTPClientError):
+ """
+ subclass of :class:`~HTTPClientError`
+
+ This indicates that the method could not be performed because the
+ requested action depended on another action and that action failed.
+ Only for WebDAV.
+
+ code: 424, title: Failed Dependency
+ """
+ ## Note: from WebDAV
+ code = 424
+ title = 'Failed Dependency'
+ explanation = (
+ 'The method could not be performed because the requested '
+ 'action dependended on another action and that action failed')
+
+############################################################
+## 5xx Server Error
+############################################################
+# Response status codes beginning with the digit "5" indicate cases in
+# which the server is aware that it has erred or is incapable of
+# performing the request. Except when responding to a HEAD request, the
+# server SHOULD include an entity containing an explanation of the error
+# situation, and whether it is a temporary or permanent condition. User
+# agents SHOULD display any included entity to the user. These response
+# codes are applicable to any request method.
+
+class HTTPServerError(HTTPError):
+ """
+ base class for the 500's, where the server is in-error
+
+ This is an error condition in which the server is presumed to be
+ in-error. This is usually unexpected, and thus requires a traceback;
+ ideally, opening a support ticket for the customer. Unless specialized,
+ this is a '500 Internal Server Error'
+ """
+ code = 500
+ title = 'Internal Server Error'
+ explanation = (
+ 'The server has either erred or is incapable of performing '
+ 'the requested operation.')
+
+class HTTPInternalServerError(HTTPServerError):
+ pass
+
+class HTTPNotImplemented(HTTPServerError):
+ """
+ subclass of :class:`~HTTPServerError`
+
+ This indicates that the server does not support the functionality
+ required to fulfill the request.
+
+ code: 501, title: Not Implemented
+ """
+ # differences from webob.exc.HTTPNotAcceptable:
+ #
+ # - body_template_obj not overridden (it tried to use request environ's
+ # REQUEST_METHOD)
+ code = 501
+ title = 'Not Implemented'
+
+class HTTPBadGateway(HTTPServerError):
+ """
+ subclass of :class:`~HTTPServerError`
+
+ This indicates that the server, while acting as a gateway or proxy,
+ received an invalid response from the upstream server it accessed
+ in attempting to fulfill the request.
+
+ code: 502, title: Bad Gateway
+ """
+ code = 502
+ title = 'Bad Gateway'
+ explanation = ('Bad gateway.')
+
+class HTTPServiceUnavailable(HTTPServerError):
+ """
+ subclass of :class:`~HTTPServerError`
+
+ This indicates that the server is currently unable to handle the
+ request due to a temporary overloading or maintenance of the server.
+
+ code: 503, title: Service Unavailable
+ """
+ code = 503
+ title = 'Service Unavailable'
+ explanation = ('The server is currently unavailable. '
+ 'Please try again at a later time.')
+
+class HTTPGatewayTimeout(HTTPServerError):
+ """
+ subclass of :class:`~HTTPServerError`
+
+ This indicates that the server, while acting as a gateway or proxy,
+ did not receive a timely response from the upstream server specified
+ by the URI (e.g. HTTP, FTP, LDAP) or some other auxiliary server
+ (e.g. DNS) it needed to access in attempting to complete the request.
+
+ code: 504, title: Gateway Timeout
+ """
+ code = 504
+ title = 'Gateway Timeout'
+ explanation = ('The gateway has timed out.')
+
+class HTTPVersionNotSupported(HTTPServerError):
+ """
+ subclass of :class:`~HTTPServerError`
+
+ This indicates that the server does not support, or refuses to
+ support, the HTTP protocol version that was used in the request
+ message.
+
+ code: 505, title: HTTP Version Not Supported
+ """
+ code = 505
+ title = 'HTTP Version Not Supported'
+ explanation = ('The HTTP version is not supported.')
+
+class HTTPInsufficientStorage(HTTPServerError):
+ """
+ subclass of :class:`~HTTPServerError`
+
+ This indicates that the server does not have enough space to save
+ the resource.
+
+ code: 507, title: Insufficient Storage
+ """
+ code = 507
+ title = 'Insufficient Storage'
+ explanation = ('There was not enough space to save the resource')
+
+NotFound = HTTPNotFound # bw compat
+Forbidden = HTTPForbidden # bw compat
+
+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):
+ """
+ This exception is raised when :app:`Pyramid` cannot
+ successfully decode a URL or a URL path segment. This exception
+ it behaves just like the Python builtin
+ :exc:`UnicodeDecodeError`. It is a subclass of the builtin
+ :exc:`UnicodeDecodeError` exception only for identity purposes,
+ mostly so an exception view can be registered when a URL cannot be
+ decoded.
+ """
+
+class ConfigurationError(ZCE):
+ """ Raised when inappropriate input values are supplied to an API
+ method of a :term:`Configurator`"""
+
+
+def abort(status_code, **kw):
+ """Aborts the request immediately by raising an HTTP exception based on a
+ status code. Example::
+
+ abort(404) # raises an HTTPNotFound exception.
+
+ The values passed as ``kw`` are provided to the exception's constructor.
+ """
+ exc = status_map[status_code](**kw)
+ raise exc
+
+
+def redirect(url, code=302, **kw):
+ """Raises an :class:`~HTTPFound` (302) redirect exception to the
+ URL specified by ``url``.
+
+ Optionally, a code variable may be passed with the status code of
+ the redirect, ie::
+
+ redirect(route_url('foo', request), code=303)
+
+ The values passed as ``kw`` are provided to the exception constructor.
+
+ """
+ exc = status_map[code]
+ raise exc(location=url, **kw)
+
+def default_exceptionresponse_view(context, request):
+ if not isinstance(context, Exception):
+ # backwards compat for an exception response view registered via
+ # config.set_notfound_view or config.set_forbidden_view
+ # instead of as a proper exception view
+ context = request.exception or context
+ return context
+
+status_map={}
+for name, value in globals().items():
+ if (isinstance(value, (type, types.ClassType)) and
+ issubclass(value, HTTPException)
+ and not name.startswith('_')):
+ code = getattr(value, 'code', None)
+ if code:
+ status_map[code] = value
+del name, value
+
diff --git a/pyramid/router.py b/pyramid/router.py
index b8a8639aa..9cd682623 100644
--- a/pyramid/router.py
+++ b/pyramid/router.py
@@ -16,7 +16,7 @@ from pyramid.interfaces import IViewClassifier
from pyramid.events import ContextFound
from pyramid.events import NewRequest
from pyramid.events import NewResponse
-from pyramid.exceptions import NotFound
+from pyramid.response import HTTPNotFound
from pyramid.request import Request
from pyramid.threadlocal import manager
from pyramid.traversal import DefaultRootFactory
@@ -153,7 +153,7 @@ class Router(object):
logger and logger.debug(msg)
else:
msg = request.path_info
- raise NotFound(msg)
+ raise HTTPNotFound(msg)
else:
response = view_callable(context, request)
diff --git a/pyramid/testing.py b/pyramid/testing.py
index a512ede4b..4d7dd252a 100644
--- a/pyramid/testing.py
+++ b/pyramid/testing.py
@@ -17,7 +17,7 @@ from pyramid.interfaces import ISession
from pyramid.config import Configurator
from pyramid.decorator import reify
-from pyramid.exceptions import Forbidden
+from pyramid.response import HTTPForbidden
from pyramid.response import Response
from pyramid.registry import Registry
from pyramid.security import Authenticated
@@ -217,7 +217,7 @@ def registerView(name, result='', view=None, for_=(Interface, Interface),
else:
def _secure(context, request):
if not has_permission(permission, context, request):
- raise Forbidden('no permission').exception
+ raise HTTPForbidden('no permission')
else:
return view(context, request)
_secure.__call_permissive__ = view
diff --git a/pyramid/tests/fixtureapp/views.py b/pyramid/tests/fixtureapp/views.py
index 9ab985e32..3125c972f 100644
--- a/pyramid/tests/fixtureapp/views.py
+++ b/pyramid/tests/fixtureapp/views.py
@@ -1,6 +1,6 @@
from zope.interface import Interface
from webob import Response
-from pyramid.exceptions import Forbidden
+from pyramid.response import HTTPForbidden
def fixture_view(context, request):
""" """
@@ -16,7 +16,7 @@ def exception_view(context, request):
def protected_view(context, request):
""" """
- raise Forbidden()
+ raise HTTPForbidden()
class IDummy(Interface):
pass
diff --git a/pyramid/tests/forbiddenapp/__init__.py b/pyramid/tests/forbiddenapp/__init__.py
index 614aff037..9ad2dc801 100644
--- a/pyramid/tests/forbiddenapp/__init__.py
+++ b/pyramid/tests/forbiddenapp/__init__.py
@@ -1,5 +1,5 @@
from webob import Response
-from pyramid.exceptions import Forbidden
+from pyramid.response import HTTPForbidden
def x_view(request): # pragma: no cover
return Response('this is private!')
@@ -8,7 +8,7 @@ def forbidden_view(context, request):
msg = context.message
result = context.result
message = msg + '\n' + str(result)
- resp = Forbidden()
+ resp = HTTPForbidden()
resp.body = message
return resp
@@ -20,4 +20,4 @@ def includeme(config):
config._set_authentication_policy(authn_policy)
config._set_authorization_policy(authz_policy)
config.add_view(x_view, name='x', permission='private')
- config.add_view(forbidden_view, context=Forbidden)
+ config.add_view(forbidden_view, context=HTTPForbidden)
diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py
index 7c6389253..6817c5936 100644
--- a/pyramid/tests/test_config.py
+++ b/pyramid/tests/test_config.py
@@ -50,8 +50,8 @@ class ConfiguratorTests(unittest.TestCase):
return iface
def _assertNotFound(self, wrapper, *arg):
- from pyramid.exceptions import NotFound
- self.assertRaises(NotFound, wrapper, *arg)
+ from pyramid.response import HTTPNotFound
+ self.assertRaises(HTTPNotFound, wrapper, *arg)
def _registerEventListener(self, config, event_iface=None):
if event_iface is None: # pragma: no cover
@@ -205,7 +205,7 @@ class ConfiguratorTests(unittest.TestCase):
def test_ctor_httpexception_view_default(self):
from pyramid.interfaces import IExceptionResponse
- from pyramid.exceptions import default_exceptionresponse_view
+ from pyramid.response import default_exceptionresponse_view
from pyramid.interfaces import IRequest
config = self._makeOne()
view = self._getViewCallable(config,
@@ -321,16 +321,17 @@ class ConfiguratorTests(unittest.TestCase):
def test_setup_registry_explicit_notfound_trumps_iexceptionresponse(self):
from zope.interface import implementedBy
from pyramid.interfaces import IRequest
- from pyramid.exceptions import NotFound
+ from pyramid.response import HTTPNotFound
from pyramid.registry import Registry
reg = Registry()
config = self._makeOne(reg, autocommit=True)
config.setup_registry() # registers IExceptionResponse default view
def myview(context, request):
return 'OK'
- config.add_view(myview, context=NotFound)
+ config.add_view(myview, context=HTTPNotFound)
request = self._makeRequest(config)
- view = self._getViewCallable(config, ctx_iface=implementedBy(NotFound),
+ view = self._getViewCallable(config,
+ ctx_iface=implementedBy(HTTPNotFound),
request_iface=IRequest)
result = view(None, request)
self.assertEqual(result, 'OK')
@@ -1694,14 +1695,14 @@ class ConfiguratorTests(unittest.TestCase):
self._assertNotFound(wrapper, None, request)
def test_add_view_with_header_val_missing(self):
- from pyramid.exceptions import NotFound
+ from pyramid.response import HTTPNotFound
view = lambda *arg: 'OK'
config = self._makeOne(autocommit=True)
config.add_view(view=view, header=r'Host:\d')
wrapper = self._getViewCallable(config)
request = self._makeRequest(config)
request.headers = {'NoHost':'1'}
- self.assertRaises(NotFound, wrapper, None, request)
+ self.assertRaises(HTTPNotFound, wrapper, None, request)
def test_add_view_with_accept_match(self):
view = lambda *arg: 'OK'
@@ -2228,12 +2229,13 @@ class ConfiguratorTests(unittest.TestCase):
def test_set_notfound_view(self):
from zope.interface import implementedBy
from pyramid.interfaces import IRequest
- from pyramid.exceptions import NotFound
+ from pyramid.response import HTTPNotFound
config = self._makeOne(autocommit=True)
view = lambda *arg: arg
config.set_notfound_view(view)
request = self._makeRequest(config)
- view = self._getViewCallable(config, ctx_iface=implementedBy(NotFound),
+ view = self._getViewCallable(config,
+ ctx_iface=implementedBy(HTTPNotFound),
request_iface=IRequest)
result = view(None, request)
self.assertEqual(result, (None, request))
@@ -2241,13 +2243,14 @@ class ConfiguratorTests(unittest.TestCase):
def test_set_notfound_view_request_has_context(self):
from zope.interface import implementedBy
from pyramid.interfaces import IRequest
- from pyramid.exceptions import NotFound
+ from pyramid.response import HTTPNotFound
config = self._makeOne(autocommit=True)
view = lambda *arg: arg
config.set_notfound_view(view)
request = self._makeRequest(config)
request.context = 'abc'
- view = self._getViewCallable(config, ctx_iface=implementedBy(NotFound),
+ view = self._getViewCallable(config,
+ ctx_iface=implementedBy(HTTPNotFound),
request_iface=IRequest)
result = view(None, request)
self.assertEqual(result, ('abc', request))
@@ -2256,7 +2259,7 @@ class ConfiguratorTests(unittest.TestCase):
def test_set_notfound_view_with_renderer(self):
from zope.interface import implementedBy
from pyramid.interfaces import IRequest
- from pyramid.exceptions import NotFound
+ from pyramid.response import HTTPNotFound
config = self._makeOne(autocommit=True)
view = lambda *arg: {}
config.set_notfound_view(view,
@@ -2265,7 +2268,7 @@ class ConfiguratorTests(unittest.TestCase):
try: # chameleon depends on being able to find a threadlocal registry
request = self._makeRequest(config)
view = self._getViewCallable(config,
- ctx_iface=implementedBy(NotFound),
+ ctx_iface=implementedBy(HTTPNotFound),
request_iface=IRequest)
result = view(None, request)
finally:
@@ -2275,7 +2278,7 @@ class ConfiguratorTests(unittest.TestCase):
def test_set_forbidden_view(self):
from zope.interface import implementedBy
from pyramid.interfaces import IRequest
- from pyramid.exceptions import Forbidden
+ from pyramid.response import Forbidden
config = self._makeOne(autocommit=True)
view = lambda *arg: 'OK'
config.set_forbidden_view(view)
@@ -2288,7 +2291,7 @@ class ConfiguratorTests(unittest.TestCase):
def test_set_forbidden_view_request_has_context(self):
from zope.interface import implementedBy
from pyramid.interfaces import IRequest
- from pyramid.exceptions import Forbidden
+ from pyramid.response import Forbidden
config = self._makeOne(autocommit=True)
view = lambda *arg: arg
config.set_forbidden_view(view)
@@ -2303,7 +2306,7 @@ class ConfiguratorTests(unittest.TestCase):
def test_set_forbidden_view_with_renderer(self):
from zope.interface import implementedBy
from pyramid.interfaces import IRequest
- from pyramid.exceptions import Forbidden
+ from pyramid.response import Forbidden
config = self._makeOne(autocommit=True)
view = lambda *arg: {}
config.set_forbidden_view(view,
@@ -3682,7 +3685,7 @@ class TestViewDeriver(unittest.TestCase):
"None against context None): True")
def test_debug_auth_permission_authpol_denied(self):
- from pyramid.exceptions import Forbidden
+ from pyramid.response import Forbidden
view = lambda *arg: 'OK'
self.config.registry.settings = dict(
debug_authorization=True, reload_templates=True)
@@ -3810,7 +3813,7 @@ class TestViewDeriver(unittest.TestCase):
self.assertEqual(predicates, [True, True])
def test_with_predicates_notall(self):
- from pyramid.exceptions import NotFound
+ from pyramid.response import HTTPNotFound
view = lambda *arg: 'OK'
predicates = []
def predicate1(context, request):
@@ -3823,7 +3826,7 @@ class TestViewDeriver(unittest.TestCase):
result = deriver(view)
request = self._makeRequest()
request.method = 'POST'
- self.assertRaises(NotFound, result, None, None)
+ self.assertRaises(HTTPNotFound, result, None, None)
self.assertEqual(predicates, [True, True])
def test_with_wrapper_viewname(self):
@@ -4618,14 +4621,14 @@ class TestMultiView(unittest.TestCase):
self.assertEqual(mv.get_views(request), mv.views)
def test_match_not_found(self):
- from pyramid.exceptions import NotFound
+ from pyramid.response import HTTPNotFound
mv = self._makeOne()
context = DummyContext()
request = DummyRequest()
- self.assertRaises(NotFound, mv.match, context, request)
+ self.assertRaises(HTTPNotFound, mv.match, context, request)
def test_match_predicate_fails(self):
- from pyramid.exceptions import NotFound
+ from pyramid.response import HTTPNotFound
mv = self._makeOne()
def view(context, request):
""" """
@@ -4633,7 +4636,7 @@ class TestMultiView(unittest.TestCase):
mv.views = [(100, view, None)]
context = DummyContext()
request = DummyRequest()
- self.assertRaises(NotFound, mv.match, context, request)
+ self.assertRaises(HTTPNotFound, mv.match, context, request)
def test_match_predicate_succeeds(self):
mv = self._makeOne()
@@ -4647,11 +4650,11 @@ class TestMultiView(unittest.TestCase):
self.assertEqual(result, view)
def test_permitted_no_views(self):
- from pyramid.exceptions import NotFound
+ from pyramid.response import HTTPNotFound
mv = self._makeOne()
context = DummyContext()
request = DummyRequest()
- self.assertRaises(NotFound, mv.__permitted__, context, request)
+ self.assertRaises(HTTPNotFound, mv.__permitted__, context, request)
def test_permitted_no_match_with__permitted__(self):
mv = self._makeOne()
@@ -4674,11 +4677,11 @@ class TestMultiView(unittest.TestCase):
self.assertEqual(result, False)
def test__call__not_found(self):
- from pyramid.exceptions import NotFound
+ from pyramid.response import HTTPNotFound
mv = self._makeOne()
context = DummyContext()
request = DummyRequest()
- self.assertRaises(NotFound, mv, context, request)
+ self.assertRaises(HTTPNotFound, mv, context, request)
def test___call__intermediate_not_found(self):
from pyramid.exceptions import PredicateMismatch
@@ -4696,17 +4699,17 @@ class TestMultiView(unittest.TestCase):
self.assertEqual(response, expected_response)
def test___call__raise_not_found_isnt_interpreted_as_pred_mismatch(self):
- from pyramid.exceptions import NotFound
+ from pyramid.response import HTTPNotFound
mv = self._makeOne()
context = DummyContext()
request = DummyRequest()
request.view_name = ''
def view1(context, request):
- raise NotFound
+ raise HTTPNotFound
def view2(context, request):
""" """
mv.views = [(100, view1, None), (99, view2, None)]
- self.assertRaises(NotFound, mv, context, request)
+ self.assertRaises(HTTPNotFound, mv, context, request)
def test___call__(self):
mv = self._makeOne()
@@ -4721,11 +4724,11 @@ class TestMultiView(unittest.TestCase):
self.assertEqual(response, expected_response)
def test__call_permissive__not_found(self):
- from pyramid.exceptions import NotFound
+ from pyramid.response import HTTPNotFound
mv = self._makeOne()
context = DummyContext()
request = DummyRequest()
- self.assertRaises(NotFound, mv, context, request)
+ self.assertRaises(HTTPNotFound, mv, context, request)
def test___call_permissive_has_call_permissive(self):
mv = self._makeOne()
diff --git a/pyramid/tests/test_exceptions.py b/pyramid/tests/test_exceptions.py
index f2e577416..673fb6712 100644
--- a/pyramid/tests/test_exceptions.py
+++ b/pyramid/tests/test_exceptions.py
@@ -12,6 +12,11 @@ class TestNotFound(unittest.TestCase):
self.assertEqual(e.status, '404 Not Found')
self.assertEqual(e.message, 'notfound')
+ def test_response_equivalence(self):
+ from pyramid.exceptions import NotFound
+ from pyramid.response import HTTPNotFound
+ self.assertTrue(NotFound is HTTPNotFound)
+
class TestForbidden(unittest.TestCase):
def _makeOne(self, message):
from pyramid.exceptions import Forbidden
@@ -24,294 +29,8 @@ class TestForbidden(unittest.TestCase):
self.assertEqual(e.status, '403 Forbidden')
self.assertEqual(e.message, 'forbidden')
-class Test_abort(unittest.TestCase):
- def _callFUT(self, *arg, **kw):
- from pyramid.exceptions import abort
- return abort(*arg, **kw)
-
- def test_status_404(self):
- from pyramid.exceptions import HTTPNotFound
- self.assertRaises(HTTPNotFound, self._callFUT, 404)
-
- def test_status_201(self):
- from pyramid.exceptions import HTTPCreated
- self.assertRaises(HTTPCreated, self._callFUT, 201)
-
- def test_extra_kw(self):
- from pyramid.exceptions import HTTPNotFound
- try:
- self._callFUT(404, headers=[('abc', 'def')])
- except HTTPNotFound, exc:
- self.assertEqual(exc.headers['abc'], 'def')
- else: # pragma: no cover
- raise AssertionError
-
-class Test_redirect(unittest.TestCase):
- def _callFUT(self, *arg, **kw):
- from pyramid.exceptions import redirect
- return redirect(*arg, **kw)
-
- def test_default(self):
- from pyramid.exceptions import HTTPFound
- try:
- self._callFUT('http://example.com')
- except HTTPFound, exc:
- self.assertEqual(exc.location, 'http://example.com')
- self.assertEqual(exc.status, '302 Found')
-
- def test_custom_code(self):
- from pyramid.exceptions import HTTPMovedPermanently
- try:
- self._callFUT('http://example.com', 301)
- except HTTPMovedPermanently, exc:
- self.assertEqual(exc.location, 'http://example.com')
- self.assertEqual(exc.status, '301 Moved Permanently')
-
- def test_extra_kw(self):
- from pyramid.exceptions import HTTPFound
- try:
- self._callFUT('http://example.com', headers=[('abc', 'def')])
- except HTTPFound, exc:
- self.assertEqual(exc.location, 'http://example.com')
- self.assertEqual(exc.status, '302 Found')
- self.assertEqual(exc.headers['abc'], 'def')
-
-
-class Test_default_exceptionresponse_view(unittest.TestCase):
- def _callFUT(self, context, request):
- from pyramid.exceptions import default_exceptionresponse_view
- return default_exceptionresponse_view(context, request)
-
- def test_call_with_exception(self):
- context = Exception()
- result = self._callFUT(context, None)
- self.assertEqual(result, context)
-
- def test_call_with_nonexception(self):
- request = DummyRequest()
- context = Exception()
- request.exception = context
- result = self._callFUT(None, request)
- self.assertEqual(result, context)
-
-class Test__no_escape(unittest.TestCase):
- def _callFUT(self, val):
- from pyramid.exceptions import _no_escape
- return _no_escape(val)
-
- def test_null(self):
- self.assertEqual(self._callFUT(None), '')
-
- def test_not_basestring(self):
- self.assertEqual(self._callFUT(42), '42')
-
- def test_unicode(self):
- class DummyUnicodeObject(object):
- def __unicode__(self):
- return u'42'
- duo = DummyUnicodeObject()
- self.assertEqual(self._callFUT(duo), u'42')
-
-class TestWSGIHTTPException(unittest.TestCase):
- def _getTargetClass(self):
- from pyramid.exceptions import WSGIHTTPException
- return WSGIHTTPException
-
- def _getTargetSubclass(self, code='200', title='OK',
- explanation='explanation', empty_body=False):
- cls = self._getTargetClass()
- class Subclass(cls):
- pass
- Subclass.empty_body = empty_body
- Subclass.code = code
- Subclass.title = title
- Subclass.explanation = explanation
- return Subclass
-
- def _makeOne(self, *arg, **kw):
- cls = self._getTargetClass()
- return cls(*arg, **kw)
-
- def test_ctor_sets_detail(self):
- exc = self._makeOne('message')
- self.assertEqual(exc.detail, 'message')
-
- def test_ctor_sets_comment(self):
- exc = self._makeOne(comment='comment')
- self.assertEqual(exc.comment, 'comment')
-
- def test_ctor_calls_Exception_ctor(self):
- exc = self._makeOne('message')
- self.assertEqual(exc.message, 'message')
-
- def test_ctor_calls_Response_ctor(self):
- exc = self._makeOne('message')
- self.assertEqual(exc.status, 'None None')
-
- def test_ctor_extends_headers(self):
- exc = self._makeOne(headers=[('X-Foo', 'foo')])
- self.assertEqual(exc.headers.get('X-Foo'), 'foo')
-
- def test_ctor_sets_body_template_obj(self):
- exc = self._makeOne(body_template='${foo}')
- self.assertEqual(
- exc.body_template_obj.substitute({'foo':'foo'}), 'foo')
-
- def test_ctor_with_empty_body(self):
- cls = self._getTargetSubclass(empty_body=True)
- exc = cls()
- self.assertEqual(exc.content_type, None)
- self.assertEqual(exc.content_length, None)
-
- def test_ctor_with_body_doesnt_set_default_app_iter(self):
- exc = self._makeOne(body='123')
- self.assertEqual(exc.app_iter, ['123'])
-
- def test_ctor_with_unicode_body_doesnt_set_default_app_iter(self):
- exc = self._makeOne(unicode_body=u'123')
- self.assertEqual(exc.app_iter, ['123'])
-
- def test_ctor_with_app_iter_doesnt_set_default_app_iter(self):
- exc = self._makeOne(app_iter=['123'])
- self.assertEqual(exc.app_iter, ['123'])
-
- def test_ctor_with_body_sets_default_app_iter_html(self):
- cls = self._getTargetSubclass()
- exc = cls('detail')
- body = list(exc.app_iter)[0]
- self.assertTrue(body.startswith('<html'))
- self.assertTrue('200 OK' in body)
- self.assertTrue('explanation' in body)
- self.assertTrue('detail' in body)
-
- def test_ctor_with_body_sets_default_app_iter_text(self):
- cls = self._getTargetSubclass()
- exc = cls('detail')
- exc.content_type = 'text/plain'
- body = list(exc.app_iter)[0]
- self.assertEqual(body, '200 OK\n\nexplanation\n\n\ndetail\n\n')
-
- def test__str__detail(self):
- exc = self._makeOne()
- exc.detail = 'abc'
- self.assertEqual(str(exc), 'abc')
-
- def test__str__explanation(self):
- exc = self._makeOne()
- exc.explanation = 'def'
- self.assertEqual(str(exc), 'def')
-
- def test_wsgi_response(self):
- exc = self._makeOne()
- self.assertTrue(exc is exc.wsgi_response)
-
- def test_exception(self):
- exc = self._makeOne()
- self.assertTrue(exc is exc.exception)
-
- def test__default_app_iter_no_comment_plain(self):
- cls = self._getTargetSubclass()
- exc = cls()
- exc.content_type = 'text/plain'
- body = list(exc._default_app_iter())[0]
- self.assertEqual(body, '200 OK\n\nexplanation\n\n\n\n\n')
-
- def test__default_app_iter_with_comment_plain(self):
- cls = self._getTargetSubclass()
- exc = cls(comment='comment')
- exc.content_type = 'text/plain'
- body = list(exc._default_app_iter())[0]
- self.assertEqual(body, '200 OK\n\nexplanation\n\n\n\ncomment\n')
-
- def test__default_app_iter_no_comment_html(self):
- cls = self._getTargetSubclass()
- exc = cls()
- exc.content_type = 'text/html'
- body = list(exc._default_app_iter())[0]
- self.assertFalse('<!-- ' in body)
-
- def test__default_app_iter_with_comment_html(self):
- cls = self._getTargetSubclass()
- exc = cls(comment='comment & comment')
- exc.content_type = 'text/html'
- body = list(exc._default_app_iter())[0]
- self.assertTrue('<!-- comment &amp; comment -->' in body)
-
- def test_custom_body_template_no_environ(self):
- cls = self._getTargetSubclass()
- exc = cls(body_template='${location}', location='foo')
- exc.content_type = 'text/plain'
- body = list(exc._default_app_iter())[0]
- self.assertEqual(body, '200 OK\n\nfoo')
-
- def test_custom_body_template_with_environ(self):
- cls = self._getTargetSubclass()
- from pyramid.request import Request
- request = Request.blank('/')
- exc = cls(body_template='${REQUEST_METHOD}', request=request)
- exc.content_type = 'text/plain'
- body = list(exc._default_app_iter())[0]
- self.assertEqual(body, '200 OK\n\nGET')
-
- def test_body_template_unicode(self):
- from pyramid.request import Request
- cls = self._getTargetSubclass()
- la = unicode('/La Pe\xc3\xb1a', 'utf-8')
- request = Request.blank('/')
- request.environ['unicodeval'] = la
- exc = cls(body_template='${unicodeval}', request=request)
- exc.content_type = 'text/plain'
- body = list(exc._default_app_iter())[0]
- self.assertEqual(body, '200 OK\n\n/La Pe\xc3\xb1a')
-
-class TestRenderAllExceptionsWithoutArguments(unittest.TestCase):
- def _doit(self, content_type):
- from pyramid.exceptions import status_map
- L = []
- self.assertTrue(status_map)
- for v in status_map.values():
- exc = v()
- exc.content_type = content_type
- result = list(exc.app_iter)[0]
- if exc.empty_body:
- self.assertEqual(result, '')
- else:
- self.assertTrue(exc.status in result)
- L.append(result)
- self.assertEqual(len(L), len(status_map))
-
- def test_it_plain(self):
- self._doit('text/plain')
-
- def test_it_html(self):
- self._doit('text/html')
-
-class Test_HTTPMove(unittest.TestCase):
- def _makeOne(self, *arg, **kw):
- from pyramid.exceptions import _HTTPMove
- return _HTTPMove(*arg, **kw)
-
- def test_it_location_not_passed(self):
- exc = self._makeOne()
- self.assertEqual(exc.location, '')
-
- def test_it_location_passed(self):
- exc = self._makeOne(location='foo')
- self.assertEqual(exc.location, 'foo')
-
-class TestHTTPForbidden(unittest.TestCase):
- def _makeOne(self, *arg, **kw):
- from pyramid.exceptions import HTTPForbidden
- return HTTPForbidden(*arg, **kw)
-
- def test_it_result_not_passed(self):
- exc = self._makeOne()
- self.assertEqual(exc.result, None)
-
- def test_it_result_passed(self):
- exc = self._makeOne(result='foo')
- self.assertEqual(exc.result, 'foo')
-
-class DummyRequest(object):
- exception = None
+ def test_response_equivalence(self):
+ from pyramid.exceptions import Forbidden
+ from pyramid.response import HTTPForbidden
+ self.assertTrue(Forbidden is HTTPForbidden)
diff --git a/pyramid/tests/test_httpexceptions.py b/pyramid/tests/test_httpexceptions.py
index afa5a94de..28adc9d3d 100644
--- a/pyramid/tests/test_httpexceptions.py
+++ b/pyramid/tests/test_httpexceptions.py
@@ -3,7 +3,7 @@ import unittest
class TestIt(unittest.TestCase):
def test_bwcompat_imports(self):
from pyramid.httpexceptions import HTTPNotFound as one
- from pyramid.exceptions import HTTPNotFound as two
+ from pyramid.response import HTTPNotFound as two
self.assertTrue(one is two)
diff --git a/pyramid/tests/test_response.py b/pyramid/tests/test_response.py
new file mode 100644
index 000000000..6cc87fc0a
--- /dev/null
+++ b/pyramid/tests/test_response.py
@@ -0,0 +1,308 @@
+import unittest
+
+class TestResponse(unittest.TestCase):
+ def _getTargetClass(self):
+ from pyramid.response import Response
+ return Response
+
+ def test_implements_IExceptionResponse(self):
+ from pyramid.interfaces import IExceptionResponse
+ Response = self._getTargetClass()
+ self.failUnless(IExceptionResponse.implementedBy(Response))
+
+ def test_provides_IExceptionResponse(self):
+ from pyramid.interfaces import IExceptionResponse
+ response = self._getTargetClass()()
+ self.failUnless(IExceptionResponse.providedBy(response))
+
+class Test_abort(unittest.TestCase):
+ def _callFUT(self, *arg, **kw):
+ from pyramid.response import abort
+ return abort(*arg, **kw)
+
+ def test_status_404(self):
+ from pyramid.response import HTTPNotFound
+ self.assertRaises(HTTPNotFound, self._callFUT, 404)
+
+ def test_status_201(self):
+ from pyramid.response import HTTPCreated
+ self.assertRaises(HTTPCreated, self._callFUT, 201)
+
+ def test_extra_kw(self):
+ from pyramid.response import HTTPNotFound
+ try:
+ self._callFUT(404, headers=[('abc', 'def')])
+ except HTTPNotFound, exc:
+ self.assertEqual(exc.headers['abc'], 'def')
+ else: # pragma: no cover
+ raise AssertionError
+
+class Test_redirect(unittest.TestCase):
+ def _callFUT(self, *arg, **kw):
+ from pyramid.response import redirect
+ return redirect(*arg, **kw)
+
+ def test_default(self):
+ from pyramid.response import HTTPFound
+ try:
+ self._callFUT('http://example.com')
+ except HTTPFound, exc:
+ self.assertEqual(exc.location, 'http://example.com')
+ self.assertEqual(exc.status, '302 Found')
+
+ def test_custom_code(self):
+ from pyramid.response import HTTPMovedPermanently
+ try:
+ self._callFUT('http://example.com', 301)
+ except HTTPMovedPermanently, exc:
+ self.assertEqual(exc.location, 'http://example.com')
+ self.assertEqual(exc.status, '301 Moved Permanently')
+
+ def test_extra_kw(self):
+ from pyramid.response import HTTPFound
+ try:
+ self._callFUT('http://example.com', headers=[('abc', 'def')])
+ except HTTPFound, exc:
+ self.assertEqual(exc.location, 'http://example.com')
+ self.assertEqual(exc.status, '302 Found')
+ self.assertEqual(exc.headers['abc'], 'def')
+
+
+class Test_default_exceptionresponse_view(unittest.TestCase):
+ def _callFUT(self, context, request):
+ from pyramid.response import default_exceptionresponse_view
+ return default_exceptionresponse_view(context, request)
+
+ def test_call_with_exception(self):
+ context = Exception()
+ result = self._callFUT(context, None)
+ self.assertEqual(result, context)
+
+ def test_call_with_nonexception(self):
+ request = DummyRequest()
+ context = Exception()
+ request.exception = context
+ result = self._callFUT(None, request)
+ self.assertEqual(result, context)
+
+class Test__no_escape(unittest.TestCase):
+ def _callFUT(self, val):
+ from pyramid.response import _no_escape
+ return _no_escape(val)
+
+ def test_null(self):
+ self.assertEqual(self._callFUT(None), '')
+
+ def test_not_basestring(self):
+ self.assertEqual(self._callFUT(42), '42')
+
+ def test_unicode(self):
+ class DummyUnicodeObject(object):
+ def __unicode__(self):
+ return u'42'
+ duo = DummyUnicodeObject()
+ self.assertEqual(self._callFUT(duo), u'42')
+
+class TestWSGIHTTPException(unittest.TestCase):
+ def _getTargetClass(self):
+ from pyramid.response import WSGIHTTPException
+ return WSGIHTTPException
+
+ def _getTargetSubclass(self, code='200', title='OK',
+ explanation='explanation', empty_body=False):
+ cls = self._getTargetClass()
+ class Subclass(cls):
+ pass
+ Subclass.empty_body = empty_body
+ Subclass.code = code
+ Subclass.title = title
+ Subclass.explanation = explanation
+ return Subclass
+
+ def _makeOne(self, *arg, **kw):
+ cls = self._getTargetClass()
+ return cls(*arg, **kw)
+
+ def test_ctor_sets_detail(self):
+ exc = self._makeOne('message')
+ self.assertEqual(exc.detail, 'message')
+
+ def test_ctor_sets_comment(self):
+ exc = self._makeOne(comment='comment')
+ self.assertEqual(exc.comment, 'comment')
+
+ def test_ctor_calls_Exception_ctor(self):
+ exc = self._makeOne('message')
+ self.assertEqual(exc.message, 'message')
+
+ def test_ctor_calls_Response_ctor(self):
+ exc = self._makeOne('message')
+ self.assertEqual(exc.status, 'None None')
+
+ def test_ctor_extends_headers(self):
+ exc = self._makeOne(headers=[('X-Foo', 'foo')])
+ self.assertEqual(exc.headers.get('X-Foo'), 'foo')
+
+ def test_ctor_sets_body_template_obj(self):
+ exc = self._makeOne(body_template='${foo}')
+ self.assertEqual(
+ exc.body_template_obj.substitute({'foo':'foo'}), 'foo')
+
+ def test_ctor_with_empty_body(self):
+ cls = self._getTargetSubclass(empty_body=True)
+ exc = cls()
+ self.assertEqual(exc.content_type, None)
+ self.assertEqual(exc.content_length, None)
+
+ def test_ctor_with_body_doesnt_set_default_app_iter(self):
+ exc = self._makeOne(body='123')
+ self.assertEqual(exc.app_iter, ['123'])
+
+ def test_ctor_with_unicode_body_doesnt_set_default_app_iter(self):
+ exc = self._makeOne(unicode_body=u'123')
+ self.assertEqual(exc.app_iter, ['123'])
+
+ def test_ctor_with_app_iter_doesnt_set_default_app_iter(self):
+ exc = self._makeOne(app_iter=['123'])
+ self.assertEqual(exc.app_iter, ['123'])
+
+ def test_ctor_with_body_sets_default_app_iter_html(self):
+ cls = self._getTargetSubclass()
+ exc = cls('detail')
+ body = list(exc.app_iter)[0]
+ self.assertTrue(body.startswith('<html'))
+ self.assertTrue('200 OK' in body)
+ self.assertTrue('explanation' in body)
+ self.assertTrue('detail' in body)
+
+ def test_ctor_with_body_sets_default_app_iter_text(self):
+ cls = self._getTargetSubclass()
+ exc = cls('detail')
+ exc.content_type = 'text/plain'
+ body = list(exc.app_iter)[0]
+ self.assertEqual(body, '200 OK\n\nexplanation\n\n\ndetail\n\n')
+
+ def test__str__detail(self):
+ exc = self._makeOne()
+ exc.detail = 'abc'
+ self.assertEqual(str(exc), 'abc')
+
+ def test__str__explanation(self):
+ exc = self._makeOne()
+ exc.explanation = 'def'
+ self.assertEqual(str(exc), 'def')
+
+ def test_wsgi_response(self):
+ exc = self._makeOne()
+ self.assertTrue(exc is exc.wsgi_response)
+
+ def test_exception(self):
+ exc = self._makeOne()
+ self.assertTrue(exc is exc.exception)
+
+ def test__default_app_iter_no_comment_plain(self):
+ cls = self._getTargetSubclass()
+ exc = cls()
+ exc.content_type = 'text/plain'
+ body = list(exc._default_app_iter())[0]
+ self.assertEqual(body, '200 OK\n\nexplanation\n\n\n\n\n')
+
+ def test__default_app_iter_with_comment_plain(self):
+ cls = self._getTargetSubclass()
+ exc = cls(comment='comment')
+ exc.content_type = 'text/plain'
+ body = list(exc._default_app_iter())[0]
+ self.assertEqual(body, '200 OK\n\nexplanation\n\n\n\ncomment\n')
+
+ def test__default_app_iter_no_comment_html(self):
+ cls = self._getTargetSubclass()
+ exc = cls()
+ exc.content_type = 'text/html'
+ body = list(exc._default_app_iter())[0]
+ self.assertFalse('<!-- ' in body)
+
+ def test__default_app_iter_with_comment_html(self):
+ cls = self._getTargetSubclass()
+ exc = cls(comment='comment & comment')
+ exc.content_type = 'text/html'
+ body = list(exc._default_app_iter())[0]
+ self.assertTrue('<!-- comment &amp; comment -->' in body)
+
+ def test_custom_body_template_no_environ(self):
+ cls = self._getTargetSubclass()
+ exc = cls(body_template='${location}', location='foo')
+ exc.content_type = 'text/plain'
+ body = list(exc._default_app_iter())[0]
+ self.assertEqual(body, '200 OK\n\nfoo')
+
+ def test_custom_body_template_with_environ(self):
+ cls = self._getTargetSubclass()
+ from pyramid.request import Request
+ request = Request.blank('/')
+ exc = cls(body_template='${REQUEST_METHOD}', request=request)
+ exc.content_type = 'text/plain'
+ body = list(exc._default_app_iter())[0]
+ self.assertEqual(body, '200 OK\n\nGET')
+
+ def test_body_template_unicode(self):
+ from pyramid.request import Request
+ cls = self._getTargetSubclass()
+ la = unicode('/La Pe\xc3\xb1a', 'utf-8')
+ request = Request.blank('/')
+ request.environ['unicodeval'] = la
+ exc = cls(body_template='${unicodeval}', request=request)
+ exc.content_type = 'text/plain'
+ body = list(exc._default_app_iter())[0]
+ self.assertEqual(body, '200 OK\n\n/La Pe\xc3\xb1a')
+
+class TestRenderAllExceptionsWithoutArguments(unittest.TestCase):
+ def _doit(self, content_type):
+ from pyramid.response import status_map
+ L = []
+ self.assertTrue(status_map)
+ for v in status_map.values():
+ exc = v()
+ exc.content_type = content_type
+ result = list(exc.app_iter)[0]
+ if exc.empty_body:
+ self.assertEqual(result, '')
+ else:
+ self.assertTrue(exc.status in result)
+ L.append(result)
+ self.assertEqual(len(L), len(status_map))
+
+ def test_it_plain(self):
+ self._doit('text/plain')
+
+ def test_it_html(self):
+ self._doit('text/html')
+
+class Test_HTTPMove(unittest.TestCase):
+ def _makeOne(self, *arg, **kw):
+ from pyramid.response import _HTTPMove
+ return _HTTPMove(*arg, **kw)
+
+ def test_it_location_not_passed(self):
+ exc = self._makeOne()
+ self.assertEqual(exc.location, '')
+
+ def test_it_location_passed(self):
+ exc = self._makeOne(location='foo')
+ self.assertEqual(exc.location, 'foo')
+
+class TestHTTPForbidden(unittest.TestCase):
+ def _makeOne(self, *arg, **kw):
+ from pyramid.response import HTTPForbidden
+ return HTTPForbidden(*arg, **kw)
+
+ def test_it_result_not_passed(self):
+ exc = self._makeOne()
+ self.assertEqual(exc.result, None)
+
+ def test_it_result_passed(self):
+ exc = self._makeOne(result='foo')
+ self.assertEqual(exc.result, 'foo')
+
+class DummyRequest(object):
+ exception = None
+
diff --git a/pyramid/tests/test_router.py b/pyramid/tests/test_router.py
index b869a3830..106f7c57d 100644
--- a/pyramid/tests/test_router.py
+++ b/pyramid/tests/test_router.py
@@ -136,37 +136,37 @@ class TestRouter(unittest.TestCase):
self.assertEqual(router.request_factory, DummyRequestFactory)
def test_call_traverser_default(self):
- from pyramid.exceptions import NotFound
+ from pyramid.response import HTTPNotFound
environ = self._makeEnviron()
logger = self._registerLogger()
router = self._makeOne()
start_response = DummyStartResponse()
- why = exc_raised(NotFound, router, environ, start_response)
+ why = exc_raised(HTTPNotFound, router, environ, start_response)
self.assertTrue('/' in why[0], why)
self.assertFalse('debug_notfound' in why[0])
self.assertEqual(len(logger.messages), 0)
def test_traverser_raises_notfound_class(self):
- from pyramid.exceptions import NotFound
+ from pyramid.response import HTTPNotFound
environ = self._makeEnviron()
context = DummyContext()
- self._registerTraverserFactory(context, raise_error=NotFound)
+ self._registerTraverserFactory(context, raise_error=HTTPNotFound)
router = self._makeOne()
start_response = DummyStartResponse()
- self.assertRaises(NotFound, router, environ, start_response)
+ self.assertRaises(HTTPNotFound, router, environ, start_response)
def test_traverser_raises_notfound_instance(self):
- from pyramid.exceptions import NotFound
+ from pyramid.response import HTTPNotFound
environ = self._makeEnviron()
context = DummyContext()
- self._registerTraverserFactory(context, raise_error=NotFound('foo'))
+ self._registerTraverserFactory(context, raise_error=HTTPNotFound('foo'))
router = self._makeOne()
start_response = DummyStartResponse()
- why = exc_raised(NotFound, router, environ, start_response)
+ why = exc_raised(HTTPNotFound, router, environ, start_response)
self.assertTrue('foo' in why[0], why)
def test_traverser_raises_forbidden_class(self):
- from pyramid.exceptions import Forbidden
+ from pyramid.response import Forbidden
environ = self._makeEnviron()
context = DummyContext()
self._registerTraverserFactory(context, raise_error=Forbidden)
@@ -175,7 +175,7 @@ class TestRouter(unittest.TestCase):
self.assertRaises(Forbidden, router, environ, start_response)
def test_traverser_raises_forbidden_instance(self):
- from pyramid.exceptions import Forbidden
+ from pyramid.response import Forbidden
environ = self._makeEnviron()
context = DummyContext()
self._registerTraverserFactory(context, raise_error=Forbidden('foo'))
@@ -185,20 +185,20 @@ class TestRouter(unittest.TestCase):
self.assertTrue('foo' in why[0], why)
def test_call_no_view_registered_no_isettings(self):
- from pyramid.exceptions import NotFound
+ from pyramid.response import HTTPNotFound
environ = self._makeEnviron()
context = DummyContext()
self._registerTraverserFactory(context)
logger = self._registerLogger()
router = self._makeOne()
start_response = DummyStartResponse()
- why = exc_raised(NotFound, router, environ, start_response)
+ why = exc_raised(HTTPNotFound, router, environ, start_response)
self.assertTrue('/' in why[0], why)
self.assertFalse('debug_notfound' in why[0])
self.assertEqual(len(logger.messages), 0)
def test_call_no_view_registered_debug_notfound_false(self):
- from pyramid.exceptions import NotFound
+ from pyramid.response import HTTPNotFound
environ = self._makeEnviron()
context = DummyContext()
self._registerTraverserFactory(context)
@@ -206,13 +206,13 @@ class TestRouter(unittest.TestCase):
self._registerSettings(debug_notfound=False)
router = self._makeOne()
start_response = DummyStartResponse()
- why = exc_raised(NotFound, router, environ, start_response)
+ why = exc_raised(HTTPNotFound, router, environ, start_response)
self.assertTrue('/' in why[0], why)
self.assertFalse('debug_notfound' in why[0])
self.assertEqual(len(logger.messages), 0)
def test_call_no_view_registered_debug_notfound_true(self):
- from pyramid.exceptions import NotFound
+ from pyramid.response import HTTPNotFound
environ = self._makeEnviron()
context = DummyContext()
self._registerTraverserFactory(context)
@@ -220,7 +220,7 @@ class TestRouter(unittest.TestCase):
logger = self._registerLogger()
router = self._makeOne()
start_response = DummyStartResponse()
- why = exc_raised(NotFound, router, environ, start_response)
+ why = exc_raised(HTTPNotFound, router, environ, start_response)
self.assertTrue(
"debug_notfound of url http://localhost:8080/; path_info: '/', "
"context:" in why[0])
@@ -323,7 +323,7 @@ class TestRouter(unittest.TestCase):
def test_call_view_registered_specific_fail(self):
from zope.interface import Interface
from zope.interface import directlyProvides
- from pyramid.exceptions import NotFound
+ from pyramid.response import HTTPNotFound
from pyramid.interfaces import IViewClassifier
class IContext(Interface):
pass
@@ -339,12 +339,12 @@ class TestRouter(unittest.TestCase):
self._registerView(view, '', IViewClassifier, IRequest, IContext)
router = self._makeOne()
start_response = DummyStartResponse()
- self.assertRaises(NotFound, router, environ, start_response)
+ self.assertRaises(HTTPNotFound, router, environ, start_response)
def test_call_view_raises_forbidden(self):
from zope.interface import Interface
from zope.interface import directlyProvides
- from pyramid.exceptions import Forbidden
+ from pyramid.response import Forbidden
class IContext(Interface):
pass
from pyramid.interfaces import IRequest
@@ -368,17 +368,17 @@ class TestRouter(unittest.TestCase):
pass
from pyramid.interfaces import IRequest
from pyramid.interfaces import IViewClassifier
- from pyramid.exceptions import NotFound
+ from pyramid.response import HTTPNotFound
context = DummyContext()
directlyProvides(context, IContext)
self._registerTraverserFactory(context, subpath=[''])
response = DummyResponse()
- view = DummyView(response, raise_exception=NotFound("notfound"))
+ view = DummyView(response, raise_exception=HTTPNotFound("notfound"))
environ = self._makeEnviron()
self._registerView(view, '', IViewClassifier, IRequest, IContext)
router = self._makeOne()
start_response = DummyStartResponse()
- why = exc_raised(NotFound, router, environ, start_response)
+ why = exc_raised(HTTPNotFound, router, environ, start_response)
self.assertEqual(why[0], 'notfound')
def test_call_request_has_response_callbacks(self):
@@ -566,7 +566,7 @@ class TestRouter(unittest.TestCase):
"pattern: 'archives/:action/:article', "))
def test_call_route_match_miss_debug_routematch(self):
- from pyramid.exceptions import NotFound
+ from pyramid.response import HTTPNotFound
logger = self._registerLogger()
self._registerSettings(debug_routematch=True)
self._registerRouteRequest('foo')
@@ -577,7 +577,7 @@ class TestRouter(unittest.TestCase):
self._registerRootFactory(context)
router = self._makeOne()
start_response = DummyStartResponse()
- self.assertRaises(NotFound, router, environ, start_response)
+ self.assertRaises(HTTPNotFound, router, environ, start_response)
self.assertEqual(len(logger.messages), 1)
self.assertEqual(
@@ -627,11 +627,11 @@ class TestRouter(unittest.TestCase):
def test_root_factory_raises_notfound(self):
from pyramid.interfaces import IRootFactory
- from pyramid.exceptions import NotFound
+ from pyramid.response import HTTPNotFound
from zope.interface import Interface
from zope.interface import directlyProvides
def rootfactory(request):
- raise NotFound('from root factory')
+ raise HTTPNotFound('from root factory')
self.registry.registerUtility(rootfactory, IRootFactory)
class IContext(Interface):
pass
@@ -640,12 +640,12 @@ class TestRouter(unittest.TestCase):
environ = self._makeEnviron()
router = self._makeOne()
start_response = DummyStartResponse()
- why = exc_raised(NotFound, router, environ, start_response)
+ why = exc_raised(HTTPNotFound, router, environ, start_response)
self.assertTrue('from root factory' in why[0])
def test_root_factory_raises_forbidden(self):
from pyramid.interfaces import IRootFactory
- from pyramid.exceptions import Forbidden
+ from pyramid.response import Forbidden
from zope.interface import Interface
from zope.interface import directlyProvides
def rootfactory(request):
diff --git a/pyramid/tests/test_testing.py b/pyramid/tests/test_testing.py
index 58ca2b7d9..0288884b7 100644
--- a/pyramid/tests/test_testing.py
+++ b/pyramid/tests/test_testing.py
@@ -150,7 +150,7 @@ class Test_registerView(TestBase):
def test_registerView_with_permission_denying(self):
from pyramid import testing
- from pyramid.exceptions import Forbidden
+ from pyramid.response import HTTPForbidden
def view(context, request):
""" """
view = testing.registerView('moo.html', view=view, permission='bar')
@@ -160,7 +160,7 @@ class Test_registerView(TestBase):
from pyramid.view import render_view_to_response
request = DummyRequest()
request.registry = self.registry
- self.assertRaises(Forbidden, render_view_to_response,
+ self.assertRaises(HTTPForbidden, render_view_to_response,
None, request, 'moo.html')
def test_registerView_with_permission_denying2(self):
diff --git a/pyramid/view.py b/pyramid/view.py
index 975464124..0b5c7cdc9 100644
--- a/pyramid/view.py
+++ b/pyramid/view.py
@@ -8,8 +8,8 @@ from pyramid.interfaces import IRoutesMapper
from pyramid.interfaces import IView
from pyramid.interfaces import IViewClassifier
-from pyramid.exceptions import HTTPFound
-from pyramid.exceptions import default_exceptionresponse_view
+from pyramid.response import HTTPFound
+from pyramid.response import default_exceptionresponse_view
from pyramid.renderers import RendererHelper
from pyramid.static import static_view
from pyramid.threadlocal import get_current_registry
@@ -48,7 +48,7 @@ def render_view_to_response(context, request, name='', secure=True):
protected by a permission, the permission will be checked before
calling the view function. If the permission check disallows view
execution (based on the current :term:`authorization policy`), a
- :exc:`pyramid.exceptions.Forbidden` exception will be raised.
+ :exc:`pyramid.response.HTTPForbidden` exception will be raised.
The exception's ``args`` attribute explains why the view access
was disallowed.
@@ -92,7 +92,7 @@ def render_view_to_iterable(context, request, name='', secure=True):
permission, the permission will be checked before the view
function is invoked. If the permission check disallows view
execution (based on the current :term:`authentication policy`), a
- :exc:`pyramid.exceptions.Forbidden` exception will be raised;
+ :exc:`pyramid.response.HTTPForbidden` exception will be raised;
its ``args`` attribute explains why the view access was
disallowed.
@@ -121,7 +121,7 @@ def render_view(context, request, name='', secure=True):
permission, the permission will be checked before the view is
invoked. If the permission check disallows view execution (based
on the current :term:`authorization policy`), a
- :exc:`pyramid.exceptions.Forbidden` exception will be raised;
+ :exc:`pyramid.response.HTTPForbidden` exception will be raised;
its ``args`` attribute explains why the view access was
disallowed.
@@ -249,14 +249,13 @@ class AppendSlashNotFoundViewFactory(object):
.. code-block:: python
- from pyramid.exceptions import NotFound
+ from pyramid.response import HTTPNotFound
from pyramid.view import AppendSlashNotFoundViewFactory
- from pyramid.exceptions import HTTPNotFound
def notfound_view(context, request): return HTTPNotFound('nope')
custom_append_slash = AppendSlashNotFoundViewFactory(notfound_view)
- config.add_view(custom_append_slash, context=NotFound)
+ config.add_view(custom_append_slash, context=HTTPNotFound)
The ``notfound_view`` supplied must adhere to the two-argument
view callable calling convention of ``(context, request)``
@@ -303,9 +302,9 @@ routes are not considered when attempting to find a matching route.
Use the :meth:`pyramid.config.Configurator.add_view` method to configure this
view as the Not Found view::
- from pyramid.exceptions import NotFound
+ from pyramid.response import HTTPNotFound
from pyramid.view import append_slash_notfound_view
- config.add_view(append_slash_notfound_view, context=NotFound)
+ config.add_view(append_slash_notfound_view, context=HTTPNotFound)
See also :ref:`changing_the_notfound_view`.