Chris McDonough
2011-06-11 99edc51a3b05309c7f5d98ff96289ec51b1d7660
- 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.
39 files modified
3239 ■■■■ changed files
CHANGES.txt 77 ●●●● patch | view | raw | blame | history
TODO.txt 6 ●●●●● patch | view | raw | blame | history
docs/api/httpexceptions.rst 6 ●●●●● patch | view | raw | blame | history
docs/api/response.rst 1 ●●●● patch | view | raw | blame | history
docs/glossary.rst 4 ●●●● patch | view | raw | blame | history
docs/narr/hooks.rst 42 ●●●● patch | view | raw | blame | history
docs/narr/renderers.rst 81 ●●●● patch | view | raw | blame | history
docs/narr/router.rst 12 ●●●● patch | view | raw | blame | history
docs/narr/testing.rst 4 ●●●● patch | view | raw | blame | history
docs/narr/urldispatch.rst 4 ●●●● patch | view | raw | blame | history
docs/narr/views.rst 211 ●●●●● patch | view | raw | blame | history
docs/narr/webob.rst 23 ●●●● patch | view | raw | blame | history
docs/tutorials/wiki/authorization.rst 22 ●●●● patch | view | raw | blame | history
docs/tutorials/wiki/definingviews.rst 8 ●●●● patch | view | raw | blame | history
docs/tutorials/wiki/src/authorization/tutorial/login.py 4 ●●●● patch | view | raw | blame | history
docs/tutorials/wiki/src/authorization/tutorial/views.py 2 ●●● patch | view | raw | blame | history
docs/tutorials/wiki/src/views/tutorial/views.py 2 ●●● patch | view | raw | blame | history
docs/tutorials/wiki2/definingviews.rst 4 ●●●● patch | view | raw | blame | history
docs/tutorials/wiki2/src/authorization/tutorial/__init__.py 2 ●●● patch | view | raw | blame | history
docs/tutorials/wiki2/src/authorization/tutorial/login.py 2 ●●● patch | view | raw | blame | history
docs/tutorials/wiki2/src/authorization/tutorial/views.py 2 ●●● patch | view | raw | blame | history
docs/tutorials/wiki2/src/views/tutorial/views.py 2 ●●● patch | view | raw | blame | history
docs/whatsnew-1.1.rst 12 ●●●●● patch | view | raw | blame | history
pyramid/config.py 6 ●●●● patch | view | raw | blame | history
pyramid/exceptions.py 6 ●●●● patch | view | raw | blame | history
pyramid/httpexceptions.py 902 ●●●●● patch | view | raw | blame | history
pyramid/interfaces.py 16 ●●●● patch | view | raw | blame | history
pyramid/response.py 946 ●●●●● patch | view | raw | blame | history
pyramid/router.py 17 ●●●●● patch | view | raw | blame | history
pyramid/testing.py 2 ●●● patch | view | raw | blame | history
pyramid/tests/fixtureapp/views.py 2 ●●● patch | view | raw | blame | history
pyramid/tests/forbiddenapp/__init__.py 2 ●●● patch | view | raw | blame | history
pyramid/tests/test_config.py 46 ●●●● patch | view | raw | blame | history
pyramid/tests/test_exceptions.py 15 ●●●● patch | view | raw | blame | history
pyramid/tests/test_httpexceptions.py 278 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_response.py 305 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_router.py 117 ●●●● patch | view | raw | blame | history
pyramid/tests/test_testing.py 2 ●●● patch | view | raw | blame | history
pyramid/view.py 44 ●●●● patch | view | raw | blame | history
CHANGES.txt
@@ -37,12 +37,10 @@
- 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 @@
  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.
  ``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.
- 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``.
- 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 @@
- 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::
  interface.  See the section in the Hooks chapter of the documentation
  entitled "Changing How Pyramid Treats Response Objects".
     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.:
     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 @@
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 @@
  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
------------
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
-----------
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
docs/api/response.rst
@@ -8,3 +8,4 @@
.. autoclass:: Response
   :members:
   :inherited-members:
docs/glossary.rst
@@ -594,7 +594,7 @@
   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 @@
   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.
