diff options
| author | Chris McDonough <chrism@plope.com> | 2011-06-14 04:31:26 -0400 |
|---|---|---|
| committer | Chris McDonough <chrism@plope.com> | 2011-06-14 04:31:26 -0400 |
| commit | 53d11e7793317eee0f756b1e77b853ae7e1e6726 (patch) | |
| tree | 977589720594f73e3b4ad2e341aebe0e8a2da84c | |
| parent | 92099080859976ce78882de477ddc2c01bc880b2 (diff) | |
| download | pyramid-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.txt | 19 | ||||
| -rw-r--r-- | pyramid/httpexceptions.py | 43 | ||||
| -rw-r--r-- | pyramid/tests/test_httpexceptions.py | 77 |
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 & 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 + |
