8000 - Move default app_iter generation logic into __call__ for · alex-python/pyramid@53d11e7 · GitHub
[go: up one dir, main page]

Skip to content

Commit 53d11e7

Browse files
committed
- 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.
1 parent 9209908 commit 53d11e7

File tree

3 files changed

+89
-50
lines changed

3 files changed

+89
-50
lines changed

CHANGES.txt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,25 @@ Behavior Changes
320320
``webob.response.Response`` (in order to directly implement the
321321
``pyramid.interfaces.IResponse`` interface).
322322

323+
- The "exception response" objects importable from ``pyramid.httpexceptions``
324+
(e.g. ``HTTPNotFound``) are no longer just import aliases for classes that
325+
actually live in ``webob.exc``. Instead, we've defined our own exception
326+
classes within the module that mirror and emulate the ``webob.exc``
327+
exception response objects almost entirely. We do this in order to a)
328+
allow the exception responses to subclass ``pyramid.response.Response``,
329+
which speeds up response generation slightly due to the way the Pyramid
330+
router works, b) allows us to provide alternate __call__ logic which also
331+
speeds up response generation, c) allows the exception classes to provide
332+
for the proper value of ``self.RequestClass`` (pyramid.request.Request), d)
333+
allows us freedom from having to think about backwards compatibility code
334+
present in ``webob.exc`` having to do with Python 2.4, which we no longer
335+
support, e) We change the behavior of two classes (HTTPNotFound and
336+
HTTPForbidden) in the module so that they can be used internally for
337+
notfound and forbidden exceptions, f) allows us to influence the docstrings
338+
of the exception classes to provide Pyramid-specific documentation, and g)
339+
allows us to silence a stupid deprecation warning under Python 2.6 when the
340+
response objects are used as exceptions (related to ``self.message``).
341+
323342
Backwards Incompatibilities
324343
---------------------------
325344

pyramid/httpexceptions.py

Lines changed: 17 additions & 26 deletions
< A3E2 tr class="diff-line-row">
Original file line numberDiff line numberDiff line change
@@ -143,21 +143,16 @@ class WSGIHTTPException(Response, HTTPException):
143143
# body_template_obj = Template('response template')
144144