docs/narr/hooks.rst
@@ -21,7 +21,7 @@
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 @@
.. 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 @@
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 @@
.. 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 :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 @@
   :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 @@
.. 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 @@
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 @@
                                     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 @@
             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
docs/narr/renderers.rst
@@ -11,7 +11,6 @@
.. code-block:: python
   :linenos:
   from pyramid.response import Response
   from pyramid.view import view_config
   @view_config(renderer='json')
@@ -77,39 +76,52 @@
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.
.. code-block:: python
   :linenos:
   from pyramid.response import HTTPFound
   def view(request):
       return HTTPFound(location='http://example.com') # any renderer avoided
Likewise for a "plain old response":
:class:`pyramid.response.Response` class as a response, no renderer
will be employed.
.. code-block:: python
   :linenos:
   from pyramid.response import Response
   from pyramid.view import view_config
   @view_config(renderer='json')
   def view(request):
       return Response('OK') # any renderer avoided
       return Response('OK') # 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.
Likewise for an :term:`HTTP exception` response:
.. code-block:: python
   :linenos:
   from pyramid.httpexceptions import HTTPNotFound
   from pyramid.view import view_config
   @view_config(renderer='json')
   def view(request):
       return HTTPFound(location='http://example.com') # json renderer avoided
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 @@
       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`.
docs/narr/router.rst
@@ -82,8 +82,8 @@
   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 @@
   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
docs/narr/testing.rst
@@ -191,7 +191,7 @@
   :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 @@
           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)
docs/narr/urldispatch.rst
@@ -917,7 +917,7 @@
   :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 @@
.. code-block:: python
     :linenos:
     from pyramid.response import HTTPNotFound
     from pyramid.httpexceptions import HTTPNotFound
     from pyramid.view import AppendSlashNotFoundViewFactory
     def notfound_view(context, request):
docs/narr/views.rst
@@ -230,29 +230,29 @@
   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.
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:
status
  The HTTP status code (including the name) for the response as a string.
  E.g. ``200 OK`` or ``401 Unauthorized``.
.. code-block:: python
   :linenos:
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')]``
   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_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".
: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 @@
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,43 +280,23 @@
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:
.. code-block:: python
   :linenos:
   from pyramid.response import abort
   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.
An HTTP exception, instead of being raised, can alternately be *returned*
(HTTP exceptions are also valid response objects):
@@ -356,10 +304,53 @@
.. code-block:: python
   :linenos:
   from pyramid.response import HTTPUnauthorized
   from pyramid.httpexceptions import HTTPUnauthorized
   def aview(request):
       return HTTPUnauthorized()
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.httpexceptions import responsecode
   def aview(request):
       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 @@
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 @@
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 @@
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.
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.
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.
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
docs/narr/webob.rst
@@ -362,20 +362,21 @@
: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 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.
docs/tutorials/wiki/authorization.rst
@@ -131,17 +131,17 @@
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
~~~~~~~~~~~~~~~~~~~~~~~
docs/tutorials/wiki/definingviews.rst
@@ -83,10 +83,10 @@
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
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 @@
@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')
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
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
docs/tutorials/wiki2/definingviews.rst
@@ -90,8 +90,8 @@
   :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.
docs/tutorials/wiki2/src/authorization/tutorial/__init__.py
@@ -39,7 +39,7 @@
    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()
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
docs/tutorials/wiki2/src/authorization/tutorial/views.py
@@ -2,7 +2,7 @@
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
docs/tutorials/wiki2/src/views/tutorial/views.py
@@ -2,7 +2,7 @@
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
docs/whatsnew-1.1.rst
@@ -63,12 +63,6 @@
  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
@@ -80,6 +74,10 @@
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
@@ -177,7 +175,7 @@
  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
pyramid/config.py
@@ -56,10 +56,10 @@
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
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``
pyramid/httpexceptions.py
@@ -110,7 +110,907 @@
``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
pyramid/interfaces.py
@@ -46,10 +46,14 @@
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 @@
    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 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. """
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 IResponse
from pyramid.interfaces import IExceptionResponse
class Response(_Response, Exception):
    implements(IExceptionResponse)
class Response(_Response):
    implements(IResponse)
    
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
    Raise this exception within :term:`view` code to immediately
    return the :term:`Not Found view` to the invoking user.  Usually
    this is a basic ``404`` page, but the Not Found view can be
    customized as necessary.  See :ref:`changing_the_notfound_view`.
    This exception's constructor accepts a ``detail`` argument
    (the first argument), which should be a string.  The value of this
    string will be available as the ``message`` attribute of this exception,
    for availability to the :term:`Not Found View`.
    """
    code = 404
    title = 'Not Found'
    explanation = ('The resource could not be found.')
class HTTPMethodNotAllowed(HTTPClientError):
    """
    subclass of :class:`~HTTPClientError`
    This indicates that the method specified in the Request-Line is
    not allowed for the resource identified by the Request-URI.
    code: 405, title: Method Not Allowed
    """
    # differences from webob.exc.HTTPMethodNotAllowed:
    #
    # - body_template_obj not overridden (it tried to use request environ's
    #   REQUEST_METHOD)
    code = 405
    title = 'Method Not Allowed'
class HTTPNotAcceptable(HTTPClientError):
    """
    subclass of :class:`~HTTPClientError`
    This indicates the resource identified by the request is only
    capable of generating response entities which have content
    characteristics not acceptable according to the accept headers
    sent in the request.
    code: 406, title: Not Acceptable
    """
    # differences from webob.exc.HTTPNotAcceptable:
    #
    # - body_template_obj not overridden (it tried to use request environ's
    #   HTTP_ACCEPT)
    code = 406
    title = 'Not Acceptable'
class HTTPProxyAuthenticationRequired(HTTPClientError):
    """
    subclass of :class:`~HTTPClientError`
    This is similar to 401, but indicates that the client must first
    authenticate itself with the proxy.
    code: 407, title: Proxy Authentication Required
    """
    code = 407
    title = 'Proxy Authentication Required'
    explanation = ('Authentication with a local proxy is needed.')
class HTTPRequestTimeout(HTTPClientError):
    """
    subclass of :class:`~HTTPClientError`
    This indicates that the client did not produce a request within
    the time that the server was prepared to wait.
    code: 408, title: Request Timeout
    """
    code = 408
    title = 'Request Timeout'
    explanation = ('The server has waited too long for the request to '
                   'be sent by the client.')
class HTTPConflict(HTTPClientError):
    """
    subclass of :class:`~HTTPClientError`
    This indicates that the request could not be completed due to a
    conflict with the current state of the resource.
    code: 409, title: Conflict
    """
    code = 409
    title = 'Conflict'
    explanation = ('There was a conflict when trying to complete '
                   'your request.')
class HTTPGone(HTTPClientError):
    """
    subclass of :class:`~HTTPClientError`
    This indicates that the requested resource is no longer available
    at the server and no forwarding address is known.
    code: 410, title: Gone
    """
    code = 410
    title = 'Gone'
    explanation = ('This resource is no longer available.  No forwarding '
                   'address is given.')
class HTTPLengthRequired(HTTPClientError):
    """
    subclass of :class:`~HTTPClientError`
    This indicates that the the server refuses to accept the request
    without a defined Content-Length.
    code: 411, title: Length Required
    """
    code = 411
    title = 'Length Required'
    explanation = ('Content-Length header required.')
class HTTPPreconditionFailed(HTTPClientError):
    """
    subclass of :class:`~HTTPClientError`
    This indicates that the precondition given in one or more of the
    request-header fields evaluated to false when it was tested on the
    server.
    code: 412, title: Precondition Failed
    """
    code = 412
    title = 'Precondition Failed'
    explanation = ('Request precondition failed.')
class HTTPRequestEntityTooLarge(HTTPClientError):
    """
    subclass of :class:`~HTTPClientError`
    This indicates that the server is refusing to process a request
    because the request entity is larger than the server is willing or
    able to process.
    code: 413, title: Request Entity Too Large
    """
    code = 413
    title = 'Request Entity Too Large'
    explanation = ('The body of your request was too large for this server.')
class HTTPRequestURITooLong(HTTPClientError):
    """
    subclass of :class:`~HTTPClientError`
    This indicates that the server is refusing to service the request
    because the Request-URI is longer than the server is willing to
    interpret.
    code: 414, title: Request-URI Too Long
    """
    code = 414
    title = 'Request-URI Too Long'
    explanation = ('The request URI was too long for this server.')
class HTTPUnsupportedMediaType(HTTPClientError):
    """
    subclass of :class:`~HTTPClientError`
    This indicates that the server is refusing to service the request
    because the entity of the request is in a format not supported by
    the requested resource for the requested method.
    code: 415, title: Unsupported Media Type
    """
    # differences from webob.exc.HTTPUnsupportedMediaType:
    #
    # - body_template_obj not overridden (it tried to use request environ's
    #   CONTENT_TYPE)
    code = 415
    title = 'Unsupported Media Type'
class HTTPRequestRangeNotSatisfiable(HTTPClientError):
    """
    subclass of :class:`~HTTPClientError`
    The server SHOULD return a response with this status code if a
    request included a Range request-header field, and none of the
    range-specifier values in this field overlap the current extent
    of the selected resource, and the request did not include an
    If-Range request-header field.
    code: 416, title: Request Range Not Satisfiable
    """
    code = 416
    title = 'Request Range Not Satisfiable'
    explanation = ('The Range requested is not available.')
class HTTPExpectationFailed(HTTPClientError):
    """
    subclass of :class:`~HTTPClientError`
    This indidcates that the expectation given in an Expect
    request-header field could not be met by this server.
    code: 417, title: Expectation Failed
    """
    code = 417
    title = 'Expectation Failed'
    explanation = ('Expectation failed.')
class HTTPUnprocessableEntity(HTTPClientError):
    """
    subclass of :class:`~HTTPClientError`
    This indicates that the server is unable to process the contained
    instructions. Only for WebDAV.
    code: 422, title: Unprocessable Entity
    """
    ## Note: from WebDAV
    code = 422
    title = 'Unprocessable Entity'
    explanation = 'Unable to process the contained instructions'
class HTTPLocked(HTTPClientError):
    """
    subclass of :class:`~HTTPClientError`
    This indicates that the resource is locked. Only for WebDAV
    code: 423, title: Locked
    """
    ## Note: from WebDAV
    code = 423
    title = 'Locked'
    explanation = ('The resource is locked')
class HTTPFailedDependency(HTTPClientError):
    """
    subclass of :class:`~HTTPClientError`
    This indicates that the method could not be performed because the
    requested action depended on another action and that action failed.
    Only for WebDAV.
    code: 424, title: Failed Dependency
    """
    ## Note: from WebDAV
    code = 424
    title = 'Failed Dependency'
    explanation = (
        'The method could not be performed because the requested '
        'action dependended on another action and that action failed')
############################################################
## 5xx Server Error
############################################################
#  Response status codes beginning with the digit "5" indicate cases in
#  which the server is aware that it has erred or is incapable of
#  performing the request. Except when responding to a HEAD request, the
#  server SHOULD include an entity containing an explanation of the error
#  situation, and whether it is a temporary or permanent condition. User
#  agents SHOULD display any included entity to the user. These response
#  codes are applicable to any request method.
class HTTPServerError(HTTPError):
    """
    base class for the 500's, where the server is in-error
    This is an error condition in which the server is presumed to be
    in-error.  This is usually unexpected, and thus requires a traceback;
    ideally, opening a support ticket for the customer. Unless specialized,
    this is a '500 Internal Server Error'
    """
    code = 500
    title = 'Internal Server Error'
    explanation = (
      'The server has either erred or is incapable of performing '
      'the requested operation.')
class HTTPInternalServerError(HTTPServerError):
    pass
class HTTPNotImplemented(HTTPServerError):
    """
    subclass of :class:`~HTTPServerError`
    This indicates that the server does not support the functionality
    required to fulfill the request.
    code: 501, title: Not Implemented
    """
    # differences from webob.exc.HTTPNotAcceptable:
    #
    # - body_template_obj not overridden (it tried to use request environ's
    #   REQUEST_METHOD)
    code = 501
    title = 'Not Implemented'
class HTTPBadGateway(HTTPServerError):
    """
    subclass of :class:`~HTTPServerError`
    This indicates that the server, while acting as a gateway or proxy,
    received an invalid response from the upstream server it accessed
    in attempting to fulfill the request.
    code: 502, title: Bad Gateway
    """
    code = 502
    title = 'Bad Gateway'
    explanation = ('Bad gateway.')
class HTTPServiceUnavailable(HTTPServerError):
    """
    subclass of :class:`~HTTPServerError`
    This indicates that the server is currently unable to handle the
    request due to a temporary overloading or maintenance of the server.
    code: 503, title: Service Unavailable
    """
    code = 503
    title = 'Service Unavailable'
    explanation = ('The server is currently unavailable. '
                   'Please try again at a later time.')
class HTTPGatewayTimeout(HTTPServerError):
    """
    subclass of :class:`~HTTPServerError`
    This indicates that the server, while acting as a gateway or proxy,
    did not receive a timely response from the upstream server specified
    by the URI (e.g. HTTP, FTP, LDAP) or some other auxiliary server
    (e.g. DNS) it needed to access in attempting to complete the request.
    code: 504, title: Gateway Timeout
    """
    code = 504
    title = 'Gateway Timeout'
    explanation = ('The gateway has timed out.')
class HTTPVersionNotSupported(HTTPServerError):
    """
    subclass of :class:`~HTTPServerError`
    This indicates that the server does not support, or refuses to
    support, the HTTP protocol version that was used in the request
    message.
    code: 505, title: HTTP Version Not Supported
    """
    code = 505
    title = 'HTTP Version Not Supported'
    explanation = ('The HTTP version is not supported.')
class HTTPInsufficientStorage(HTTPServerError):
    """
    subclass of :class:`~HTTPServerError`
    This indicates that the server does not have enough space to save
    the resource.
    code: 507, title: Insufficient Storage
    """
    code = 507
    title = 'Insufficient Storage'
    explanation = ('There was not enough space to save the resource')
NotFound = HTTPNotFound # bw compat
Forbidden = HTTPForbidden # bw compat
class PredicateMismatch(NotFound):
    """
    Internal exception (not an API) raised by multiviews when no
    view matches.  This exception subclasses the ``NotFound``
    exception only one reason: if it reaches the main exception
    handler, it should be treated like a ``NotFound`` by any exception
    view registrations.
    """
class URLDecodeError(UnicodeDecodeError):
    """
    This exception is raised when :app:`Pyramid` cannot
    successfully decode a URL or a URL path segment.  This exception
    it behaves just like the Python builtin
    :exc:`UnicodeDecodeError`. It is a subclass of the builtin
    :exc:`UnicodeDecodeError` exception only for identity purposes,
    mostly so an exception view can be registered when a URL cannot be
    decoded.
    """
class ConfigurationError(ZCE):
    """ Raised when inappropriate input values are supplied to an API
    method of a :term:`Configurator`"""
def abort(status_code, **kw):
    """Aborts the request immediately by raising an HTTP exception based on a
    status code. Example::
        abort(404) # raises an HTTPNotFound exception.
    The values passed as ``kw`` are provided to the exception's constructor.
    """
    exc = status_map[status_code](**kw)
    raise exc
def redirect(url, code=302, **kw):
    """Raises an :class:`~HTTPFound` (302) redirect exception to the
    URL specified by ``url``.
    Optionally, a code variable may be passed with the status code of
    the redirect, ie::
        redirect(route_url('foo', request), code=303)
    The values passed as ``kw`` are provided to the exception constructor.
    """
    exc = status_map[code]
    raise exc(location=url, **kw)
def default_exceptionresponse_view(context, request):
    if not isinstance(context, Exception):
        # backwards compat for an exception response view registered via
        # config.set_notfound_view or config.set_forbidden_view
        # instead of as a proper exception view
        context = request.exception or context
    return context
status_map={}
for name, value in globals().items():
    if (isinstance(value, (type, types.ClassType)) and
        issubclass(value, HTTPException)
        and not name.startswith('_')):
        code = getattr(value, 'code', None)
        if code:
            status_map[code] = value
del name, value
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.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 @@
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 @@
                '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
pyramid/testing.py
@@ -17,7 +17,7 @@
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
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):
    """ """
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!')
pyramid/tests/test_config.py
@@ -50,7 +50,7 @@
        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 @@
    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 @@
    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 @@
        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 @@
    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 @@
    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 @@
    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 @@
    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 @@
    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 @@
    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 @@
        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 @@
                         "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 @@
        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 @@
        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 @@
        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 @@
        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 @@
        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 @@
        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 @@
        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()
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 @@
    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 @@
    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)
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
pyramid/tests/test_response.py
@@ -5,304 +5,13 @@
        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
        self.failUnless(IResponse.implementedBy(cls))
    def _makeOne(self, *arg, **kw):
        cls = self._getTargetClass()
        return cls(*arg, **kw)
    def test_ctor_sets_detail(self):
        exc = self._makeOne('message')
        self.assertEqual(exc.detail, 'message')
    def test_ctor_sets_comment(self):
        exc = self._makeOne(comment='comment')
        self.assertEqual(exc.comment, 'comment')
    def test_ctor_calls_Exception_ctor(self):
        exc = self._makeOne('message')
        self.assertEqual(exc.message, 'message')
    def test_ctor_calls_Response_ctor(self):
        exc = self._makeOne('message')
        self.assertEqual(exc.status, 'None None')
    def test_ctor_extends_headers(self):
        exc = self._makeOne(headers=[('X-Foo', 'foo')])
        self.assertEqual(exc.headers.get('X-Foo'), 'foo')
    def test_ctor_sets_body_template_obj(self):
        exc = self._makeOne(body_template='${foo}')
        self.assertEqual(
            exc.body_template_obj.substitute({'foo':'foo'}), 'foo')
    def test_ctor_with_empty_body(self):
        cls = self._getTargetSubclass(empty_body=True)
        exc = cls()
        self.assertEqual(exc.content_type, None)
        self.assertEqual(exc.content_length, None)
    def test_ctor_with_body_doesnt_set_default_app_iter(self):
        exc = self._makeOne(body='123')
        self.assertEqual(exc.app_iter, ['123'])
    def test_ctor_with_unicode_body_doesnt_set_default_app_iter(self):
        exc = self._makeOne(unicode_body=u'123')
        self.assertEqual(exc.app_iter, ['123'])
    def test_ctor_with_app_iter_doesnt_set_default_app_iter(self):
        exc = self._makeOne(app_iter=['123'])
        self.assertEqual(exc.app_iter, ['123'])
    def test_ctor_with_body_sets_default_app_iter_html(self):
        cls = self._getTargetSubclass()
        exc = cls('detail')
        body = list(exc.app_iter)[0]
        self.assertTrue(body.startswith('<html'))
        self.assertTrue('200 OK' in body)
        self.assertTrue('explanation' in body)
        self.assertTrue('detail' in body)
    def test_ctor_with_body_sets_default_app_iter_text(self):
        cls = self._getTargetSubclass()
        exc = cls('detail')
        exc.content_type = 'text/plain'
        body = list(exc.app_iter)[0]
        self.assertEqual(body, '200 OK\n\nexplanation\n\n\ndetail\n\n')
    def test__str__detail(self):
        exc = self._makeOne()
        exc.detail = 'abc'
        self.assertEqual(str(exc), 'abc')
    def test__str__explanation(self):
        exc = self._makeOne()
        exc.explanation = 'def'
        self.assertEqual(str(exc), 'def')
    def test_wsgi_response(self):
        exc = self._makeOne()
        self.assertTrue(exc is exc.wsgi_response)
    def test_exception(self):
        exc = self._makeOne()
        self.assertTrue(exc is exc.exception)
    def test__default_app_iter_no_comment_plain(self):
        cls = self._getTargetSubclass()
        exc = cls()
        exc.content_type = 'text/plain'
        body = list(exc._default_app_iter())[0]
        self.assertEqual(body, '200 OK\n\nexplanation\n\n\n\n\n')
    def test__default_app_iter_with_comment_plain(self):
        cls = self._getTargetSubclass()
        exc = cls(comment='comment')
        exc.content_type = 'text/plain'
        body = list(exc._default_app_iter())[0]
        self.assertEqual(body, '200 OK\n\nexplanation\n\n\n\ncomment\n')
    def test__default_app_iter_no_comment_html(self):
        cls = self._getTargetSubclass()
        exc = cls()
        exc.content_type = 'text/html'
        body = list(exc._default_app_iter())[0]
        self.assertFalse('<!-- ' in body)
    def test__default_app_iter_with_comment_html(self):
        cls = self._getTargetSubclass()
        exc = cls(comment='comment & comment')
        exc.content_type = 'text/html'
        body = list(exc._default_app_iter())[0]
        self.assertTrue('<!-- comment &amp; comment -->' in body)
    def test_custom_body_template_no_environ(self):
        cls = self._getTargetSubclass()
        exc = cls(body_template='${location}', location='foo')
        exc.content_type = 'text/plain'
        body = list(exc._default_app_iter())[0]
        self.assertEqual(body, '200 OK\n\nfoo')
    def test_custom_body_template_with_environ(self):
        cls = self._getTargetSubclass()
        from pyramid.request import Request
        request = Request.blank('/')
        exc = cls(body_template='${REQUEST_METHOD}', request=request)
        exc.content_type = 'text/plain'
        body = list(exc._default_app_iter())[0]
        self.assertEqual(body, '200 OK\n\nGET')
    def test_body_template_unicode(self):
        from pyramid.request import Request
        cls = self._getTargetSubclass()
        la = unicode('/La Pe\xc3\xb1a', 'utf-8')
        request = Request.blank('/')
        request.environ['unicodeval'] = la
        exc = cls(body_template='${unicodeval}', request=request)
        exc.content_type = 'text/plain'
        body = list(exc._default_app_iter())[0]
        self.assertEqual(body, '200 OK\n\n/La Pe\xc3\xb1a')
class TestRenderAllExceptionsWithoutArguments(unittest.TestCase):
    def _doit(self, content_type):
        from pyramid.response import status_map
        L = []
        self.assertTrue(status_map)
        for v in status_map.values():
            exc = v()
            exc.content_type = content_type
            result = list(exc.app_iter)[0]
            if exc.empty_body:
                self.assertEqual(result, '')
            else:
                self.assertTrue(exc.status in result)
            L.append(result)
        self.assertEqual(len(L), len(status_map))
    def test_it_plain(self):
        self._doit('text/plain')
    def test_it_html(self):
        self._doit('text/html')
class Test_HTTPMove(unittest.TestCase):
    def _makeOne(self, *arg, **kw):
        from pyramid.response import _HTTPMove
        return _HTTPMove(*arg, **kw)
    def test_it_location_not_passed(self):
        exc = self._makeOne()
        self.assertEqual(exc.location, '')
    def test_it_location_passed(self):
        exc = self._makeOne(location='foo')
        self.assertEqual(exc.location, 'foo')
class TestHTTPForbidden(unittest.TestCase):
    def _makeOne(self, *arg, **kw):
        from pyramid.response import HTTPForbidden
        return HTTPForbidden(*arg, **kw)
    def test_it_result_not_passed(self):
        exc = self._makeOne()
        self.assertEqual(exc.result, None)
    def test_it_result_passed(self):
        exc = self._makeOne(result='foo')
        self.assertEqual(exc.result, 'foo')
class DummyRequest(object):
    exception = None
    def test_provides_IResponse(self):
        from pyramid.interfaces import IResponse
        inst = self._getTargetClass()()
        self.failUnless(IResponse.providedBy(inst))
pyramid/tests/test_router.py
@@ -2,6 +2,19 @@
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 @@
        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 @@
        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 @@
        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 @@
        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 @@
        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 @@
        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 @@
    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 @@
    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 @@
        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 @@
            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 @@
            "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 @@
    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 @@
    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 @@
        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 @@
        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 @@
    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):
pyramid/tests/test_testing.py
@@ -150,7 +150,7 @@
    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')
pyramid/view.py
@@ -8,8 +8,8 @@
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 @@
    ``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 @@
    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 @@
    ``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 @@
    .. 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 @@
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)