summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@plope.com>2011-06-14 05:34:47 -0400
committerChris McDonough <chrism@plope.com>2011-06-14 05:34:47 -0400
commit31d78e7ea78343dcf9405a86d4d817a1efda16dc (patch)
tree53762e1b87ee7e03e2cdb12cc5b3aee7ff4b942c
parenta4d5525cdbb6b7e614939b20a340b989258779ca (diff)
parentcecfc9e459166f3de13141954a61eaa2d6c905f2 (diff)
downloadpyramid-31d78e7ea78343dcf9405a86d4d817a1efda16dc.tar.gz
pyramid-31d78e7ea78343dcf9405a86d4d817a1efda16dc.tar.bz2
pyramid-31d78e7ea78343dcf9405a86d4d817a1efda16dc.zip
merge httpexception-utils branch
-rw-r--r--CHANGES.txt101
-rw-r--r--TODO.txt16
-rw-r--r--docs/api/config.rst2
-rw-r--r--docs/api/httpexceptions.rst2
-rw-r--r--docs/api/interfaces.rst2
-rw-r--r--docs/api/request.rst33
-rw-r--r--docs/api/response.rst1
-rw-r--r--docs/designdefense.rst4
-rw-r--r--docs/glossary.rst26
-rw-r--r--docs/narr/assets.rst2
-rw-r--r--docs/narr/hooks.rst145
-rw-r--r--docs/narr/renderers.rst75
-rw-r--r--docs/narr/router.rst59
-rw-r--r--docs/narr/testing.rst13
-rw-r--r--docs/narr/urldispatch.rst6
-rw-r--r--docs/narr/views.rst236
-rw-r--r--docs/narr/webob.rst105
-rw-r--r--docs/tutorials/wiki/authorization.rst22
-rw-r--r--docs/tutorials/wiki/definingviews.rst10
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/login.py2
-rw-r--r--docs/tutorials/wiki2/definingviews.rst3
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/__init__.py2
-rw-r--r--docs/whatsnew-1.1.rst35
-rw-r--r--pyramid/__init__.py7
-rw-r--r--pyramid/config.py105
-rw-r--r--pyramid/exceptions.py83
-rw-r--r--pyramid/httpexceptions.py1055
-rw-r--r--pyramid/interfaces.py230
-rw-r--r--pyramid/registry.py17
-rw-r--r--pyramid/renderers.py4
-rw-r--r--pyramid/request.py11
-rw-r--r--pyramid/response.py9
-rw-r--r--pyramid/router.py29
-rw-r--r--pyramid/session.py14
-rw-r--r--pyramid/testing.py4
-rw-r--r--pyramid/tests/fixtureapp/views.py4
-rw-r--r--pyramid/tests/forbiddenapp/__init__.py4
-rw-r--r--pyramid/tests/test_config.py212
-rw-r--r--pyramid/tests/test_exceptions.py54
-rw-r--r--pyramid/tests/test_httpexceptions.py306
-rw-r--r--pyramid/tests/test_request.py28
-rw-r--r--pyramid/tests/test_response.py17
-rw-r--r--pyramid/tests/test_router.py110
-rw-r--r--pyramid/tests/test_session.py15
-rw-r--r--pyramid/tests/test_testing.py4
-rw-r--r--pyramid/tests/test_view.py31
-rw-r--r--pyramid/view.py92
47 files changed, 2684 insertions, 663 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index 0254ac2b0..c58ff755b 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -39,6 +39,11 @@ Documentation
- Added API docs for ``pyramid.authentication.SessionAuthenticationPolicy``.
+- Added API docs for ``pyramid.httpexceptions.responsecode``.
+
+- Added "HTTP Exceptions" section to Views narrative chapter including a
+ description of ``pyramid.httpexceptions.responsecode``.
+
Features
--------
@@ -109,6 +114,48 @@ Features
section entitled "Static Routes" in the URL Dispatch narrative chapter for
more information.
+- A default exception view for the context
+ ``pyramid.interfaces.IExceptionResponse`` is now registered by default.
+ This means that an instance of any exception response class imported from
+ ``pyramid.httpexceptions`` (such as ``HTTPFound``) can now be raised from
+ within view code; when raised, this exception view will render the
+ exception to a response.
+
+- A function named ``pyramid.httpexceptions.responsecode`` is a shortcut that
+ can be used to create HTTP exception response objects using an HTTP integer
+ status code.
+
+- The Configurator now accepts an additional keyword argument named
+ ``exceptionresponse_view``. By default, this argument is populated with a
+ default exception view function that will be used when a response is raised
+ as an exception. When ``None`` is passed for this value, an exception view
+ for responses will not be registered. Passing ``None`` returns the
+ behavior of raising an HTTP exception to that of Pyramid 1.0 (the exception
+ will propagate to middleware and to the WSGI server).
+
+- The ``pyramid.request.Request`` class now has a ``ResponseClass`` interface
+ which points at ``pyramid.response.Response``.
+
+- The ``pyramid.request.Response`` class now has a ``RequestClass`` interface
+ which points at ``pyramid.response.Request``.
+
+- It is now possible to return an arbitrary object from a Pyramid view
+ callable even if a renderer is not used, as long as a suitable adapter to
+ ``pyramid.interfaces.IResponse`` is registered for the type of the returned
+ object by using the new
+ ``pyramid.config.Configurator.add_response_adapter`` API. See the section
+ in the Hooks chapter of the documentation entitled "Changing How Pyramid
+ Treats View Responses".
+
+- 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.
+
+- New method named ``pyramid.request.Request.is_response``. This method
+ should be used instead of the ``pyramid.view.is_response`` function, which
+ has been deprecated.
+
Bug Fixes
---------
@@ -239,6 +286,14 @@ Deprecations
1.0 and before). In a future version, these methods will be removed
entirely.
+- Deprecated ``pyramid.view.is_response`` function in favor of (newly-added)
+ ``pyramid.request.Request.is_response`` method. Determining if an object
+ is truly a valid response object now requires access to the registry, which
+ is only easily available as a request attribute. The
+ ``pyramid.view.is_response`` function will still work until it is removed,
+ but now may return an incorrect answer under some (very uncommon)
+ circumstances.
+
Behavior Changes
----------------
@@ -249,7 +304,7 @@ Behavior Changes
For example, ${ myhtml | n }.
See https://github.com/Pylons/pyramid/issues/193.
-- 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
@@ -280,6 +335,50 @@ Behavior Changes
implements its own ``__getattr__``, ``__setattr__`` or ``__delattr__`` as a
result.
+- ``pyramid.response.Response`` is now a *subclass* of
+ ``webob.response.Response`` (in order to directly implement the
+ ``pyramid.interfaces.IResponse`` interface).
+
+- The "exception response" objects importable from ``pyramid.httpexceptions``
+ (e.g. ``HTTPNotFound``) are no longer just import aliases for classes that
+ actually live in ``webob.exc``. Instead, we've defined our own exception
+ classes within the module that mirror and emulate the ``webob.exc``
+ exception response objects almost entirely. We do this in order to a)
+ allow the exception responses to subclass ``pyramid.response.Response``,
+ which speeds up response generation slightly due to the way the Pyramid
+ router works, b) allows us to provide alternate __call__ logic which also
+ speeds up response generation, c) allows the exception classes to provide
+ for the proper value of ``self.RequestClass`` (pyramid.request.Request), d)
+ allows us freedom from having to think about backwards compatibility code
+ present in ``webob.exc`` having to do with Python 2.4, which we no longer
+ support, e) We change the behavior of two classes (HTTPNotFound and
+ HTTPForbidden) in the module so that they can be used internally for
+ notfound and forbidden exceptions, f) allows us to influence the docstrings
+ of the exception classes to provide Pyramid-specific documentation, and g)
+ allows us to silence a stupid deprecation warning under Python 2.6 when the
+ response objects are used as exceptions (related to ``self.message``).
+
+Backwards Incompatibilities
+---------------------------
+
+- The Pyramid router now, by default, expects response objects returned from
+ view callables to implement the ``pyramid.interfaces.IResponse`` interface.
+ Unlike the Pyramid 1.0 version of this interface, objects which implement
+ IResponse now must define a ``__call__`` method that accepts ``environ``
+ and ``start_response``, and which returns an ``app_iter`` iterable, among
+ other things. Previously, it was possible to return any object which had
+ the three WebOb ``app_iter``, ``headerlist``, and ``status`` attributes as
+ a response, so this is a backwards incompatibility. It is possible to get
+ backwards compatibility back by registering an adapter to IResponse from
+ the type of object you're now returning from view callables. See the
+ section in the Hooks chapter of the documentation entitled "Changing How
+ Pyramid Treats View Responses".
+
+- The ``pyramid.interfaces.IResponse`` interface is now much more extensive.
+ Previously it defined only ``app_iter``, ``status`` and ``headerlist``; now
+ it is basically intended to directly mirror the ``webob.Response`` API,
+ which has many methods and attributes.
+
Dependencies
------------
diff --git a/TODO.txt b/TODO.txt
index 0f7d6342c..fb72d42b5 100644
--- a/TODO.txt
+++ b/TODO.txt
@@ -1,13 +1,21 @@
Pyramid TODOs
=============
+Must-Have
+---------
+
+- Copy exception templates from webob.exc into pyramid.httpexceptions and
+ ensure they all work.
+
+- Docs mention ``exception.args[0]`` as a way to get messages; check that
+ this works.
+
+- Deprecate response_foo attrs on request at attribute set time rather than
+ lookup time.
+
Should-Have
-----------
-- Consider adding a default exception view for HTTPException and attendant
- ``redirect`` and ``abort`` functions ala Pylons (promised Mike I'd enable
- this in 1.1).
-
- Add narrative docs for wsgiapp and wsgiapp2.
Nice-to-Have
diff --git a/docs/api/config.rst b/docs/api/config.rst
index 2b9d7bcef..274ee0292 100644
--- a/docs/api/config.rst
+++ b/docs/api/config.rst
@@ -40,6 +40,8 @@
.. automethod:: add_renderer(name, factory)
+ .. automethod:: add_response_adapter
+
.. automethod:: add_route
.. automethod:: add_static_view(name, path, cache_max_age=3600, permission='__no_permission_required__')
diff --git a/docs/api/httpexceptions.rst b/docs/api/httpexceptions.rst
index 57ca8092c..325d5af03 100644
--- a/docs/api/httpexceptions.rst
+++ b/docs/api/httpexceptions.rst
@@ -11,6 +11,8 @@
integer "401" maps to
:class:`pyramid.httpexceptions.HTTPUnauthorized`).
+ .. autofunction:: responsecode
+
.. autoclass:: HTTPException
.. autoclass:: HTTPOk
diff --git a/docs/api/interfaces.rst b/docs/api/interfaces.rst
index ac282fbcc..51a1963b5 100644
--- a/docs/api/interfaces.rst
+++ b/docs/api/interfaces.rst
@@ -57,4 +57,6 @@ Other Interfaces
.. autointerface:: IMultiDict
:members:
+ .. autointerface:: IResponse
+ :members:
diff --git a/docs/api/request.rst b/docs/api/request.rst
index 8cb424658..27ce395ac 100644
--- a/docs/api/request.rst
+++ b/docs/api/request.rst
@@ -107,7 +107,9 @@
return {'text':'Value that will be used by the renderer'}
Mutations to this response object will be preserved in the response sent
- to the client after rendering.
+ to the client after rendering. For more information about using
+ ``request.response`` in conjunction with a renderer, see
+ :ref:`request_response_attr`.
Non-renderer code can also make use of request.response instead of
creating a response "by hand". For example, in view code::
@@ -162,20 +164,21 @@
.. attribute:: response_*
- .. warning:: As of Pyramid 1.1, assignment to ``response_*`` attrs are
- deprecated. Assigning to one will cause a deprecation warning to be
- emitted. Instead of assigning ``response_*`` attributes to the
- request, use API of the the :attr:`pyramid.request.Request.response`
- object (exposed to view code as ``request.response``) to influence
- response behavior.
-
- You can set attributes on a :class:`pyramid.request.Request` which will
- influence the behavor of *rendered* responses (views which use a
- :term:`renderer` and which don't directly return a response). These
- attributes begin with ``response_``, such as ``response_headerlist``. If
- you need to influence response values from a view that uses a renderer
- (such as the status code, a header, the content type, etc) see,
- :ref:`response_prefixed_attrs`.
+ In Pyramid 1.0, you could set attributes on a
+ :class:`pyramid.request.Request` which influenced the behavor of
+ *rendered* responses (views which use a :term:`renderer` and which
+ don't directly return a response). These attributes began with
+ ``response_``, such as ``response_headerlist``. If you needed to
+ influence response values from a view that uses a renderer (such as the
+ status code, a header, the content type, etc) you would set these
+ attributes. See :ref:`response_prefixed_attrs` for further discussion.
+ As of Pyramid 1.1, assignment to ``response_*`` attrs are deprecated.
+ Assigning to one is still supported but will cause a deprecation
+ warning to be emitted, and eventually the feature will be removed. For
+ new code, instead of assigning ``response_*`` attributes to the
+ request, use API of the the :attr:`pyramid.request.Request.response`
+ object (exposed to view code as ``request.response``) to influence
+ rendered response behavior.
.. note::
diff --git a/docs/api/response.rst b/docs/api/response.rst
index c545b4977..e67b15568 100644
--- a/docs/api/response.rst
+++ b/docs/api/response.rst
@@ -8,3 +8,4 @@
.. autoclass:: Response
:members:
:inherited-members:
+
diff --git a/docs/designdefense.rst b/docs/designdefense.rst
index 0321113fa..77711016d 100644
--- a/docs/designdefense.rst
+++ b/docs/designdefense.rst
@@ -428,7 +428,7 @@ allowing people to define "custom" view predicates:
:linenos:
from pyramid.view import view_config
- from webob import Response
+ from pyramid.response import Response
def subpath(context, request):
return request.subpath and request.subpath[0] == 'abc'
@@ -1558,7 +1558,7 @@ comments take into account what we've discussed in the
.. code-block:: python
:linenos:
- from webob import Response # explicit response objects, no TL
+ from pyramid.response import Response # explicit response objects, no TL
from paste.httpserver import serve # explicitly WSGI
def hello_world(request): # accepts a request; no request thread local reqd
diff --git a/docs/glossary.rst b/docs/glossary.rst
index 579d89afd..e45317dae 100644
--- a/docs/glossary.rst
+++ b/docs/glossary.rst
@@ -16,12 +16,12 @@ Glossary
positional argument, returns a ``WebOb`` compatible request.
response
- An object that has three attributes: ``app_iter`` (representing an
- iterable body), ``headerlist`` (representing the http headers sent
- to the user agent), and ``status`` (representing the http status
- string sent to the user agent). This is the interface defined for
- ``WebOb`` response objects. See :ref:`webob_chapter` for
- information about response objects.
+ An object returned by a :term:`view callable` that represents response
+ data returned to the requesting user agent. It must implements the
+ :class:`pyramid.interfaces.IResponse` interface. A response object is
+ typically an instance of the :class:`pyramid.response.Response` class or
+ a subclass such as :class:`pyramid.httpexceptions.HTTPFound`. See
+ :ref:`webob_chapter` for information about response objects.
Repoze
"Repoze" is essentially a "brand" of software developed by `Agendaless
@@ -594,7 +594,7 @@ Glossary
Not Found view
An :term:`exception view` invoked by :app:`Pyramid` when the
- developer explicitly raises a ``pyramid.exceptions.NotFound``
+ developer explicitly raises a ``pyramid.httpexceptions.HTTPNotFound``
exception from within :term:`view` code or :term:`root factory`
code, or when the current request doesn't match any :term:`view
configuration`. :app:`Pyramid` provides a default
@@ -604,7 +604,7 @@ Glossary
Forbidden view
An :term:`exception view` invoked by :app:`Pyramid` when the
developer explicitly raises a
- ``pyramid.exceptions.Forbidden`` 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.
@@ -618,6 +618,12 @@ Glossary
request processing. See :ref:`exception_views` for more
information.
+ HTTP Exception
+ The set of exception classes defined in :mod:`pyramid.httpexceptions`.
+ These can be used to generate responses with various status codes when
+ raised or returned from a :term:`view callable`. See also
+ :ref:`http_exceptions`.
+
thread local
A thread-local variable is one which is essentially a global variable
in terms of how it is accessed and treated, however, each `thread
@@ -894,5 +900,7 @@ Glossary
http://docs.python.org/distutils/index.html for more information.
:term:`setuptools` is actually an *extension* of the Distutils.
-
+ exception response
+ A :term:`response` that is generated as the result of a raised exception
+ being caught by an :term:`exception view`.
diff --git a/docs/narr/assets.rst b/docs/narr/assets.rst
index 8d0e7058c..0d50b0106 100644
--- a/docs/narr/assets.rst
+++ b/docs/narr/assets.rst
@@ -358,7 +358,7 @@ do so, do things "by hand". First define the view callable.
:linenos:
import os
- from webob import Response
+ from pyramid.response import Response
def favicon_view(request):
here = os.path.dirname(__file__)
diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst
index be139ad74..8426f11fd 100644
--- a/docs/narr/hooks.rst
+++ b/docs/narr/hooks.rst
@@ -21,7 +21,7 @@ configuration.
The :term:`not found view` callable is a view callable like any other. The
:term:`view configuration` which causes it to be a "not found" view consists
-only of naming the :exc:`pyramid.exceptions.NotFound` class as the
+only of naming the :exc:`pyramid.httpexceptions.HTTPNotFound` class as the
``context`` of the view configuration.
If your application uses :term:`imperative configuration`, you can replace
@@ -31,9 +31,9 @@ method to register an "exception view":
.. code-block:: python
:linenos:
- from pyramid.exceptions import NotFound
+ from pyramid.httpexceptions import HTTPNotFound
from helloworld.views import notfound_view
- config.add_view(notfound_view, context=NotFound)
+ config.add_view(notfound_view, context=HTTPNotFound)
Replace ``helloworld.views.notfound_view`` with a reference to the
:term:`view callable` you want to use to represent the Not Found view.
@@ -42,8 +42,8 @@ Like any other view, the notfound view must accept at least a ``request``
parameter, or both ``context`` and ``request``. The ``request`` is the
current :term:`request` representing the denied action. The ``context`` (if
used in the call signature) will be the instance of the
-:exc:`~pyramid.exceptions.NotFound` exception that caused the view to be
-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:
@@ -56,19 +56,20 @@ Here's some sample code that implements a minimal NotFound view callable:
return HTTPNotFound()
.. note:: When a NotFound view callable is invoked, it is passed a
- :term:`request`. The ``exception`` attribute of the request will
- be an instance of the :exc:`~pyramid.exceptions.NotFound`
- exception that caused the not found view to be called. The value
- of ``request.exception.args[0]`` will be a value explaining why the
- not found error was raised. This message will be different when
- the ``debug_notfound`` environment setting is true than it is when
- it is false.
+ :term:`request`. The ``exception`` attribute of the request will be an
+ instance of the :exc:`~pyramid.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
+ ``debug_notfound`` environment setting is true than it is when it is
+ false.
.. warning:: When a NotFound view callable accepts an argument list as
described in :ref:`request_and_context_view_definitions`, the ``context``
passed as the first argument to the view callable will be the
- :exc:`~pyramid.exceptions.NotFound` exception instance. If available, the
- resource context will still be available as ``request.context``.
+ :exc:`~pyramid.httpexceptions.HTTPNotFound` exception instance. If
+ available, the resource context will still be available as
+ ``request.context``.
.. index::
single: forbidden view
@@ -85,7 +86,7 @@ the view which generates it can be overridden as necessary.
The :term:`forbidden view` callable is a view callable like any other. The
:term:`view configuration` which causes it to be a "forbidden" view consists
-only of naming the :exc:`pyramid.exceptions.Forbidden` 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,8 +97,8 @@ view":
:linenos:
from helloworld.views import forbidden_view
- from pyramid.exceptions import Forbidden
- config.add_view(forbidden_view, context=Forbidden)
+ from pyramid.httpexceptions import HTTPForbidden
+ config.add_view(forbidden_view, context=HTTPForbidden)
Replace ``helloworld.views.forbidden_view`` with a reference to the Python
:term:`view callable` you want to use to represent the Forbidden view.
@@ -121,13 +122,13 @@ Here's some sample code that implements a minimal forbidden view:
return Response('forbidden')
.. note:: When a forbidden view callable is invoked, it is passed a
- :term:`request`. The ``exception`` attribute of the request will
- be an instance of the :exc:`~pyramid.exceptions.Forbidden`
- exception that caused the forbidden view to be called. The value
- of ``request.exception.args[0]`` will be a value explaining why the
- forbidden was raised. This message will be different when the
- ``debug_authorization`` environment setting is true than it is when
- it is false.
+ :term:`request`. The ``exception`` attribute of the request will be an
+ instance of the :exc:`~pyramid.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
+ false.
.. index::
single: request factory
@@ -522,6 +523,100 @@ The default context URL generator is available for perusal as the class
:term:`Pylons` GitHub Pyramid repository.
.. index::
+ single: IResponse
+
+.. _using_iresponse:
+
+Changing How Pyramid Treats View Responses
+------------------------------------------
+
+It is possible to control how Pyramid treats the result of calling a view
+callable on a per-type basis by using a hook involving
+:method:`pyramid.config.Configurator.add_response_adapter`.
+
+.. note:: This feature is new as of Pyramid 1.1.
+
+Pyramid, in various places, adapts the result of calling a view callable to
+the :class:`~pyramid.interfaces.IResponse` interface to ensure that the
+object returned by the view callable is a "true" response object. The vast
+majority of time, the result of this adaptation is the result object itself,
+as view callables written by "civilians" who read the narrative documentation
+contained in this manual will always return something that implements the
+:class:`~pyramid.interfaces.IResponse` interface. Most typically, this will
+be an instance of the :class:`pyramid.response.Response` class or a subclass.
+If a civilian returns a non-Response object from a view callable that isn't
+configured to use a :term:`renderer`, he will typically expect the router to
+raise an error. However, you can hook Pyramid in such a way that users can
+return arbitrary values from a view callable by providing an adapter which
+converts the arbitrary return value into something that implements
+:class:`~pyramid.interfaces.IResponse`.
+
+For example, if you'd like to allow view callables to return bare string
+objects (without requiring a a :term:`renderer` to convert a string to a
+response object), you can register an adapter which converts the string to a
+Response:
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.response import Response
+
+ def string_response_adapter(s):
+ response = Response(s)
+ return response
+
+ # config is an instance of pyramid.config.Configurator
+
+ config.add_response_adapter(string_response_adapter, str)
+
+Likewise, if you want to be able to return a simplified kind of response
+object from view callables, you can use the IResponse hook to register an
+adapter to the more complex IResponse interface:
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.response import Response
+
+ class SimpleResponse(object):
+ def __init__(self, body):
+ self.body = body
+
+ def simple_response_adapter(simple_response):
+ response = Response(simple_response.body)
+ return response
+
+ # config is an instance of pyramid.config.Configurator
+
+ config.add_response_adapter(simple_response_adapter, SimpleResponse)
+
+If you want to implement your own Response object instead of using the
+:class:`pyramid.response.Response` object in any capacity at all, you'll have
+to make sure the object implements every attribute and method outlined in
+:class:`pyramid.interfaces.IResponse` and you'll have to ensure that it's
+marked up with ``zope.interface.implements(IResponse)``:
+
+ from pyramid.interfaces import IResponse
+ from zope.interface import implements
+
+ class MyResponse(object):
+ implements(IResponse)
+ # ... an implementation of every method and attribute
+ # documented in IResponse should follow ...
+
+When an alternate response object implementation is returned by a view
+callable, if that object asserts that it implements
+:class:`~pyramid.interfaces.IResponse` (via
+``zope.interface.implements(IResponse)``) , an adapter needn't be registered
+for the object; Pyramid will use it directly.
+
+An IResponse adapter for ``webob.Response`` (as opposed to
+:class:`pyramid.response.Response`) is registered by Pyramid by default at
+startup time, as by their nature, instances of this class (and instances of
+subclasses of the class) will natively provide IResponse. The adapter
+registered for ``webob.Response`` simply returns the response object.
+
+.. index::
single: view mapper
.. _using_a_view_mapper:
@@ -590,7 +685,7 @@ A user might make use of these framework components like so:
# user application
- from webob import Response
+ from pyramid.response import Response
from pyramid.config import Configurator
import pyramid_handlers
from paste.httpserver import serve
diff --git a/docs/narr/renderers.rst b/docs/narr/renderers.rst
index b284fe73f..18cc8e539 100644
--- a/docs/narr/renderers.rst
+++ b/docs/narr/renderers.rst
@@ -72,30 +72,56 @@ When this configuration is added to an application, the
which renders view return values to a :term:`JSON` response serialization.
Other built-in renderers include renderers which use the :term:`Chameleon`
-templating language to render a dictionary to a response.
+templating language to render a dictionary to a response. Additional
+renderers can be added by developers to the system as necessary (see
+:ref:`adding_and_overriding_renderers`).
+
+Views which use a renderer 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.httpexceptions.HTTPFound` class as a response, no renderer
+:class:`pyramid.response.Response` class as a response, no renderer
will be employed.
.. code-block:: python
:linenos:
- from pyramid.httpexceptions import HTTPFound
+ from pyramid.response import Response
+ from pyramid.view import view_config
+
+ @view_config(renderer='json')
+ def view(request):
+ return Response('OK') # json renderer avoided
+
+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') # any renderer avoided
+ return HTTPFound(location='http://example.com') # json renderer avoided
-Views which use a renderer can vary non-body response attributes (such as
-headers and the HTTP status code) by attaching a property to the
-``request.response`` attribute See :ref:`request_response_attr`.
+You can of course also return the ``request.response`` attribute instead to
+avoid rendering:
-Additional renderers can be added by developers to the system as necessary
-(see :ref:`adding_and_overriding_renderers`).
+.. 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)
@@ -363,9 +389,34 @@ callable that uses a renderer, assign the ``status`` attribute to the
request.response.status = '404 Not Found'
return {'URL':request.URL}
+Note that mutations of ``request.response`` in views which return a Response
+object directly will have no effect unless the response object returned *is*
+``request.response``. For example, the following example calls
+``request.response.set_cookie``, but this call will have no effect, because a
+different Response object is returned.
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.response import Response
+
+ def view(request):
+ request.response.set_cookie('abc', '123') # this has no effect
+ return Response('OK') # because we're returning a different response
+
+If you mutate ``request.response`` and you'd like the mutations to have an
+effect, you must return ``request.response``:
+
+.. code-block:: python
+ :linenos:
+
+ def view(request):
+ request.response.set_cookie('abc', '123')
+ return request.response
+
For more information on attributes of the request, see the API documentation
in :ref:`request_module`. For more information on the API of
-``request.response``, see :class:`pyramid.response.Response`.
+``request.response``, see :attr:`pyramid.request.Request.response`.
.. _response_prefixed_attrs:
diff --git a/docs/narr/router.rst b/docs/narr/router.rst
index 11f84d4ea..0812f7ec7 100644
--- a/docs/narr/router.rst
+++ b/docs/narr/router.rst
@@ -77,40 +77,37 @@ processing?
#. A :class:`~pyramid.events.ContextFound` :term:`event` is
sent to any subscribers.
-#. :app:`Pyramid` looks up a :term:`view` callable using the
- context, the request, and the view name. If a view callable
- doesn't exist for this combination of objects (based on the type of
- the context, the type of the request, and the value of the view
- name, and any :term:`predicate` attributes applied to the view
- configuration), :app:`Pyramid` raises a
- :class:`~pyramid.exceptions.NotFound` exception, which is meant
- to be caught by a surrounding exception handler.
+#. :app:`Pyramid` looks up a :term:`view` callable using the context, the
+ request, and the view name. If a view callable doesn't exist for this
+ combination of objects (based on the type of the context, the type of the
+ request, and the value of the view name, and any :term:`predicate`
+ attributes applied to the view configuration), :app:`Pyramid` raises a
+ :class:`~pyramid.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.
-#. If an :term:`authorization policy` is in use, and the view was
- protected by a :term:`permission`, :app:`Pyramid` passes the
- context, the request, and the view_name to a function which
- determines whether the view being asked for can be executed by the
- requesting user, based on credential information in the request and
- security information attached to the context. If it returns
- ``True``, :app:`Pyramid` calls the view callable to obtain a
- response. If it returns ``False``, it raises a
- :class:`~pyramid.exceptions.Forbidden` exception, which is meant
- to be called by a surrounding exception handler.
+#. If an :term:`authorization policy` is in use, and the view was protected
+ by a :term:`permission`, :app:`Pyramid` passes the context, the request,
+ and the view_name to a function which determines whether the view being
+ asked for can be executed by the requesting user, based on credential
+ information in the request and security information attached to the
+ context. If it returns ``True``, :app:`Pyramid` calls the view callable
+ to obtain a response. If it returns ``False``, it raises a
+ :class:`~pyramid.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.exceptions.NotFound` or
- :class:`~pyramid.exceptions.Forbidden`), the router catches the
- exception, and attaches it to the request as the ``exception``
- attribute. It then attempts to find a :term:`exception view` for
- the exception that was caught. If it finds an exception view
- callable, that callable is called, and is presumed to generate a
- response. If an :term:`exception view` that matches the exception
- cannot be found, the exception is reraised.
+ :term:`traversal`, by a :term:`view callable` or by :app:`Pyramid` itself
+ (such as when it raises :class:`~pyramid.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
+ called, and is presumed to generate a response. If an :term:`exception
+ view` that matches the exception cannot be found, the exception is
+ reraised.
#. The following steps occur only when a :term:`response` could be
successfully generated by a normal :term:`view callable` or an
@@ -118,9 +115,9 @@ processing?
any :term:`response callback` functions attached via
:meth:`~pyramid.request.Request.add_response_callback`. A
:class:`~pyramid.events.NewResponse` :term:`event` is then sent to any
- subscribers. The response object's ``app_iter``, ``status``, and
- ``headerlist`` attributes are then used to generate a WSGI response. The
- response is sent back to the upstream WSGI server.
+ subscribers. The response object's ``__call__`` method is then used to
+ generate a WSGI response. The response is sent back to the upstream WSGI
+ server.
#. :app:`Pyramid` will attempt to execute any :term:`finished
callback` functions attached via
diff --git a/docs/narr/testing.rst b/docs/narr/testing.rst
index bd45388c2..05e851fde 100644
--- a/docs/narr/testing.rst
+++ b/docs/narr/testing.rst
@@ -191,11 +191,11 @@ function.
:linenos:
from pyramid.security import has_permission
- from pyramid.exceptions import Forbidden
+ from pyramid.httpexceptions import HTTPForbidden
def view_fn(request):
if not has_permission('edit', request.context, request):
- raise Forbidden
+ raise HTTPForbidden
return {'greeting':'hello'}
Without doing anything special during a unit test, the call to
@@ -207,7 +207,7 @@ application registry is not created and populated (e.g. by initializing the
configurator with an authorization policy), like when you invoke application
code via a unit test, :app:`Pyramid` API functions will tend to either fail
or return default results. So how do you test the branch of the code in this
-view function that raises :exc:`Forbidden`?
+view function that raises :exc:`HTTPForbidden`?
The testing API provided by :app:`Pyramid` allows you to simulate various
application registry registrations for use under a unit testing framework
@@ -230,16 +230,15 @@ without needing to invoke the actual application configuration implied by its
testing.tearDown()
def test_view_fn_forbidden(self):
- from pyramid.exceptions import Forbidden
+ from pyramid.httpexceptions import HTTPForbidden
from my.package import view_fn
self.config.testing_securitypolicy(userid='hank',
permissive=False)
request = testing.DummyRequest()
request.context = testing.DummyResource()
- self.assertRaises(Forbidden, view_fn, request)
+ self.assertRaises(HTTPForbidden, view_fn, request)
def test_view_fn_allowed(self):
- from pyramid.exceptions import Forbidden
from my.package import view_fn
self.config.testing_securitypolicy(userid='hank',
permissive=True)
@@ -265,7 +264,7 @@ We call the function being tested with the manufactured request. When the
function is called, :func:`pyramid.security.has_permission` will call the
"dummy" authentication policy we've registered through
:meth:`~pyramid.config.Configuration.testing_securitypolicy`, which denies
-access. We check that the view function raises a :exc:`Forbidden` error.
+access. We check that the view function raises a :exc:`HTTPForbidden` error.
The second test method, named ``test_view_fn_allowed`` tests the alternate
case, where the authentication policy allows access. Notice that we pass
diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst
index 5df1eb3af..f94ed3ba8 100644
--- a/docs/narr/urldispatch.rst
+++ b/docs/narr/urldispatch.rst
@@ -917,7 +917,7 @@ the application's startup configuration, adding the following stanza:
:linenos:
config.add_view('pyramid.view.append_slash_notfound_view',
- context='pyramid.exceptions.NotFound')
+ 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,14 +945,14 @@ view as the first argument to its constructor. For instance:
.. code-block:: python
:linenos:
- from pyramid.exceptions import NotFound
+ from pyramid.httpexceptions import HTTPNotFound
from pyramid.view import AppendSlashNotFoundViewFactory
def notfound_view(context, request):
return HTTPNotFound('It aint there, stop trying!')
custom_append_slash = AppendSlashNotFoundViewFactory(notfound_view)
- config.add_view(custom_append_slash, context=NotFound)
+ config.add_view(custom_append_slash, context=HTTPNotFound)
The ``notfound_view`` supplied must adhere to the two-argument view callable
calling convention of ``(context, request)`` (``context`` will be the
diff --git a/docs/narr/views.rst b/docs/narr/views.rst
index 5c9bd91af..e3d0a37e5 100644
--- a/docs/narr/views.rst
+++ b/docs/narr/views.rst
@@ -51,14 +51,14 @@ the request object contains everything your application needs to know
about the specific HTTP request being made.
A view callable's ultimate responsibility is to create a :mod:`Pyramid`
-:term:`Response` object. This can be done by creating the response
-object in the view callable code and returning it directly, as we will
-be doing in this chapter. However, if a view callable does not return a
-response itself, it can be configured to use a :term:`renderer` that
-converts its return value into a :term:`Response` object. Using
-renderers is the common way that templates are used with view callables
-to generate markup. See the :ref:`renderers_chapter` chapter for
-details.
+:term:`Response` object. This can be done by creating the response object in
+the view callable code and returning it directly, as we will be doing in this
+chapter. However, if a view callable does not return a response itself, it
+can be configured to use a :term:`renderer` that converts its return value
+into a :term:`Response` object. Using renderers is the common way that
+templates are used with view callables to generate markup: see the
+:ref:`renderers_chapter` chapter for details. In some cases, a response may
+also be generated by raising an exception within a view callable.
.. index::
single: view calling convention
@@ -230,112 +230,130 @@ implements the :term:`Response` interface is to return a
def view(request):
return Response('OK')
-You don't need to always use :class:`~pyramid.response.Response` to represent
-a response. :app:`Pyramid` provides a range of different "exception" classes
-which can act as response objects too. For example, an instance of the class
-:class:`pyramid.httpexceptions.HTTPFound` is also a valid response object
-(see :ref:`http_redirect`). A view can actually return any object that has
-the following attributes.
+:app:`Pyramid` provides a range of different "exception" classes which
+inherit from :class:`pyramid.response.Response`. For example, an instance of
+the class :class:`pyramid.httpexceptions.HTTPFound` is also a valid response
+object because it inherits from :class:`~pyramid.response.Response`. For
+examples, see :ref:`http_exceptions` and ref:`http_redirect`.
-status
- The HTTP status code (including the name) for the response as a string.
- E.g. ``200 OK`` or ``401 Unauthorized``.
+You can also return objects from view callables that aren't instances of (or
+instances of classes which are subclasses of)
+:class:`pyramid.response.Response` in various circumstances. This can be
+helpful when writing tests and when attempting to share code between view
+callables. See :ref:`renderers_chapter` for the common way to allow for
+this. A much less common way to allow for view callables to return
+non-Response objects is documented in :ref:`using_iresponse`.
-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')]``
+.. index::
+ single: view exceptions
-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.
+.. _special_exceptions_in_callables:
+
+Using Special Exceptions In View Callables
+------------------------------------------
+
+Usually when a Python exception is raised within a view callable,
+:app:`Pyramid` allows the exception to propagate all the way out to the
+:term:`WSGI` server which invoked the application. It is usually caught and
+logged there.
-These attributes form the structure of the "Pyramid Response interface".
+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. These are known as :term:`HTTP
+exception` objects.
.. index::
- single: view http redirect
- single: http redirect (from a view)
+ single: HTTP exceptions
-.. _http_redirect:
+.. _http_exceptions:
-Using a View Callable to Do an HTTP Redirect
---------------------------------------------
+HTTP Exceptions
+~~~~~~~~~~~~~~~
+
+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.
-You can issue an HTTP redirect from within a view by returning a particular
-kind of response.
+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.httpexceptions import HTTPFound
+ from pyramid.httpexceptions import HTTPUnauthorized
- def myview(request):
- return HTTPFound(location='http://example.com')
+ def aview(request):
+ raise HTTPUnauthorized()
-All exception types from the :mod:`pyramid.httpexceptions` module implement
-the :term:`Response` interface; any can be returned as the response from a
-view. See :mod:`pyramid.httpexceptions` for the documentation for the
-``HTTPFound`` exception; it also includes other response types that imply
-other HTTP response codes, such as ``HTTPUnauthorized`` for ``401
-Unauthorized``.
+An HTTP exception, instead of being raised, can alternately be *returned*
+(HTTP exceptions are also valid response objects):
-.. note::
+.. code-block:: python
+ :linenos:
- Although exception types from the :mod:`pyramid.httpexceptions` module are
- in fact bona fide Python :class:`Exception` types, the :app:`Pyramid` view
- machinery expects them to be *returned* by a view callable rather than
- *raised*.
+ from pyramid.httpexceptions import HTTPUnauthorized
- It is possible, however, in Python 2.5 and above, to configure an
- *exception view* to catch these exceptions, and return an appropriate
- :class:`~pyramid.response.Response`. The simplest such view could just
- catch and return the original exception. See :ref:`exception_views` for
- more details.
+ def aview(request):
+ return HTTPUnauthorized()
-.. index::
- single: view exceptions
+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.
-.. _special_exceptions_in_callables:
+.. code-block:: python
+ :linenos:
-Using Special Exceptions In View Callables
-------------------------------------------
+ from pyramid.httpexceptions import responsecode
-Usually when a Python exception is raised within a view callable,
-:app:`Pyramid` allows the exception to propagate all the way out to the
-:term:`WSGI` server which invoked the application.
+ 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`.
-However, for convenience, two special exceptions exist which are always
-handled by :app:`Pyramid` itself. These are
-:exc:`pyramid.exceptions.NotFound` and :exc:`pyramid.exceptions.Forbidden`.
-Both are exception classes which accept a single positional constructor
-argument: a ``message``.
+How Pyramid Uses HTTP Exceptions
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-If :exc:`~pyramid.exceptions.NotFound` is raised within view code, the result
-of the :term:`Not Found View` will be returned to the user agent which
-performed the request.
+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.exceptions.Forbidden` is raised within view code, the result
-of the :term:`Forbidden View` will be returned to the user agent which
-performed the request.
+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.
-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]``.
+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
.. _exception_views:
-Exception Views
----------------
+Custom Exception Views
+----------------------
-The machinery which allows the special :exc:`~pyramid.exceptions.NotFound` and
-:exc:`~pyramid.exceptions.Forbidden` exceptions to be caught by specialized
-views as described in :ref:`special_exceptions_in_callables` can also be used
-by application developers to convert arbitrary exceptions to responses.
+The machinery which allows 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
@@ -359,6 +377,7 @@ raises a ``helloworld.exceptions.ValidationFailure`` exception:
.. code-block:: python
:linenos:
+ from pyramid.view import view_config
from helloworld.exceptions import ValidationFailure
@view_config(context=ValidationFailure)
@@ -370,8 +389,8 @@ raises a ``helloworld.exceptions.ValidationFailure`` exception:
Assuming that a :term:`scan` was run to pick up this view registration, this
view callable will be invoked whenever a
``helloworld.exceptions.ValidationFailure`` is raised by your application's
-view code. The same exception raised by a custom root factory or a custom
-traverser is also caught and hooked.
+view code. The same exception raised by a custom root factory, a custom
+traverser, or a custom view or route predicate is also caught and hooked.
Other normal view predicates can also be used in combination with an
exception view registration:
@@ -380,12 +399,13 @@ exception view registration:
:linenos:
from pyramid.view import view_config
- from pyramid.exceptions import NotFound
- from pyramid.httpexceptions import HTTPNotFound
+ from helloworld.exceptions import ValidationFailure
- @view_config(context=NotFound, route_name='home')
- def notfound_view(request):
- return HTTPNotFound()
+ @view_config(context=ValidationFailure, route_name='home')
+ def failed_validation(exc, request):
+ response = Response('Failed validation: %s' % exc.msg)
+ response.status_int = 500
+ return response
The above exception view names the ``route_name`` of ``home``, meaning that
it will only be called when the route matched has a name of ``home``. You
@@ -407,7 +427,45 @@ exception views which have a name will be ignored.
can use an exception as ``context`` for a normal view.
Exception views can be configured with any view registration mechanism:
-``@view_config`` decorator, ZCML, or imperative ``add_view`` styles.
+``@view_config`` decorator or imperative ``add_view`` styles.
+
+.. index::
+ single: view http redirect
+ single: http redirect (from a view)
+
+.. _http_redirect:
+
+Using a View Callable to Do an HTTP Redirect
+--------------------------------------------
+
+You can issue an HTTP redirect by using the
+:class:`pyramid.httpexceptions.HTTPFound` class. Raising or returning an
+instance of this class will cause the client to receive a "302 Found"
+response.
+
+To do so, you can *return* a :class:`pyramid.httpexceptions.HTTPFound`
+instance.
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.httpexceptions import HTTPFound
+
+ def myview(request):
+ return HTTPFound(location='http://example.com')
+
+Alternately, you can *raise* an HTTPFound exception instead of returning one.
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.httpexceptions import HTTPFound
+
+ def myview(request):
+ raise HTTPFound(location='http://example.com')
+
+When the instance is raised, it is caught by the default :term:`exception
+response` handler and turned into a response.
.. index::
single: unicode, views, and forms
diff --git a/docs/narr/webob.rst b/docs/narr/webob.rst
index 072ca1c74..0ff8e1de7 100644
--- a/docs/narr/webob.rst
+++ b/docs/narr/webob.rst
@@ -10,15 +10,15 @@ Request and Response Objects
.. note:: This chapter is adapted from a portion of the :term:`WebOb`
documentation, originally written by Ian Bicking.
-:app:`Pyramid` uses the :term:`WebOb` package to supply
+:app:`Pyramid` uses the :term:`WebOb` package as a basis for its
:term:`request` and :term:`response` object implementations. The
-:term:`request` object that is passed to a :app:`Pyramid`
-:term:`view` is an instance of the :class:`pyramid.request.Request`
-class, which is a subclass of :class:`webob.Request`. The
-:term:`response` returned from a :app:`Pyramid` :term:`view`
-:term:`renderer` is an instance of the :mod:`webob.Response` class.
-Users can also return an instance of :mod:`webob.Response` directly
-from a view as necessary.
+:term:`request` object that is passed to a :app:`Pyramid` :term:`view` is an
+instance of the :class:`pyramid.request.Request` class, which is a subclass
+of :class:`webob.Request`. The :term:`response` returned from a
+:app:`Pyramid` :term:`view` :term:`renderer` is an instance of the
+:mod:`pyramid.response.Response` class, which is a subclass of the
+:class:`webob.Response` class. Users can also return an instance of
+:class:`pyramid.response.Response` directly from a view as necessary.
WebOb is a project separate from :app:`Pyramid` with a separate set of
authors and a fully separate `set of documentation
@@ -26,16 +26,15 @@ authors and a fully separate `set of documentation
standard WebOb request, which is documented in the :ref:`request_module` API
documentation.
-WebOb provides objects for HTTP requests and responses. Specifically
-it does this by wrapping the `WSGI <http://wsgi.org>`_ request
-environment and response status/headers/app_iter (body).
+WebOb provides objects for HTTP requests and responses. Specifically it does
+this by wrapping the `WSGI <http://wsgi.org>`_ request environment and
+response status, header list, and app_iter (body) values.
-WebOb request and response objects provide many conveniences for
-parsing WSGI requests and forming WSGI responses. WebOb is a nice way
-to represent "raw" WSGI requests and responses; however, we won't
-cover that use case in this document, as users of :app:`Pyramid`
-don't typically need to use the WSGI-related features of WebOb
-directly. The `reference documentation
+WebOb request and response objects provide many conveniences for parsing WSGI
+requests and forming WSGI responses. WebOb is a nice way to represent "raw"
+WSGI requests and responses; however, we won't cover that use case in this
+document, as users of :app:`Pyramid` don't typically need to use the
+WSGI-related features of WebOb directly. The `reference documentation
<http://pythonpaste.org/webob/reference.html>`_ shows many examples of
creating requests and using response objects in this manner, however.
@@ -170,9 +169,9 @@ of the request. I'll show various values for an example URL
Methods
+++++++
-There are `several methods
-<http://pythonpaste.org/webob/class-webob.Request.html#__init__>`_ but
-only a few you'll use often:
+There are methods of request objects documented in
+:class:`pyramid.request.Request` but you'll find that you won't use very many
+of them. Here are a couple that might be useful:
``Request.blank(base_url)``:
Creates a new request with blank information, based at the given
@@ -183,9 +182,9 @@ only a few you'll use often:
subrequests).
``req.get_response(wsgi_application)``:
- This method calls the given WSGI application with this request,
- and returns a `Response`_ object. You can also use this for
- subrequests, or testing.
+ This method calls the given WSGI application with this request, and
+ returns a :class:`pyramid.response.Response` object. You can also use
+ this for subrequests, or testing.
.. index::
single: request (and unicode)
@@ -259,8 +258,10 @@ Response
~~~~~~~~
The :app:`Pyramid` response object can be imported as
-:class:`pyramid.response.Response`. This import location is merely a facade
-for its original location: ``webob.Response``.
+:class:`pyramid.response.Response`. This class is a subclass of the
+``webob.Response`` class. The subclass does not add or change any
+functionality, so the WebOb Response documentation will be completely
+relevant for this class as well.
A response object has three fundamental parts:
@@ -283,8 +284,8 @@ A response object has three fundamental parts:
``response.body_file`` (a file-like object; writing to it appends
to ``app_iter``).
-Everything else in the object derives from this underlying state.
-Here's the highlights:
+Everything else in the object typically derives from this underlying state.
+Here are some highlights:
``response.content_type``
The content type *not* including the ``charset`` parameter.
@@ -359,16 +360,18 @@ Exception Responses
+++++++++++++++++++
To facilitate error responses like ``404 Not Found``, the module
-:mod:`webob.exc` contains classes for each kind of error response. These
-include boring, but appropriate error bodies. The exceptions exposed by this
-module, when used under :app:`Pyramid`, should be imported from the
-:mod:`pyramid.httpexceptions` "facade" module. This import location is merely
-a facade for the original location of these exceptions: ``webob.exc``.
-
-Each class is named ``pyramid.httpexceptions.HTTP*``, where ``*`` is the reason
-for the error. For instance, :class:`pyramid.httpexceptions.HTTPNotFound`. It
-subclasses :class:`pyramid.Response`, so you can manipulate the instances in
-the same way. A typical example is:
+:mod:`pyramid.httpexceptions` contains classes for each kind of error
+response. These include boring, but appropriate error bodies. The
+exceptions exposed by this module, when used under :app:`Pyramid`, should be
+imported from the :mod:`pyramid.httpexceptions` module. This import location
+contains subclasses and replacements that mirror those in the ``webob.exc``
+module.
+
+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
@@ -381,33 +384,11 @@ the same way. A typical example is:
# or:
response = HTTPMovedPermanently(location=new_url)
-These are not exceptions unless you are using Python 2.5+, because
-they are new-style classes which are not allowed as exceptions until
-Python 2.5. To get an exception object use ``response.exception``.
-You can use this like:
-
-.. code-block:: python
- :linenos:
-
- from pyramid.httpexceptions import HTTPException
- from pyramid.httpexceptions import HTTPNotFound
-
- def aview(request):
- try:
- # ... stuff ...
- raise HTTPNotFound('No such resource').exception
- except HTTPException, e:
- return request.get_response(e)
-
-The exceptions are still WSGI applications, but you cannot set
-attributes like ``content_type``, ``charset``, etc. on these exception
-objects.
-
More Details
++++++++++++
More details about the response object API are available in the
-:mod:`pyramid.response` documentation. More details about exception responses
-are in the :mod:`pyramid.httpexceptions` API documentation. The `WebOb
-documentation <http://pythonpaste.org/webob>`_ is also useful.
+:mod:`pyramid.response` documentation. More details about exception
+responses are in the :mod:`pyramid.httpexceptions` API documentation. The
+`WebOb documentation <http://pythonpaste.org/webob>`_ is also useful.
diff --git a/docs/tutorials/wiki/authorization.rst b/docs/tutorials/wiki/authorization.rst
index 358c1d5eb..46c953f6d 100644
--- a/docs/tutorials/wiki/authorization.rst
+++ b/docs/tutorials/wiki/authorization.rst
@@ -145,17 +145,17 @@ callable.
The first view configuration decorator configures the ``login`` view callable
so it will be invoked when someone visits ``/login`` (when the context is a
Wiki and the view name is ``login``). The second decorator (with context of
-``pyramid.exceptions.Forbidden``) specifies a :term:`forbidden view`. This
-configures our login view to be presented to the user when :app:`Pyramid`
-detects that a view invocation can not be authorized. Because we've
-configured a forbidden view, the ``login`` view callable will be invoked
-whenever one of our users tries to execute a view callable that they are not
-allowed to invoke as determined by the :term:`authorization policy` in use.
-In our application, for example, this means that if a user has not logged in,
-and he tries to add or edit a Wiki page, he will be shown the login form.
-Before being allowed to continue on to the add or edit form, he will have to
-provide credentials that give him permission to add or edit via this login
-form.
+``pyramid.httpexceptions.HTTPForbidden``) specifies a :term:`forbidden view`.
+This configures our login view to be presented to the user when
+:app:`Pyramid` detects that a view invocation can not be authorized. Because
+we've configured a forbidden view, the ``login`` view callable will be
+invoked whenever one of our users tries to execute a view callable that they
+are not allowed to invoke as determined by the :term:`authorization policy`
+in use. In our application, for example, this means that if a user has not
+logged in, and he tries to add or edit a Wiki page, he will be shown the
+login form. Before being allowed to continue on to the add or edit form, he
+will have to provide credentials that give him permission to add or edit via
+this login form.
Changing Existing Views
~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/docs/tutorials/wiki/definingviews.rst b/docs/tutorials/wiki/definingviews.rst
index ae4fa6ffb..b111c9b4a 100644
--- a/docs/tutorials/wiki/definingviews.rst
+++ b/docs/tutorials/wiki/definingviews.rst
@@ -84,10 +84,12 @@ No renderer is necessary when a view returns a response object.
The ``view_wiki`` view callable always redirects to the URL of a Page
resource named "FrontPage". To do so, it returns an instance of the
:class:`pyramid.httpexceptions.HTTPFound` class (instances of which implement
-the WebOb :term:`response` interface). The :func:`pyramid.url.resource_url`
-API. :func:`pyramid.url.resource_url` constructs a URL to the ``FrontPage``
-page resource (e.g. ``http://localhost:6543/FrontPage``), and uses it as the
-"location" of the HTTPFound response, forming an HTTP redirect.
+the :class:`pyramid.interfaces.IResponse` interface like
+:class:`pyramid.response.Response` does). The
+:func:`pyramid.url.resource_url` API. :func:`pyramid.url.resource_url`
+constructs a URL to the ``FrontPage`` page resource
+(e.g. ``http://localhost:6543/FrontPage``), and uses it as the "location" of
+the HTTPFound response, forming an HTTP redirect.
The ``view_page`` view function
-------------------------------
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/login.py b/docs/tutorials/wiki/src/authorization/tutorial/login.py
index 463db71a6..334115880 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/login.py
+++ b/docs/tutorials/wiki/src/authorization/tutorial/login.py
@@ -9,7 +9,7 @@ from tutorial.security import USERS
@view_config(context='tutorial.models.Wiki', name='login',
renderer='templates/login.pt')
-@view_config(context='pyramid.exceptions.Forbidden',
+@view_config(context='pyramid.httpexceptions.HTTPForbidden',
renderer='templates/login.pt')
def login(request):
login_url = resource_url(request.context, request, 'login')
diff --git a/docs/tutorials/wiki2/definingviews.rst b/docs/tutorials/wiki2/definingviews.rst
index cea376b77..c91d1d914 100644
--- a/docs/tutorials/wiki2/definingviews.rst
+++ b/docs/tutorials/wiki2/definingviews.rst
@@ -91,7 +91,8 @@ a URL which represents the path to our "FrontPage".
The ``view_wiki`` function returns an instance of the
:class:`pyramid.httpexceptions.HTTPFound` class (instances of which implement
-the WebOb :term:`response` interface), It will use the
+the :class:`pyramid.interfaces.IResponse` interface like
+:class:`pyramid.response.Response` does), It will use the
:func:`pyramid.url.route_url` API to construct a URL to the ``FrontPage``
page (e.g. ``http://localhost:6543/FrontPage``), and will use it as the
"location" of the HTTPFound response, forming an HTTP redirect.
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py b/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py
index 05183d3d4..4cd84eda5 100644
--- a/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py
@@ -39,7 +39,7 @@ def main(global_config, **settings):
config.add_view('tutorial.views.edit_page', route_name='edit_page',
renderer='tutorial:templates/edit.pt', permission='edit')
config.add_view('tutorial.login.login',
- context='pyramid.exceptions.Forbidden',
+ context='pyramid.httpexceptions.HTTPForbidden',
renderer='tutorial:templates/login.pt')
return config.make_wsgi_app()
diff --git a/docs/whatsnew-1.1.rst b/docs/whatsnew-1.1.rst
index ce2f7210a..172a20343 100644
--- a/docs/whatsnew-1.1.rst
+++ b/docs/whatsnew-1.1.rst
@@ -18,6 +18,9 @@ The major feature additions in Pyramid 1.1 are:
- Support for "static" routes.
+- Default HTTP exception view and associated ``redirect`` and ``abort``
+ convenience functions.
+
``request.response``
~~~~~~~~~~~~~~~~~~~~
@@ -50,6 +53,25 @@ Static Routes
be useful for URL generation via ``route_url`` and ``route_path``. See the
section entitled :ref:`static_route_narr` for more information.
+Default HTTP Exception View
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+- A default exception view for the context :exc:`webob.exc.HTTPException`
+ (aka :class:`pyramid.httpexceptions.HTTPException`) is now registered by
+ default. This means that an instance of any exception class imported from
+ :mod:`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.
+
+ 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
+ that will be used when an HTTP exception is raised. When ``None`` is
+ passed for this value, an exception view for HTTP exceptions will not be
+ registered. Passing ``None`` returns the behavior of raising an HTTP
+ exception to that of Pyramid 1.0 (the exception will propagate to
+ middleware and to the WSGI server).
+
Minor Feature Additions
-----------------------
@@ -57,6 +79,10 @@ Minor Feature Additions
:class:`pyramid.authentication.SessionAuthenticationPolicy`, which uses a
session to store credentials.
+- A function named :func:`pyramid.httpexceptions.responsecode` is a shortcut
+ that can be used to create HTTP exception response objects using an HTTP
+ integer status code.
+
- Integers and longs passed as ``elements`` to
:func:`pyramid.url.resource_url` or
:meth:`pyramid.request.Request.resource_url` e.g. ``resource_url(context,
@@ -162,7 +188,7 @@ Deprecations and Behavior Differences
expected an environ object in BFG 1.0 and before). In a future version,
these methods will be removed entirely.
-- A custom request factory is now required to return a response object that
+- A custom request factory is now required to return a request object that
has a ``response`` attribute (or "reified"/lazy property) if they the
request is meant to be used in a view that uses a renderer. This
``response`` attribute should be an instance of the class
@@ -235,3 +261,10 @@ Documentation Enhancements
- Added a section to the "URL Dispatch" narrative chapter regarding the new
"static" route feature entitled :ref:`static_route_narr`.
+
+- Added API docs for :func:`pyramid.httpexceptions.abort` and
+ :func:`pyramid.httpexceptions.redirect`.
+
+- Added :ref:`http_exceptions` section to Views narrative chapter including a
+ description of :func:`pyramid.httpexceptions.abort`` and
+ :func:`pyramid.httpexceptions.redirect`.
diff --git a/pyramid/__init__.py b/pyramid/__init__.py
index 5f6a326f8..473d5e1c6 100644
--- a/pyramid/__init__.py
+++ b/pyramid/__init__.py
@@ -1,2 +1,5 @@
-# pyramid package
-
+from pyramid.request import Request
+from pyramid.response import Response
+Response.RequestClass = Request
+Request.ResponseClass = Response
+del Request, Response
diff --git a/pyramid/config.py b/pyramid/config.py
index 4e06a9b2e..70b5cd639 100644
--- a/pyramid/config.py
+++ b/pyramid/config.py
@@ -36,6 +36,7 @@ from pyramid.interfaces import IRendererFactory
from pyramid.interfaces import IRendererGlobalsFactory
from pyramid.interfaces import IRequest
from pyramid.interfaces import IRequestFactory
+from pyramid.interfaces import IResponse
from pyramid.interfaces import IRootFactory
from pyramid.interfaces import IRouteRequest
from pyramid.interfaces import IRoutesMapper
@@ -56,9 +57,10 @@ from pyramid.compat import md5
from pyramid.compat import any
from pyramid.events import ApplicationCreated
from pyramid.exceptions import ConfigurationError
-from pyramid.exceptions import Forbidden
-from pyramid.exceptions import NotFound
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
@@ -80,9 +82,7 @@ from pyramid.traversal import find_interface
from pyramid.traversal import traversal_path
from pyramid.urldispatch import RoutesMapper
from pyramid.util import DottedNameResolver
-from pyramid.view import default_exceptionresponse_view
from pyramid.view import render_view_to_response
-from pyramid.view import is_response
DEFAULT_RENDERERS = (
('.mak', mako_renderer_factory),
@@ -139,7 +139,8 @@ class Configurator(object):
``package``, ``settings``, ``root_factory``, ``authentication_policy``,
``authorization_policy``, ``renderers`` ``debug_logger``,
``locale_negotiator``, ``request_factory``, ``renderer_globals_factory``,
- ``default_permission``, ``session_factory``, and ``autocommit``.
+ ``default_permission``, ``session_factory``, ``default_view_mapper``,
+ ``autocommit``, and ``exceptionresponse_view``.
If the ``registry`` argument is passed as a non-``None`` value, it
must be an instance of the :class:`pyramid.registry.Registry`
@@ -254,7 +255,17 @@ class Configurator(object):
:term:`view mapper` factory for view configurations that don't otherwise
specify one (see :class:`pyramid.interfaces.IViewMapperFactory`). If a
default_view_mapper is not passed, a superdefault view mapper will be
- used. """
+ used.
+
+ If ``exceptionresponse_view`` is passed, it must be a :term:`view
+ callable` or ``None``. If it is a view callable, it will be used as an
+ exception view callable when an :term:`exception response` is raised. If
+ ``exceptionresponse_view`` is ``None``, no exception response view will
+ be registered, and all raised exception responses will be bubbled up to
+ Pyramid's caller. By
+ default, the ``pyramid.httpexceptions.default_exceptionresponse_view``
+ function is used as the ``exceptionresponse_view``. This argument is new
+ in Pyramid 1.1. """
manager = manager # for testing injection
venusian = venusian # for testing injection
@@ -277,6 +288,7 @@ class Configurator(object):
session_factory=None,
default_view_mapper=None,
autocommit=False,
+ exceptionresponse_view=default_exceptionresponse_view,
):
if package is None:
package = caller_package()
@@ -302,6 +314,7 @@ class Configurator(object):
default_permission=default_permission,
session_factory=session_factory,
default_view_mapper=default_view_mapper,
+ exceptionresponse_view=exceptionresponse_view,
)
def _set_settings(self, mapping):
@@ -400,7 +413,8 @@ class Configurator(object):
def _fix_registry(self):
""" Fix up a ZCA component registry that is not a
pyramid.registry.Registry by adding analogues of ``has_listeners``,
- and ``notify`` through monkey-patching."""
+ ``notify``, ``queryAdapterOrSelf``, and ``registerSelfAdapter``
+ through monkey-patching."""
_registry = self.registry
@@ -412,6 +426,23 @@ class Configurator(object):
if not hasattr(_registry, 'has_listeners'):
_registry.has_listeners = True
+ if not hasattr(_registry, 'queryAdapterOrSelf'):
+ def queryAdapterOrSelf(object, interface, default=None):
+ if not interface.providedBy(object):
+ return _registry.queryAdapter(object, interface,
+ default=default)
+ return object
+ _registry.queryAdapterOrSelf = queryAdapterOrSelf
+
+ if not hasattr(_registry, 'registerSelfAdapter'):
+ def registerSelfAdapter(required=None, provided=None,
+ name=u'', info=u'', event=True):
+ return _registry.registerAdapter(lambda x: x,
+ required=required,
+ provided=provided, name=name,
+ info=info, event=event)
+ _registry.registerSelfAdapter = registerSelfAdapter
+
def _make_context(self, autocommit=False):
context = PyramidConfigurationMachine()
registerCommonDirectives(context)
@@ -658,7 +689,8 @@ class Configurator(object):
renderers=DEFAULT_RENDERERS, debug_logger=None,
locale_negotiator=None, request_factory=None,
renderer_globals_factory=None, default_permission=None,
- session_factory=None, default_view_mapper=None):
+ session_factory=None, default_view_mapper=None,
+ exceptionresponse_view=default_exceptionresponse_view):
""" When you pass a non-``None`` ``registry`` argument to the
:term:`Configurator` constructor, no initial 'setup' is performed
against the registry. This is because the registry you pass in may
@@ -679,6 +711,9 @@ class Configurator(object):
self._fix_registry()
self._set_settings(settings)
self._set_root_factory(root_factory)
+ # cope with WebOb response objects that aren't decorated with IResponse
+ from webob import Response as WebobResponse
+ registry.registerSelfAdapter((WebobResponse,), IResponse)
debug_logger = self.maybe_dotted(debug_logger)
if debug_logger is None:
debug_logger = make_stream_logger('pyramid.debug', sys.stderr)
@@ -688,8 +723,9 @@ class Configurator(object):
authorization_policy)
for name, renderer in renderers:
self.add_renderer(name, renderer)
- self.add_view(default_exceptionresponse_view,
- context=IExceptionResponse)
+ if exceptionresponse_view is not None:
+ exceptionresponse_view = self.maybe_dotted(exceptionresponse_view)
+ self.add_view(exceptionresponse_view, context=IExceptionResponse)
if locale_negotiator:
locale_negotiator = self.maybe_dotted(locale_negotiator)
registry.registerUtility(locale_negotiator, ILocaleNegotiator)
@@ -705,7 +741,7 @@ class Configurator(object):
if session_factory is not None:
self.set_session_factory(session_factory)
# commit before adding default_view_mapper, as the
- # default_exceptionresponse_view above requires the superdefault view
+ # exceptionresponse_view above requires the superdefault view
# mapper
self.commit()
if default_view_mapper is not None:
@@ -851,6 +887,30 @@ class Configurator(object):
self.action(None, register)
return subscriber
+ @action_method
+ def add_response_adapter(self, adapter, type_or_iface):
+ """ When an object of type (or interface) ``type_or_iface`` is
+ returned from a view callable, Pyramid will use the adapter
+ ``adapter`` to convert it into an object which implements the
+ :class:`pyramid.interfaces.IResponse` interface. If ``adapter`` is
+ None, an object returned of type (or interface) ``type_or_iface``
+ will itself be used as a response object.
+
+ ``adapter`` and ``type_or_interface`` may be Python objects or
+ strings representing dotted names to importable Python global
+ objects.
+
+ See :ref:`using_iresponse` for more information."""
+ adapter = self.maybe_dotted(adapter)
+ type_or_iface = self.maybe_dotted(type_or_iface)
+ def register():
+ reg = self.registry
+ if adapter is None:
+ reg.registerSelfAdapter((type_or_iface,), IResponse)
+ else:
+ reg.registerAdapter(adapter, (type_or_iface,), IResponse)
+ self.action((IResponse, type_or_iface), register)
+
def add_settings(self, settings=None, **kw):
"""Augment the ``settings`` argument passed in to the Configurator
constructor with one or more 'setting' key/value pairs. A setting is
@@ -1978,7 +2038,8 @@ class Configurator(object):
def bwcompat_view(context, request):
context = getattr(request, 'context', None)
return view(context, request)
- return self.add_view(bwcompat_view, context=Forbidden, wrapper=wrapper)
+ return self.add_view(bwcompat_view, context=HTTPForbidden,
+ wrapper=wrapper)
@action_method
def set_notfound_view(self, view=None, attr=None, renderer=None,
@@ -2018,7 +2079,8 @@ class Configurator(object):
def bwcompat_view(context, request):
context = getattr(request, 'context', None)
return view(context, request)
- return self.add_view(bwcompat_view, context=NotFound, wrapper=wrapper)
+ return self.add_view(bwcompat_view, context=HTTPNotFound,
+ wrapper=wrapper)
@action_method
def set_request_factory(self, factory):
@@ -2826,7 +2888,7 @@ class ViewDeriver(object):
return view(context, request)
msg = getattr(request, 'authdebug_message',
'Unauthorized: %s failed permission check' % view)
- raise Forbidden(msg, result)
+ raise HTTPForbidden(msg, result=result)
_secured_view.__call_permissive__ = view
_secured_view.__permitted__ = _permitted
_secured_view.__permission__ = permission
@@ -2875,7 +2937,8 @@ class ViewDeriver(object):
def predicate_wrapper(context, request):
if all((predicate(context, request) for predicate in predicates)):
return view(context, request)
- raise PredicateMismatch('predicate mismatch for view %s' % view)
+ raise PredicateMismatch(
+ 'predicate mismatch for view %s' % view)
def checker(context, request):
return all((predicate(context, request) for predicate in
predicates))
@@ -2920,22 +2983,24 @@ class ViewDeriver(object):
def _rendered_view(context, request):
renderer = static_renderer
- response = wrapped_view(context, request)
- if not is_response(response):
+ result = wrapped_view(context, request)
+ registry = self.kw['registry']
+ response = registry.queryAdapterOrSelf(result, IResponse)
+ if response is None:
attrs = getattr(request, '__dict__', {})
if 'override_renderer' in attrs:
# renderer overridden by newrequest event or other
renderer_name = attrs.pop('override_renderer')
renderer = RendererHelper(name=renderer_name,
package=self.kw.get('package'),
- registry = self.kw['registry'])
+ registry = registry)
if '__view__' in attrs:
view_inst = attrs.pop('__view__')
else:
view_inst = getattr(wrapped_view, '__original_view__',
wrapped_view)
- return renderer.render_view(request, response, view_inst,
- context)
+ response = renderer.render_view(request, result, view_inst,
+ context)
return response
return _rendered_view
diff --git a/pyramid/exceptions.py b/pyramid/exceptions.py
index 771d71b88..151fc241f 100644
--- a/pyramid/exceptions.py
+++ b/pyramid/exceptions.py
@@ -1,84 +1,12 @@
from zope.configuration.exceptions import ConfigurationError as ZCE
-from zope.interface import implements
-from pyramid.decorator import reify
-from pyramid.interfaces import IExceptionResponse
-import cgi
+from pyramid.httpexceptions import HTTPNotFound
+from pyramid.httpexceptions import HTTPForbidden
-class ExceptionResponse(Exception):
- """ Abstract class to support behaving as a WSGI response object """
- implements(IExceptionResponse)
- status = None
+NotFound = HTTPNotFound # bw compat
+Forbidden = HTTPForbidden # bw compat
- def __init__(self, message=''):
- Exception.__init__(self, message) # B / C
- self.message = message
-
- @reify # defer execution until asked explicitly
- def app_iter(self):
- return [
- """
- <html>
- <title>%s</title>
- <body>
- <h1>%s</h1>
- <code>%s</code>
- </body>
- </html>
- """ % (self.status, self.status, cgi.escape(self.message))
- ]
-
- @reify # defer execution until asked explicitly
- def headerlist(self):
- return [
- ('Content-Length', str(len(self.app_iter[0]))),
- ('Content-Type', 'text/html')
- ]
-
-
-class Forbidden(ExceptionResponse):
- """
- Raise this exception within :term:`view` code to immediately return the
- :term:`forbidden view` to the invoking user. Usually this is a basic
- ``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 accepts two arguments. The first argument,
- ``message``, should be a string. The value of this string will be used
- as the ``message`` attribute of the exception object. The second
- 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.
- The ``result`` value will be used as the ``result`` attribute of the
- exception object.
-
- 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.
- """
- status = '403 Forbidden'
- def __init__(self, message='', result=None):
- ExceptionResponse.__init__(self, message)
- self.message = message
- self.result = result
-
-class NotFound(ExceptionResponse):
- """
- Raise this exception within :term:`view` code to immediately
- return the :term:`Not Found view` to the invoking user. Usually
- this is a basic ``404`` page, but the Not Found view can be
- customized as necessary. See :ref:`changing_the_notfound_view`.
-
- This exception's constructor accepts a single positional 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`.
- """
- status = '404 Not Found'
-
-class PredicateMismatch(NotFound):
+class PredicateMismatch(HTTPNotFound):
"""
Internal exception (not an API) raised by multiviews when no
view matches. This exception subclasses the ``NotFound``
@@ -102,3 +30,4 @@ class ConfigurationError(ZCE):
""" Raised when inappropriate input values are supplied to an API
method of a :term:`Configurator`"""
+
diff --git a/pyramid/httpexceptions.py b/pyramid/httpexceptions.py
index f56910b53..6d689988e 100644
--- a/pyramid/httpexceptions.py
+++ b/pyramid/httpexceptions.py
@@ -1,50 +1,1005 @@
-from webob.exc import __doc__
-from webob.exc import status_map
-
-# Parent classes
-from webob.exc import HTTPException
-from webob.exc import WSGIHTTPException
-from webob.exc import HTTPOk
-from webob.exc import HTTPRedirection
-from webob.exc import HTTPError
-from webob.exc import HTTPClientError
-from webob.exc import HTTPServerError
-
-# Child classes
-from webob.exc import HTTPCreated
-from webob.exc import HTTPAccepted
-from webob.exc import HTTPNonAuthoritativeInformation
-from webob.exc import HTTPNoContent
-from webob.exc import HTTPResetContent
-from webob.exc import HTTPPartialContent
-from webob.exc import HTTPMultipleChoices
-from webob.exc import HTTPMovedPermanently
-from webob.exc import HTTPFound
-from webob.exc import HTTPSeeOther
-from webob.exc import HTTPNotModified
-from webob.exc import HTTPUseProxy
-from webob.exc import HTTPTemporaryRedirect
-from webob.exc import HTTPBadRequest
-from webob.exc import HTTPUnauthorized
-from webob.exc import HTTPPaymentRequired
-from webob.exc import HTTPForbidden
-from webob.exc import HTTPNotFound
-from webob.exc import HTTPMethodNotAllowed
-from webob.exc import HTTPNotAcceptable
-from webob.exc import HTTPProxyAuthenticationRequired
-from webob.exc import HTTPRequestTimeout
-from webob.exc import HTTPConflict
-from webob.exc import HTTPGone
-from webob.exc import HTTPLengthRequired
-from webob.exc import HTTPPreconditionFailed
-from webob.exc import HTTPRequestEntityTooLarge
-from webob.exc import HTTPRequestURITooLong
-from webob.exc import HTTPUnsupportedMediaType
-from webob.exc import HTTPRequestRangeNotSatisfiable
-from webob.exc import HTTPExpectationFailed
-from webob.exc import HTTPInternalServerError
-from webob.exc import HTTPNotImplemented
-from webob.exc import HTTPBadGateway
-from webob.exc import HTTPServiceUnavailable
-from webob.exc import HTTPGatewayTimeout
-from webob.exc import HTTPVersionNotSupported
+"""
+HTTP Exceptions
+---------------
+
+This module contains Pyramid HTTP exception classes. Each class relates to a
+single HTTP status code. Each class is a subclass of the
+:class:`~HTTPException`. Each exception class is also a :term:`response`
+object.
+
+Each exception class has a status code according to `RFC 2068
+<http://www.ietf.org/rfc/rfc2068.txt>`_: codes with 100-300 are not really
+errors; 400s are client errors, and 500s are server errors.
+
+Exception
+ HTTPException
+ HTTPOk
+ * 200 - HTTPOk
+ * 201 - HTTPCreated
+ * 202 - HTTPAccepted
+ * 203 - HTTPNonAuthoritativeInformation
+ * 204 - HTTPNoContent
+ * 205 - HTTPResetContent
+ * 206 - HTTPPartialContent
+ HTTPRedirection
+ * 300 - HTTPMultipleChoices
+ * 301 - HTTPMovedPermanently
+ * 302 - HTTPFound
+ * 303 - HTTPSeeOther
+ * 304 - HTTPNotModified
+ * 305 - HTTPUseProxy
+ * 306 - Unused (not implemented, obviously)
+ * 307 - HTTPTemporaryRedirect
+ HTTPError
+ HTTPClientError
+ * 400 - HTTPBadRequest
+ * 401 - HTTPUnauthorized
+ * 402 - HTTPPaymentRequired
+ * 403 - HTTPForbidden
+ * 404 - HTTPNotFound
+ * 405 - HTTPMethodNotAllowed
+ * 406 - HTTPNotAcceptable
+ * 407 - HTTPProxyAuthenticationRequired
+ * 408 - HTTPRequestTimeout
+ * 409 - HTTPConflict
+ * 410 - HTTPGone
+ * 411 - HTTPLengthRequired
+ * 412 - HTTPPreconditionFailed
+ * 413 - HTTPRequestEntityTooLarge
+ * 414 - HTTPRequestURITooLong
+ * 415 - HTTPUnsupportedMediaType
+ * 416 - HTTPRequestRangeNotSatisfiable
+ * 417 - HTTPExpectationFailed
+ HTTPServerError
+ * 500 - HTTPInternalServerError
+ * 501 - HTTPNotImplemented
+ * 502 - HTTPBadGateway
+ * 503 - HTTPServiceUnavailable
+ * 504 - HTTPGatewayTimeout
+ * 505 - HTTPVersionNotSupported
+
+Each HTTP exception has the following attributes:
+
+ ``code``
+ the HTTP status code for the exception
+
+ ``title``
+ remainder of the status line (stuff after the code)
+
+ ``explanation``
+ a plain-text explanation of the error message that is
+ not subject to environment or header substitutions;
+ it is accessible in the template via ${explanation}
+
+ ``detail``
+ a plain-text message customization that is not subject
+ to environment or header substitutions; accessible in
+ the template via ${detail}
+
+ ``body_template``
+ a ``String.template``-format content fragment used for environment
+ and header substitution; the default template includes both
+ the explanation and further detail provided in the
+ message.
+
+Each HTTP exception accepts the following parameters:
+
+ ``detail``
+ a plain-text override of the default ``detail``
+
+ ``headers``
+ a list of (k,v) header pairs
+
+ ``comment``
+ a plain-text additional information which is
+ usually stripped/hidden for end-users
+
+ ``body_template``
+ a ``string.Template`` object containing a content fragment in HTML
+ that frames the explanation and further detail
+
+Substitution of response headers into template values is always performed.
+Substitution of WSGI environment values is performed if a ``request`` is
+passed to the exception's constructor.
+
+The subclasses of :class:`~_HTTPMove`
+(:class:`~HTTPMultipleChoices`, :class:`~HTTPMovedPermanently`,
+:class:`~HTTPFound`, :class:`~HTTPSeeOther`, :class:`~HTTPUseProxy` and
+:class:`~HTTPTemporaryRedirect`) are redirections that require a ``Location``
+field. Reflecting this, these subclasses have one additional keyword argument:
+``location``, which indicates the location to which to redirect.
+"""
+
+import types
+from string import Template
+
+from 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
+ """ Base class for all :term:`exception response` objects."""
+
+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:
+ #
+ # - bases plaintext vs. html result on self.content_type rather than
+ # on request accept header
+ #
+ # - doesn't use "strip_tags" (${br} placeholder for <br/>, no other html
+ # in default body template)
+ #
+ # - sets a default app_iter onto self during __call__ using a template if
+ # no body, app_iter, or unicode_body is set onto the response (instead of
+ # 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
+
+ def __str__(self):
+ return self.detail or self.explanation
+
+ def _default_app_iter(self, environ):
+ 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
+ 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)
+ return [page]
+
+ @property
+ def wsgi_response(self):
+ # bw compat only
+ return self
+
+ exception = wsgi_response # bw compat only
+
+ def __call__(self, environ, start_response):
+ if not self.body and not self.empty_body:
+ self.app_iter = self._default_app_iter(environ)
+ return Response.__call__(self, environ, start_response)
+
+class HTTPError(WSGIHTTPException):
+ """
+ base class for exceptions with status codes in the 400s and 500s
+
+ This is an exception which indicates that an error has occurred,
+ and that any work in progress should not be committed.
+ """
+
+class HTTPRedirection(WSGIHTTPException):
+ """
+ base class for exceptions with status codes in the 300s (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 exceptions with status codes in the 200s (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 400s, 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 500s, where the server is in-error
+
+ This is an error condition in which the server is presumed to be
+ in-error. Unless specialized, this is a '500 Internal Server Error'.
+ """
+ code = 500
+ title = 'Internal Server Error'
+ explanation = (
+ 'The server has either erred or is incapable of performing '
+ 'the requested operation.')
+
+class HTTPInternalServerError(HTTPServerError):
+ pass
+
+class HTTPNotImplemented(HTTPServerError):
+ """
+ subclass of :class:`~HTTPServerError`
+
+ This indicates that the server does not support the functionality
+ required to fulfill the request.
+
+ code: 501, title: Not Implemented
+ """
+ # differences from webob.exc.HTTPNotAcceptable:
+ #
+ # - body_template_obj not overridden (it tried to use request environ's
+ # REQUEST_METHOD)
+ code = 501
+ title = 'Not Implemented'
+
+class HTTPBadGateway(HTTPServerError):
+ """
+ subclass of :class:`~HTTPServerError`
+
+ This indicates that the server, while acting as a gateway or proxy,
+ received an invalid response from the upstream server it accessed
+ in attempting to fulfill the request.
+
+ code: 502, title: Bad Gateway
+ """
+ code = 502
+ title = 'Bad Gateway'
+ explanation = ('Bad gateway.')
+
+class HTTPServiceUnavailable(HTTPServerError):
+ """
+ subclass of :class:`~HTTPServerError`
+
+ This indicates that the server is currently unable to handle the
+ request due to a temporary overloading or maintenance of the server.
+
+ code: 503, title: Service Unavailable
+ """
+ code = 503
+ title = 'Service Unavailable'
+ explanation = ('The server is currently unavailable. '
+ 'Please try again at a later time.')
+
+class HTTPGatewayTimeout(HTTPServerError):
+ """
+ subclass of :class:`~HTTPServerError`
+
+ This indicates that the server, while acting as a gateway or proxy,
+ did not receive a timely response from the upstream server specified
+ by the URI (e.g. HTTP, FTP, LDAP) or some other auxiliary server
+ (e.g. DNS) it needed to access in attempting to complete the request.
+
+ code: 504, title: Gateway Timeout
+ """
+ code = 504
+ title = 'Gateway Timeout'
+ explanation = ('The gateway has timed out.')
+
+class HTTPVersionNotSupported(HTTPServerError):
+ """
+ subclass of :class:`~HTTPServerError`
+
+ This indicates that the server does not support, or refuses to
+ support, the HTTP protocol version that was used in the request
+ message.
+
+ code: 505, title: HTTP Version Not Supported
+ """
+ code = 505
+ title = 'HTTP Version Not Supported'
+ explanation = ('The HTTP version is not supported.')
+
+class HTTPInsufficientStorage(HTTPServerError):
+ """
+ subclass of :class:`~HTTPServerError`
+
+ This indicates that the server does not have enough space to save
+ the resource.
+
+ code: 507, title: Insufficient Storage
+ """
+ code = 507
+ title = 'Insufficient Storage'
+ explanation = ('There was not enough space to save the resource')
+
+def responsecode(status_code, **kw):
+ """Creates an HTTP exception based on a status code. Example::
+
+ raise responsecode(404) # raises an HTTPNotFound exception.
+
+ The values passed as ``kw`` are provided to the exception's constructor.
+ """
+ exc = status_map[status_code](**kw)
+ return exc
+
+def default_exceptionresponse_view(context, request):
+ if not isinstance(context, Exception):
+ # backwards compat for an exception response view registered via
+ # config.set_notfound_view or config.set_forbidden_view
+ # instead of as a proper exception view
+ context = request.exception or context
+ return context
+
+status_map={}
+for name, value in globals().items():
+ if (isinstance(value, (type, types.ClassType)) and
+ issubclass(value, HTTPException)
+ and not name.startswith('_')):
+ code = getattr(value, 'code', None)
+ if code:
+ status_map[code] = value
+del name, value
+
+
+
diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py
index d200d15cf..fee8d549d 100644
--- a/pyramid/interfaces.py
+++ b/pyramid/interfaces.py
@@ -46,21 +46,225 @@ class IApplicationCreated(Interface):
IWSGIApplicationCreatedEvent = IApplicationCreated # b /c
-class IResponse(Interface): # not an API
- status = Attribute('WSGI status code of response')
- headerlist = Attribute('List of response headers')
- app_iter = Attribute('Iterable representing the response body')
+class IResponse(Interface):
+ """ Represents a WSGI response using the WebOb response interface. Some
+ attribute and method documentation of this interface references `RFC 2616
+ <http://www.w3.org/Protocols/rfc2616/>`_.
+
+ This interface is most famously implemented by
+ :class:`pyramid.response.Response` and the HTTP exception classes in
+ :mod:`pyramid.httpexceptions`."""
+
+ RequestClass = Attribute(
+ """ Alias for :class:`pyramid.request.Request` """)
+
+ def __call__(environ, start_response):
+ """ :term:`WSGI` call interface, should call the start_response
+ callback and should return an iterable"""
+
+ accept_ranges = Attribute(
+ """Gets and sets and deletes the Accept-Ranges header. For more
+ information on Accept-Ranges see RFC 2616, section 14.5""")
+
+ age = Attribute(
+ """Gets and sets and deletes the Age header. Converts using int.
+ For more information on Age see RFC 2616, section 14.6.""")
+
+ allow = Attribute(
+ """Gets and sets and deletes the Allow header. Converts using
+ list. For more information on Allow see RFC 2616, Section 14.7.""")
+
+ app_iter = Attribute(
+ """Returns the app_iter of the response.
+
+ If body was set, this will create an app_iter from that body
+ (a single-item list)""")
+
+ def app_iter_range(start, stop):
+ """ Return a new app_iter built from the response app_iter that
+ serves up only the given start:stop range. """
+
+ body = Attribute(
+ """The body of the response, as a str. This will read in the entire
+ app_iter if necessary.""")
+
+ body_file = Attribute(
+ """A file-like object that can be used to write to the body. If you
+ passed in a list app_iter, that app_iter will be modified by writes.""")
+
+ cache_control = Attribute(
+ """Get/set/modify the Cache-Control header (RFC 2616 section 14.9)""")
+
+ cache_expires = Attribute(
+ """ Get/set the Cache-Control and Expires headers. This sets the
+ response to expire in the number of seconds passed when set. """)
+
+ charset = Attribute(
+ """Get/set the charset (in the Content-Type)""")
+
+ def conditional_response_app(environ, start_response):
+ """ Like the normal __call__ interface, but checks conditional
+ headers:
+
+ - If-Modified-Since (304 Not Modified; only on GET, HEAD)
+
+ - If-None-Match (304 Not Modified; only on GET, HEAD)
+
+ - Range (406 Partial Content; only on GET, HEAD)"""
+
+ content_disposition = Attribute(
+ """Gets and sets and deletes the Content-Disposition header.
+ For more information on Content-Disposition see RFC 2616 section
+ 19.5.1.""")
+
+ content_encoding = Attribute(
+ """Gets and sets and deletes the Content-Encoding header. For more
+ information about Content-Encoding see RFC 2616 section 14.11.""")
+
+ content_language = Attribute(
+ """Gets and sets and deletes the Content-Language header. Converts
+ using list. For more information about Content-Language see RFC 2616
+ section 14.12.""")
+
+ content_length = Attribute(
+ """Gets and sets and deletes the Content-Length header. For more
+ information on Content-Length see RFC 2616 section 14.17.
+ Converts using int. """)
+
+ content_location = Attribute(
+ """Gets and sets and deletes the Content-Location header. For more
+ information on Content-Location see RFC 2616 section 14.14.""")
+
+ content_md5 = Attribute(
+ """Gets and sets and deletes the Content-MD5 header. For more
+ information on Content-MD5 see RFC 2616 section 14.14.""")
+
+ content_range = Attribute(
+ """Gets and sets and deletes the Content-Range header. For more
+ information on Content-Range see section 14.16. Converts using
+ ContentRange object.""")
+
+ content_type = Attribute(
+ """Get/set the Content-Type header (or None), without the charset
+ or any parameters. If you include parameters (or ; at all) when
+ setting the content_type, any existing parameters will be deleted;
+ otherwise they will be preserved.""")
+
+ content_type_params = Attribute(
+ """A dictionary of all the parameters in the content type. This is
+ not a view, set to change, modifications of the dict would not
+ be applied otherwise.""")
+
+ def copy():
+ """ Makes a copy of the response and returns the copy. """
+
+ date = Attribute(
+ """Gets and sets and deletes the Date header. For more information on
+ Date see RFC 2616 section 14.18. Converts using HTTP date.""")
+
+ def delete_cookie(key, path='/', domain=None):
+ """ Delete a cookie from the client. Note that path and domain must
+ match how the cookie was originally set. This sets the cookie to the
+ empty string, and max_age=0 so that it should expire immediately. """
+
+ def encode_content(encoding='gzip', lazy=False):
+ """ Encode the content with the given encoding (only gzip and
+ identity are supported)."""
+
+ environ = Attribute(
+ """Get/set the request environ associated with this response,
+ if any.""")
+
+ etag = Attribute(
+ """ Gets and sets and deletes the ETag header. For more information
+ on ETag see RFC 2616 section 14.19. Converts using Entity tag.""")
+
+ expires = Attribute(
+ """ Gets and sets and deletes the Expires header. For more
+ information on Expires see RFC 2616 section 14.21. Converts using
+ HTTP date.""")
+
+ headerlist = Attribute(
+ """ The list of response headers. """)
+
+ headers = Attribute(
+ """ The headers in a dictionary-like object """)
+
+ last_modified = Attribute(
+ """ Gets and sets and deletes the Last-Modified header. For more
+ information on Last-Modified see RFC 2616 section 14.29. Converts
+ using HTTP date.""")
+
+ location = Attribute(
+ """ Gets and sets and deletes the Location header. For more
+ information on Location see RFC 2616 section 14.30.""")
+
+ def md5_etag(body=None, set_content_md5=False):
+ """ Generate an etag for the response object using an MD5 hash of the
+ body (the body parameter, or self.body if not given). Sets self.etag.
+ If set_content_md5 is True sets self.content_md5 as well """
+
+ def merge_cookies(resp):
+ """ Merge the cookies that were set on this response with the given
+ resp object (which can be any WSGI application). If the resp is a
+ webob.Response object, then the other object will be modified
+ in-place. """
+
+ pragma = Attribute(
+ """ Gets and sets and deletes the Pragma header. For more information
+ on Pragma see RFC 2616 section 14.32. """)
+
+ request = Attribute(
+ """ Return the request associated with this response if any. """)
+
+ retry_after = Attribute(
+ """ Gets and sets and deletes the Retry-After header. For more
+ information on Retry-After see RFC 2616 section 14.37. Converts
+ using HTTP date or delta seconds.""")
+
+ server = Attribute(
+ """ Gets and sets and deletes the Server header. For more information
+ on Server see RFC216 section 14.38. """)
+
+ def set_cookie(key, value='', max_age=None, path='/', domain=None,
+ secure=False, httponly=False, comment=None, expires=None,
+ overwrite=False):
+ """ Set (add) a cookie for the response """
+
+ status = Attribute(
+ """ The status string. """)
+
+ status_int = Attribute(
+ """ The status as an integer """)
+
+ unicode_body = Attribute(
+ """ Get/set the unicode value of the body (using the charset of
+ the Content-Type)""")
+
+ def unset_cookie(key, strict=True):
+ """ Unset a cookie with the given name (remove it from the
+ response)."""
+
+ vary = Attribute(
+ """Gets and sets and deletes the Vary header. For more information
+ on Vary see section 14.44. Converts using list.""")
+
+ www_authenticate = Attribute(
+ """ Gets and sets and deletes the WWW-Authenticate header. For more
+ information on WWW-Authenticate see RFC 2616 section 14.47. Converts
+ using 'parse_auth' and 'serialize_auth'. """)
class IException(Interface): # not an API
""" An interface representing a generic exception """
class IExceptionResponse(IException, IResponse):
- """ An interface representing a WSGI response which is also an
- exception object. Register an exception view using this interface
- as a ``context`` to apply the registered view for all exception
- types raised by :app:`Pyramid` internally
- (:class:`pyramid.exceptions.NotFound` and
- :class:`pyramid.exceptions.Forbidden`)."""
+ """ An interface representing a WSGI response which is also an exception
+ object. Register an exception view using this interface as a ``context``
+ to apply the registered view for all exception types raised by
+ :app:`Pyramid` internally (any exception that inherits from
+ :class:`pyramid.response.Response`, including
+ :class:`pyramid.httpexceptions.HTTPNotFound` and
+ :class:`pyramid.httpexceptions.HTTPForbidden`)."""
class IBeforeRender(Interface):
"""
@@ -273,11 +477,7 @@ class IExceptionViewClassifier(Interface):
class IView(Interface):
def __call__(context, request):
- """ Must return an object that implements IResponse. May
- optionally raise ``pyramid.exceptions.Forbidden`` if an
- authorization failure is detected during view execution or
- ``pyramid.exceptions.NotFound`` if the not found page is
- meant to be returned."""
+ """ Must return an object that implements IResponse. """
class ISecuredView(IView):
""" *Internal only* interface. Not an API. """
diff --git a/pyramid/registry.py b/pyramid/registry.py
index 37e230dc3..5db0a11e2 100644
--- a/pyramid/registry.py
+++ b/pyramid/registry.py
@@ -1,4 +1,5 @@
from zope.component.registry import Components
+from zope.interface import providedBy
from pyramid.interfaces import ISettings
@@ -28,6 +29,22 @@ class Registry(Components, dict):
self.has_listeners = True
return result
+ def registerSelfAdapter(self, required=None, provided=None, name=u'',
+ info=u'', event=True):
+ # registerAdapter analogue which always returns the object itself
+ # when required is matched
+ return self.registerAdapter(lambda x: x, required=required,
+ provided=provided, name=name,
+ info=info, event=event)
+
+ def queryAdapterOrSelf(self, object, interface, default=None):
+ # queryAdapter analogue which returns the object if it implements
+ # the interface, otherwise it will return an adaptation to the
+ # interface
+ if not interface.providedBy(object):
+ return self.queryAdapter(object, interface, default=default)
+ return object
+
def registerHandler(self, *arg, **kw):
result = Components.registerHandler(self, *arg, **kw)
self.has_listeners = True
diff --git a/pyramid/renderers.py b/pyramid/renderers.py
index a6dce9b3a..6865067dd 100644
--- a/pyramid/renderers.py
+++ b/pyramid/renderers.py
@@ -316,9 +316,7 @@ class RendererHelper(object):
'context':context,
'request':request
}
- return self.render_to_response(response, system,
- request=request)
-
+ return self.render_to_response(response, system, request=request)
def render(self, value, system_values, request=None):
renderer = self.renderer
diff --git a/pyramid/request.py b/pyramid/request.py
index d387a0b2f..06dbddd29 100644
--- a/pyramid/request.py
+++ b/pyramid/request.py
@@ -5,12 +5,14 @@ from zope.interface.interface import InterfaceClass
from webob import BaseRequest
from pyramid.interfaces import IRequest
+from pyramid.interfaces import IResponse
from pyramid.interfaces import ISessionFactory
from pyramid.interfaces import IResponseFactory
from pyramid.exceptions import ConfigurationError
from pyramid.decorator import reify
from pyramid.response import Response
+from pyramid.threadlocal import get_current_registry
from pyramid.url import resource_url
from pyramid.url import route_url
from pyramid.url import static_url
@@ -321,6 +323,15 @@ class Request(BaseRequest):
default=Response)
return response_factory()
+ def is_response(self, ob):
+ """ Return ``True`` if the object passed as ``ob`` is a valid
+ response object, ``False`` otherwise."""
+ registry = self.registry
+ adapted = registry.queryAdapterOrSelf(ob, IResponse)
+ if adapted is None:
+ return False
+ return adapted is ob
+
# b/c dict interface for "root factory" code that expects a bare
# environ. Explicitly omitted dict methods: clear (unnecessary),
# copy (implemented by WebOb), fromkeys (unnecessary); deprecated
diff --git a/pyramid/response.py b/pyramid/response.py
index 26f27b142..68496e386 100644
--- a/pyramid/response.py
+++ b/pyramid/response.py
@@ -1,2 +1,7 @@
-from webob import Response
-Response = Response # pyflakes
+from webob import Response as _Response
+from zope.interface import implements
+from pyramid.interfaces import IResponse
+
+class Response(_Response):
+ implements(IResponse)
+
diff --git a/pyramid/router.py b/pyramid/router.py
index b8a8639aa..8e33332df 100644
--- a/pyramid/router.py
+++ b/pyramid/router.py
@@ -12,11 +12,12 @@ from pyramid.interfaces import IRoutesMapper
from pyramid.interfaces import ITraverser
from pyramid.interfaces import IView
from pyramid.interfaces import IViewClassifier
+from pyramid.interfaces import IResponse
from pyramid.events import ContextFound
from pyramid.events import NewRequest
from pyramid.events import NewResponse
-from pyramid.exceptions import NotFound
+from pyramid.httpexceptions import HTTPNotFound
from pyramid.request import Request
from pyramid.threadlocal import manager
from pyramid.traversal import DefaultRootFactory
@@ -153,9 +154,9 @@ class Router(object):
logger and logger.debug(msg)
else:
msg = request.path_info
- raise NotFound(msg)
+ raise HTTPNotFound(msg)
else:
- response = view_callable(context, request)
+ result = view_callable(context, request)
# handle exceptions raised during root finding and view-exec
except Exception, why:
@@ -177,30 +178,26 @@ class Router(object):
# repoze.bfg.message docs-deprecated in Pyramid 1.0
environ['repoze.bfg.message'] = msg
- response = view_callable(why, request)
+ result = view_callable(why, request)
# process the response
+ response = registry.queryAdapterOrSelf(result, IResponse)
+ if response is None:
+ raise ValueError(
+ 'Could not convert view return value "%s" into a '
+ 'response object' % (result,))
has_listeners and registry.notify(NewResponse(request,response))
if request.response_callbacks:
request._process_response_callbacks(response)
- try:
- headers = response.headerlist
- app_iter = response.app_iter
- status = response.status
- except AttributeError:
- raise ValueError(
- 'Non-response object returned from view named %s '
- '(and no renderer): %r' % (view_name, response))
-
finally:
if request is not None and request.finished_callbacks:
request._process_finished_callbacks()
- start_response(status, headers)
- return app_iter
-
+ return response(request.environ, start_response)
+
finally:
manager.pop()
+
diff --git a/pyramid/session.py b/pyramid/session.py
index 5772c80d0..bb3226a1e 100644
--- a/pyramid/session.py
+++ b/pyramid/session.py
@@ -8,8 +8,6 @@ try:
except ImportError: # pragma: no cover
import pickle
-from webob import Response
-
import base64
import binascii
import hmac
@@ -213,17 +211,7 @@ def UnencryptedCookieSessionFactoryConfig(
'Cookie value is too long to store (%s bytes)' %
len(cookieval)
)
- if hasattr(response, 'set_cookie'):
- # ``response`` is a "real" webob response
- set_cookie = response.set_cookie
- else:
- # ``response`` is not a "real" webob response, cope
- def set_cookie(*arg, **kw):
- tmp_response = Response()
- tmp_response.set_cookie(*arg, **kw)
- response.headerlist.append(
- tmp_response.headerlist[-1])
- set_cookie(
+ response.set_cookie(
self._cookie_name,
value=cookieval,
max_age = self._cookie_max_age,
diff --git a/pyramid/testing.py b/pyramid/testing.py
index 36cc38830..86276df1e 100644
--- a/pyramid/testing.py
+++ b/pyramid/testing.py
@@ -17,7 +17,7 @@ from pyramid.interfaces import ISession
from pyramid.config import Configurator
from pyramid.decorator import reify
-from pyramid.exceptions import Forbidden
+from pyramid.httpexceptions import HTTPForbidden
from pyramid.response import Response
from pyramid.registry import Registry
from pyramid.security import Authenticated
@@ -217,7 +217,7 @@ def registerView(name, result='', view=None, for_=(Interface, Interface),
else:
def _secure(context, request):
if not has_permission(permission, context, request):
- raise Forbidden('no permission')
+ raise HTTPForbidden('no permission')
else:
return view(context, request)
_secure.__call_permissive__ = view
diff --git a/pyramid/tests/fixtureapp/views.py b/pyramid/tests/fixtureapp/views.py
index 9ab985e32..cbfc5a574 100644
--- a/pyramid/tests/fixtureapp/views.py
+++ b/pyramid/tests/fixtureapp/views.py
@@ -1,6 +1,6 @@
from zope.interface import Interface
from webob import Response
-from pyramid.exceptions import Forbidden
+from pyramid.httpexceptions import HTTPForbidden
def fixture_view(context, request):
""" """
@@ -16,7 +16,7 @@ def exception_view(context, request):
def protected_view(context, request):
""" """
- raise Forbidden()
+ raise HTTPForbidden()
class IDummy(Interface):
pass
diff --git a/pyramid/tests/forbiddenapp/__init__.py b/pyramid/tests/forbiddenapp/__init__.py
index ed9aa8357..7001b87f5 100644
--- a/pyramid/tests/forbiddenapp/__init__.py
+++ b/pyramid/tests/forbiddenapp/__init__.py
@@ -1,7 +1,5 @@
-from cgi import escape
from webob import Response
from pyramid.httpexceptions import HTTPForbidden
-from pyramid.exceptions import Forbidden
def x_view(request): # pragma: no cover
return Response('this is private!')
@@ -22,4 +20,4 @@ def includeme(config):
config._set_authentication_policy(authn_policy)
config._set_authorization_policy(authz_policy)
config.add_view(x_view, name='x', permission='private')
- config.add_view(forbidden_view, context=Forbidden)
+ config.add_view(forbidden_view, context=HTTPForbidden)
diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py
index 97a93616d..cc4a037c2 100644
--- a/pyramid/tests/test_config.py
+++ b/pyramid/tests/test_config.py
@@ -50,8 +50,8 @@ class ConfiguratorTests(unittest.TestCase):
return iface
def _assertNotFound(self, wrapper, *arg):
- from pyramid.exceptions import NotFound
- self.assertRaises(NotFound, wrapper, *arg)
+ from pyramid.httpexceptions import HTTPNotFound
+ self.assertRaises(HTTPNotFound, wrapper, *arg)
def _registerEventListener(self, config, event_iface=None):
if event_iface is None: # pragma: no cover
@@ -203,6 +203,35 @@ class ConfiguratorTests(unittest.TestCase):
self.assertEqual(config.registry.getUtility(IViewMapperFactory),
mapper)
+ def test_ctor_httpexception_view_default(self):
+ from pyramid.interfaces import IExceptionResponse
+ from pyramid.httpexceptions import default_exceptionresponse_view
+ from pyramid.interfaces import IRequest
+ config = self._makeOne()
+ view = self._getViewCallable(config,
+ ctx_iface=IExceptionResponse,
+ request_iface=IRequest)
+ self.failUnless(view is default_exceptionresponse_view)
+
+ def test_ctor_exceptionresponse_view_None(self):
+ from pyramid.interfaces import IExceptionResponse
+ from pyramid.interfaces import IRequest
+ config = self._makeOne(exceptionresponse_view=None)
+ view = self._getViewCallable(config,
+ ctx_iface=IExceptionResponse,
+ request_iface=IRequest)
+ self.failUnless(view is None)
+
+ def test_ctor_exceptionresponse_view_custom(self):
+ from pyramid.interfaces import IExceptionResponse
+ from pyramid.interfaces import IRequest
+ def exceptionresponse_view(context, request): pass
+ config = self._makeOne(exceptionresponse_view=exceptionresponse_view)
+ view = self._getViewCallable(config,
+ ctx_iface=IExceptionResponse,
+ request_iface=IRequest)
+ self.failUnless(view is exceptionresponse_view)
+
def test_with_package_module(self):
from pyramid.tests import test_configuration
import pyramid.tests
@@ -260,27 +289,58 @@ class ConfiguratorTests(unittest.TestCase):
result = config.absolute_asset_spec('templates')
self.assertEqual(result, 'pyramid.tests:templates')
- def test_setup_registry_fixed(self):
- class DummyRegistry(object):
- def subscribers(self, events, name):
- self.events = events
- return events
- def registerUtility(self, *arg, **kw):
- pass
+ def test__fix_registry_has_listeners(self):
reg = DummyRegistry()
config = self._makeOne(reg)
- config.add_view = lambda *arg, **kw: False
- config.setup_registry()
+ config._fix_registry()
self.assertEqual(reg.has_listeners, True)
+
+ def test__fix_registry_notify(self):
+ reg = DummyRegistry()
+ config = self._makeOne(reg)
+ config._fix_registry()
self.assertEqual(reg.notify(1), None)
self.assertEqual(reg.events, (1,))
+ def test__fix_registry_queryAdapterOrSelf(self):
+ from zope.interface import Interface
+ class IFoo(Interface):
+ pass
+ class Foo(object):
+ implements(IFoo)
+ class Bar(object):
+ pass
+ adaptation = ()
+ foo = Foo()
+ bar = Bar()
+ reg = DummyRegistry(adaptation)
+ config = self._makeOne(reg)
+ config._fix_registry()
+ self.assertTrue(reg.queryAdapterOrSelf(foo, IFoo) is foo)
+ self.assertTrue(reg.queryAdapterOrSelf(bar, IFoo) is adaptation)
+
+ def test__fix_registry_registerSelfAdapter(self):
+ reg = DummyRegistry()
+ config = self._makeOne(reg)
+ config._fix_registry()
+ reg.registerSelfAdapter('required', 'provided', name='abc')
+ self.assertEqual(len(reg.adapters), 1)
+ args, kw = reg.adapters[0]
+ self.assertEqual(args[0]('abc'), 'abc')
+ self.assertEqual(kw,
+ {'info': u'', 'provided': 'provided',
+ 'required': 'required', 'name': 'abc', 'event': True})
+
+ def test_setup_registry_calls_fix_registry(self):
+ reg = DummyRegistry()
+ config = self._makeOne(reg)
+ config.add_view = lambda *arg, **kw: False
+ config.setup_registry()
+ self.assertEqual(reg.has_listeners, True)
+
def test_setup_registry_registers_default_exceptionresponse_view(self):
from pyramid.interfaces import IExceptionResponse
from pyramid.view import default_exceptionresponse_view
- class DummyRegistry(object):
- def registerUtility(self, *arg, **kw):
- pass
reg = DummyRegistry()
config = self._makeOne(reg)
views = []
@@ -289,19 +349,29 @@ class ConfiguratorTests(unittest.TestCase):
self.assertEqual(views[0], ((default_exceptionresponse_view,),
{'context':IExceptionResponse}))
+ def test_setup_registry_registers_default_webob_iresponse_adapter(self):
+ from webob import Response
+ from pyramid.interfaces import IResponse
+ config = self._makeOne()
+ config.setup_registry()
+ response = Response()
+ self.assertTrue(
+ config.registry.queryAdapter(response, IResponse) is response)
+
def test_setup_registry_explicit_notfound_trumps_iexceptionresponse(self):
from zope.interface import implementedBy
from pyramid.interfaces import IRequest
- from pyramid.exceptions import NotFound
+ from pyramid.httpexceptions import HTTPNotFound
from pyramid.registry import Registry
reg = Registry()
config = self._makeOne(reg, autocommit=True)
config.setup_registry() # registers IExceptionResponse default view
def myview(context, request):
return 'OK'
- config.add_view(myview, context=NotFound)
+ config.add_view(myview, context=HTTPNotFound)
request = self._makeRequest(config)
- view = self._getViewCallable(config, ctx_iface=implementedBy(NotFound),
+ view = self._getViewCallable(config,
+ ctx_iface=implementedBy(HTTPNotFound),
request_iface=IRequest)
result = view(None, request)
self.assertEqual(result, 'OK')
@@ -1665,14 +1735,14 @@ class ConfiguratorTests(unittest.TestCase):
self._assertNotFound(wrapper, None, request)
def test_add_view_with_header_val_missing(self):
- from pyramid.exceptions import NotFound
+ from pyramid.httpexceptions import HTTPNotFound
view = lambda *arg: 'OK'
config = self._makeOne(autocommit=True)
config.add_view(view=view, header=r'Host:\d')
wrapper = self._getViewCallable(config)
request = self._makeRequest(config)
request.headers = {'NoHost':'1'}
- self.assertRaises(NotFound, wrapper, None, request)
+ self.assertRaises(HTTPNotFound, wrapper, None, request)
def test_add_view_with_accept_match(self):
view = lambda *arg: 'OK'
@@ -2199,12 +2269,13 @@ class ConfiguratorTests(unittest.TestCase):
def test_set_notfound_view(self):
from zope.interface import implementedBy
from pyramid.interfaces import IRequest
- from pyramid.exceptions import NotFound
+ from pyramid.httpexceptions import HTTPNotFound
config = self._makeOne(autocommit=True)
view = lambda *arg: arg
config.set_notfound_view(view)
request = self._makeRequest(config)
- view = self._getViewCallable(config, ctx_iface=implementedBy(NotFound),
+ view = self._getViewCallable(config,
+ ctx_iface=implementedBy(HTTPNotFound),
request_iface=IRequest)
result = view(None, request)
self.assertEqual(result, (None, request))
@@ -2212,13 +2283,14 @@ class ConfiguratorTests(unittest.TestCase):
def test_set_notfound_view_request_has_context(self):
from zope.interface import implementedBy
from pyramid.interfaces import IRequest
- from pyramid.exceptions import NotFound
+ from pyramid.httpexceptions import HTTPNotFound
config = self._makeOne(autocommit=True)
view = lambda *arg: arg
config.set_notfound_view(view)
request = self._makeRequest(config)
request.context = 'abc'
- view = self._getViewCallable(config, ctx_iface=implementedBy(NotFound),
+ view = self._getViewCallable(config,
+ ctx_iface=implementedBy(HTTPNotFound),
request_iface=IRequest)
result = view(None, request)
self.assertEqual(result, ('abc', request))
@@ -2227,7 +2299,7 @@ class ConfiguratorTests(unittest.TestCase):
def test_set_notfound_view_with_renderer(self):
from zope.interface import implementedBy
from pyramid.interfaces import IRequest
- from pyramid.exceptions import NotFound
+ from pyramid.httpexceptions import HTTPNotFound
config = self._makeOne(autocommit=True)
view = lambda *arg: {}
config.set_notfound_view(view,
@@ -2236,7 +2308,7 @@ class ConfiguratorTests(unittest.TestCase):
try: # chameleon depends on being able to find a threadlocal registry
request = self._makeRequest(config)
view = self._getViewCallable(config,
- ctx_iface=implementedBy(NotFound),
+ ctx_iface=implementedBy(HTTPNotFound),
request_iface=IRequest)
result = view(None, request)
finally:
@@ -2246,12 +2318,13 @@ class ConfiguratorTests(unittest.TestCase):
def test_set_forbidden_view(self):
from zope.interface import implementedBy
from pyramid.interfaces import IRequest
- from pyramid.exceptions import Forbidden
+ from pyramid.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')
@@ -2259,13 +2332,14 @@ class ConfiguratorTests(unittest.TestCase):
def test_set_forbidden_view_request_has_context(self):
from zope.interface import implementedBy
from pyramid.interfaces import IRequest
- from pyramid.exceptions 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))
@@ -2274,7 +2348,7 @@ class ConfiguratorTests(unittest.TestCase):
def test_set_forbidden_view_with_renderer(self):
from zope.interface import implementedBy
from pyramid.interfaces import IRequest
- from pyramid.exceptions import Forbidden
+ from pyramid.httpexceptions import HTTPForbidden
config = self._makeOne(autocommit=True)
view = lambda *arg: {}
config.set_forbidden_view(view,
@@ -2283,7 +2357,7 @@ class ConfiguratorTests(unittest.TestCase):
try: # chameleon requires a threadlocal registry
request = self._makeRequest(config)
view = self._getViewCallable(config,
- ctx_iface=implementedBy(Forbidden),
+ ctx_iface=implementedBy(HTTPForbidden),
request_iface=IRequest)
result = view(None, request)
finally:
@@ -2554,6 +2628,34 @@ class ConfiguratorTests(unittest.TestCase):
self.assertEqual(config.registry.getUtility(IRendererFactory, 'name'),
pyramid.tests)
+ def test_add_response_adapter(self):
+ from pyramid.interfaces import IResponse
+ config = self._makeOne(autocommit=True)
+ class Adapter(object):
+ def __init__(self, other):
+ self.other = other
+ config.add_response_adapter(Adapter, str)
+ result = config.registry.queryAdapter('foo', IResponse)
+ self.assertTrue(result.other, 'foo')
+
+ def test_add_response_adapter_self(self):
+ from pyramid.interfaces import IResponse
+ config = self._makeOne(autocommit=True)
+ class Adapter(object):
+ pass
+ config.add_response_adapter(None, Adapter)
+ adapter = Adapter()
+ result = config.registry.queryAdapter(adapter, IResponse)
+ self.assertTrue(result is adapter)
+
+ def test_add_response_adapter_dottednames(self):
+ from pyramid.interfaces import IResponse
+ config = self._makeOne(autocommit=True)
+ config.add_response_adapter('pyramid.response.Response',
+ 'types.StringType')
+ result = config.registry.queryAdapter('foo', IResponse)
+ self.assertTrue(result.body, 'foo')
+
def test_scan_integration(self):
import os
from zope.interface import alsoProvides
@@ -3653,7 +3755,7 @@ class TestViewDeriver(unittest.TestCase):
"None against context None): True")
def test_debug_auth_permission_authpol_denied(self):
- from pyramid.exceptions import Forbidden
+ from pyramid.httpexceptions import HTTPForbidden
view = lambda *arg: 'OK'
self.config.registry.settings = dict(
debug_authorization=True, reload_templates=True)
@@ -3668,7 +3770,7 @@ class TestViewDeriver(unittest.TestCase):
request = self._makeRequest()
request.view_name = 'view_name'
request.url = 'url'
- self.assertRaises(Forbidden, result, None, request)
+ self.assertRaises(HTTPForbidden, result, None, request)
self.assertEqual(len(logger.messages), 1)
self.assertEqual(logger.messages[0],
"debug_authorization of url url (view name "
@@ -3781,7 +3883,7 @@ class TestViewDeriver(unittest.TestCase):
self.assertEqual(predicates, [True, True])
def test_with_predicates_notall(self):
- from pyramid.exceptions import NotFound
+ from pyramid.httpexceptions import HTTPNotFound
view = lambda *arg: 'OK'
predicates = []
def predicate1(context, request):
@@ -3794,7 +3896,7 @@ class TestViewDeriver(unittest.TestCase):
result = deriver(view)
request = self._makeRequest()
request.method = 'POST'
- self.assertRaises(NotFound, result, None, None)
+ self.assertRaises(HTTPNotFound, result, None, None)
self.assertEqual(predicates, [True, True])
def test_with_wrapper_viewname(self):
@@ -4589,14 +4691,14 @@ class TestMultiView(unittest.TestCase):
self.assertEqual(mv.get_views(request), mv.views)
def test_match_not_found(self):
- from pyramid.exceptions import NotFound
+ from pyramid.httpexceptions import HTTPNotFound
mv = self._makeOne()
context = DummyContext()
request = DummyRequest()
- self.assertRaises(NotFound, mv.match, context, request)
+ self.assertRaises(HTTPNotFound, mv.match, context, request)
def test_match_predicate_fails(self):
- from pyramid.exceptions import NotFound
+ from pyramid.httpexceptions import HTTPNotFound
mv = self._makeOne()
def view(context, request):
""" """
@@ -4604,7 +4706,7 @@ class TestMultiView(unittest.TestCase):
mv.views = [(100, view, None)]
context = DummyContext()
request = DummyRequest()
- self.assertRaises(NotFound, mv.match, context, request)
+ self.assertRaises(HTTPNotFound, mv.match, context, request)
def test_match_predicate_succeeds(self):
mv = self._makeOne()
@@ -4618,11 +4720,11 @@ class TestMultiView(unittest.TestCase):
self.assertEqual(result, view)
def test_permitted_no_views(self):
- from pyramid.exceptions import NotFound
+ from pyramid.httpexceptions import HTTPNotFound
mv = self._makeOne()
context = DummyContext()
request = DummyRequest()
- self.assertRaises(NotFound, mv.__permitted__, context, request)
+ self.assertRaises(HTTPNotFound, mv.__permitted__, context, request)
def test_permitted_no_match_with__permitted__(self):
mv = self._makeOne()
@@ -4645,11 +4747,11 @@ class TestMultiView(unittest.TestCase):
self.assertEqual(result, False)
def test__call__not_found(self):
- from pyramid.exceptions import NotFound
+ from pyramid.httpexceptions import HTTPNotFound
mv = self._makeOne()
context = DummyContext()
request = DummyRequest()
- self.assertRaises(NotFound, mv, context, request)
+ self.assertRaises(HTTPNotFound, mv, context, request)
def test___call__intermediate_not_found(self):
from pyramid.exceptions import PredicateMismatch
@@ -4667,17 +4769,17 @@ class TestMultiView(unittest.TestCase):
self.assertEqual(response, expected_response)
def test___call__raise_not_found_isnt_interpreted_as_pred_mismatch(self):
- from pyramid.exceptions import NotFound
+ from pyramid.httpexceptions import HTTPNotFound
mv = self._makeOne()
context = DummyContext()
request = DummyRequest()
request.view_name = ''
def view1(context, request):
- raise NotFound
+ raise HTTPNotFound
def view2(context, request):
""" """
mv.views = [(100, view1, None), (99, view2, None)]
- self.assertRaises(NotFound, mv, context, request)
+ self.assertRaises(HTTPNotFound, mv, context, request)
def test___call__(self):
mv = self._makeOne()
@@ -4692,11 +4794,11 @@ class TestMultiView(unittest.TestCase):
self.assertEqual(response, expected_response)
def test__call_permissive__not_found(self):
- from pyramid.exceptions import NotFound
+ from pyramid.httpexceptions import HTTPNotFound
mv = self._makeOne()
context = DummyContext()
request = DummyRequest()
- self.assertRaises(NotFound, mv, context, request)
+ self.assertRaises(HTTPNotFound, mv, context, request)
def test___call_permissive_has_call_permissive(self):
mv = self._makeOne()
@@ -5100,3 +5202,17 @@ def dummy_extend(config, discrim):
def dummy_extend2(config, discrim):
config.action(discrim, None, config.registry)
+class DummyRegistry(object):
+ def __init__(self, adaptation=None):
+ self.utilities = []
+ self.adapters = []
+ self.adaptation = adaptation
+ def subscribers(self, events, name):
+ self.events = events
+ return events
+ def registerUtility(self, *arg, **kw):
+ self.utilities.append((arg, kw))
+ def registerAdapter(self, *arg, **kw):
+ self.adapters.append((arg, kw))
+ def queryAdapter(self, *arg, **kw):
+ return self.adaptation
diff --git a/pyramid/tests/test_exceptions.py b/pyramid/tests/test_exceptions.py
index 5d0fa1e1a..50182ee5c 100644
--- a/pyramid/tests/test_exceptions.py
+++ b/pyramid/tests/test_exceptions.py
@@ -1,26 +1,15 @@
import unittest
-class TestExceptionResponse(unittest.TestCase):
- def _makeOne(self, message):
- from pyramid.exceptions import ExceptionResponse
- return ExceptionResponse(message)
-
- def test_app_iter(self):
- exc = self._makeOne('')
- self.assertTrue('<code></code>' in exc.app_iter[0])
-
- def test_headerlist(self):
- exc = self._makeOne('')
- headerlist = exc.headerlist
- headerlist.sort()
- app_iter = exc.app_iter
- clen = str(len(app_iter[0]))
- self.assertEqual(headerlist[0], ('Content-Length', clen))
- self.assertEqual(headerlist[1], ('Content-Type', 'text/html'))
-
- def test_withmessage(self):
- exc = self._makeOne('abc&123')
- self.assertTrue('<code>abc&amp;123</code>' in exc.app_iter[0])
+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):
@@ -28,10 +17,16 @@ class TestNotFound(unittest.TestCase):
return NotFound(message)
def test_it(self):
- from pyramid.exceptions import ExceptionResponse
+ from pyramid.interfaces import IExceptionResponse
e = self._makeOne('notfound')
- self.assertTrue(isinstance(e, ExceptionResponse))
+ self.assertTrue(IExceptionResponse.providedBy(e))
self.assertEqual(e.status, '404 Not Found')
+ self.assertEqual(e.message, 'notfound')
+
+ def test_response_equivalence(self):
+ from pyramid.exceptions import NotFound
+ from pyramid.httpexceptions import HTTPNotFound
+ self.assertTrue(NotFound is HTTPNotFound)
class TestForbidden(unittest.TestCase):
def _makeOne(self, message):
@@ -39,7 +34,14 @@ class TestForbidden(unittest.TestCase):
return Forbidden(message)
def test_it(self):
- from pyramid.exceptions import ExceptionResponse
- e = self._makeOne('unauthorized')
- self.assertTrue(isinstance(e, ExceptionResponse))
+ from pyramid.interfaces import IExceptionResponse
+ e = self._makeOne('forbidden')
+ self.assertTrue(IExceptionResponse.providedBy(e))
self.assertEqual(e.status, '403 Forbidden')
+ self.assertEqual(e.message, 'forbidden')
+
+ def test_response_equivalence(self):
+ from pyramid.exceptions import Forbidden
+ from pyramid.httpexceptions import HTTPForbidden
+ self.assertTrue(Forbidden is HTTPForbidden)
+
diff --git a/pyramid/tests/test_httpexceptions.py b/pyramid/tests/test_httpexceptions.py
new file mode 100644
index 000000000..60bde460e
--- /dev/null
+++ b/pyramid/tests/test_httpexceptions.py
@@ -0,0 +1,306 @@
+import unittest
+
+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')
+ environ = _makeEnviron()
+ start_response = DummyStartResponse()
+ body = list(exc(environ, start_response))[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'
+ environ = _makeEnviron()
+ start_response = DummyStartResponse()
+ body = list(exc(environ, start_response))[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__calls_start_response(self):
+ cls = self._getTargetSubclass()
+ exc = cls()
+ exc.content_type = 'text/plain'
+ environ = _makeEnviron()
+ start_response = DummyStartResponse()
+ exc(environ, start_response)
+ self.assertTrue(start_response.headerlist)
+ self.assertEqual(start_response.status, '200 OK')
+
+ def test__default_app_iter_no_comment_plain(self):
+ cls = self._getTargetSubclass()
+ exc = cls()
+ exc.content_type = 'text/plain'
+ environ = _makeEnviron()
+ start_response = DummyStartResponse()
+ body = list(exc(environ, start_response))[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'
+ environ = _makeEnviron()
+ start_response = DummyStartResponse()
+ body = list(exc(environ, start_response))[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'
+ environ = _makeEnviron()
+ start_response = DummyStartResponse()
+ body = list(exc(environ, start_response))[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'
+ environ = _makeEnviron()
+ start_response = DummyStartResponse()
+ body = list(exc(environ, start_response))[0]
+ self.assertTrue('<!-- comment &amp; comment -->' in body)
+
+ def test_custom_body_template(self):
+ cls = self._getTargetSubclass()
+ exc = cls(body_template='${REQUEST_METHOD}')
+ exc.content_type = 'text/plain'
+ environ = _makeEnviron()
+ start_response = DummyStartResponse()
+ body = list(exc(environ, start_response))[0]
+ self.assertEqual(body, '200 OK\n\nGET')
+
+ def test_body_template_unicode(self):
+ cls = self._getTargetSubclass()
+ la = unicode('/La Pe\xc3\xb1a', 'utf-8')
+ environ = _makeEnviron(unicodeval=la)
+ exc = cls(body_template='${unicodeval}')
+ exc.content_type = 'text/plain'
+ start_response = DummyStartResponse()
+ body = list(exc(environ, start_response))[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():
+ environ = _makeEnviron()
+ start_response = DummyStartResponse()
+ exc = v()
+ exc.content_type = content_type
+ result = list(exc(environ, start_response))[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
+
+class DummyStartResponse(object):
+ def __call__(self, status, headerlist):
+ self.status = status
+ self.headerlist = headerlist
+
+def _makeEnviron(**kw):
+ environ = {'REQUEST_METHOD':'GET',
+ 'wsgi.url_scheme':'http',
+ 'SERVER_NAME':'localhost',
+ 'SERVER_PORT':'80'}
+ environ.update(kw)
+ return environ
+
diff --git a/pyramid/tests/test_request.py b/pyramid/tests/test_request.py
index e35856ce0..90c55b0f0 100644
--- a/pyramid/tests/test_request.py
+++ b/pyramid/tests/test_request.py
@@ -203,7 +203,35 @@ class TestRequest(unittest.TestCase):
self.assertEqual(result, 'abc')
self.assertEqual(info.args,
('pyramid.tests:static/foo.css', request, {}) )
+
+ def test_is_response_false(self):
+ request = self._makeOne({})
+ request.registry = self.config.registry
+ self.assertEqual(request.is_response('abc'), False)
+
+ def test_is_response_false_adapter_is_not_self(self):
+ from pyramid.interfaces import IResponse
+ request = self._makeOne({})
+ request.registry = self.config.registry
+ def adapter(ob):
+ return object()
+ class Foo(object):
+ pass
+ foo = Foo()
+ request.registry.registerAdapter(adapter, (Foo,), IResponse)
+ self.assertEqual(request.is_response(foo), False)
+ def test_is_response_adapter_true(self):
+ from pyramid.interfaces import IResponse
+ request = self._makeOne({})
+ request.registry = self.config.registry
+ class Foo(object):
+ pass
+ foo = Foo()
+ def adapter(ob):
+ return ob
+ request.registry.registerAdapter(adapter, (Foo,), IResponse)
+ self.assertEqual(request.is_response(foo), True)
class TestRequestDeprecatedMethods(unittest.TestCase):
def setUp(self):
diff --git a/pyramid/tests/test_response.py b/pyramid/tests/test_response.py
new file mode 100644
index 000000000..46eb298d1
--- /dev/null
+++ b/pyramid/tests/test_response.py
@@ -0,0 +1,17 @@
+import unittest
+
+class TestResponse(unittest.TestCase):
+ def _getTargetClass(self):
+ from pyramid.response import Response
+ return Response
+
+ 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))
+
diff --git a/pyramid/tests/test_router.py b/pyramid/tests/test_router.py
index b869a3830..5fd2cf01e 100644
--- a/pyramid/tests/test_router.py
+++ b/pyramid/tests/test_router.py
@@ -136,69 +136,70 @@ class TestRouter(unittest.TestCase):
self.assertEqual(router.request_factory, DummyRequestFactory)
def test_call_traverser_default(self):
- from pyramid.exceptions import NotFound
+ from pyramid.httpexceptions import HTTPNotFound
environ = self._makeEnviron()
logger = self._registerLogger()
router = self._makeOne()
start_response = DummyStartResponse()
- why = exc_raised(NotFound, router, environ, start_response)
+ why = exc_raised(HTTPNotFound, router, environ, start_response)
self.assertTrue('/' in why[0], why)
self.assertFalse('debug_notfound' in why[0])
self.assertEqual(len(logger.messages), 0)
def test_traverser_raises_notfound_class(self):
- from pyramid.exceptions import NotFound
+ from pyramid.httpexceptions import HTTPNotFound
environ = self._makeEnviron()
context = DummyContext()
- self._registerTraverserFactory(context, raise_error=NotFound)
+ self._registerTraverserFactory(context, raise_error=HTTPNotFound)
router = self._makeOne()
start_response = DummyStartResponse()
- self.assertRaises(NotFound, router, environ, start_response)
+ self.assertRaises(HTTPNotFound, router, environ, start_response)
def test_traverser_raises_notfound_instance(self):
- from pyramid.exceptions import NotFound
+ from pyramid.httpexceptions import HTTPNotFound
environ = self._makeEnviron()
context = DummyContext()
- self._registerTraverserFactory(context, raise_error=NotFound('foo'))
+ self._registerTraverserFactory(context, raise_error=HTTPNotFound('foo'))
router = self._makeOne()
start_response = DummyStartResponse()
- why = exc_raised(NotFound, router, environ, start_response)
+ why = exc_raised(HTTPNotFound, router, environ, start_response)
self.assertTrue('foo' in why[0], why)
def test_traverser_raises_forbidden_class(self):
- from pyramid.exceptions import Forbidden
+ from pyramid.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.exceptions 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.exceptions import NotFound
+ from pyramid.httpexceptions import HTTPNotFound
environ = self._makeEnviron()
context = DummyContext()
self._registerTraverserFactory(context)
logger = self._registerLogger()
router = self._makeOne()
start_response = DummyStartResponse()
- why = exc_raised(NotFound, router, environ, start_response)
+ why = exc_raised(HTTPNotFound, router, environ, start_response)
self.assertTrue('/' in why[0], why)
self.assertFalse('debug_notfound' in why[0])
self.assertEqual(len(logger.messages), 0)
def test_call_no_view_registered_debug_notfound_false(self):
- from pyramid.exceptions import NotFound
+ from pyramid.httpexceptions import HTTPNotFound
environ = self._makeEnviron()
context = DummyContext()
self._registerTraverserFactory(context)
@@ -206,13 +207,13 @@ class TestRouter(unittest.TestCase):
self._registerSettings(debug_notfound=False)
router = self._makeOne()
start_response = DummyStartResponse()
- why = exc_raised(NotFound, router, environ, start_response)
+ why = exc_raised(HTTPNotFound, router, environ, start_response)
self.assertTrue('/' in why[0], why)
self.assertFalse('debug_notfound' in why[0])
self.assertEqual(len(logger.messages), 0)
def test_call_no_view_registered_debug_notfound_true(self):
- from pyramid.exceptions import NotFound
+ from pyramid.httpexceptions import HTTPNotFound
environ = self._makeEnviron()
context = DummyContext()
self._registerTraverserFactory(context)
@@ -220,7 +221,7 @@ class TestRouter(unittest.TestCase):
logger = self._registerLogger()
router = self._makeOne()
start_response = DummyStartResponse()
- why = exc_raised(NotFound, router, environ, start_response)
+ why = exc_raised(HTTPNotFound, router, environ, start_response)
self.assertTrue(
"debug_notfound of url http://localhost:8080/; path_info: '/', "
"context:" in why[0])
@@ -235,7 +236,7 @@ class TestRouter(unittest.TestCase):
self.assertTrue("view_name: ''" in message)
self.assertTrue("subpath: []" in message)
- def test_call_view_returns_nonresponse(self):
+ def test_call_view_returns_non_iresponse(self):
from pyramid.interfaces import IViewClassifier
context = DummyContext()
self._registerTraverserFactory(context)
@@ -246,6 +247,24 @@ class TestRouter(unittest.TestCase):
start_response = DummyStartResponse()
self.assertRaises(ValueError, router, environ, start_response)
+ def test_call_view_returns_adapted_response(self):
+ from pyramid.response import Response
+ from pyramid.interfaces import IViewClassifier
+ from pyramid.interfaces import IResponse
+ context = DummyContext()
+ self._registerTraverserFactory(context)
+ environ = self._makeEnviron()
+ view = DummyView('abc')
+ self._registerView(view, '', IViewClassifier, None, None)
+ router = self._makeOne()
+ start_response = DummyStartResponse()
+ def make_response(s):
+ return Response(s)
+ router.registry.registerAdapter(make_response, (str,), IResponse)
+ app_iter = router(environ, start_response)
+ self.assertEqual(app_iter, ['abc'])
+ self.assertEqual(start_response.status, '200 OK')
+
def test_call_view_registered_nonspecific_default_path(self):
from pyramid.interfaces import IViewClassifier
context = DummyContext()
@@ -323,7 +342,7 @@ class TestRouter(unittest.TestCase):
def test_call_view_registered_specific_fail(self):
from zope.interface import Interface
from zope.interface import directlyProvides
- from pyramid.exceptions import NotFound
+ from pyramid.httpexceptions import HTTPNotFound
from pyramid.interfaces import IViewClassifier
class IContext(Interface):
pass
@@ -339,12 +358,12 @@ class TestRouter(unittest.TestCase):
self._registerView(view, '', IViewClassifier, IRequest, IContext)
router = self._makeOne()
start_response = DummyStartResponse()
- self.assertRaises(NotFound, router, environ, start_response)
+ self.assertRaises(HTTPNotFound, router, environ, start_response)
def test_call_view_raises_forbidden(self):
from zope.interface import Interface
from zope.interface import directlyProvides
- from pyramid.exceptions import Forbidden
+ from pyramid.httpexceptions import HTTPForbidden
class IContext(Interface):
pass
from pyramid.interfaces import IRequest
@@ -353,12 +372,13 @@ class TestRouter(unittest.TestCase):
directlyProvides(context, IContext)
self._registerTraverserFactory(context, subpath=[''])
response = DummyResponse()
- view = DummyView(response, raise_exception=Forbidden("unauthorized"))
+ view = DummyView(response,
+ raise_exception=HTTPForbidden("unauthorized"))
environ = self._makeEnviron()
self._registerView(view, '', IViewClassifier, IRequest, IContext)
router = self._makeOne()
start_response = DummyStartResponse()
- why = exc_raised(Forbidden, router, environ, start_response)
+ why = exc_raised(HTTPForbidden, router, environ, start_response)
self.assertEqual(why[0], 'unauthorized')
def test_call_view_raises_notfound(self):
@@ -368,17 +388,17 @@ class TestRouter(unittest.TestCase):
pass
from pyramid.interfaces import IRequest
from pyramid.interfaces import IViewClassifier
- from pyramid.exceptions import NotFound
+ from pyramid.httpexceptions import HTTPNotFound
context = DummyContext()
directlyProvides(context, IContext)
self._registerTraverserFactory(context, subpath=[''])
response = DummyResponse()
- view = DummyView(response, raise_exception=NotFound("notfound"))
+ view = DummyView(response, raise_exception=HTTPNotFound("notfound"))
environ = self._makeEnviron()
self._registerView(view, '', IViewClassifier, IRequest, IContext)
router = self._makeOne()
start_response = DummyStartResponse()
- why = exc_raised(NotFound, router, environ, start_response)
+ why = exc_raised(HTTPNotFound, router, environ, start_response)
self.assertEqual(why[0], 'notfound')
def test_call_request_has_response_callbacks(self):
@@ -566,7 +586,7 @@ class TestRouter(unittest.TestCase):
"pattern: 'archives/:action/:article', "))
def test_call_route_match_miss_debug_routematch(self):
- from pyramid.exceptions import NotFound
+ from pyramid.httpexceptions import HTTPNotFound
logger = self._registerLogger()
self._registerSettings(debug_routematch=True)
self._registerRouteRequest('foo')
@@ -577,7 +597,7 @@ class TestRouter(unittest.TestCase):
self._registerRootFactory(context)
router = self._makeOne()
start_response = DummyStartResponse()
- self.assertRaises(NotFound, router, environ, start_response)
+ self.assertRaises(HTTPNotFound, router, environ, start_response)
self.assertEqual(len(logger.messages), 1)
self.assertEqual(
@@ -627,11 +647,11 @@ class TestRouter(unittest.TestCase):
def test_root_factory_raises_notfound(self):
from pyramid.interfaces import IRootFactory
- from pyramid.exceptions import NotFound
+ from pyramid.httpexceptions import HTTPNotFound
from zope.interface import Interface
from zope.interface import directlyProvides
def rootfactory(request):
- raise NotFound('from root factory')
+ raise HTTPNotFound('from root factory')
self.registry.registerUtility(rootfactory, IRootFactory)
class IContext(Interface):
pass
@@ -640,16 +660,16 @@ class TestRouter(unittest.TestCase):
environ = self._makeEnviron()
router = self._makeOne()
start_response = DummyStartResponse()
- why = exc_raised(NotFound, router, environ, start_response)
+ why = exc_raised(HTTPNotFound, router, environ, start_response)
self.assertTrue('from root factory' in why[0])
def test_root_factory_raises_forbidden(self):
from pyramid.interfaces import IRootFactory
- from pyramid.exceptions import Forbidden
+ from pyramid.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
@@ -658,7 +678,7 @@ class TestRouter(unittest.TestCase):
environ = self._makeEnviron()
router = self._makeOne()
start_response = DummyStartResponse()
- why = exc_raised(Forbidden, router, environ, start_response)
+ why = exc_raised(HTTPForbidden, router, environ, start_response)
self.assertTrue('from root factory' in why[0])
def test_root_factory_exception_propagating(self):
@@ -829,7 +849,7 @@ class TestRouter(unittest.TestCase):
result = router(environ, start_response)
self.assertEqual(result, ["Hello, world"])
- def test_exception_view_returns_non_response(self):
+ def test_exception_view_returns_non_iresponse(self):
from pyramid.interfaces import IRequest
from pyramid.interfaces import IViewClassifier
from pyramid.interfaces import IExceptionViewClassifier
@@ -1054,12 +1074,22 @@ class DummyStartResponse:
def __call__(self, status, headers):
self.status = status
self.headers = headers
-
-class DummyResponse:
+
+from pyramid.interfaces import IResponse
+from zope.interface import implements
+
+class DummyResponse(object):
+ implements(IResponse)
headerlist = ()
app_iter = ()
+ environ = None
def __init__(self, status='200 OK'):
self.status = status
+
+ def __call__(self, environ, start_response):
+ self.environ = environ
+ start_response(self.status, self.headerlist)
+ return self.app_iter
class DummyThreadLocalManager:
def __init__(self):
diff --git a/pyramid/tests/test_session.py b/pyramid/tests/test_session.py
index 17a437af6..5c6454a38 100644
--- a/pyramid/tests/test_session.py
+++ b/pyramid/tests/test_session.py
@@ -90,16 +90,8 @@ class TestUnencryptedCookieSession(unittest.TestCase):
self.assertEqual(session._set_cookie(response), True)
self.assertEqual(response.headerlist[-1][0], 'Set-Cookie')
- def test__set_cookie_other_kind_of_response(self):
- request = testing.DummyRequest()
- request.exception = None
- session = self._makeOne(request)
- session['abc'] = 'x'
- response = DummyResponse()
- self.assertEqual(session._set_cookie(response), True)
- self.assertEqual(len(response.headerlist), 1)
-
def test__set_cookie_options(self):
+ from pyramid.response import Response
request = testing.DummyRequest()
request.exception = None
session = self._makeOne(request,
@@ -110,10 +102,9 @@ class TestUnencryptedCookieSession(unittest.TestCase):
cookie_httponly = True,
)
session['abc'] = 'x'
- response = DummyResponse()
+ response = Response()
self.assertEqual(session._set_cookie(response), True)
- self.assertEqual(len(response.headerlist), 1)
- cookieval= response.headerlist[0][1]
+ cookieval= response.headerlist[-1][1]
val, domain, path, secure, httponly = [x.strip() for x in
cookieval.split(';')]
self.assertTrue(val.startswith('abc='))
diff --git a/pyramid/tests/test_testing.py b/pyramid/tests/test_testing.py
index 58ca2b7d9..159a88ebd 100644
--- a/pyramid/tests/test_testing.py
+++ b/pyramid/tests/test_testing.py
@@ -150,7 +150,7 @@ class Test_registerView(TestBase):
def test_registerView_with_permission_denying(self):
from pyramid import testing
- from pyramid.exceptions import Forbidden
+ from pyramid.httpexceptions import HTTPForbidden
def view(context, request):
""" """
view = testing.registerView('moo.html', view=view, permission='bar')
@@ -160,7 +160,7 @@ class Test_registerView(TestBase):
from pyramid.view import render_view_to_response
request = DummyRequest()
request.registry = self.registry
- self.assertRaises(Forbidden, render_view_to_response,
+ self.assertRaises(HTTPForbidden, render_view_to_response,
None, request, 'moo.html')
def test_registerView_with_permission_denying2(self):
diff --git a/pyramid/tests/test_view.py b/pyramid/tests/test_view.py
index b1d48b98b..b42224d4c 100644
--- a/pyramid/tests/test_view.py
+++ b/pyramid/tests/test_view.py
@@ -120,6 +120,19 @@ class RenderViewToIterableTests(BaseTest, unittest.TestCase):
secure=True)
self.assertEqual(iterable, ())
+ def test_call_view_returns_iresponse_adaptable(self):
+ from pyramid.response import Response
+ request = self._makeRequest()
+ context = self._makeContext()
+ view = make_view('123')
+ self._registerView(request.registry, view, 'registered')
+ def str_response(s):
+ return Response(s)
+ request.registry.registerAdapter(str_response, (str,), IResponse)
+ iterable = self._callFUT(context, request, name='registered',
+ secure=True)
+ self.assertEqual(iterable, ['123'])
+
def test_call_view_registered_insecure_no_call_permissive(self):
context = self._makeContext()
request = self._makeRequest()
@@ -185,6 +198,14 @@ class RenderViewTests(BaseTest, unittest.TestCase):
self.assertEqual(s, 'anotherview')
class TestIsResponse(unittest.TestCase):
+ def setUp(self):
+ from zope.deprecation import __show__
+ __show__.off()
+
+ def tearDown(self):
+ from zope.deprecation import __show__
+ __show__.on()
+
def _callFUT(self, *arg, **kw):
from pyramid.view import is_response
return is_response(*arg, **kw)
@@ -536,9 +557,15 @@ def make_view(response):
class DummyRequest:
exception = None
-class DummyResponse:
- status = '200 OK'
+from pyramid.interfaces import IResponse
+from zope.interface import implements
+
+class DummyResponse(object):
+ implements(IResponse)
headerlist = ()
+ app_iter = ()
+ status = '200 OK'
+ environ = None
def __init__(self, body=None):
if body is None:
self.app_iter = ()
diff --git a/pyramid/view.py b/pyramid/view.py
index 2563f1e43..afa10fd0f 100644
--- a/pyramid/view.py
+++ b/pyramid/view.py
@@ -4,11 +4,13 @@ import venusian
from zope.interface import providedBy
from zope.deprecation import deprecated
+from pyramid.interfaces import IResponse
from pyramid.interfaces import IRoutesMapper
from pyramid.interfaces import IView
from pyramid.interfaces import IViewClassifier
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
@@ -44,12 +46,12 @@ def render_view_to_response(context, request, name='', secure=True):
``name`` / ``context`` / and ``request``).
If `secure`` is ``True``, and the :term:`view callable` found is
- protected by a permission, the permission will be checked before
- calling the view function. If the permission check disallows view
- execution (based on the current :term:`authorization policy`), a
- :exc:`pyramid.exceptions.Forbidden` 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))
@@ -87,19 +89,23 @@ def render_view_to_iterable(context, request, name='', secure=True):
of this function by calling ``''.join(iterable)``, or just use
:func:`pyramid.view.render_view` instead.
- If ``secure`` is ``True``, and the view is protected by a
- permission, the permission will be checked before the view
- function is invoked. If the permission check disallows view
- execution (based on the current :term:`authentication policy`), a
- :exc:`pyramid.exceptions.Forbidden` 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."""
response = render_view_to_response(context, request, name, secure)
if response is None:
return None
+ try:
+ reg = request.registry
+ except AttributeError:
+ reg = get_current_registry()
+ response = reg.queryAdapterOrSelf(response, IResponse)
return response.app_iter
def render_view(context, request, name='', secure=True):
@@ -116,12 +122,11 @@ def render_view(context, request, name='', secure=True):
``app_iter`` attribute. This function will return ``None`` if a
corresponding view cannot be found.
- If ``secure`` is ``True``, and the view is protected by a
- permission, the permission will be checked before the view is
- invoked. If the permission check disallows view execution (based
- on the current :term:`authorization policy`), a
- :exc:`pyramid.exceptions.Forbidden` 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."""
@@ -130,21 +135,6 @@ def render_view(context, request, name='', secure=True):
return None
return ''.join(iterable)
-def is_response(ob):
- """ Return ``True`` if ``ob`` implements the interface implied by
- :ref:`the_response`. ``False`` if not.
-
- .. note:: This isn't a true interface or subclass check. Instead, it's a
- duck-typing check, as response objects are not obligated to be of a
- particular class or provide any particular Zope interface."""
-
- # response objects aren't obligated to implement a Zope interface,
- # so we do it the hard way
- if ( hasattr(ob, 'app_iter') and hasattr(ob, 'headerlist') and
- hasattr(ob, 'status') ):
- return True
- return False
-
class view_config(object):
""" A function, class or method :term:`decorator` which allows a
developer to create view registrations nearer to a :term:`view
@@ -243,14 +233,6 @@ deprecated(
'pyramid.view.view_config instead (API-compat, simple '
'rename).')
-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
-
class AppendSlashNotFoundViewFactory(object):
""" There can only be one :term:`Not Found view` in any
:app:`Pyramid` application. Even if you use
@@ -271,14 +253,13 @@ class AppendSlashNotFoundViewFactory(object):
.. code-block:: python
- from pyramid.exceptions import NotFound
- from pyramid.view import AppendSlashNotFoundViewFactory
from pyramid.httpexceptions import HTTPNotFound
+ from pyramid.view import AppendSlashNotFoundViewFactory
def notfound_view(context, request): return HTTPNotFound('nope')
custom_append_slash = AppendSlashNotFoundViewFactory(notfound_view)
- config.add_view(custom_append_slash, context=NotFound)
+ config.add_view(custom_append_slash, context=HTTPNotFound)
The ``notfound_view`` supplied must adhere to the two-argument
view callable calling convention of ``(context, request)``
@@ -294,7 +275,7 @@ class AppendSlashNotFoundViewFactory(object):
if not isinstance(context, Exception):
# backwards compat for an append_notslash_view registered via
# config.set_notfound_view instead of as a proper exception view
- context = request.exception
+ context = getattr(request, 'exception', None) or context
path = request.path
registry = request.registry
mapper = registry.queryUtility(IRoutesMapper)
@@ -325,12 +306,27 @@ routes are not considered when attempting to find a matching route.
Use the :meth:`pyramid.config.Configurator.add_view` method to configure this
view as the Not Found view::
- from pyramid.exceptions import NotFound
+ from pyramid.httpexceptions import HTTPNotFound
from pyramid.view import append_slash_notfound_view
- config.add_view(append_slash_notfound_view, context=NotFound)
+ config.add_view(append_slash_notfound_view, context=HTTPNotFound)
See also :ref:`changing_the_notfound_view`.
"""
+def is_response(ob):
+ """ Return ``True`` if ``ob`` implements the interface implied by
+ :ref:`the_response`. ``False`` if not.
+
+ .. warning:: This function is deprecated as of :app:`Pyramid` 1.1. New
+ code should not use it. Instead, new code should use the
+ :func:`pyramid.request.Request.is_response` method."""
+ if ( hasattr(ob, 'app_iter') and hasattr(ob, 'headerlist') and
+ hasattr(ob, 'status') ):
+ return True
+ return False
+deprecated(
+ 'is_response',
+ 'pyramid.view.is_response is deprecated as of Pyramid 1.1. Use '
+ 'pyramid.request.Request.is_response instead.')