CHANGES.txt | ●●●●● patch | view | raw | blame | history | |
pyramid/httpexceptions.py | ●●●●● patch | view | raw | blame | history | |
pyramid/tests/test_httpexceptions.py | ●●●●● patch | view | raw | blame | history |
CHANGES.txt
@@ -320,6 +320,25 @@ ``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 --------------------------- pyramid/httpexceptions.py
@@ -143,21 +143,16 @@ # 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 @@ 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_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): """ pyramid/tests/test_httpexceptions.py
@@ -138,7 +138,9 @@ 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 @@ 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 @@ 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 & 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 @@ 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 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