summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@plope.com>2011-06-11 05:35:27 -0400
committerChris McDonough <chrism@plope.com>2011-06-11 05:35:27 -0400
commit99edc51a3b05309c7f5d98ff96289ec51b1d7660 (patch)
treec8ddaa62b21c54eb996f5e375abd5bf9f5198806
parentdf15ed98612e7962e3122da52d8d5f5b9d8882b2 (diff)
downloadpyramid-99edc51a3b05309c7f5d98ff96289ec51b1d7660.tar.gz
pyramid-99edc51a3b05309c7f5d98ff96289ec51b1d7660.tar.bz2
pyramid-99edc51a3b05309c7f5d98ff96289ec51b1d7660.zip
- Pyramid now expects Response objects to have a __call__
method which implements the WSGI application interface instead of the three webob attrs status, headerlist and app_iter. Backwards compatibility exists for code which returns response objects that do not have a __call__. - pyramid.response.Response is no longer an exception (and therefore cannot be raised in order to generate a response). - Changed my mind about moving stuff from pyramid.httpexceptions to pyramid.response. The stuff I moved over has been moved back to pyramid.httpexceptions.
-rw-r--r--CHANGES.txt79
-rw-r--r--TODO.txt6
-rw-r--r--docs/api/httpexceptions.rst6
-rw-r--r--docs/api/response.rst1
-rw-r--r--docs/glossary.rst4
-rw-r--r--docs/narr/hooks.rst42
-rw-r--r--docs/narr/renderers.rst69
-rw-r--r--docs/narr/router.rst12
-rw-r--r--docs/narr/testing.rst4
-rw-r--r--docs/narr/urldispatch.rst4
-rw-r--r--docs/narr/views.rst209
-rw-r--r--docs/narr/webob.rst23
-rw-r--r--docs/tutorials/wiki/authorization.rst22
-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--docs/whatsnew-1.1.rst12
-rw-r--r--pyramid/config.py6
-rw-r--r--pyramid/exceptions.py6
-rw-r--r--pyramid/httpexceptions.py902
-rw-r--r--pyramid/interfaces.py16
-rw-r--r--pyramid/response.py946
-rw-r--r--pyramid/router.py17
-rw-r--r--pyramid/testing.py2
-rw-r--r--pyramid/tests/fixtureapp/views.py2
-rw-r--r--pyramid/tests/forbiddenapp/__init__.py2
-rw-r--r--pyramid/tests/test_config.py46
-rw-r--r--pyramid/tests/test_exceptions.py15
-rw-r--r--pyramid/tests/test_httpexceptions.py278
-rw-r--r--pyramid/tests/test_response.py305
-rw-r--r--pyramid/tests/test_router.py117
-rw-r--r--pyramid/tests/test_testing.py2
-rw-r--r--pyramid/view.py44
39 files changed, 1632 insertions, 1595 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index 7840bc525..e413f0657 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -37,12 +37,10 @@ Documentation
- Added "What's New in Pyramid 1.1" to HTML rendering of documentation.
-- Added API docs for ``pyramid.httpexceptions.abort`` and
- ``pyramid.httpexceptions.redirect``.
+- Added API docs for ``pyramid.httpexceptions.responsecode``.
- Added "HTTP Exceptions" section to Views narrative chapter including a
- description of ``pyramid.httpexceptions.abort``; adjusted redirect section
- to note ``pyramid.httpexceptions.redirect``.
+ description of ``pyramid.httpexceptions.responsecode``.
Features
--------
@@ -105,18 +103,15 @@ Features
more information.
- A default exception view for the context
- ``pyramid.interfaces.IExceptionResponse`` (aka
- ``pyramid.response.Response`` or ``pyramid.httpexceptions.HTTPException``)
- is now registered by default. This means that an instance of any exception
- response class imported from ``pyramid.httpexceptions`` (such as
- ``HTTPFound``) can now be raised from within view code; when raised, this
- exception view will render the exception to a response.
-
-- New functions named ``pyramid.httpexceptions.abort`` and
- ``pyramid.httpexceptions.redirect`` perform the equivalent of their Pylons
- brethren when an HTTP exception handler is registered. These functions
- take advantage of the newly registered exception view for
- ``webob.exc.HTTPException``.
+ ``pyramid.interfaces.IExceptionResponse`` is now registered by default.
+ This means that an instance of any exception response class imported from
+ ``pyramid.httpexceptions`` (such as ``HTTPFound``) can now be raised from
+ within view code; when raised, this exception view will render the
+ exception to a response.
+
+- A function named ``pyramid.httpexceptions.responsecode`` is a shortcut that
+ can be used to create HTTP exception response objects using an HTTP integer
+ status code.
- The Configurator now accepts an additional keyword argument named
``exceptionresponse_view``. By default, this argument is populated with a
@@ -135,28 +130,13 @@ Features
- It is now possible to control how the Pyramid router calls the WSGI
``start_response`` callable and obtains the WSGI ``app_iter`` based on
adapting the response object to the new ``pyramid.interfaces.IResponder``
- interface. The default ``IResponder`` uses Pyramid 1.0's logic to do this.
- To override the responder::
-
- from pyramid.interfaces import IResponder
- from pyramid.response import Response
- from myapp import MyResponder
-
- config.registry.registerAdapter(MyResponder, (Response,),
- IResponder, name='')
-
- This makes it possible to reuse response object implementations which have,
- for example, their own ``__call__`` expected to be used as a WSGI
- application (like ``pyramid.response.Response``), e.g.:
+ interface. See the section in the Hooks chapter of the documentation
+ entitled "Changing How Pyramid Treats Response Objects".
- class MyResponder(object):
- def __init__(self, response):
- """ Obtain a reference to the response """
- self.response = response
- def __call__(self, request, start_response):
- """ Call start_response and return an app_iter """
- app_iter = self.response(request.environ, start_response)
- return app_iter
+- The Pyramid router will now, by default, call the ``__call__`` method of
+ WebOb response objects when returning a WSGI response. This means that,
+ among other things, the ``conditional_response`` feature of WebOb response
+ objects will now behave properly.
Bug Fixes
---------
@@ -291,7 +271,7 @@ Deprecations
Behavior Changes
----------------
-- A custom request factory is now required to return a response object that
+- A custom request factory is now required to return a request object that
has a ``response`` attribute (or "reified"/lazy property) if they the
request is meant to be used in a view that uses a renderer. This
``response`` attribute should be an instance of the class
@@ -323,10 +303,25 @@ Behavior Changes
result.
- ``pyramid.response.Response`` is now a *subclass* of
- ``webob.response.Response``. It also inherits from the built-in Python
- ``Exception`` class and implements the
- ``pyramid.interfaces.IExceptionResponse`` class so it can be raised as an
- exception from view code.
+ ``webob.response.Response`` (in order to directly implement the
+ ``pyramid.interfaces.IResponse`` interface).
+
+- The ``pyramid.interfaces.IResponse`` interface now includes a ``__call__``
+ method which has the WSGI application call signature (and which expects an
+ iterable as a result).
+
+- The Pyramid router now, by default, expects response objects returned from
+ views to implement the WSGI application interface (a ``__call__`` method
+ that accepts ``environ`` and ``start_response``, and which returns an
+ ``app_iter`` iterable). If such a method exists, Pyramid will now call it
+ in order to satisfy the WSGI request. Backwards compatibility code in the
+ default responder exists which will fall back to the older behavior, but
+ Pyramid will raise a deprecation warning if it is reached. See the section
+ in the Hooks chapter of the documentation entitled "Changing How Pyramid
+ Treats Response Objects" to default back to the older behavior, where the
+ ``app_iter``, ``headerlist``, and ``status`` attributes of the object were
+ consulted directly (without any indirection through ``__call__``) to
+ silence the deprecation warnings.
Dependencies
------------
diff --git a/TODO.txt b/TODO.txt
index d85f3b7f0..04c6e60d7 100644
--- a/TODO.txt
+++ b/TODO.txt
@@ -1,6 +1,12 @@
Pyramid TODOs
=============
+Must-Have
+---------
+
+- Depend on only __call__ interface or only 3-attr interface in builtin code
+ that deals with response objects.
+
Should-Have
-----------
diff --git a/docs/api/httpexceptions.rst b/docs/api/httpexceptions.rst
index 73da4126b..325d5af03 100644
--- a/docs/api/httpexceptions.rst
+++ b/docs/api/httpexceptions.rst
@@ -5,16 +5,14 @@
.. automodule:: pyramid.httpexceptions
- .. autofunction:: abort
-
- .. autofunction:: redirect
-
.. attribute:: status_map
A mapping of integer status code to exception class (eg. the
integer "401" maps to
:class:`pyramid.httpexceptions.HTTPUnauthorized`).
+ .. autofunction:: responsecode
+
.. autoclass:: HTTPException
.. autoclass:: HTTPOk
diff --git a/docs/api/response.rst b/docs/api/response.rst
index c545b4977..e67b15568 100644
--- a/docs/api/response.rst
+++ b/docs/api/response.rst
@@ -8,3 +8,4 @@
.. autoclass:: Response
:members:
:inherited-members:
+
diff --git a/docs/glossary.rst b/docs/glossary.rst
index dbab331c1..079a069b4 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.response.HTTPNotFound``
+ developer explicitly raises a ``pyramid.httpexceptions.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.response.HTTPForbidden`` exception from within
+ ``pyramid.httpexceptions.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 aa151d281..b6a781417 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.response.HTTPNotFound` class as the
+only of naming the :exc:`pyramid.httpexceptions.HTTPNotFound` class as the
``context`` of the view configuration.
If your application uses :term:`imperative configuration`, you can replace
@@ -31,7 +31,7 @@ method to register an "exception view":
.. code-block:: python
:linenos:
- from pyramid.response import HTTPNotFound
+ from pyramid.httpexceptions import HTTPNotFound
from helloworld.views import notfound_view
config.add_view(notfound_view, context=HTTPNotFound)
@@ -42,22 +42,22 @@ 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.response.HTTPNotFound` exception that caused the view to be
-called.
+:exc:`~pyramid.httpexceptions.HTTPNotFound` exception that caused the view to
+be called.
Here's some sample code that implements a minimal NotFound view callable:
.. code-block:: python
:linenos:
- from pyramid.response import HTTPNotFound
+ from pyramid.httpexceptions 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.response.HTTPNotFound` exception that
+ instance of the :exc:`~pyramid.httpexceptions.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
@@ -67,8 +67,9 @@ Here's some sample code that implements a minimal NotFound view callable:
.. 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.response.HTTPNotFound` exception instance. If available,
- the resource context will still be available as ``request.context``.
+ :exc:`~pyramid.httpexceptions.HTTPNotFound` exception instance. If
+ available, the resource context will still be available as
+ ``request.context``.
.. index::
single: forbidden view
@@ -85,7 +86,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.response.HTTPForbidden` class as the
+only of naming the :exc:`pyramid.httpexceptions.HTTPForbidden` class as the
``context`` of the view configuration.
You can replace the forbidden view by using the
@@ -96,7 +97,7 @@ view":
:linenos:
from helloworld.views import forbidden_view
- from pyramid.response import HTTPForbidden
+ from pyramid.httpexceptions import HTTPForbidden
config.add_view(forbidden_view, context=HTTPForbidden)
Replace ``helloworld.views.forbidden_view`` with a reference to the Python
@@ -122,8 +123,8 @@ Here's some sample code that implements a minimal forbidden view:
.. 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.response.HTTPForbidden` exception that
- caused the forbidden view to be called. The value of
+ instance of the :exc:`~pyramid.httpexceptions.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
@@ -532,10 +533,10 @@ Changing How Pyramid Treats Response Objects
It is possible to control how the Pyramid :term:`router` calls the WSGI
``start_response`` callable and obtains the WSGI ``app_iter`` based on
adapting the response object to the :class: `pyramid.interfaces.IResponder`
-interface. The default ``IResponder`` uses the three attributes ``status``,
-``headerlist``, and ``app_iter`` attached to the response object, and calls
-``start_response`` with the status and headerlist, returning the
-``app_iter``. To override the responder::
+interface. The default responder uses the ``__call__`` method of a response
+object, passing it the WSGI environ and the WSGI ``start_response`` callable
+(the response is assumed to be a WSGI application). To override the
+responder::
from pyramid.interfaces import IResponder
from pyramid.response import Response
@@ -545,8 +546,9 @@ interface. The default ``IResponder`` uses the three attributes ``status``,
IResponder, name='')
Overriding makes it possible to reuse response object implementations which
-have, for example, their own ``__call__`` expected to be used as a WSGI
-application (like :class:`pyramid.response.Response`), e.g.:
+have, for example, the ``app_iter``, ``headerlist`` and ``status`` attributes
+of an object returned as a response instead of trying to use the object's
+``__call__`` method::
class MyResponder(object):
def __init__(self, response):
@@ -554,8 +556,8 @@ application (like :class:`pyramid.response.Response`), e.g.:
self.response = response
def __call__(self, request, start_response):
""" Call start_response and return an app_iter """
- app_iter = self.response(request.environ, start_response)
- return app_iter
+ start_response(self.response.status, self.response.headerlist)
+ return self.response.app_iter
.. index::
single: view mapper
diff --git a/docs/narr/renderers.rst b/docs/narr/renderers.rst
index c7a3d7837..99ee14908 100644
--- a/docs/narr/renderers.rst
+++ b/docs/narr/renderers.rst
@@ -11,7 +11,6 @@ Response interface, :app:`Pyramid` will attempt to use a
.. code-block:: python
:linenos:
- from pyramid.response import Response
from pyramid.view import view_config
@view_config(renderer='json')
@@ -77,39 +76,52 @@ 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`.
+Views which use a renderer and return a non-Response value 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
+returns a Response object directly, 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.response.HTTPFound` class as a response, no renderer will be
-employed.
+:class:`pyramid.response.Response` class as a response, no renderer
+will be employed.
.. code-block:: python
:linenos:
- from pyramid.response import HTTPFound
+ from pyramid.response import Response
+ from pyramid.view import view_config
+ @view_config(renderer='json')
def view(request):
- return HTTPFound(location='http://example.com') # any renderer avoided
+ return Response('OK') # json renderer avoided
-Likewise for a "plain old response":
+Likewise for an :term:`HTTP exception` response:
.. code-block:: python
:linenos:
- from pyramid.response import Response
+ from pyramid.httpexceptions import HTTPNotFound
+ from pyramid.view import view_config
+ @view_config(renderer='json')
def view(request):
- return Response('OK') # any renderer avoided
+ return HTTPFound(location='http://example.com') # json renderer avoided
-Mutations to ``request.response`` in views which return a Response object
-like this directly (unless that response *is* ``request.response``) will be
-ignored.
+You can of course also return the ``request.response`` attribute instead to
+avoid rendering:
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.view import view_config
+
+ @view_config(renderer='json')
+ def view(request):
+ request.response.body = 'OK'
+ return request.response # json renderer avoided
.. index::
single: renderers (built-in)
@@ -377,6 +389,31 @@ callable that uses a renderer, assign the ``status`` attribute to the
request.response.status = '404 Not Found'
return {'URL':request.URL}
+Note that mutations of ``request.response`` in views which return a Response
+object directly will have no effect unless the response object returned *is*
+``request.response``. For example, the following example calls
+``request.response.set_cookie``, but this call will have no effect, because a
+different Response object is returned.
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.response import Response
+
+ def view(request):
+ request.response.set_cookie('abc', '123') # this has no effect
+ return Response('OK') # because we're returning a different response
+
+If you mutate ``request.response`` and you'd like the mutations to have an
+effect, you must return ``request.response``:
+
+.. code-block:: python
+ :linenos:
+
+ def view(request):
+ request.response.set_cookie('abc', '123')
+ return request.response
+
For more information on attributes of the request, see the API documentation
in :ref:`request_module`. For more information on the API of
``request.response``, see :class:`pyramid.response.Response`.
diff --git a/docs/narr/router.rst b/docs/narr/router.rst
index 44fa9835b..30d54767e 100644
--- a/docs/narr/router.rst
+++ b/docs/narr/router.rst
@@ -82,8 +82,8 @@ processing?
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.
+ :class:`~pyramid.httpexceptions.HTTPNotFound` exception, which is meant to
+ be caught by a surrounding :term:`exception view`.
#. If a view callable was found, :app:`Pyramid` attempts to call
the view function.
@@ -95,13 +95,13 @@ processing?
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.
+ :class:`~pyramid.httpexceptions.HTTPForbidden` exception, which is meant
+ to be called by a surrounding :term:`exception view`.
#. 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.response.HTTPNotFound` or
- :class:`~pyramid.response.HTTPForbidden`), the router catches the
+ (such as when it raises :class:`~pyramid.httpexceptions.HTTPNotFound` or
+ :class:`~pyramid.httpexceptions.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
diff --git a/docs/narr/testing.rst b/docs/narr/testing.rst
index 862eda9f0..05e851fde 100644
--- a/docs/narr/testing.rst
+++ b/docs/narr/testing.rst
@@ -191,7 +191,7 @@ function.
:linenos:
from pyramid.security import has_permission
- from pyramid.response import HTTPForbidden
+ from pyramid.httpexceptions import HTTPForbidden
def view_fn(request):
if not has_permission('edit', request.context, request):
@@ -230,7 +230,7 @@ without needing to invoke the actual application configuration implied by its
testing.tearDown()
def test_view_fn_forbidden(self):
- from pyramid.response import HTTPForbidden
+ from pyramid.httpexceptions import HTTPForbidden
from my.package import view_fn
self.config.testing_securitypolicy(userid='hank',
permissive=False)
diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst
index e5228b81e..f94ed3ba8 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.response.HTTPNotFound')
+ context='pyramid.httpexceptions.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,7 +945,7 @@ view as the first argument to its constructor. For instance:
.. code-block:: python
:linenos:
- from pyramid.response import HTTPNotFound
+ from pyramid.httpexceptions import HTTPNotFound
from pyramid.view import AppendSlashNotFoundViewFactory
def notfound_view(context, request):
diff --git a/docs/narr/views.rst b/docs/narr/views.rst
index 73a7c2e2a..990828f80 100644
--- a/docs/narr/views.rst
+++ b/docs/narr/views.rst
@@ -230,29 +230,29 @@ implements the :term:`Response` interface is to return a
def view(request):
return Response('OK')
-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.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.
-
-status
- The HTTP status code (including the name) for the response as a string.
- E.g. ``200 OK`` or ``401 Unauthorized``.
-
-headerlist
- A sequence of tuples representing the list of headers that should be
- set in the response. E.g. ``[('Content-Type', 'text/html'),
- ('Content-Length', '412')]``
-
-app_iter
- An iterable representing the body of the response. This can be a
- list, e.g. ``['<html><head></head><body>Hello
- world!</body></html>']`` or it can be a file-like object, or any
- other sort of iterable.
-
-These attributes form the structure of the "Pyramid Response interface".
+You don't need to use :class:`~pyramid.response.Response` to represent a
+response. A view can actually return any object that has a ``__call__``
+method that implements the :term:`WSGI` application call interface. For
+example, an instance of the following class could be successfully returned by
+a view callable as a response object:
+
+.. code-block:: python
+ :linenos:
+
+ class SimpleResponse(object):
+ def __call__(self, environ, start_response):
+ """ Call the ``start_response`` callback and return
+ an iterable """
+ body = 'Hello World!'
+ headers = [('Content-Type', 'text/plain'),
+ ('Content-Length', str(len(body)))]
+ start_response('200 OK', headers)
+ return [body]
+
+: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
+(see :ref:`http_exceptions` and ref:`http_redirect`).
.. index::
single: view exceptions
@@ -269,40 +269,8 @@ logged there.
However, for convenience, a special set of exceptions exists. When one of
these exceptions is raised within a view callable, it will always cause
-:app:`Pyramid` to generate a response. Two categories of special exceptions
-exist: internal exceptions and HTTP exceptions.
-
-Internal Exceptions
-~~~~~~~~~~~~~~~~~~~
-
-: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.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.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
-argument: a ``message``. In all cases, the message provided to the exception
-constructor is made available to the view which :app:`Pyramid` invokes as
-``request.exception.args[0]``.
-
-An example:
-
-.. code-block:: python
- :linenos:
-
- from pyramid.response import HTTPNotFound
-
- def aview(request):
- raise HTTPNotFound('not found!')
+:app:`Pyramid` to generate a response. These are known as :term:`HTTP
+exception` objects.
.. index::
single: HTTP exceptions
@@ -312,54 +280,77 @@ An example:
HTTP Exceptions
~~~~~~~~~~~~~~~
-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.
+All classes documented in the :mod:`pyramid.httpexceptions` module documented
+as inheriting from the :class:`pryamid.httpexceptions.HTTPException` are
+:term:`http exception` objects. An instances of an HTTP exception object may
+either be *returned* or *raised* from within view code. In either case
+(return or raise) the instance will be used as as the view's response.
-For example, the :class:`pyramid.response.HTTPUnauthorized` exception
+For example, the :class:`pyramid.httpexceptions.HTTPUnauthorized` exception
can be raised. This will cause a response to be generated with a ``401
Unauthorized`` status:
.. code-block:: python
:linenos:
- from pyramid.response import HTTPUnauthorized
+ from pyramid.httpexceptions import HTTPUnauthorized
def aview(request):
raise HTTPUnauthorized()
-A shortcut for importing and raising an HTTP exception is the
-: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:
+An HTTP exception, instead of being raised, can alternately be *returned*
+(HTTP exceptions are also valid response objects):
.. code-block:: python
:linenos:
- from pyramid.response import abort
+ from pyramid.httpexceptions import HTTPUnauthorized
def aview(request):
- abort(401)
-
-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.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.
+ return HTTPUnauthorized()
-An HTTP exception, instead of being raised, can alternately be *returned*
-(HTTP exceptions are also valid response objects):
+A shortcut for creating an HTTP exception is the
+:func:`pyramid.httpexceptions.responsecode` function. This function accepts
+an HTTP status code and returns the corresponding HTTP exception. For
+example, instead of importing and constructing a
+:class:`~pyramid.httpexceptions.HTTPUnauthorized` response object, you can
+use the :func:`~pyramid.httpexceptions.responsecode` function to construct
+and return the same object.
.. code-block:: python
:linenos:
- from pyramid.response import HTTPUnauthorized
+ from pyramid.httpexceptions import responsecode
def aview(request):
- return HTTPUnauthorized()
+ raise responsecode(401)
+
+This is the case because ``401`` is the HTTP status code for "HTTP
+Unauthorized". Therefore, ``raise responsecode(401)`` is functionally
+equivalent to ``raise HTTPUnauthorized()``. Documentation which maps each
+HTTP response code to its purpose and its associated HTTP exception object is
+provided within :mod:`pyramid.httpexceptions`.
+
+How Pyramid Uses HTTP Exceptions
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+HTTP exceptions are meant to be used directly by application application
+developers. However, Pyramid itself will raise two HTTP exceptions at
+various points during normal operations:
+:exc:`pyramid.httpexceptions.HTTPNotFound` and
+:exc:`pyramid.httpexceptions.HTTPForbidden`. Pyramid will raise the
+:exc:`~pyramid.httpexceptions.HTTPNotFound` exception are raised when it
+cannot find a view to service a request. Pyramid will raise the
+:exc:`~pyramid.httpexceptions.Forbidden` exception or when authorization was
+forbidden by a security policy.
+
+If :exc:`~pyramid.httpexceptions.HTTPNotFound` is raised by Pyramid itself or
+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.httpexceptions.HTTPForbidden` is raised by Pyramid itself
+within view code, the result of the :term:`Forbidden View` will be returned
+to the user agent which performed the request.
.. index::
single: exception views
@@ -369,11 +360,10 @@ An HTTP exception, instead of being raised, can alternately be *returned*
Custom Exception Views
----------------------
-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.
+The machinery which allows HTTP exceptions to be raised 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
@@ -409,8 +399,8 @@ raises a ``helloworld.exceptions.ValidationFailure`` exception:
Assuming that a :term:`scan` was run to pick up this view registration, this
view callable will be invoked whenever a
``helloworld.exceptions.ValidationFailure`` is raised by your application's
-view code. The same exception raised by a custom root factory or a custom
-traverser is also caught and hooked.
+view code. The same exception raised by a custom root factory, a custom
+traverser, or a custom view or route predicate is also caught and hooked.
Other normal view predicates can also be used in combination with an
exception view registration:
@@ -458,57 +448,34 @@ Exception views can be configured with any view registration mechanism:
Using a View Callable to Do an HTTP Redirect
--------------------------------------------
-Two methods exist to redirect to another URL from within a view callable: a
-short form and a long form. The short form should be preferred when
-possible.
-
-Short Form
-~~~~~~~~~~
-
-You can issue an HTTP redirect from within a view callable by using the
-: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.response import redirect
-
- def myview(request):
- redirect('http://example.com')
-
-Long Form
-~~~~~~~~~
-
-You can issue an HTTP redirect from within a view "by hand" instead of
-relying on the :func:`pyramid.response.redirect` function to do it for
-you.
+You can issue an HTTP redirect by using the
+:class:`pyramid.httpexceptions.HTTPFound` class. Raising or returning an
+instance of this class will cause the client to receive a "302 Found"
+response.
-To do so, you can *return* a :class:`pyramid.response.HTTPFound`
+To do so, you can *return* a :class:`pyramid.httpexceptions.HTTPFound`
instance.
.. code-block:: python
:linenos:
- from pyramid.response import HTTPFound
+ from pyramid.httpexceptions import HTTPFound
def myview(request):
return HTTPFound(location='http://example.com')
-Or, alternately, you can *raise* an HTTPFound exception instead of returning
-one.
+Alternately, you can *raise* an HTTPFound exception instead of returning one.
.. code-block:: python
:linenos:
- from pyramid.response import HTTPFound
+ from pyramid.httpexceptions import HTTPFound
def myview(request):
raise HTTPFound(location='http://example.com')
-The above form of generating a response by raising HTTPFound is completely
-equivalent to ``redirect('http://example.com')``.
+When the instance is raised, it is caught by the default :term:`exception
+response` handler and turned into a response.
.. index::
single: unicode, views, and forms
diff --git a/docs/narr/webob.rst b/docs/narr/webob.rst
index 6cd9418ce..70ab5eea8 100644
--- a/docs/narr/webob.rst
+++ b/docs/narr/webob.rst
@@ -362,20 +362,21 @@ 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.response` module. This import location contains subclasses and
-replacements that mirror those in the original ``webob.exc``.
+:mod:`pyramid.httpexceptions` module. This import location contains
+subclasses and replacements that mirror those in the original ``webob.exc``.
-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:
+Each class is named ``pyramid.httpexceptions.HTTP*``, where ``*`` is the
+reason for the error. For instance,
+:class:`pyramid.httpexceptions.HTTPNotFound` subclasses
+:class:`pyramid.Response`, so you can manipulate the instances in the same
+way. A typical example is:
.. ignore-next-block
.. code-block:: python
:linenos:
- from pyramid.response import HTTPNotFound
- from pyramid.response import HTTPMovedPermanently
+ from pyramid.httpexceptions import HTTPNotFound
+ from pyramid.httpexceptions import HTTPMovedPermanently
response = HTTPNotFound('There is no such resource')
# or:
@@ -385,7 +386,7 @@ 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.response` API documentation. The `WebOb
-documentation <http://pythonpaste.org/webob>`_ is also useful.
+:mod:`pyramid.response` documentation. More details about exception
+responses are in the :mod:`pyramid.httpexceptions` API documentation. The
+`WebOb documentation <http://pythonpaste.org/webob>`_ is also useful.
diff --git a/docs/tutorials/wiki/authorization.rst b/docs/tutorials/wiki/authorization.rst
index 3b102958e..de5c9486d 100644
--- a/docs/tutorials/wiki/authorization.rst
+++ b/docs/tutorials/wiki/authorization.rst
@@ -131,17 +131,17 @@ 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.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
-whenever one of our users tries to execute a view callable that they are not
-allowed to invoke as determined by the :term:`authorization policy` in use.
-In our application, for example, this means that if a user has not logged in,
-and he tries to add or edit a Wiki page, he will be shown the login form.
-Before being allowed to continue on to the add or edit form, he will have to
-provide credentials that give him permission to add or edit via this login
-form.
+``pyramid.httpexceptions.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 whenever one of our users tries to execute a view callable that they
+are not allowed to invoke as determined by the :term:`authorization policy`
+in use. In our application, for example, this means that if a user has not
+logged in, and he tries to add or edit a Wiki page, he will be shown the
+login form. Before being allowed to continue on to the add or edit form, he
+will have to provide credentials that give him permission to add or edit via
+this login form.
Changing Existing Views
~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/docs/tutorials/wiki/definingviews.rst b/docs/tutorials/wiki/definingviews.rst
index ea8842294..b6c083bbf 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.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
+: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
"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 822b19b9e..334115880 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.response import HTTPFound
+from pyramid.httpexceptions 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.response.HTTPForbidden',
+@view_config(context='pyramid.httpexceptions.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 67550d58e..a83e17de4 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.response import HTTPFound
+from pyramid.httpexceptions 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 d72cbd3fd..42420f2fe 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.response import HTTPFound
+from pyramid.httpexceptions 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 32e3c0b24..832f90b92 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.response.HTTPFound` class (instances of which implement the
-WebOb :term:`response` interface), It will use the
+:class:`pyramid.httpexceptions.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 42013622c..4cd84eda5 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.response.HTTPForbidden',
+ context='pyramid.httpexceptions.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 2bc8a7201..7a1d1f663 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.response import HTTPFound
+from pyramid.httpexceptions 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 ed441295c..5abd8391e 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.response import HTTPFound
+from pyramid.httpexceptions 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 80d817d99..b8896abe7 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.response import HTTPFound
+from pyramid.httpexceptions import HTTPFound
from pyramid.url import route_url
from tutorial.models import DBSession
diff --git a/docs/whatsnew-1.1.rst b/docs/whatsnew-1.1.rst
index 488328519..533ae3637 100644
--- a/docs/whatsnew-1.1.rst
+++ b/docs/whatsnew-1.1.rst
@@ -63,12 +63,6 @@ Default HTTP Exception View
from within view code; when raised, this exception view will render the
exception to a response.
- New convenience functions named :func:`pyramid.httpexceptions.abort` and
- :func:`pyramid.httpexceptions.redirect` perform the equivalent of their
- Pylons brethren when an HTTP exception handler is registered. These
- functions take advantage of the newly registered exception view for
- :exc:`webob.exc.HTTPException`.
-
To allow for configuration of this feature, the :term:`Configurator` now
accepts an additional keyword argument named ``httpexception_view``. By
default, this argument is populated with a default exception view function
@@ -81,6 +75,10 @@ Default HTTP Exception View
Minor Feature Additions
-----------------------
+- A function named :func:`pyramid.httpexceptions.responsecode` is a shortcut
+ that can be used to create HTTP exception response objects using an HTTP
+ integer status code.
+
- Integers and longs passed as ``elements`` to
:func:`pyramid.url.resource_url` or
:meth:`pyramid.request.Request.resource_url` e.g. ``resource_url(context,
@@ -177,7 +175,7 @@ Deprecations and Behavior Differences
expected an environ object in BFG 1.0 and before). In a future version,
these methods will be removed entirely.
-- A custom request factory is now required to return a response object that
+- A custom request factory is now required to return a request object that
has a ``response`` attribute (or "reified"/lazy property) if they the
request is meant to be used in a view that uses a renderer. This
``response`` attribute should be an instance of the class
diff --git a/pyramid/config.py b/pyramid/config.py
index ab1729c06..91ba414b3 100644
--- a/pyramid/config.py
+++ b/pyramid/config.py
@@ -56,10 +56,10 @@ from pyramid.compat import md5
from pyramid.compat import any
from pyramid.events import ApplicationCreated
from pyramid.exceptions import ConfigurationError
-from pyramid.response import default_exceptionresponse_view
-from pyramid.response import HTTPForbidden
-from pyramid.response import HTTPNotFound
from pyramid.exceptions import PredicateMismatch
+from pyramid.httpexceptions import default_exceptionresponse_view
+from pyramid.httpexceptions import HTTPForbidden
+from pyramid.httpexceptions import HTTPNotFound
from pyramid.i18n import get_localizer
from pyramid.log import make_stream_logger
from pyramid.mako_templating import renderer_factory as mako_renderer_factory
diff --git a/pyramid/exceptions.py b/pyramid/exceptions.py
index 2484f94a3..151fc241f 100644
--- a/pyramid/exceptions.py
+++ b/pyramid/exceptions.py
@@ -1,12 +1,12 @@
from zope.configuration.exceptions import ConfigurationError as ZCE
-from pyramid.response import HTTPNotFound
-from pyramid.response import HTTPForbidden
+from pyramid.httpexceptions import HTTPNotFound
+from pyramid.httpexceptions import HTTPForbidden
NotFound = HTTPNotFound # bw compat
Forbidden = HTTPForbidden # bw compat
-class PredicateMismatch(NotFound):
+class PredicateMismatch(HTTPNotFound):
"""
Internal exception (not an API) raised by multiviews when no
view matches. This exception subclasses the ``NotFound``
diff --git a/pyramid/httpexceptions.py b/pyramid/httpexceptions.py
index dbb530b4a..a692380f8 100644
--- a/pyramid/httpexceptions.py
+++ b/pyramid/httpexceptions.py
@@ -110,7 +110,907 @@ field. Reflecting this, these subclasses have one additional keyword argument:
``location``, which indicates the location to which to redirect.
"""
-from pyramid.response import * # API
+import types
+from string import Template
+
+from zope.interface import implements
+
+from webob import html_escape as _html_escape
+
+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 accept header
+ #
+ # - 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 using a template (ala the replaced version's "generate_response")
+ #
+ # - 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).
+ #
+ # - documentation improvements (Pyramid-specific docstrings where necessary)
+ #
+ 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')
+
+def responsecode(status_code, **kw):
+ """Creates an HTTP exception based on a status code. Example::
+
+ raise responsecode(404) # raises an HTTPNotFound exception.
+
+ The values passed as ``kw`` are provided to the exception's constructor.
+ """
+ exc = status_map[status_code](**kw)
+ return exc
+
+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/interfaces.py b/pyramid/interfaces.py
index d5d382492..b8ff2d4c9 100644
--- a/pyramid/interfaces.py
+++ b/pyramid/interfaces.py
@@ -46,11 +46,15 @@ class IApplicationCreated(Interface):
IWSGIApplicationCreatedEvent = IApplicationCreated # b /c
-class IResponse(Interface): # not an API
+class IResponse(Interface):
status = Attribute('WSGI status code of response')
headerlist = Attribute('List of response headers')
app_iter = Attribute('Iterable representing the response body')
+ def __call__(environ, start_response):
+ """ WSGI call interface, should call the start_response callback
+ and should return an iterable """
+
class IException(Interface): # not an API
""" An interface representing a generic exception """
@@ -60,8 +64,8 @@ class IExceptionResponse(IException, IResponse):
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:`pyramid.httpexceptions.HTTPNotFound` and
+ :class:`pyramid.httpexceptions.HTTPForbidden`)."""
class IBeforeRender(Interface):
"""
@@ -282,11 +286,7 @@ class IExceptionViewClassifier(Interface):
class IView(Interface):
def __call__(context, request):
- """ Must return an object that implements IResponse. May
- optionally raise ``pyramid.response.HTTPForbidden`` if an
- authorization failure is detected during view execution or
- ``pyramid.response.HTTPNotFound`` if the not found page is
- meant to be returned."""
+ """ Must return an object that implements IResponse. """
class ISecuredView(IView):
""" *Internal only* interface. Not an API. """
diff --git a/pyramid/response.py b/pyramid/response.py
index 6e6af32c8..1d2ef296f 100644
--- a/pyramid/response.py
+++ b/pyramid/response.py
@@ -1,947 +1,7 @@
-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 accept header
- #
- # - 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 using a template (ala the replaced version's "generate_response")
- #
- # - 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
+from pyramid.interfaces import IResponse
- 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.
+class Response(_Response):
+ implements(IResponse)
- 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 92c6cc920..4d2750efb 100644
--- a/pyramid/router.py
+++ b/pyramid/router.py
@@ -1,3 +1,5 @@
+import warnings
+
from zope.interface import implements
from zope.interface import providedBy
@@ -17,7 +19,7 @@ from pyramid.interfaces import IResponder
from pyramid.events import ContextFound
from pyramid.events import NewRequest
from pyramid.events import NewResponse
-from pyramid.response import HTTPNotFound
+from pyramid.httpexceptions import HTTPNotFound
from pyramid.request import Request
from pyramid.threadlocal import manager
from pyramid.traversal import DefaultRootFactory
@@ -203,6 +205,11 @@ class Router(object):
def default_responder(response):
def inner(request, start_response):
+ # __call__ is default 1.1 response API
+ call = getattr(response, '__call__', None)
+ if call is not None:
+ return call(request.environ, start_response)
+ # start 1.0 bw compat (use headerlist, app_iter, status)
try:
headers = response.headerlist
app_iter = response.app_iter
@@ -212,6 +219,14 @@ def default_responder(response):
'Non-response object returned from view '
'(and no renderer): %r' % (response))
start_response(status, headers)
+ warnings.warn(
+ 'As of Pyramid 1.1, an object used as a response object is '
+ 'required to have a "__call__" method if an IResponder adapter is '
+ 'not registered for its type. See "Deprecations" in "What\'s New '
+ 'in Pyramid 1.1" within the general Pyramid documentation for '
+ 'further details.',
+ DeprecationWarning,
+ 3)
return app_iter
return inner
diff --git a/pyramid/testing.py b/pyramid/testing.py
index 4d7dd252a..86276df1e 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.response import HTTPForbidden
+from pyramid.httpexceptions import HTTPForbidden
from pyramid.response import Response
from pyramid.registry import Registry
from pyramid.security import Authenticated
diff --git a/pyramid/tests/fixtureapp/views.py b/pyramid/tests/fixtureapp/views.py
index 3125c972f..cbfc5a574 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.response import HTTPForbidden
+from pyramid.httpexceptions import HTTPForbidden
def fixture_view(context, request):
""" """
diff --git a/pyramid/tests/forbiddenapp/__init__.py b/pyramid/tests/forbiddenapp/__init__.py
index 9ad2dc801..7001b87f5 100644
--- a/pyramid/tests/forbiddenapp/__init__.py
+++ b/pyramid/tests/forbiddenapp/__init__.py
@@ -1,5 +1,5 @@
from webob import Response
-from pyramid.response import HTTPForbidden
+from pyramid.httpexceptions import HTTPForbidden
def x_view(request): # pragma: no cover
return Response('this is private!')
diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py
index 6817c5936..703a2577c 100644
--- a/pyramid/tests/test_config.py
+++ b/pyramid/tests/test_config.py
@@ -50,7 +50,7 @@ class ConfiguratorTests(unittest.TestCase):
return iface
def _assertNotFound(self, wrapper, *arg):
- from pyramid.response import HTTPNotFound
+ from pyramid.httpexceptions import HTTPNotFound
self.assertRaises(HTTPNotFound, wrapper, *arg)
def _registerEventListener(self, config, event_iface=None):
@@ -205,7 +205,7 @@ class ConfiguratorTests(unittest.TestCase):
def test_ctor_httpexception_view_default(self):
from pyramid.interfaces import IExceptionResponse
- from pyramid.response import default_exceptionresponse_view
+ from pyramid.httpexceptions import default_exceptionresponse_view
from pyramid.interfaces import IRequest
config = self._makeOne()
view = self._getViewCallable(config,
@@ -321,7 +321,7 @@ 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.response import HTTPNotFound
+ from pyramid.httpexceptions import HTTPNotFound
from pyramid.registry import Registry
reg = Registry()
config = self._makeOne(reg, autocommit=True)
@@ -1695,7 +1695,7 @@ class ConfiguratorTests(unittest.TestCase):
self._assertNotFound(wrapper, None, request)
def test_add_view_with_header_val_missing(self):
- from pyramid.response import HTTPNotFound
+ from pyramid.httpexceptions import HTTPNotFound
view = lambda *arg: 'OK'
config = self._makeOne(autocommit=True)
config.add_view(view=view, header=r'Host:\d')
@@ -2229,7 +2229,7 @@ class ConfiguratorTests(unittest.TestCase):
def test_set_notfound_view(self):
from zope.interface import implementedBy
from pyramid.interfaces import IRequest
- from pyramid.response import HTTPNotFound
+ from pyramid.httpexceptions import HTTPNotFound
config = self._makeOne(autocommit=True)
view = lambda *arg: arg
config.set_notfound_view(view)
@@ -2243,7 +2243,7 @@ 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.response import HTTPNotFound
+ from pyramid.httpexceptions import HTTPNotFound
config = self._makeOne(autocommit=True)
view = lambda *arg: arg
config.set_notfound_view(view)
@@ -2259,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.response import HTTPNotFound
+ from pyramid.httpexceptions import HTTPNotFound
config = self._makeOne(autocommit=True)
view = lambda *arg: {}
config.set_notfound_view(view,
@@ -2278,12 +2278,13 @@ class ConfiguratorTests(unittest.TestCase):
def test_set_forbidden_view(self):
from zope.interface import implementedBy
from pyramid.interfaces import IRequest
- from pyramid.response import Forbidden
+ from pyramid.httpexceptions import HTTPForbidden
config = self._makeOne(autocommit=True)
view = lambda *arg: 'OK'
config.set_forbidden_view(view)
request = self._makeRequest(config)
- view = self._getViewCallable(config, ctx_iface=implementedBy(Forbidden),
+ view = self._getViewCallable(config,
+ ctx_iface=implementedBy(HTTPForbidden),
request_iface=IRequest)
result = view(None, request)
self.assertEqual(result, 'OK')
@@ -2291,13 +2292,14 @@ 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.response import Forbidden
+ from pyramid.httpexceptions import HTTPForbidden
config = self._makeOne(autocommit=True)
view = lambda *arg: arg
config.set_forbidden_view(view)
request = self._makeRequest(config)
request.context = 'abc'
- view = self._getViewCallable(config, ctx_iface=implementedBy(Forbidden),
+ view = self._getViewCallable(config,
+ ctx_iface=implementedBy(HTTPForbidden),
request_iface=IRequest)
result = view(None, request)
self.assertEqual(result, ('abc', request))
@@ -2306,7 +2308,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.response import Forbidden
+ from pyramid.httpexceptions import HTTPForbidden
config = self._makeOne(autocommit=True)
view = lambda *arg: {}
config.set_forbidden_view(view,
@@ -2315,7 +2317,7 @@ class ConfiguratorTests(unittest.TestCase):
try: # chameleon requires a threadlocal registry
request = self._makeRequest(config)
view = self._getViewCallable(config,
- ctx_iface=implementedBy(Forbidden),
+ ctx_iface=implementedBy(HTTPForbidden),
request_iface=IRequest)
result = view(None, request)
finally:
@@ -3685,7 +3687,7 @@ class TestViewDeriver(unittest.TestCase):
"None against context None): True")
def test_debug_auth_permission_authpol_denied(self):
- from pyramid.response import Forbidden
+ from pyramid.httpexceptions import HTTPForbidden
view = lambda *arg: 'OK'
self.config.registry.settings = dict(
debug_authorization=True, reload_templates=True)
@@ -3700,7 +3702,7 @@ class TestViewDeriver(unittest.TestCase):
request = self._makeRequest()
request.view_name = 'view_name'
request.url = 'url'
- self.assertRaises(Forbidden, result, None, request)
+ self.assertRaises(HTTPForbidden, result, None, request)
self.assertEqual(len(logger.messages), 1)
self.assertEqual(logger.messages[0],
"debug_authorization of url url (view name "
@@ -3813,7 +3815,7 @@ class TestViewDeriver(unittest.TestCase):
self.assertEqual(predicates, [True, True])
def test_with_predicates_notall(self):
- from pyramid.response import HTTPNotFound
+ from pyramid.httpexceptions import HTTPNotFound
view = lambda *arg: 'OK'
predicates = []
def predicate1(context, request):
@@ -4621,14 +4623,14 @@ class TestMultiView(unittest.TestCase):
self.assertEqual(mv.get_views(request), mv.views)
def test_match_not_found(self):
- from pyramid.response import HTTPNotFound
+ from pyramid.httpexceptions import HTTPNotFound
mv = self._makeOne()
context = DummyContext()
request = DummyRequest()
self.assertRaises(HTTPNotFound, mv.match, context, request)
def test_match_predicate_fails(self):
- from pyramid.response import HTTPNotFound
+ from pyramid.httpexceptions import HTTPNotFound
mv = self._makeOne()
def view(context, request):
""" """
@@ -4650,7 +4652,7 @@ class TestMultiView(unittest.TestCase):
self.assertEqual(result, view)
def test_permitted_no_views(self):
- from pyramid.response import HTTPNotFound
+ from pyramid.httpexceptions import HTTPNotFound
mv = self._makeOne()
context = DummyContext()
request = DummyRequest()
@@ -4677,7 +4679,7 @@ class TestMultiView(unittest.TestCase):
self.assertEqual(result, False)
def test__call__not_found(self):
- from pyramid.response import HTTPNotFound
+ from pyramid.httpexceptions import HTTPNotFound
mv = self._makeOne()
context = DummyContext()
request = DummyRequest()
@@ -4699,7 +4701,7 @@ class TestMultiView(unittest.TestCase):
self.assertEqual(response, expected_response)
def test___call__raise_not_found_isnt_interpreted_as_pred_mismatch(self):
- from pyramid.response import HTTPNotFound
+ from pyramid.httpexceptions import HTTPNotFound
mv = self._makeOne()
context = DummyContext()
request = DummyRequest()
@@ -4724,7 +4726,7 @@ class TestMultiView(unittest.TestCase):
self.assertEqual(response, expected_response)
def test__call_permissive__not_found(self):
- from pyramid.response import HTTPNotFound
+ from pyramid.httpexceptions import HTTPNotFound
mv = self._makeOne()
context = DummyContext()
request = DummyRequest()
diff --git a/pyramid/tests/test_exceptions.py b/pyramid/tests/test_exceptions.py
index 673fb6712..50182ee5c 100644
--- a/pyramid/tests/test_exceptions.py
+++ b/pyramid/tests/test_exceptions.py
@@ -1,5 +1,16 @@
import unittest
+class TestBWCompat(unittest.TestCase):
+ def test_bwcompat_notfound(self):
+ from pyramid.exceptions import NotFound as one
+ from pyramid.httpexceptions import HTTPNotFound as two
+ self.assertTrue(one is two)
+
+ def test_bwcompat_forbidden(self):
+ from pyramid.exceptions import Forbidden as one
+ from pyramid.httpexceptions import HTTPForbidden as two
+ self.assertTrue(one is two)
+
class TestNotFound(unittest.TestCase):
def _makeOne(self, message):
from pyramid.exceptions import NotFound
@@ -14,7 +25,7 @@ class TestNotFound(unittest.TestCase):
def test_response_equivalence(self):
from pyramid.exceptions import NotFound
- from pyramid.response import HTTPNotFound
+ from pyramid.httpexceptions import HTTPNotFound
self.assertTrue(NotFound is HTTPNotFound)
class TestForbidden(unittest.TestCase):
@@ -31,6 +42,6 @@ class TestForbidden(unittest.TestCase):
def test_response_equivalence(self):
from pyramid.exceptions import Forbidden
- from pyramid.response import HTTPForbidden
+ from pyramid.httpexceptions import HTTPForbidden
self.assertTrue(Forbidden is HTTPForbidden)
diff --git a/pyramid/tests/test_httpexceptions.py b/pyramid/tests/test_httpexceptions.py
index 28adc9d3d..629bbe225 100644
--- a/pyramid/tests/test_httpexceptions.py
+++ b/pyramid/tests/test_httpexceptions.py
@@ -1,9 +1,277 @@
import unittest
-class TestIt(unittest.TestCase):
- def test_bwcompat_imports(self):
- from pyramid.httpexceptions import HTTPNotFound as one
- from pyramid.response import HTTPNotFound as two
- self.assertTrue(one is two)
+class Test_responsecode(unittest.TestCase):
+ def _callFUT(self, *arg, **kw):
+ from pyramid.httpexceptions import responsecode
+ return responsecode(*arg, **kw)
+
+ def test_status_404(self):
+ from pyramid.httpexceptions import HTTPNotFound
+ self.assertEqual(self._callFUT(404).__class__, HTTPNotFound)
+
+ def test_status_201(self):
+ from pyramid.httpexceptions import HTTPCreated
+ self.assertEqual(self._callFUT(201).__class__, HTTPCreated)
+
+ def test_extra_kw(self):
+ resp = self._callFUT(404, headers=[('abc', 'def')])
+ self.assertEqual(resp.headers['abc'], 'def')
+
+class Test_default_exceptionresponse_view(unittest.TestCase):
+ def _callFUT(self, context, request):
+ from pyramid.httpexceptions 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.httpexceptions 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.httpexceptions 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_implements_IResponse(self):
+ from pyramid.interfaces import IResponse
+ cls = self._getTargetClass()
+ self.failUnless(IResponse.implementedBy(cls))
+
+ def test_provides_IResponse(self):
+ from pyramid.interfaces import IResponse
+ inst = self._getTargetClass()()
+ self.failUnless(IResponse.providedBy(inst))
+
+ def test_implements_IExceptionResponse(self):
+ from pyramid.interfaces import IExceptionResponse
+ cls = self._getTargetClass()
+ self.failUnless(IExceptionResponse.implementedBy(cls))
+
+ def test_provides_IExceptionResponse(self):
+ from pyramid.interfaces import IExceptionResponse
+ inst = self._getTargetClass()()
+ self.failUnless(IExceptionResponse.providedBy(inst))
+
+ 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.httpexceptions 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.httpexceptions 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.httpexceptions 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_response.py b/pyramid/tests/test_response.py
index 6cc87fc0a..46eb298d1 100644
--- a/pyramid/tests/test_response.py
+++ b/pyramid/tests/test_response.py
@@ -5,304 +5,13 @@ class TestResponse(unittest.TestCase):
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):
+ def test_implements_IResponse(self):
+ from pyramid.interfaces import IResponse
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')
+ self.failUnless(IResponse.implementedBy(cls))
- 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
+ def test_provides_IResponse(self):
+ from pyramid.interfaces import IResponse
+ inst = self._getTargetClass()()
+ self.failUnless(IResponse.providedBy(inst))
diff --git a/pyramid/tests/test_router.py b/pyramid/tests/test_router.py
index a89de7a36..765a26751 100644
--- a/pyramid/tests/test_router.py
+++ b/pyramid/tests/test_router.py
@@ -2,6 +2,19 @@ import unittest
from pyramid import testing
+def hide_warnings(wrapped):
+ import warnings
+ def wrapper(*arg, **kw):
+ warnings.filterwarnings('ignore')
+ try:
+ wrapped(*arg, **kw)
+ finally:
+ warnings.resetwarnings()
+ wrapper.__name__ = wrapped.__name__
+ wrapper.__doc__ = wrapped.__doc__
+ return wrapper
+
+
class TestRouter(unittest.TestCase):
def setUp(self):
testing.setUp()
@@ -136,7 +149,7 @@ class TestRouter(unittest.TestCase):
self.assertEqual(router.request_factory, DummyRequestFactory)
def test_call_traverser_default(self):
- from pyramid.response import HTTPNotFound
+ from pyramid.httpexceptions import HTTPNotFound
environ = self._makeEnviron()
logger = self._registerLogger()
router = self._makeOne()
@@ -147,7 +160,7 @@ class TestRouter(unittest.TestCase):
self.assertEqual(len(logger.messages), 0)
def test_traverser_raises_notfound_class(self):
- from pyramid.response import HTTPNotFound
+ from pyramid.httpexceptions import HTTPNotFound
environ = self._makeEnviron()
context = DummyContext()
self._registerTraverserFactory(context, raise_error=HTTPNotFound)
@@ -156,7 +169,7 @@ class TestRouter(unittest.TestCase):
self.assertRaises(HTTPNotFound, router, environ, start_response)
def test_traverser_raises_notfound_instance(self):
- from pyramid.response import HTTPNotFound
+ from pyramid.httpexceptions import HTTPNotFound
environ = self._makeEnviron()
context = DummyContext()
self._registerTraverserFactory(context, raise_error=HTTPNotFound('foo'))
@@ -166,26 +179,27 @@ class TestRouter(unittest.TestCase):
self.assertTrue('foo' in why[0], why)
def test_traverser_raises_forbidden_class(self):
- from pyramid.response import Forbidden
+ from pyramid.httpexceptions import HTTPForbidden
environ = self._makeEnviron()
context = DummyContext()
- self._registerTraverserFactory(context, raise_error=Forbidden)
+ self._registerTraverserFactory(context, raise_error=HTTPForbidden)
router = self._makeOne()
start_response = DummyStartResponse()
- self.assertRaises(Forbidden, router, environ, start_response)
+ self.assertRaises(HTTPForbidden, router, environ, start_response)
def test_traverser_raises_forbidden_instance(self):
- from pyramid.response import Forbidden
+ from pyramid.httpexceptions import HTTPForbidden
environ = self._makeEnviron()
context = DummyContext()
- self._registerTraverserFactory(context, raise_error=Forbidden('foo'))
+ self._registerTraverserFactory(context,
+ raise_error=HTTPForbidden('foo'))
router = self._makeOne()
start_response = DummyStartResponse()
- why = exc_raised(Forbidden, router, environ, start_response)
+ why = exc_raised(HTTPForbidden, router, environ, start_response)
self.assertTrue('foo' in why[0], why)
def test_call_no_view_registered_no_isettings(self):
- from pyramid.response import HTTPNotFound
+ from pyramid.httpexceptions import HTTPNotFound
environ = self._makeEnviron()
context = DummyContext()
self._registerTraverserFactory(context)
@@ -198,7 +212,7 @@ class TestRouter(unittest.TestCase):
self.assertEqual(len(logger.messages), 0)
def test_call_no_view_registered_debug_notfound_false(self):
- from pyramid.response import HTTPNotFound
+ from pyramid.httpexceptions import HTTPNotFound
environ = self._makeEnviron()
context = DummyContext()
self._registerTraverserFactory(context)
@@ -212,7 +226,7 @@ class TestRouter(unittest.TestCase):
self.assertEqual(len(logger.messages), 0)
def test_call_no_view_registered_debug_notfound_true(self):
- from pyramid.response import HTTPNotFound
+ from pyramid.httpexceptions import HTTPNotFound
environ = self._makeEnviron()
context = DummyContext()
self._registerTraverserFactory(context)
@@ -323,7 +337,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.response import HTTPNotFound
+ from pyramid.httpexceptions import HTTPNotFound
from pyramid.interfaces import IViewClassifier
class IContext(Interface):
pass
@@ -344,7 +358,7 @@ class TestRouter(unittest.TestCase):
def test_call_view_raises_forbidden(self):
from zope.interface import Interface
from zope.interface import directlyProvides
- from pyramid.response import Forbidden
+ from pyramid.httpexceptions import HTTPForbidden
class IContext(Interface):
pass
from pyramid.interfaces import IRequest
@@ -353,12 +367,13 @@ class TestRouter(unittest.TestCase):
directlyProvides(context, IContext)
self._registerTraverserFactory(context, subpath=[''])
response = DummyResponse()
- view = DummyView(response, raise_exception=Forbidden("unauthorized"))
+ view = DummyView(response,
+ raise_exception=HTTPForbidden("unauthorized"))
environ = self._makeEnviron()
self._registerView(view, '', IViewClassifier, IRequest, IContext)
router = self._makeOne()
start_response = DummyStartResponse()
- why = exc_raised(Forbidden, router, environ, start_response)
+ why = exc_raised(HTTPForbidden, router, environ, start_response)
self.assertEqual(why[0], 'unauthorized')
def test_call_view_raises_notfound(self):
@@ -368,7 +383,7 @@ class TestRouter(unittest.TestCase):
pass
from pyramid.interfaces import IRequest
from pyramid.interfaces import IViewClassifier
- from pyramid.response import HTTPNotFound
+ from pyramid.httpexceptions import HTTPNotFound
context = DummyContext()
directlyProvides(context, IContext)
self._registerTraverserFactory(context, subpath=[''])
@@ -597,7 +612,7 @@ class TestRouter(unittest.TestCase):
"pattern: 'archives/:action/:article', "))
def test_call_route_match_miss_debug_routematch(self):
- from pyramid.response import HTTPNotFound
+ from pyramid.httpexceptions import HTTPNotFound
logger = self._registerLogger()
self._registerSettings(debug_routematch=True)
self._registerRouteRequest('foo')
@@ -658,7 +673,7 @@ class TestRouter(unittest.TestCase):
def test_root_factory_raises_notfound(self):
from pyramid.interfaces import IRootFactory
- from pyramid.response import HTTPNotFound
+ from pyramid.httpexceptions import HTTPNotFound
from zope.interface import Interface
from zope.interface import directlyProvides
def rootfactory(request):
@@ -676,11 +691,11 @@ class TestRouter(unittest.TestCase):
def test_root_factory_raises_forbidden(self):
from pyramid.interfaces import IRootFactory
- from pyramid.response import Forbidden
+ from pyramid.httpexceptions import HTTPForbidden
from zope.interface import Interface
from zope.interface import directlyProvides
def rootfactory(request):
- raise Forbidden('from root factory')
+ raise HTTPForbidden('from root factory')
self.registry.registerUtility(rootfactory, IRootFactory)
class IContext(Interface):
pass
@@ -689,7 +704,7 @@ class TestRouter(unittest.TestCase):
environ = self._makeEnviron()
router = self._makeOne()
start_response = DummyStartResponse()
- why = exc_raised(Forbidden, router, environ, start_response)
+ why = exc_raised(HTTPForbidden, router, environ, start_response)
self.assertTrue('from root factory' in why[0])
def test_root_factory_exception_propagating(self):
@@ -1057,6 +1072,52 @@ class TestRouter(unittest.TestCase):
start_response = DummyStartResponse()
self.assertRaises(RuntimeError, router, environ, start_response)
+class Test_default_responder(unittest.TestCase):
+ def _makeOne(self, response):
+ from pyramid.router import default_responder
+ return default_responder(response)
+
+ def test_has_call(self):
+ response = DummyResponse()
+ response.app_iter = ['123']
+ response.headerlist = [('a', '1')]
+ responder = self._makeOne(response)
+ request = DummyRequest({'a':'1'})
+ start_response = DummyStartResponse()
+ app_iter = responder(request, start_response)
+ self.assertEqual(app_iter, response.app_iter)
+ self.assertEqual(start_response.status, response.status)
+ self.assertEqual(start_response.headers, response.headerlist)
+ self.assertEqual(response.environ, request.environ)
+
+ @hide_warnings
+ def test_without_call_success(self):
+ response = DummyResponseWithoutCall()
+ response.app_iter = ['123']
+ response.headerlist = [('a', '1')]
+ responder = self._makeOne(response)
+ request = DummyRequest({'a':'1'})
+ start_response = DummyStartResponse()
+ app_iter = responder(request, start_response)
+ self.assertEqual(app_iter, response.app_iter)
+ self.assertEqual(start_response.status, response.status)
+ self.assertEqual(start_response.headers, response.headerlist)
+
+ @hide_warnings
+ def test_without_call_exception(self):
+ response = DummyResponseWithoutCall()
+ del response.status
+ responder = self._makeOne(response)
+ request = DummyRequest({'a':'1'})
+ start_response = DummyStartResponse()
+ self.assertRaises(ValueError, responder, request, start_response)
+
+
+class DummyRequest(object):
+ def __init__(self, environ=None):
+ if environ is None: environ = {}
+ self.environ = environ
+
class DummyContext:
pass
@@ -1085,12 +1146,20 @@ class DummyStartResponse:
def __call__(self, status, headers):
self.status = status
self.headers = headers
-
-class DummyResponse:
+
+class DummyResponseWithoutCall:
headerlist = ()
app_iter = ()
def __init__(self, status='200 OK'):
self.status = status
+
+class DummyResponse(DummyResponseWithoutCall):
+ environ = None
+
+ def __call__(self, environ, start_response):
+ self.environ = environ
+ start_response(self.status, self.headerlist)
+ return self.app_iter
class DummyThreadLocalManager:
def __init__(self):
diff --git a/pyramid/tests/test_testing.py b/pyramid/tests/test_testing.py
index 0288884b7..159a88ebd 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.response import HTTPForbidden
+ from pyramid.httpexceptions import HTTPForbidden
def view(context, request):
""" """
view = testing.registerView('moo.html', view=view, permission='bar')
diff --git a/pyramid/view.py b/pyramid/view.py
index 0b5c7cdc9..9a4be7580 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.response import HTTPFound
-from pyramid.response import default_exceptionresponse_view
+from pyramid.httpexceptions import HTTPFound
+from pyramid.httpexceptions import default_exceptionresponse_view
from pyramid.renderers import RendererHelper
from pyramid.static import static_view
from pyramid.threadlocal import get_current_registry
@@ -45,12 +45,12 @@ def render_view_to_response(context, request, name='', secure=True):
``name`` / ``context`` / and ``request``).
If `secure`` is ``True``, and the :term:`view callable` found is
- 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.response.HTTPForbidden` exception will be raised.
- The exception's ``args`` attribute explains why the view access
- was disallowed.
+ 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.httpexceptions.HTTPForbidden` exception will be raised.
+ The exception's ``args`` attribute explains why the view access was
+ disallowed.
If ``secure`` is ``False``, no permission checking is done."""
provides = [IViewClassifier] + map(providedBy, (request, context))
@@ -88,13 +88,12 @@ def render_view_to_iterable(context, request, name='', secure=True):
of this function by calling ``''.join(iterable)``, or just use
:func:`pyramid.view.render_view` instead.
- If ``secure`` is ``True``, and the view is protected by a
- 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.response.HTTPForbidden` exception will be raised;
- its ``args`` attribute explains why the view access was
- disallowed.
+ If ``secure`` is ``True``, and the view is protected by a 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.httpexceptions.HTTPForbidden` exception will be raised; its
+ ``args`` attribute explains why the view access was disallowed.
If ``secure`` is ``False``, no permission checking is
done."""
@@ -117,12 +116,11 @@ def render_view(context, request, name='', secure=True):
``app_iter`` attribute. This function will return ``None`` if a
corresponding view cannot be found.
- If ``secure`` is ``True``, and the view is protected by a
- 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.response.HTTPForbidden` exception will be raised;
- its ``args`` attribute explains why the view access was
+ If ``secure`` is ``True``, and the view is protected by a 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.httpexceptions.HTTPForbidden` exception will be
+ raised; its ``args`` attribute explains why the view access was
disallowed.
If ``secure`` is ``False``, no permission checking is done."""
@@ -249,7 +247,7 @@ class AppendSlashNotFoundViewFactory(object):
.. code-block:: python
- from pyramid.response import HTTPNotFound
+ from pyramid.httpexceptions import HTTPNotFound
from pyramid.view import AppendSlashNotFoundViewFactory
def notfound_view(context, request): return HTTPNotFound('nope')
@@ -302,7 +300,7 @@ 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.response import HTTPNotFound
+ from pyramid.httpexceptions import HTTPNotFound
from pyramid.view import append_slash_notfound_view
config.add_view(append_slash_notfound_view, context=HTTPNotFound)