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