summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@plope.com>2011-06-14 04:31:26 -0400
committerChris McDonough <chrism@plope.com>2011-06-14 04:31:26 -0400
commit53d11e7793317eee0f756b1e77b853ae7e1e6726 (patch)
tree977589720594f73e3b4ad2e341aebe0e8a2da84c
parent92099080859976ce78882de477ddc2c01bc880b2 (diff)
downloadpyramid-53d11e7793317eee0f756b1e77b853ae7e1e6726.tar.gz
pyramid-53d11e7793317eee0f756b1e77b853ae7e1e6726.tar.bz2
pyramid-53d11e7793317eee0f756b1e77b853ae7e1e6726.zip
- Move default app_iter generation logic into __call__ for
exception responses. - Add note about why we've created a shadow exception hierarchy parallel to that of webob.exc.
-rw-r--r--CHANGES.txt19
-rw-r--r--pyramid/httpexceptions.py43
-rw-r--r--pyramid/tests/test_httpexceptions.py77
3 files changed, 89 insertions, 50 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index ea4bedc7e..a2976d1a2 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -320,6 +320,25 @@ Behavior Changes
``webob.response.Response`` (in order to directly implement the
``pyramid.interfaces.IResponse`` interface).
+- The "exception response" objects importable from ``pyramid.httpexceptions``
+ (e.g. ``HTTPNotFound``) are no longer just import aliases for classes that
+ actually live in ``webob.exc``. Instead, we've defined our own exception
+ classes within the module that mirror and emulate the ``webob.exc``
+ exception response objects almost entirely. We do this in order to a)
+ allow the exception responses to subclass ``pyramid.response.Response``,
+ which speeds up response generation slightly due to the way the Pyramid
+ router works, b) allows us to provide alternate __call__ logic which also
+ speeds up response generation, c) allows the exception classes to provide
+ for the proper value of ``self.RequestClass`` (pyramid.request.Request), d)
+ allows us freedom from having to think about backwards compatibility code
+ present in ``webob.exc`` having to do with Python 2.4, which we no longer
+ support, e) We change the behavior of two classes (HTTPNotFound and
+ HTTPForbidden) in the module so that they can be used internally for
+ notfound and forbidden exceptions, f) allows us to influence the docstrings
+ of the exception classes to provide Pyramid-specific documentation, and g)
+ allows us to silence a stupid deprecation warning under Python 2.6 when the
+ response objects are used as exceptions (related to ``self.message``).
+
Backwards Incompatibilities
---------------------------
diff --git a/pyramid/httpexceptions.py b/pyramid/httpexceptions.py
index f3df574a0..6d689988e 100644
--- a/pyramid/httpexceptions.py
+++ b/pyramid/httpexceptions.py
@@ -143,21 +143,16 @@ class WSGIHTTPException(Response, HTTPException):
# body_template_obj = Template('response template')
# differences from webob.exc.WSGIHTTPException:
- # - not a WSGI application (just a response)
#
- # as a result:
- #
- # - bases plaintext vs. html result on self.content_type rather than
- # on request accept header
- #
- # - doesn't add request.environ keys to template substitutions unless
- # 'request' is passed as a constructor keyword argument.
+ # - bases plaintext vs. html result on self.content_type rather than
+ # on request accept header
#
# - doesn't use "strip_tags" (${br} placeholder for <br/>, no other html
# in default body template)
#
- # - sets a default app_iter if no body, app_iter, or unicode_body is
- # passed using a template (ala the replaced version's "generate_response")
+ # - sets a default app_iter onto self during __call__ using a template if
+ # no body, app_iter, or unicode_body is set onto the response (instead of
+ # the replaced version's "generate_response")
#
# - explicitly sets self.message = detail to prevent whining by Python
# 2.6.5+ access of Exception.message
@@ -213,18 +208,11 @@ ${body}''')
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).
+ def _default_app_iter(self, environ):
html_comment = ''
comment = self.comment or ''
content_type = self.content_type or ''
@@ -250,24 +238,27 @@ ${body}''')
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 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
+ return [page]
@property
- def exception(self):
+ def wsgi_response(self):
# bw compat only
return self
- wsgi_response = exception # bw compat only
+
+ exception = wsgi_response # bw compat only
+
+ def __call__(self, environ, start_response):
+ if not self.body and not self.empty_body:
+ self.app_iter = self._default_app_iter(environ)
+ return Response.__call__(self, environ, start_response)
class HTTPError(WSGIHTTPException):
"""
diff --git a/pyramid/tests/test_httpexceptions.py b/pyramid/tests/test_httpexceptions.py
index 629bbe225..60bde460e 100644
--- a/pyramid/tests/test_httpexceptions.py
+++ b/pyramid/tests/test_httpexceptions.py
@@ -138,7 +138,9 @@ class TestWSGIHTTPException(unittest.TestCase):
def test_ctor_with_body_sets_default_app_iter_html(self):
cls = self._getTargetSubclass()
exc = cls('detail')
- body = list(exc.app_iter)[0]
+ environ = _makeEnviron()
+ start_response = DummyStartResponse()
+ body = list(exc(environ, start_response))[0]
self.assertTrue(body.startswith('<html'))
self.assertTrue('200 OK' in body)
self.assertTrue('explanation' in body)
@@ -148,7 +150,9 @@ class TestWSGIHTTPException(unittest.TestCase):
cls = self._getTargetSubclass()
exc = cls('detail')
exc.content_type = 'text/plain'
- body = list(exc.app_iter)[0]
+ environ = _makeEnviron()
+ start_response = DummyStartResponse()
+ body = list(exc(environ, start_response))[0]
self.assertEqual(body, '200 OK\n\nexplanation\n\n\ndetail\n\n')
def test__str__detail(self):
@@ -169,59 +173,69 @@ class TestWSGIHTTPException(unittest.TestCase):
exc = self._makeOne()
self.assertTrue(exc is exc.exception)
+ def test__calls_start_response(self):
+ cls = self._getTargetSubclass()
+ exc = cls()
+ exc.content_type = 'text/plain'
+ environ = _makeEnviron()
+ start_response = DummyStartResponse()
+ exc(environ, start_response)
+ self.assertTrue(start_response.headerlist)
+ self.assertEqual(start_response.status, '200 OK')
+
def test__default_app_iter_no_comment_plain(self):
cls = self._getTargetSubclass()
exc = cls()
exc.content_type = 'text/plain'
- body = list(exc._default_app_iter())[0]
+ environ = _makeEnviron()
+ start_response = DummyStartResponse()
+ body = list(exc(environ, start_response))[0]
self.assertEqual(body, '200 OK\n\nexplanation\n\n\n\n\n')
def test__default_app_iter_with_comment_plain(self):
cls = self._getTargetSubclass()
exc = cls(comment='comment')
exc.content_type = 'text/plain'
- body = list(exc._default_app_iter())[0]
+ environ = _makeEnviron()
+ start_response = DummyStartResponse()
+ body = list(exc(environ, start_response))[0]
self.assertEqual(body, '200 OK\n\nexplanation\n\n\n\ncomment\n')
def test__default_app_iter_no_comment_html(self):
cls = self._getTargetSubclass()
exc = cls()
exc.content_type = 'text/html'
- body = list(exc._default_app_iter())[0]
+ environ = _makeEnviron()
+ start_response = DummyStartResponse()
+ body = list(exc(environ, start_response))[0]
self.assertFalse('<!-- ' in body)
def test__default_app_iter_with_comment_html(self):
cls = self._getTargetSubclass()
exc = cls(comment='comment & comment')
exc.content_type = 'text/html'
- body = list(exc._default_app_iter())[0]
+ environ = _makeEnviron()
+ start_response = DummyStartResponse()
+ body = list(exc(environ, start_response))[0]
self.assertTrue('<!-- comment &amp; comment -->' in body)
- def test_custom_body_template_no_environ(self):
+ def test_custom_body_template(self):
cls = self._getTargetSubclass()
- exc = cls(body_template='${location}', location='foo')
+ exc = cls(body_template='${REQUEST_METHOD}')
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]
+ environ = _makeEnviron()
+ start_response = DummyStartResponse()
+ body = list(exc(environ, start_response))[0]
self.assertEqual(body, '200 OK\n\nGET')
def test_body_template_unicode(self):
- 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)
+ environ = _makeEnviron(unicodeval=la)
+ exc = cls(body_template='${unicodeval}')
exc.content_type = 'text/plain'
- body = list(exc._default_app_iter())[0]
+ start_response = DummyStartResponse()
+ body = list(exc(environ, start_response))[0]
self.assertEqual(body, '200 OK\n\n/La Pe\xc3\xb1a')
class TestRenderAllExceptionsWithoutArguments(unittest.TestCase):
@@ -230,9 +244,11 @@ class TestRenderAllExceptionsWithoutArguments(unittest.TestCase):
L = []
self.assertTrue(status_map)
for v in status_map.values():
+ environ = _makeEnviron()
+ start_response = DummyStartResponse()
exc = v()
exc.content_type = content_type
- result = list(exc.app_iter)[0]
+ result = list(exc(environ, start_response))[0]
if exc.empty_body:
self.assertEqual(result, '')
else:
@@ -275,3 +291,16 @@ class TestHTTPForbidden(unittest.TestCase):
class DummyRequest(object):
exception = None
+class DummyStartResponse(object):
+ def __call__(self, status, headerlist):
+ self.status = status
+ self.headerlist = headerlist
+
+def _makeEnviron(**kw):
+ environ = {'REQUEST_METHOD':'GET',
+ 'wsgi.url_scheme':'http',
+ 'SERVER_NAME':'localhost',
+ 'SERVER_PORT':'80'}
+ environ.update(kw)
+ return environ
+