diff options
| author | Chris McDonough <chrism@plope.com> | 2011-05-16 02:11:56 -0400 |
|---|---|---|
| committer | Chris McDonough <chrism@plope.com> | 2011-05-16 02:11:56 -0400 |
| commit | 1ffb8e3cc21603b29ccd78152f82cca7f61a09b1 (patch) | |
| tree | a58614dbc7ccbd49d2f8a744e2c5aea01b24e444 | |
| parent | 2ce65257cce304bd0f14d3b1bd4fd83ab957398b (diff) | |
| download | pyramid-1ffb8e3cc21603b29ccd78152f82cca7f61a09b1.tar.gz pyramid-1ffb8e3cc21603b29ccd78152f82cca7f61a09b1.tar.bz2 pyramid-1ffb8e3cc21603b29ccd78152f82cca7f61a09b1.zip | |
- Added API docs for ``pyramid.httpexceptions.abort`` and
``pyramid.httpexceptions.redirect``.
- Added "HTTP Exceptions" section to Views narrative chapter including a
description of ``pyramid.httpexceptions.abort``; adjusted redirect section
to note ``pyramid.httpexceptions.redirect``.
- A default exception view for the context ``webob.exc.HTTPException`` (aka
``pyramid.httpexceptions.HTTPException``) is now registered by default.
This means that an instance of any exception class imported from
``pyramid.httpexceptions`` (such as ``HTTPFound``) can now be raised from
within view code; when raised, this exception view will render the
exception to a response.
- New functions named ``pyramid.httpexceptions.abort`` and
``pyramid.httpexceptions.redirect`` perform the equivalent of their Pylons
brethren when an HTTP exception handler is registered. These functions
take advantage of the newly registered exception view for
``webob.exc.HTTPException``.
- The Configurator now accepts an additional keyword argument named
``httpexception_view``. By default, this argument is populated with a
default exception view function that will be used when an HTTP exception is
raised. When ``None`` is passed for this value, an exception view for HTTP
exceptions will not be registered. Passing ``None`` returns the behavior
of raising an HTTP exception to that of Pyramid 1.0 (the exception will
propagate to middleware and to the WSGI server).
| -rw-r--r-- | CHANGES.txt | 28 | ||||
| -rw-r--r-- | TODO.txt | 4 | ||||
| -rw-r--r-- | docs/api/httpexceptions.rst | 4 | ||||
| -rw-r--r-- | docs/glossary.rst | 6 | ||||
| -rw-r--r-- | docs/narr/views.rst | 249 | ||||
| -rw-r--r-- | docs/whatsnew-1.1.rst | 35 | ||||
| -rw-r--r-- | pyramid/config.py | 25 | ||||
| -rw-r--r-- | pyramid/httpexceptions.py | 33 | ||||
| -rw-r--r-- | pyramid/tests/test_config.py | 46 | ||||
| -rw-r--r-- | pyramid/tests/test_httpexceptions.py | 79 |
10 files changed, 432 insertions, 77 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index 0992af9ef..756d1345c 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -37,6 +37,13 @@ Documentation - Added "What's New in Pyramid 1.1" to HTML rendering of documentation. +- Added API docs for ``pyramid.httpexceptions.abort`` and + ``pyramid.httpexceptions.redirect``. + +- Added "HTTP Exceptions" section to Views narrative chapter including a + description of ``pyramid.httpexceptions.abort``; adjusted redirect section + to note ``pyramid.httpexceptions.redirect``. + Features -------- @@ -97,6 +104,27 @@ Features section entitled "Static Routes" in the URL Dispatch narrative chapter for more information. +- A default exception view for the context ``webob.exc.HTTPException`` (aka + ``pyramid.httpexceptions.HTTPException``) is now registered by default. + This means that an instance of any exception class imported from + ``pyramid.httpexceptions`` (such as ``HTTPFound``) can now be raised from + within view code; when raised, this exception view will render the + exception to a response. + +- New functions named ``pyramid.httpexceptions.abort`` and + ``pyramid.httpexceptions.redirect`` perform the equivalent of their Pylons + brethren when an HTTP exception handler is registered. These functions + take advantage of the newly registered exception view for + ``webob.exc.HTTPException``. + +- The Configurator now accepts an additional keyword argument named + ``httpexception_view``. By default, this argument is populated with a + default exception view function that will be used when an HTTP exception is + raised. When ``None`` is passed for this value, an exception view for HTTP + exceptions will not be registered. Passing ``None`` returns the behavior + of raising an HTTP exception to that of Pyramid 1.0 (the exception will + propagate to middleware and to the WSGI server). + Bug Fixes --------- @@ -4,10 +4,6 @@ Pyramid TODOs Should-Have ----------- -- Consider adding a default exception view for HTTPException and attendant - ``redirect`` and ``abort`` functions ala Pylons (promised Mike I'd enable - this in 1.1). - - Add narrative docs for wsgiapp and wsgiapp2. Nice-to-Have diff --git a/docs/api/httpexceptions.rst b/docs/api/httpexceptions.rst index 57ca8092c..73da4126b 100644 --- a/docs/api/httpexceptions.rst +++ b/docs/api/httpexceptions.rst @@ -5,6 +5,10 @@ .. automodule:: pyramid.httpexceptions + .. autofunction:: abort + + .. autofunction:: redirect + .. attribute:: status_map A mapping of integer status code to exception class (eg. the diff --git a/docs/glossary.rst b/docs/glossary.rst index e1e9e76a9..797343e5e 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -618,6 +618,12 @@ Glossary request processing. See :ref:`exception_views` for more information. + HTTP Exception + The set of exception classes defined in :mod:`pyramid.httpexceptions`. + These can be used to generate responses with various status codes when + raised or returned from a :term:`view callable`. See also + :ref:`http_exceptions`. + thread local A thread-local variable is one which is essentially a global variable in terms of how it is accessed and treated, however, each `thread diff --git a/docs/narr/views.rst b/docs/narr/views.rst index 5c9bd91af..465cd3c0d 100644 --- a/docs/narr/views.rst +++ b/docs/narr/views.rst @@ -51,14 +51,14 @@ the request object contains everything your application needs to know about the specific HTTP request being made. A view callable's ultimate responsibility is to create a :mod:`Pyramid` -:term:`Response` object. This can be done by creating the response -object in the view callable code and returning it directly, as we will -be doing in this chapter. However, if a view callable does not return a -response itself, it can be configured to use a :term:`renderer` that -converts its return value into a :term:`Response` object. Using -renderers is the common way that templates are used with view callables -to generate markup. See the :ref:`renderers_chapter` chapter for -details. +:term:`Response` object. This can be done by creating the response object in +the view callable code and returning it directly, as we will be doing in this +chapter. However, if a view callable does not return a response itself, it +can be configured to use a :term:`renderer` that converts its return value +into a :term:`Response` object. Using renderers is the common way that +templates are used with view callables to generate markup: see the +:ref:`renderers_chapter` chapter for details. In some cases, a response may +also be generated by raising an exception within a view callable. .. index:: single: view calling convention @@ -234,8 +234,8 @@ You don't need to always use :class:`~pyramid.response.Response` to represent a response. :app:`Pyramid` provides a range of different "exception" classes which can act as response objects too. For example, an instance of the class :class:`pyramid.httpexceptions.HTTPFound` is also a valid response object -(see :ref:`http_redirect`). A view can actually return any object that has -the following attributes. +(see :ref:`http_exceptions` and ref:`http_redirect`). A view can actually +return any object that has the following attributes. status The HTTP status code (including the name) for the response as a string. @@ -255,46 +255,6 @@ app_iter These attributes form the structure of the "Pyramid Response interface". .. index:: - single: view http redirect - single: http redirect (from a view) - -.. _http_redirect: - -Using a View Callable to Do an HTTP Redirect --------------------------------------------- - -You can issue an HTTP redirect from within a view by returning a particular -kind of response. - -.. code-block:: python - :linenos: - - from pyramid.httpexceptions import HTTPFound - - def myview(request): - return HTTPFound(location='http://example.com') - -All exception types from the :mod:`pyramid.httpexceptions` module implement -the :term:`Response` interface; any can be returned as the response from a -view. See :mod:`pyramid.httpexceptions` for the documentation for the -``HTTPFound`` exception; it also includes other response types that imply -other HTTP response codes, such as ``HTTPUnauthorized`` for ``401 -Unauthorized``. - -.. note:: - - Although exception types from the :mod:`pyramid.httpexceptions` module are - in fact bona fide Python :class:`Exception` types, the :app:`Pyramid` view - machinery expects them to be *returned* by a view callable rather than - *raised*. - - It is possible, however, in Python 2.5 and above, to configure an - *exception view* to catch these exceptions, and return an appropriate - :class:`~pyramid.response.Response`. The simplest such view could just - catch and return the original exception. See :ref:`exception_views` for - more details. - -.. index:: single: view exceptions .. _special_exceptions_in_callables: @@ -304,13 +264,21 @@ Using Special Exceptions In View Callables Usually when a Python exception is raised within a view callable, :app:`Pyramid` allows the exception to propagate all the way out to the -:term:`WSGI` server which invoked the application. +:term:`WSGI` server which invoked the application. It is usually caught and +logged there. -However, for convenience, two special exceptions exist which are always -handled by :app:`Pyramid` itself. These are -:exc:`pyramid.exceptions.NotFound` and :exc:`pyramid.exceptions.Forbidden`. -Both are exception classes which accept a single positional constructor -argument: a ``message``. +However, for convenience, a special set of exceptions exists. When one of +these exceptions is raised within a view callable, it will always cause +:app:`Pyramid` to generate a response. Two categories of special exceptions +exist: internal exceptions and HTTP exceptions. + +Internal Exceptions +~~~~~~~~~~~~~~~~~~~ + +:exc:`pyramid.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. 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 @@ -320,22 +288,100 @@ If :exc:`~pyramid.exceptions.Forbidden` is raised within view code, the result of the :term:`Forbidden View` will be returned to the user agent which performed the request. -In all cases, the message provided to the exception constructor is made -available to the view which :app:`Pyramid` invokes as +Both are exception classes which accept a single positional constructor +argument: a ``message``. In all cases, the message provided to the exception +constructor is made available to the view which :app:`Pyramid` invokes as ``request.exception.args[0]``. +An example: + +.. code-block:: python + :linenos: + + from pyramid.exceptions import NotFound + + def aview(request): + raise NotFound('not found!') + +Internal exceptions may not be *returned* in order to generate a response, +they must always be *raised*. + +.. index:: + single: HTTP exceptions + +.. _http_exceptions: + +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. + +For example, the :class:`pyramid.httpexceptions.HTTPUnauthorized` exception +can be raised. This will cause a response to be generated with a ``401 +Unauthorized`` status: + +.. code-block:: python + :linenos: + + from pyramid.httpexceptions import 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 +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 + + def aview(request): + abort(401) + +This is the case because ``401`` is the HTTP status code for "HTTP +Unauthorized". Therefore, ``abort(401)`` is functionally equivalent to +``raise HTTPUnauthorized()``. Other exceptions in +:mod:`pyramid.httpexceptions` can be raised via +:func:`pyramid.httpexceptions.abort` as well, as long as the status code +associated with the exception is provided to the function. + +An HTTP exception, instead of being raised, can alternately be *returned* +(HTTP exceptions are also valid response objects): + +.. code-block:: python + :linenos: + + from pyramid.httpexceptions 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.Foribdden` 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 .. _exception_views: -Exception Views ---------------- +Custom Exception Views +---------------------- -The machinery which allows the special :exc:`~pyramid.exceptions.NotFound` and -:exc:`~pyramid.exceptions.Forbidden` exceptions to be caught by specialized -views as described in :ref:`special_exceptions_in_callables` can also be used -by application developers to convert arbitrary exceptions to responses. +The machinery which allows :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. To register a view that should be called whenever a particular exception is raised from with :app:`Pyramid` view code, use the exception class or one of @@ -359,6 +405,7 @@ raises a ``helloworld.exceptions.ValidationFailure`` exception: .. code-block:: python :linenos: + from pyramid.view import view_config from helloworld.exceptions import ValidationFailure @view_config(context=ValidationFailure) @@ -380,12 +427,13 @@ exception view registration: :linenos: from pyramid.view import view_config - from pyramid.exceptions import NotFound - from pyramid.httpexceptions import HTTPNotFound + from helloworld.exceptions import ValidationFailure - @view_config(context=NotFound, route_name='home') - def notfound_view(request): - return HTTPNotFound() + @view_config(context=ValidationFailure, route_name='home') + def failed_validation(exc, request): + response = Response('Failed validation: %s' % exc.msg) + response.status_int = 500 + return response The above exception view names the ``route_name`` of ``home``, meaning that it will only be called when the route matched has a name of ``home``. You @@ -407,7 +455,68 @@ exception views which have a name will be ignored. can use an exception as ``context`` for a normal view. Exception views can be configured with any view registration mechanism: -``@view_config`` decorator, ZCML, or imperative ``add_view`` styles. +``@view_config`` decorator or imperative ``add_view`` styles. + +.. index:: + single: view http redirect + single: http redirect (from a view) + +.. _http_redirect: + +Using a View Callable to Do an HTTP Redirect +-------------------------------------------- + +Two methods exist to redirect to another URL from within a view callable: a +short form and a long form. The short form should be preferred when +possible. + +Short Form +~~~~~~~~~~ + +You can issue an HTTP redirect from within a view callable by using the +:func:`pyramid.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. + +.. code-block:: python + :linenos: + + from pyramid.httpexceptions import redirect + + def myview(request): + redirect('http://example.com') + +Long Form +~~~~~~~~~ + +You can issue an HTTP redirect from within a view "by hand" instead of +relying on the :func:`pyramid.httpexceptions.redirect` function to do it for +you. + +To do so, you can *return* a :class:`pyramid.httpexceptions.HTTPFound` +instance. + +.. code-block:: python + :linenos: + + from pyramid.httpexceptions import HTTPFound + + def myview(request): + return HTTPFound(location='http://example.com') + +Or, alternately, you can *raise* an HTTPFound exception instead of returning +one. + +.. code-block:: python + :linenos: + + from pyramid.httpexceptions import HTTPFound + + def myview(request): + raise HTTPFound(location='http://example.com') + +The above form of generating a response by raising HTTPFound is completely +equivalent to ``redirect('http://example.com')``. .. index:: single: unicode, views, and forms diff --git a/docs/whatsnew-1.1.rst b/docs/whatsnew-1.1.rst index 992e0b637..488328519 100644 --- a/docs/whatsnew-1.1.rst +++ b/docs/whatsnew-1.1.rst @@ -18,6 +18,9 @@ The major feature additions in Pyramid 1.1 are: - Support for "static" routes. +- Default HTTP exception view and associated ``redirect`` and ``abort`` + convenience functions. + ``request.response`` ~~~~~~~~~~~~~~~~~~~~ @@ -50,6 +53,31 @@ Static Routes be useful for URL generation via ``route_url`` and ``route_path``. See the section entitled :ref:`static_route_narr` for more information. +Default HTTP Exception View +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- A default exception view for the context :exc:`webob.exc.HTTPException` + (aka :class:`pyramid.httpexceptions.HTTPException`) is now registered by + default. This means that an instance of any exception class imported from + :mod:`pyramid.httpexceptions` (such as ``HTTPFound``) can now be raised + from within view code; when raised, this exception view will render the + exception to a response. + + New convenience functions named :func:`pyramid.httpexceptions.abort` and + :func:`pyramid.httpexceptions.redirect` perform the equivalent of their + Pylons brethren when an HTTP exception handler is registered. These + functions take advantage of the newly registered exception view for + :exc:`webob.exc.HTTPException`. + + To allow for configuration of this feature, the :term:`Configurator` now + accepts an additional keyword argument named ``httpexception_view``. By + default, this argument is populated with a default exception view function + that will be used when an HTTP exception is raised. When ``None`` is + passed for this value, an exception view for HTTP exceptions will not be + registered. Passing ``None`` returns the behavior of raising an HTTP + exception to that of Pyramid 1.0 (the exception will propagate to + middleware and to the WSGI server). + Minor Feature Additions ----------------------- @@ -222,3 +250,10 @@ Documentation Enhancements - Added a section to the "URL Dispatch" narrative chapter regarding the new "static" route feature entitled :ref:`static_route_narr`. + +- Added API docs for :func:`pyramid.httpexceptions.abort` and + :func:`pyramid.httpexceptions.redirect`. + +- Added :ref:`http_exceptions` section to Views narrative chapter including a + description of :func:`pyramid.httpexceptions.abort`` and + :func:`pyramid.httpexceptions.redirect`. diff --git a/pyramid/config.py b/pyramid/config.py index 4e06a9b2e..85a66a837 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -59,6 +59,8 @@ from pyramid.exceptions import ConfigurationError from pyramid.exceptions import Forbidden from pyramid.exceptions import NotFound from pyramid.exceptions import PredicateMismatch +from pyramid.httpexceptions import HTTPException +from pyramid.httpexceptions import default_httpexception_view from pyramid.i18n import get_localizer from pyramid.log import make_stream_logger from pyramid.mako_templating import renderer_factory as mako_renderer_factory @@ -139,7 +141,8 @@ class Configurator(object): ``package``, ``settings``, ``root_factory``, ``authentication_policy``, ``authorization_policy``, ``renderers`` ``debug_logger``, ``locale_negotiator``, ``request_factory``, ``renderer_globals_factory``, - ``default_permission``, ``session_factory``, and ``autocommit``. + ``default_permission``, ``session_factory``, ``default_view_mapper``, + ``autocommit``, and ``httpexception_view``. If the ``registry`` argument is passed as a non-``None`` value, it must be an instance of the :class:`pyramid.registry.Registry` @@ -254,7 +257,17 @@ class Configurator(object): :term:`view mapper` factory for view configurations that don't otherwise specify one (see :class:`pyramid.interfaces.IViewMapperFactory`). If a default_view_mapper is not passed, a superdefault view mapper will be - used. """ + used. + + If ``httpexception_view`` is passed, it must be a :term:`view callable` + or ``None``. If it is a view callable, it will be used as an exception + view callable when an :term:`HTTP exception` is raised (any named + exception from the ``pyramid.httpexceptions`` module) by + :func:`pyramid.httpexceptions.abort`, + :func:`pyramid.httpexceptions.redirect` or 'by hand'. If it is ``None``, + no httpexception view will be registered. By default, the + ``pyramid.httpexceptions.default_httpexception_view`` function is + used. This behavior is new in Pyramid 1.1. """ manager = manager # for testing injection venusian = venusian # for testing injection @@ -277,6 +290,7 @@ class Configurator(object): session_factory=None, default_view_mapper=None, autocommit=False, + httpexception_view=default_httpexception_view, ): if package is None: package = caller_package() @@ -302,6 +316,7 @@ class Configurator(object): default_permission=default_permission, session_factory=session_factory, default_view_mapper=default_view_mapper, + httpexception_view=httpexception_view, ) def _set_settings(self, mapping): @@ -658,7 +673,8 @@ class Configurator(object): renderers=DEFAULT_RENDERERS, debug_logger=None, locale_negotiator=None, request_factory=None, renderer_globals_factory=None, default_permission=None, - session_factory=None, default_view_mapper=None): + session_factory=None, default_view_mapper=None, + httpexception_view=default_httpexception_view): """ When you pass a non-``None`` ``registry`` argument to the :term:`Configurator` constructor, no initial 'setup' is performed against the registry. This is because the registry you pass in may @@ -690,6 +706,9 @@ class Configurator(object): self.add_renderer(name, renderer) self.add_view(default_exceptionresponse_view, context=IExceptionResponse) + if httpexception_view is not None: + httpexception_view = self.maybe_dotted(httpexception_view) + self.add_view(httpexception_view, context=HTTPException) if locale_negotiator: locale_negotiator = self.maybe_dotted(locale_negotiator) registry.registerUtility(locale_negotiator, ILocaleNegotiator) diff --git a/pyramid/httpexceptions.py b/pyramid/httpexceptions.py index f56910b53..cbd87520b 100644 --- a/pyramid/httpexceptions.py +++ b/pyramid/httpexceptions.py @@ -48,3 +48,36 @@ from webob.exc import HTTPBadGateway from webob.exc import HTTPServiceUnavailable from webob.exc import HTTPGatewayTimeout from webob.exc import HTTPVersionNotSupported + +from webob.response import Response + +def abort(status_code, **kw): + """Aborts the request immediately by raising an HTTP exception. The + values in ``*kw`` will be passed to the HTTP exception constructor. + Example:: + + abort(404) # raises an HTTPNotFound exception. + """ + exc = status_map[status_code](**kw) + raise exc.exception + + +def redirect(url, code=302, **kw): + """Raises a redirect exception to the specified URL. + + Optionally, a code variable may be passed with the status code of + the redirect, ie:: + + redirect(route_url('foo', request), code=303) + + """ + exc = status_map[code] + raise exc(location=url, **kw).exception + +def default_httpexception_view(context, request): + if isinstance(context, Response): + # WSGIHTTPException, a Response (2.5+) + return context + # HTTPException, a WSGI app (2.4) + return request.get_response(context) + diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py index 97a93616d..9c8f4875b 100644 --- a/pyramid/tests/test_config.py +++ b/pyramid/tests/test_config.py @@ -203,6 +203,38 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(config.registry.getUtility(IViewMapperFactory), mapper) + def test_ctor_httpexception_view_default(self): + from zope.interface import implementedBy + from pyramid.httpexceptions import HTTPException + from pyramid.httpexceptions import default_httpexception_view + from pyramid.interfaces import IRequest + config = self._makeOne() + view = self._getViewCallable(config, + ctx_iface=implementedBy(HTTPException), + request_iface=IRequest) + self.failUnless(view is default_httpexception_view) + + def test_ctor_httpexception_view_None(self): + from zope.interface import implementedBy + from pyramid.httpexceptions import HTTPException + from pyramid.interfaces import IRequest + config = self._makeOne(httpexception_view=None) + view = self._getViewCallable(config, + ctx_iface=implementedBy(HTTPException), + request_iface=IRequest) + self.failUnless(view is None) + + def test_ctor_httpexception_view_custom(self): + from zope.interface import implementedBy + from pyramid.httpexceptions import HTTPException + from pyramid.interfaces import IRequest + def httpexception_view(context, request): pass + config = self._makeOne(httpexception_view=httpexception_view) + view = self._getViewCallable(config, + ctx_iface=implementedBy(HTTPException), + request_iface=IRequest) + self.failUnless(view is httpexception_view) + def test_with_package_module(self): from pyramid.tests import test_configuration import pyramid.tests @@ -289,6 +321,20 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(views[0], ((default_exceptionresponse_view,), {'context':IExceptionResponse})) + def test_setup_registry_registers_default_httpexception_view(self): + from pyramid.httpexceptions import HTTPException + from pyramid.httpexceptions import default_httpexception_view + class DummyRegistry(object): + def registerUtility(self, *arg, **kw): + pass + reg = DummyRegistry() + config = self._makeOne(reg) + views = [] + config.add_view = lambda *arg, **kw: views.append((arg, kw)) + config.setup_registry() + self.assertEqual(views[1], ((default_httpexception_view,), + {'context':HTTPException})) + def test_setup_registry_explicit_notfound_trumps_iexceptionresponse(self): from zope.interface import implementedBy from pyramid.interfaces import IRequest diff --git a/pyramid/tests/test_httpexceptions.py b/pyramid/tests/test_httpexceptions.py new file mode 100644 index 000000000..843f9485a --- /dev/null +++ b/pyramid/tests/test_httpexceptions.py @@ -0,0 +1,79 @@ +import unittest + +class Test_abort(unittest.TestCase): + def _callFUT(self, *arg, **kw): + from pyramid.httpexceptions import abort + return abort(*arg, **kw) + + def test_status_404(self): + from pyramid.httpexceptions import HTTPNotFound + self.assertRaises(HTTPNotFound().exception.__class__, + self._callFUT, 404) + + def test_status_201(self): + from pyramid.httpexceptions import HTTPCreated + self.assertRaises(HTTPCreated().exception.__class__, + self._callFUT, 201) + + def test_extra_kw(self): + from pyramid.httpexceptions import HTTPNotFound + try: + self._callFUT(404, headers=[('abc', 'def')]) + except HTTPNotFound().exception.__class__, 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.httpexceptions import redirect + return redirect(*arg, **kw) + + def test_default(self): + from pyramid.httpexceptions import HTTPFound + try: + self._callFUT('http://example.com') + except HTTPFound().exception.__class__, exc: + self.assertEqual(exc.location, 'http://example.com') + self.assertEqual(exc.status, '302 Found') + + def test_custom_code(self): + from pyramid.httpexceptions import HTTPMovedPermanently + try: + self._callFUT('http://example.com', 301) + except HTTPMovedPermanently().exception.__class__, exc: + self.assertEqual(exc.location, 'http://example.com') + self.assertEqual(exc.status, '301 Moved Permanently') + + def test_extra_kw(self): + from pyramid.httpexceptions import HTTPFound + try: + self._callFUT('http://example.com', headers=[('abc', 'def')]) + except HTTPFound().exception.__class__, exc: + self.assertEqual(exc.location, 'http://example.com') + self.assertEqual(exc.status, '302 Found') + self.assertEqual(exc.headers['abc'], 'def') + + +class Test_default_httpexception_view(unittest.TestCase): + def _callFUT(self, context, request): + from pyramid.httpexceptions import default_httpexception_view + return default_httpexception_view(context, request) + + def test_call_with_response(self): + from pyramid.response import Response + r = Response() + result = self._callFUT(r, None) + self.assertEqual(result, r) + + def test_call_with_nonresponse(self): + request = DummyRequest() + result = self._callFUT(None, request) + self.assertEqual(result, 'response') + +class DummyRequest(object): + def get_response(self, context): + return 'response' + + + |