145145
# differences from webob.exc.WSGIHTTPException:
146-
# - not a WSGI application (just a response)
147146
#
148-
# as a result:
149-
#
150-
# - bases plaintext vs. html result on self.content_type rather than
151-
# on request accept header
152-
#
153-
# - doesn't add request.environ keys to template substitutions unless
154-
# 'request' is passed as a constructor keyword argument.
147+
# - bases plaintext vs. html result on self.content_type rather than
148+
# on request accept header
155149
#
156150
# - doesn't use "strip_tags" (${br} placeholder for <br/>, no other html
157151
# in default body template)
158152
#
159-
# - sets a default app_iter if no body, app_iter, or unicode_body is
160-
# passed using a template (ala the replaced version's "generate_response")
153+
# - sets a default app_iter onto self during __call__ using a template if
154+
# no body, app_iter, or unicode_body is set onto the response (instead of
155+
# the replaced version's "generate_response")
161156
#
162157
# - explicitly sets self.message = detail to prevent whining by Python
163158
# 2.6.5+ access of Exception.message
@@ -213,18 +208,11 @@ def __init__(self, detail=None, headers=None, comment=None,
213208
if self.empty_body:
214209
del self.content_type
215210
del self.content_length
216-
elif not ('unicode_body' in kw or 'body' in kw or 'app_iter' in kw):
217-
self.app_iter = self._default_app_iter()
218211

219212
def __str__(self):
220213
return self.detail or self.explanation
221214

222-
def _default_app_iter(self):
223-
# This is a generator which defers the creation of the response page
224-
# body; we use a generator because we want to ensure that if
225-
# attributes of this response are changed after it is constructed, we
226-
# use the changed values rather than the values at time of construction
227-
# (e.g. self.content_type or self.charset).
215+
def _default_app_iter(self, environ):
228216
html_comment = ''
229217
comment = self.comment or ''
230218
content_type = self.content_type or ''
@@ -250,24 +238,27 @@ def _default_app_iter(self):
250238
body_tmpl = self.body_template_obj
251239
if WSGIHTTPException.body_template_obj is not body_tmpl:
252240
# Custom template; add headers to args
253-
environ = self.environ
254-
if environ is not None:
255-
for k, v in environ.items():
256-
args[k] = escape(v)
241+
for k, v in environ.items():
242+
args[k] = escape(v)
257243
for k, v in self.headers.items():
258244
args[k.lower()] = escape(v)
259245
body = body_tmpl.substitute(args)
260246
page = page_template.substitute(status=self.status, body=body)
261247
if isinstance(page, unicode):
262248
page = page.encode(self.charset)
263-
yield page
264-
raise StopIteration
249+
return [page]
265250

266251
@property
267-
def exception(self):
252+
def wsgi_response(self):
268253
# bw compat only
269254
return self
270-
wsgi_response = exception # bw compat only
255+
256+
exception = wsgi_response # bw compat only
257+
258+
def __call__(self, environ, start_response):
259+
if not self.body and not self.empty_body:
260+
self.app_iter = self._default_app_iter(environ)
261+
return Response.__call__(self, environ, start_response)
271262

272263
class HTTPError(WSGIHTTPException):
273264
"""

pyramid/tests/test_httpexceptions.py

Lines changed: 53 additions & 24 deletions
< F438 /colgroup>
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,9 @@ def test_ctor_with_app_iter_doesnt_set_default_app_iter(self):
138138
def test_ctor_with_body_sets_default_app_iter_html(self):
139139
cls = self._getTargetSubclass()
140140
exc = cls('detail')
141-
body = list(exc.app_iter)[0]
141+
environ = _makeEnviron()
142+
start_response = DummyStartResponse()
143+
body = list(exc(environ, start_response))[0]
142144
self.assertTrue(body.startswith('<html'))
143145
self.assertTrue('200 OK' in body)
144146
self.assertTrue('explanation' in body)
@@ -148,7 +150,9 @@ def test_ctor_with_body_sets_default_app_iter_text(self):
148150
cls = self._getTargetSubclass()
149151
exc = cls('detail')
150152
exc.content_type = 'text/plain'
151-
body = list(exc.app_iter)[0]
153+
environ = _makeEnviron()
154+
start_response = DummyStartResponse()
155+
body = list(exc(environ, start_response))[0]
152156
self.assertEqual(body, '200 OK\n\nexplanation\n\n\ndetail\n\n')
153157

154158
def test__str__detail(self):
@@ -169,59 +173,69 @@ def test_exception(self):
169173
exc = self._makeOne()
170174
self.assertTrue(exc is exc.exception)
171175

176+
def test__calls_start_response(self):
177+
cls = self._getTargetSubclass()
178+
exc = cls()
179+
exc.content_type = 'text/plain'
180+
environ = _makeEnviron()
181+
start_response = DummyStartResponse()
182+
exc(environ, start_response)
183+
self.assertTrue(start_response.headerlist)
184+
self.assertEqual(start_response.status, '200 OK')
185+
172186
def test__default_app_iter_no_comment_plain(self):
173187
cls = self._getTargetSubclass()
174188
exc = cls()
175189
exc.content_type = 'text/plain'
176-
body = list(exc._default_app_iter())[0]
190+
environ = _makeEnviron()
191+
start_response = DummyStartResponse()
192+
body = list(exc(environ, start_response))[0]
177193
self.assertEqual(body, '200 OK\n\nexplanation\n\n\n\n\n')
178194

179195
def test__default_app_iter_with_comment_plain(self):
180196
cls = self._getTargetSubclass()
181197
exc = cls(comment='comment')
182198
exc.content_type = 'text/plain'
183-
body = list(exc._default_app_iter())[0]
199+
environ = _makeEnviron()
200+
start_response = DummyStartResponse()
201+
body = list(exc(environ, start_response))[0]
184202
self.assertEqual(body, '200 OK\n\nexplanation\n\n\n\ncomment\n')
185203

186204
def test__default_app_iter_no_comment_html(self):
187205
cls = self._getTargetSubclass()
188206
exc = cls()
189207
exc.content_type = 'text/html'
190-
body = list(exc._default_app_iter())[0]
208+
environ = _makeEnviron()
209+
start_response = DummyStartResponse()
210+
body = list(exc(environ, start_response))[0]
191211
self.assertFalse('<!-- ' in body)
192212

193213
def test__default_app_iter_with_comment_html(self):
194214
cls = self._getTargetSubclass()
195215
exc = cls(comment='comment & comment')
196216
exc.content_type = 'text/html'
197-
body = list(exc._default_app_iter())[0]
217+
environ = _makeEnviron()
218+
start_response = DummyStartResponse()
219+
body = list(exc(environ, start_response))[0]
198220
self.assertTrue('<!-- comment &amp; comment -->' in body)
199221

200-
def test_custom_body_template_no_environ(self):
222+
def test_custom_body_template(self):
201223
cls = self._getTargetSubclass()
202-
exc = cls(body_template='${location}', location='foo')
224+
exc = cls(body_template='${REQUEST_METHOD}')
203225
exc.content_type = 'text/plain'
204-
body = list(exc._default_app_iter())[0]
205-
self.assertEqual(body, '200 OK\n\nfoo')
206-
207-
def test_custom_body_template_with_environ(self):
208-
cls = self._getTargetSubclass()
209-
from pyramid.request import Request
210-
request = Request.blank('/')
211-
exc = cls(body_template='${REQUEST_METHOD}', request=request)
212-
exc.content_type = 'text/plain'
213-
body = list(exc._default_app_iter())[0]
226+
environ = _makeEnviron()
227+
start_response = DummyStartResponse()
228+
body = list(exc(environ, start_response))[0]
214229
self.assertEqual(body, '200 OK\n\nGET')
215230

216231
def test_body_template_unicode(self):
217-
from pyramid.request import Request
218232
cls = self._getTargetSubclass()
219233
la = unicode('/La Pe\xc3\xb1a', 'utf-8')
220-
request = Request.blank('/')
221-
request.environ['unicodeval'] = la
222-
exc = cls(body_template='${unicodeval}', request=request)
234+
environ = _makeEnviron(unicodeval=la)
235+
exc = cls(body_template='${unicodeval}')
223236
exc.content_type = 'text/plain'
224-
body = list(exc._default_app_iter())[0]
237+
start_response = DummyStartResponse()
238+
body = list(exc(environ, start_response))[0]
225239
self.assertEqual(body, '200 OK\n\n/La Pe\xc3\xb1a')
226240

227241
class TestRenderAllExceptionsWithoutArguments(unittest.TestCase):
@@ -230,9 +244,11 @@ def _doit(self, content_type):
230244
L = []
231245
self.assertTrue(status_map)
232246
for v in status_map.values():
247+
environ = _makeEnviron()
248+
start_response = DummyStartResponse()
233249
exc = v()
234250
exc.content_type = content_type
235-
result = list(exc.app_iter)[0]
251+
result = list(exc(environ, start_response))[0]
236252
if exc.empty_body:
237253
self.assertEqual(result, '')
238254
else:
@@ -275,3 +291,16 @@ def test_it_result_passed(self):
275291
class DummyRequest(object):
276292
exception = None
277293

294+
class DummyStartResponse(object):
295+
def __call__(self, status, headerlist):
296+
self.status = status
297+
self.headerlist = headerlist
298+
299+
def _makeEnviron(**kw):
300+
environ = {'REQUEST_METHOD':'GET',
301+
'wsgi.url_scheme':'http',
302+
'SERVER_NAME':'localhost',
303+
'SERVER_PORT':'80'}
304+
environ.update(kw)
305+
return environ
306+

0 commit comments

Comments
 (0)
0