summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@plope.com>2011-05-26 14:13:45 -0400
committerChris McDonough <chrism@plope.com>2011-05-26 14:13:45 -0400
commit8c2a9e6d8d4f089222db7b30324774e94279b0e4 (patch)
tree4e92fbc6f359c2c1ca13bf5bdd52cb88e599ff62
parente1e0df9ff85d5d31b355527549e0b5654cce3af9 (diff)
downloadpyramid-8c2a9e6d8d4f089222db7b30324774e94279b0e4.tar.gz
pyramid-8c2a9e6d8d4f089222db7b30324774e94279b0e4.tar.bz2
pyramid-8c2a9e6d8d4f089222db7b30324774e94279b0e4.zip
work towards unifying NotFound/HTTPNotFound and Forbidden/HTTPForbidden; 2 tests fail
-rw-r--r--pyramid/config.py52
-rw-r--r--pyramid/exceptions.py215
-rw-r--r--pyramid/httpexceptions.py83
-rw-r--r--pyramid/router.py2
-rw-r--r--pyramid/testing.py2
-rw-r--r--pyramid/tests/forbiddenapp/__init__.py4
-rw-r--r--pyramid/tests/test_config.py45
-rw-r--r--pyramid/tests/test_exceptions.py114
-rw-r--r--pyramid/tests/test_httpexceptions.py80
-rw-r--r--pyramid/view.py31
10 files changed, 302 insertions, 326 deletions
diff --git a/pyramid/config.py b/pyramid/config.py
index 85a66a837..a20f93a90 100644
--- a/pyramid/config.py
+++ b/pyramid/config.py
@@ -56,11 +56,10 @@ from pyramid.compat import md5
from pyramid.compat import any
from pyramid.events import ApplicationCreated
from pyramid.exceptions import ConfigurationError
+from pyramid.exceptions import default_exceptionresponse_view
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
@@ -82,7 +81,6 @@ from pyramid.traversal import find_interface
from pyramid.traversal import traversal_path
from pyramid.urldispatch import RoutesMapper
from pyramid.util import DottedNameResolver
-from pyramid.view import default_exceptionresponse_view
from pyramid.view import render_view_to_response
from pyramid.view import is_response
@@ -142,7 +140,7 @@ class Configurator(object):
``authorization_policy``, ``renderers`` ``debug_logger``,
``locale_negotiator``, ``request_factory``, ``renderer_globals_factory``,
``default_permission``, ``session_factory``, ``default_view_mapper``,
- ``autocommit``, and ``httpexception_view``.
+ ``autocommit``, and ``exceptionresponse_view``.
If the ``registry`` argument is passed as a non-``None`` value, it
must be an instance of the :class:`pyramid.registry.Registry`
@@ -259,15 +257,18 @@ class Configurator(object):
default_view_mapper is not passed, a superdefault view mapper will be
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. """
+ If ``exceptionresponse_view`` is passed, it must be a :term:`view
+ callable` or ``None``. If it is a view callable, it will be used as an
+ exception view callable when an :term:`exception response` is raised (any
+ named exception from the ``pyramid.exceptions`` module that begins with
+ ``HTTP`` as well as the ``NotFound`` and ``Forbidden`` exceptions) as
+ well as exceptions raised via :func:`pyramid.exceptions.abort`,
+ :func:`pyramid.exceptions.redirect`. If ``exceptionresponse_view`` is
+ ``None``, no exception response view will be registered, and all
+ raised exception responses will be bubbled up to Pyramid's caller. By
+ default, the ``pyramid.exceptions.default_exceptionresponse_view``
+ function is used as the ``exceptionresponse_view``. This argument is new
+ in Pyramid 1.1. """
manager = manager # for testing injection
venusian = venusian # for testing injection
@@ -290,7 +291,7 @@ class Configurator(object):
session_factory=None,
default_view_mapper=None,
autocommit=False,
- httpexception_view=default_httpexception_view,
+ exceptionresponse_view=default_exceptionresponse_view,
):
if package is None:
package = caller_package()
@@ -316,7 +317,7 @@ class Configurator(object):
default_permission=default_permission,
session_factory=session_factory,
default_view_mapper=default_view_mapper,
- httpexception_view=httpexception_view,
+ exceptionresponse_view=exceptionresponse_view,
)
def _set_settings(self, mapping):
@@ -674,7 +675,7 @@ class Configurator(object):
locale_negotiator=None, request_factory=None,
renderer_globals_factory=None, default_permission=None,
session_factory=None, default_view_mapper=None,
- httpexception_view=default_httpexception_view):
+ exceptionresponse_view=default_exceptionresponse_view):
""" When you pass a non-``None`` ``registry`` argument to the
:term:`Configurator` constructor, no initial 'setup' is performed
against the registry. This is because the registry you pass in may
@@ -704,11 +705,9 @@ class Configurator(object):
authorization_policy)
for name, renderer in renderers:
self.add_renderer(name, renderer)
- self.add_view(default_exceptionresponse_view,
- context=IExceptionResponse)
- if httpexception_view is not None:
- httpexception_view = self.maybe_dotted(httpexception_view)
- self.add_view(httpexception_view, context=HTTPException)
+ if exceptionresponse_view is not None:
+ exceptionresponse_view = self.maybe_dotted(exceptionresponse_view)
+ self.add_view(exceptionresponse_view, context=IExceptionResponse)
if locale_negotiator:
locale_negotiator = self.maybe_dotted(locale_negotiator)
registry.registerUtility(locale_negotiator, ILocaleNegotiator)
@@ -724,7 +723,7 @@ class Configurator(object):
if session_factory is not None:
self.set_session_factory(session_factory)
# commit before adding default_view_mapper, as the
- # default_exceptionresponse_view above requires the superdefault view
+ # exceptionresponse_view above requires the superdefault view
# mapper
self.commit()
if default_view_mapper is not None:
@@ -2703,7 +2702,7 @@ class MultiView(object):
return view
if view.__predicated__(context, request):
return view
- raise PredicateMismatch(self.name)
+ raise PredicateMismatch(self.name).exception
def __permitted__(self, context, request):
view = self.match(context, request)
@@ -2722,7 +2721,7 @@ class MultiView(object):
return view(context, request)
except PredicateMismatch:
continue
- raise PredicateMismatch(self.name)
+ raise PredicateMismatch(self.name).exception
def wraps_view(wrapped):
def inner(self, view):
@@ -2845,7 +2844,7 @@ class ViewDeriver(object):
return view(context, request)
msg = getattr(request, 'authdebug_message',
'Unauthorized: %s failed permission check' % view)
- raise Forbidden(msg, result)
+ raise Forbidden(msg, result=result).exception
_secured_view.__call_permissive__ = view
_secured_view.__permitted__ = _permitted
_secured_view.__permission__ = permission
@@ -2894,7 +2893,8 @@ class ViewDeriver(object):
def predicate_wrapper(context, request):
if all((predicate(context, request) for predicate in predicates)):
return view(context, request)
- raise PredicateMismatch('predicate mismatch for view %s' % view)
+ raise PredicateMismatch(
+ 'predicate mismatch for view %s' % view).exception
def checker(context, request):
return all((predicate(context, request) for predicate in
predicates))
diff --git a/pyramid/exceptions.py b/pyramid/exceptions.py
index 771d71b88..60e3c7b9b 100644
--- a/pyramid/exceptions.py
+++ b/pyramid/exceptions.py
@@ -1,42 +1,91 @@
from zope.configuration.exceptions import ConfigurationError as ZCE
-from zope.interface import implements
-
-from pyramid.decorator import reify
+from zope.interface import classImplements
from pyramid.interfaces import IExceptionResponse
-import cgi
-
-class ExceptionResponse(Exception):
- """ Abstract class to support behaving as a WSGI response object """
- implements(IExceptionResponse)
- status = None
-
- def __init__(self, message=''):
- Exception.__init__(self, message) # B / C
- self.message = message
-
- @reify # defer execution until asked explicitly
- def app_iter(self):
- return [
- """
- <html>
- <title>%s</title>
- <body>
- <h1>%s</h1>
- <code>%s</code>
- </body>
- </html>
- """ % (self.status, self.status, cgi.escape(self.message))
- ]
-
- @reify # defer execution until asked explicitly
- def headerlist(self):
- return [
- ('Content-Length', str(len(self.app_iter[0]))),
- ('Content-Type', 'text/html')
- ]
-
-
-class Forbidden(ExceptionResponse):
+from webob.response import Response
+
+# Documentation proxy import
+from webob.exc import __doc__
+
+# API: status_map
+from webob.exc import status_map
+status_map = status_map.copy() # we mutate it
+
+# API: parent classes
+from webob.exc import HTTPException
+from webob.exc import WSGIHTTPException
+from webob.exc import HTTPOk
+from webob.exc import HTTPRedirection
+from webob.exc import HTTPError
+from webob.exc import HTTPClientError
+from webob.exc import HTTPServerError
+
+# slightly nasty import-time side effect to provide WSGIHTTPException
+# with IExceptionResponse interface (used during config.py exception view
+# registration)
+classImplements(WSGIHTTPException, IExceptionResponse)
+
+# API: Child classes
+from webob.exc import HTTPCreated
+from webob.exc import HTTPAccepted
+from webob.exc import HTTPNonAuthoritativeInformation
+from webob.exc import HTTPNoContent
+from webob.exc import HTTPResetContent
+from webob.exc import HTTPPartialContent
+from webob.exc import HTTPMultipleChoices
+from webob.exc import HTTPMovedPermanently
+from webob.exc import HTTPFound
+from webob.exc import HTTPSeeOther
+from webob.exc import HTTPNotModified
+from webob.exc import HTTPUseProxy
+from webob.exc import HTTPTemporaryRedirect
+from webob.exc import HTTPBadRequest
+from webob.exc import HTTPUnauthorized
+from webob.exc import HTTPPaymentRequired
+from webob.exc import HTTPMethodNotAllowed
+from webob.exc import HTTPNotAcceptable
+from webob.exc import HTTPProxyAuthenticationRequired
+from webob.exc import HTTPRequestTimeout
+from webob.exc import HTTPConflict
+from webob.exc import HTTPGone
+from webob.exc import HTTPLengthRequired
+from webob.exc import HTTPPreconditionFailed
+from webob.exc import HTTPRequestEntityTooLarge
+from webob.exc import HTTPRequestURITooLong
+from webob.exc import HTTPUnsupportedMediaType
+from webob.exc import HTTPRequestRangeNotSatisfiable
+from webob.exc import HTTPExpectationFailed
+from webob.exc import HTTPInternalServerError
+from webob.exc import HTTPNotImplemented
+from webob.exc import HTTPBadGateway
+from webob.exc import HTTPServiceUnavailable
+from webob.exc import HTTPGatewayTimeout
+from webob.exc import HTTPVersionNotSupported
+
+# API: HTTPNotFound and HTTPForbidden (redefined for bw compat)
+
+from webob.exc import HTTPForbidden as _HTTPForbidden
+from webob.exc import HTTPNotFound as _HTTPNotFound
+
+class HTTPNotFound(_HTTPNotFound):
+ """
+ Raise this exception within :term:`view` code to immediately
+ return the :term:`Not Found view` to the invoking user. Usually
+ this is a basic ``404`` page, but the Not Found view can be
+ customized as necessary. See :ref:`changing_the_notfound_view`.
+
+ This exception's constructor accepts a single positional argument, which
+ should be a string. The value of this string will be available as the
+ ``message`` attribute of this exception, for availability to the
+ :term:`Not Found View`.
+ """
+ def __init__(self, detail=None, headers=None, comment=None,
+ body_template=None, **kw):
+ self.message = detail # prevent 2.6.X whining
+ _HTTPNotFound.__init__(self, detail=detail, headers=headers,
+ comment=comment, body_template=body_template,
+ **kw)
+
+class HTTPForbidden(_HTTPForbidden):
"""
Raise this exception within :term:`view` code to immediately return the
:term:`forbidden view` to the invoking user. Usually this is a basic
@@ -58,25 +107,20 @@ class Forbidden(ExceptionResponse):
exception as necessary to provide extended information in an error
report shown to a user.
"""
- status = '403 Forbidden'
- def __init__(self, message='', result=None):
- ExceptionResponse.__init__(self, message)
- self.message = message
- self.result = result
+ def __init__(self, detail=None, headers=None, comment=None,
+ body_template=None, result=None, **kw):
+ self.message = detail # prevent 2.6.X whining
+ self.result = result # bw compat
+ _HTTPForbidden.__init__(self, detail=detail, headers=headers,
+ comment=comment, body_template=body_template,
+ **kw)
-class NotFound(ExceptionResponse):
- """
- Raise this exception within :term:`view` code to immediately
- return the :term:`Not Found view` to the invoking user. Usually
- this is a basic ``404`` page, but the Not Found view can be
- customized as necessary. See :ref:`changing_the_notfound_view`.
+NotFound = HTTPNotFound # bw compat
+Forbidden = HTTPForbidden # bw compat
- This exception's constructor accepts a single positional argument, which
- should be a string. The value of this string will be available as the
- ``message`` attribute of this exception, for availability to the
- :term:`Not Found View`.
- """
- status = '404 Not Found'
+# patch our status map with subclasses
+status_map[403] = HTTPForbidden
+status_map[404] = HTTPNotFound
class PredicateMismatch(NotFound):
"""
@@ -102,3 +146,66 @@ 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. 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 is_response(ob):
+ """ Return ``True`` if ``ob`` implements the interface implied by
+ :ref:`the_response`. ``False`` if not.
+
+ .. note:: This isn't a true interface or subclass check. Instead, it's a
+ duck-typing check, as response objects are not obligated to be of a
+ particular class or provide any particular Zope interface."""
+
+ # response objects aren't obligated to implement a Zope interface,
+ # so we do it the hard way
+ if ( hasattr(ob, 'app_iter') and hasattr(ob, 'headerlist') and
+ hasattr(ob, 'status') ):
+ return True
+ return False
+
+newstyle_exceptions = issubclass(Exception, object)
+
+if newstyle_exceptions:
+ # webob exceptions will be Response objects (Py 2.5+)
+ 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
+ # WSGIHTTPException, a Response (2.5+)
+ return context
+
+else:
+ # webob exceptions will not be Response objects (Py 2.4)
+ 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
+ # HTTPException, not a Response (2.4)
+ get_response = getattr(request, 'get_response', lambda c: c) # testing
+ return get_response(context)
diff --git a/pyramid/httpexceptions.py b/pyramid/httpexceptions.py
index cbd87520b..8b2a012cc 100644
--- a/pyramid/httpexceptions.py
+++ b/pyramid/httpexceptions.py
@@ -1,83 +1,2 @@
-from webob.exc import __doc__
-from webob.exc import status_map
-
-# Parent classes
-from webob.exc import HTTPException
-from webob.exc import WSGIHTTPException
-from webob.exc import HTTPOk
-from webob.exc import HTTPRedirection
-from webob.exc import HTTPError
-from webob.exc import HTTPClientError
-from webob.exc import HTTPServerError
-
-# Child classes
-from webob.exc import HTTPCreated
-from webob.exc import HTTPAccepted
-from webob.exc import HTTPNonAuthoritativeInformation
-from webob.exc import HTTPNoContent
-from webob.exc import HTTPResetContent
-from webob.exc import HTTPPartialContent
-from webob.exc import HTTPMultipleChoices
-from webob.exc import HTTPMovedPermanently
-from webob.exc import HTTPFound
-from webob.exc import HTTPSeeOther
-from webob.exc import HTTPNotModified
-from webob.exc import HTTPUseProxy
-from webob.exc import HTTPTemporaryRedirect
-from webob.exc import HTTPBadRequest
-from webob.exc import HTTPUnauthorized
-from webob.exc import HTTPPaymentRequired
-from webob.exc import HTTPForbidden
-from webob.exc import HTTPNotFound
-from webob.exc import HTTPMethodNotAllowed
-from webob.exc import HTTPNotAcceptable
-from webob.exc import HTTPProxyAuthenticationRequired
-from webob.exc import HTTPRequestTimeout
-from webob.exc import HTTPConflict
-from webob.exc import HTTPGone
-from webob.exc import HTTPLengthRequired
-from webob.exc import HTTPPreconditionFailed
-from webob.exc import HTTPRequestEntityTooLarge
-from webob.exc import HTTPRequestURITooLong
-from webob.exc import HTTPUnsupportedMediaType
-from webob.exc import HTTPRequestRangeNotSatisfiable
-from webob.exc import HTTPExpectationFailed
-from webob.exc import HTTPInternalServerError
-from webob.exc import HTTPNotImplemented
-from webob.exc import HTTPBadGateway
-from webob.exc import HTTPServiceUnavailable
-from webob.exc import HTTPGatewayTimeout
-from webob.exc import HTTPVersionNotSupported
-
-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)
+from pyramid.exceptions import * # bw compat
diff --git a/pyramid/router.py b/pyramid/router.py
index b8a8639aa..069db52bc 100644
--- a/pyramid/router.py
+++ b/pyramid/router.py
@@ -153,7 +153,7 @@ class Router(object):
logger and logger.debug(msg)
else:
msg = request.path_info
- raise NotFound(msg)
+ raise NotFound(msg).exception
else:
response = view_callable(context, request)
diff --git a/pyramid/testing.py b/pyramid/testing.py
index 36cc38830..a512ede4b 100644
--- a/pyramid/testing.py
+++ b/pyramid/testing.py
@@ -217,7 +217,7 @@ def registerView(name, result='', view=None, for_=(Interface, Interface),
else:
def _secure(context, request):
if not has_permission(permission, context, request):
- raise Forbidden('no permission')
+ raise Forbidden('no permission').exception
else:
return view(context, request)
_secure.__call_permissive__ = view
diff --git a/pyramid/tests/forbiddenapp/__init__.py b/pyramid/tests/forbiddenapp/__init__.py
index ed9aa8357..614aff037 100644
--- a/pyramid/tests/forbiddenapp/__init__.py
+++ b/pyramid/tests/forbiddenapp/__init__.py
@@ -1,6 +1,4 @@
-from cgi import escape
from webob import Response
-from pyramid.httpexceptions import HTTPForbidden
from pyramid.exceptions import Forbidden
def x_view(request): # pragma: no cover
@@ -10,7 +8,7 @@ def forbidden_view(context, request):
msg = context.message
result = context.result
message = msg + '\n' + str(result)
- resp = HTTPForbidden()
+ resp = Forbidden()
resp.body = message
return resp
diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py
index 9c8f4875b..7c6389253 100644
--- a/pyramid/tests/test_config.py
+++ b/pyramid/tests/test_config.py
@@ -204,36 +204,33 @@ class ConfiguratorTests(unittest.TestCase):
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 IExceptionResponse
+ from pyramid.exceptions import default_exceptionresponse_view
from pyramid.interfaces import IRequest
config = self._makeOne()
view = self._getViewCallable(config,
- ctx_iface=implementedBy(HTTPException),
+ ctx_iface=IExceptionResponse,
request_iface=IRequest)
- self.failUnless(view is default_httpexception_view)
+ self.failUnless(view is default_exceptionresponse_view)
- def test_ctor_httpexception_view_None(self):
- from zope.interface import implementedBy
- from pyramid.httpexceptions import HTTPException
+ def test_ctor_exceptionresponse_view_None(self):
+ from pyramid.interfaces import IExceptionResponse
from pyramid.interfaces import IRequest
- config = self._makeOne(httpexception_view=None)
+ config = self._makeOne(exceptionresponse_view=None)
view = self._getViewCallable(config,
- ctx_iface=implementedBy(HTTPException),
+ ctx_iface=IExceptionResponse,
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
+ def test_ctor_exceptionresponse_view_custom(self):
+ from pyramid.interfaces import IExceptionResponse
from pyramid.interfaces import IRequest
- def httpexception_view(context, request): pass
- config = self._makeOne(httpexception_view=httpexception_view)
+ def exceptionresponse_view(context, request): pass
+ config = self._makeOne(exceptionresponse_view=exceptionresponse_view)
view = self._getViewCallable(config,
- ctx_iface=implementedBy(HTTPException),
+ ctx_iface=IExceptionResponse,
request_iface=IRequest)
- self.failUnless(view is httpexception_view)
+ self.failUnless(view is exceptionresponse_view)
def test_with_package_module(self):
from pyramid.tests import test_configuration
@@ -321,20 +318,6 @@ 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_exceptions.py b/pyramid/tests/test_exceptions.py
index 5d0fa1e1a..2e9279f66 100644
--- a/pyramid/tests/test_exceptions.py
+++ b/pyramid/tests/test_exceptions.py
@@ -1,37 +1,16 @@
import unittest
-class TestExceptionResponse(unittest.TestCase):
- def _makeOne(self, message):
- from pyramid.exceptions import ExceptionResponse
- return ExceptionResponse(message)
-
- def test_app_iter(self):
- exc = self._makeOne('')
- self.assertTrue('<code></code>' in exc.app_iter[0])
-
- def test_headerlist(self):
- exc = self._makeOne('')
- headerlist = exc.headerlist
- headerlist.sort()
- app_iter = exc.app_iter
- clen = str(len(app_iter[0]))
- self.assertEqual(headerlist[0], ('Content-Length', clen))
- self.assertEqual(headerlist[1], ('Content-Type', 'text/html'))
-
- def test_withmessage(self):
- exc = self._makeOne('abc&123')
- self.assertTrue('<code>abc&amp;123</code>' in exc.app_iter[0])
-
class TestNotFound(unittest.TestCase):
def _makeOne(self, message):
from pyramid.exceptions import NotFound
return NotFound(message)
def test_it(self):
- from pyramid.exceptions import ExceptionResponse
+ from pyramid.interfaces import IExceptionResponse
e = self._makeOne('notfound')
- self.assertTrue(isinstance(e, ExceptionResponse))
+ self.assertTrue(IExceptionResponse.providedBy(e))
self.assertEqual(e.status, '404 Not Found')
+ self.assertEqual(e.message, 'notfound')
class TestForbidden(unittest.TestCase):
def _makeOne(self, message):
@@ -39,7 +18,88 @@ class TestForbidden(unittest.TestCase):
return Forbidden(message)
def test_it(self):
- from pyramid.exceptions import ExceptionResponse
- e = self._makeOne('unauthorized')
- self.assertTrue(isinstance(e, ExceptionResponse))
+ from pyramid.interfaces import IExceptionResponse
+ e = self._makeOne('forbidden')
+ self.assertTrue(IExceptionResponse.providedBy(e))
self.assertEqual(e.status, '403 Forbidden')
+ self.assertEqual(e.message, 'forbidden')
+
+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().exception.__class__,
+ self._callFUT, 404)
+
+ def test_status_201(self):
+ from pyramid.exceptions import HTTPCreated
+ self.assertRaises(HTTPCreated().exception.__class__,
+ self._callFUT, 201)
+
+ def test_extra_kw(self):
+ from pyramid.exceptions 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.exceptions import redirect
+ return redirect(*arg, **kw)
+
+ def test_default(self):
+ from pyramid.exceptions 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.exceptions 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.exceptions 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_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 DummyRequest(object):
+ exception = None
+ def get_response(self, context):
+ return 'response'
+
+
+
diff --git a/pyramid/tests/test_httpexceptions.py b/pyramid/tests/test_httpexceptions.py
index 843f9485a..afa5a94de 100644
--- a/pyramid/tests/test_httpexceptions.py
+++ b/pyramid/tests/test_httpexceptions.py
@@ -1,79 +1,9 @@
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')
+class TestIt(unittest.TestCase):
+ def test_bwcompat_imports(self):
+ from pyramid.httpexceptions import HTTPNotFound as one
+ from pyramid.exceptions import HTTPNotFound as two
+ self.assertTrue(one is two)
- 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'
-
-
-
diff --git a/pyramid/view.py b/pyramid/view.py
index 2563f1e43..d6b666cf2 100644
--- a/pyramid/view.py
+++ b/pyramid/view.py
@@ -8,7 +8,9 @@ from pyramid.interfaces import IRoutesMapper
from pyramid.interfaces import IView
from pyramid.interfaces import IViewClassifier
-from pyramid.httpexceptions import HTTPFound
+from pyramid.exceptions import HTTPFound
+from pyramid.exceptions import default_exceptionresponse_view
+from pyramid.exceptions import is_response # API
from pyramid.renderers import RendererHelper
from pyramid.static import static_view
from pyramid.threadlocal import get_current_registry
@@ -130,21 +132,6 @@ def render_view(context, request, name='', secure=True):
return None
return ''.join(iterable)
-def is_response(ob):
- """ Return ``True`` if ``ob`` implements the interface implied by
- :ref:`the_response`. ``False`` if not.
-
- .. note:: This isn't a true interface or subclass check. Instead, it's a
- duck-typing check, as response objects are not obligated to be of a
- particular class or provide any particular Zope interface."""
-
- # response objects aren't obligated to implement a Zope interface,
- # so we do it the hard way
- if ( hasattr(ob, 'app_iter') and hasattr(ob, 'headerlist') and
- hasattr(ob, 'status') ):
- return True
- return False
-
class view_config(object):
""" A function, class or method :term:`decorator` which allows a
developer to create view registrations nearer to a :term:`view
@@ -243,14 +230,6 @@ deprecated(
'pyramid.view.view_config instead (API-compat, simple '
'rename).')
-def default_exceptionresponse_view(context, request):
- if not isinstance(context, Exception):
- # backwards compat for an exception response view registered via
- # config.set_notfound_view or config.set_forbidden_view
- # instead of as a proper exception view
- context = request.exception or context
- return context
-
class AppendSlashNotFoundViewFactory(object):
""" There can only be one :term:`Not Found view` in any
:app:`Pyramid` application. Even if you use
@@ -273,7 +252,7 @@ class AppendSlashNotFoundViewFactory(object):
from pyramid.exceptions import NotFound
from pyramid.view import AppendSlashNotFoundViewFactory
- from pyramid.httpexceptions import HTTPNotFound
+ from pyramid.exceptions import HTTPNotFound
def notfound_view(context, request): return HTTPNotFound('nope')
@@ -294,7 +273,7 @@ class AppendSlashNotFoundViewFactory(object):
if not isinstance(context, Exception):
# backwards compat for an append_notslash_view registered via
# config.set_notfound_view instead of as a proper exception view
- context = request.exception
+ context = getattr(request, 'exception', None) or context
path = request.path
registry = request.registry
mapper = registry.queryUtility(IRoutesMapper)