Merge lp:~lifeless/python-oops-wsgi/extraction into lp:python-oops-wsgi

Proposed by Robert Collins
Status: Merged
Approved by: Robert Collins
Approved revision: 9
Merged at revision: 9
Proposed branch: lp:~lifeless/python-oops-wsgi/extraction
Merge into: lp:python-oops-wsgi
Diff against target: 125 lines (+58/-7)
2 files modified
oops_wsgi/middleware.py (+19/-3)
oops_wsgi/tests/test_middleware.py (+39/-4)
To merge this branch: bzr merge lp:~lifeless/python-oops-wsgi/extraction
Reviewer Review Type Date Requested Status
Jeroen T. Vermeulen (community) Approve
Review via email: mp+72380@code.launchpad.net

Description of the change

I fail at implementing wsgi right. This fixes exc_info handling AFAICT.

To post a comment you must log in.
Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :
Download full text (4.3 KiB)

Generally good, but a few notes:

=== modified file 'oops_wsgi/middleware.py'
--- oops_wsgi/middleware.py 2011-08-18 00:45:58 +0000
+++ oops_wsgi/middleware.py 2011-08-22 07:04:24 +0000
@@ -85,8 +85,23 @@
                 state['write'] = start_response(status, headers)
                 write = state['write']
             write(bytes)
- def oops_start_response(status, headers):
- state['response'] = (status, headers)
+ def oops_start_response(status, headers, exc_info=None):
+ if exc_info is not None:
+ # The app is explicitly signalling an error (rather than
+ # returning a page describing the error). Capture that and then
+ # forward to the containing element verbatim. In future we might
+ # choose to add the OOPS id to the headers (but we can't fiddle
+ # the body as it is being generated by the contained app.
+ report = config.create(
+ dict(exc_info=exc_info, url=construct_url(environ)))

Careful! You seem to be indenting by 8 instead of 4.

+ ids = config.publish(report)
+ try:
+ state['write'] = start_response(status, headers, exc_info)
+ return state['write']
+ finally:
+ del exc_info

Out of interest, whence the del?

=== modified file 'oops_wsgi/tests/test_middleware.py'
--- oops_wsgi/tests/test_middleware.py 2011-08-18 22:44:58 +0000
+++ oops_wsgi/tests/test_middleware.py 2011-08-22 07:04:24 +0000

@@ -52,7 +53,21 @@
         environ['HTTP_HOST'] = 'example.com'
         environ['PATH_INFO'] = '/demo'
         environ['QUERY_STRING'] = 'param=value'
- def start_response(status, headers):
+ def start_response(status, headers, exc_info=None):
+ if exc_info:
+ # If an exception is raised after we have written (via the app
+ # calling our oops_write) or after we have yielded data, we
+ # must supply exc_info in the call to start_response, per the
+ # wsgi spec. The easiest way to do that is to always supply
+ # exc_info when reporting an exception - that will DTRT if
+ # start_response upstream had already written data. Thus
+ # our test start_response which lives above the oops middleware
+ # captures the fact exc_info was called and all our tests that
+ # expect oopses raised expect the exc_info to be present.
+ self.calls.append(
+ ('start error', status, headers, exc_info[0],
+ exc_info[1].args))

Here too, indentation goes too far. You're not getting tabs, are you?

@@ -191,7 +206,8 @@
              'url': 'http://example.com/demo?param=value',
              'value': u'boom, yo'},
             # Then the middleware responses
- ('500 Internal Server Error', [('Content-Type', 'text/html')]),
+ ('start error', '500 Internal Server Error',
+ [('Content-Type', 'text/html')], ValueError, ('boom, yo',)),

Horrid layout. Your branch did...

Read more...

review: Approve
Revision history for this message
Robert Collins (lifeless) wrote :

All more or less actioned ;)

Revision history for this message
Robert Collins (lifeless) wrote :

(mainly more, that was meant to be funny not flippant :()

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'oops_wsgi/middleware.py'
--- oops_wsgi/middleware.py 2011-08-18 00:45:58 +0000
+++ oops_wsgi/middleware.py 2011-08-22 07:04:24 +0000
@@ -85,8 +85,23 @@
85 state['write'] = start_response(status, headers)85 state['write'] = start_response(status, headers)
86 write = state['write']86 write = state['write']
87 write(bytes)87 write(bytes)
88 def oops_start_response(status, headers):88 def oops_start_response(status, headers, exc_info=None):
89 state['response'] = (status, headers)89 if exc_info is not None:
90 # The app is explicitly signalling an error (rather than
91 # returning a page describing the error). Capture that and then
92 # forward to the containing element verbatim. In future we might
93 # choose to add the OOPS id to the headers (but we can't fiddle
94 # the body as it is being generated by the contained app.
95 report = config.create(
96 dict(exc_info=exc_info, url=construct_url(environ)))
97 ids = config.publish(report)
98 try:
99 state['write'] = start_response(status, headers, exc_info)
100 return state['write']
101 finally:
102 del exc_info
103 else:
104 state['response'] = (status, headers)
90 return oops_write105 return oops_write
91 try:106 try:
92 iterator = iter(app(environ, oops_start_response))107 iterator = iter(app(environ, oops_start_response))
@@ -112,7 +127,8 @@
112 # server figure it out.127 # server figure it out.
113 raise128 raise
114 start_response('500 Internal Server Error',129 start_response('500 Internal Server Error',
115 [('Content-Type', content_type)])130 [('Content-Type', content_type)], exc_info=exc_info)
131 del exc_info
116 if error_render is not None:132 if error_render is not None:
117 yield error_render(report)133 yield error_render(report)
118 else:134 else:
119135
=== modified file 'oops_wsgi/tests/test_middleware.py'
--- oops_wsgi/tests/test_middleware.py 2011-08-18 22:44:58 +0000
+++ oops_wsgi/tests/test_middleware.py 2011-08-22 07:04:24 +0000
@@ -20,6 +20,7 @@
20from doctest import ELLIPSIS20from doctest import ELLIPSIS
21import gc21import gc
22import socket22import socket
23import sys
23from textwrap import dedent24from textwrap import dedent
2425
25from oops import (26from oops import (
@@ -52,7 +53,21 @@
52 environ['HTTP_HOST'] = 'example.com'53 environ['HTTP_HOST'] = 'example.com'
53 environ['PATH_INFO'] = '/demo'54 environ['PATH_INFO'] = '/demo'
54 environ['QUERY_STRING'] = 'param=value'55 environ['QUERY_STRING'] = 'param=value'
55 def start_response(status, headers):56 def start_response(status, headers, exc_info=None):
57 if exc_info:
58 # If an exception is raised after we have written (via the app
59 # calling our oops_write) or after we have yielded data, we
60 # must supply exc_info in the call to start_response, per the
61 # wsgi spec. The easiest way to do that is to always supply
62 # exc_info when reporting an exception - that will DTRT if
63 # start_response upstream had already written data. Thus
64 # our test start_response which lives above the oops middleware
65 # captures the fact exc_info was called and all our tests that
66 # expect oopses raised expect the exc_info to be present.
67 self.calls.append(
68 ('start error', status, headers, exc_info[0],
69 exc_info[1].args))
70 return self.calls.append
56 if failing_start:71 if failing_start:
57 raise socket.error(errno.EPIPE, 'Connection closed')72 raise socket.error(errno.EPIPE, 'Connection closed')
58 self.calls.append((status, headers))73 self.calls.append((status, headers))
@@ -191,7 +206,8 @@
191 'url': 'http://example.com/demo?param=value',206 'url': 'http://example.com/demo?param=value',
192 'value': u'boom, yo'},207 'value': u'boom, yo'},
193 # Then the middleware responses208 # Then the middleware responses
194 ('500 Internal Server Error', [('Content-Type', 'text/html')]),209 ('start error', '500 Internal Server Error',
210 [('Content-Type', 'text/html')], ValueError, ('boom, yo',)),
195 ], self.calls)211 ], self.calls)
196 self.assertThat(iterated, DocTestMatches(dedent("""\212 self.assertThat(iterated, DocTestMatches(dedent("""\
197 <html>213 <html>
@@ -219,7 +235,8 @@
219 'url': 'http://example.com/demo?param=value',235 'url': 'http://example.com/demo?param=value',
220 'value': u'boom, yo'},236 'value': u'boom, yo'},
221 # Then the middleware responses237 # Then the middleware responses
222 ('500 Internal Server Error', [('Content-Type', 'text/json')]),238 ('start error', '500 Internal Server Error',
239 [('Content-Type', 'text/json')], ValueError, ('boom, yo',)),
223 ], self.calls)240 ], self.calls)
224 self.assertThat(iterated, DocTestMatches(dedent("""\241 self.assertThat(iterated, DocTestMatches(dedent("""\
225 {"oopsid" : "..."}"""), ELLIPSIS))242 {"oopsid" : "..."}"""), ELLIPSIS))
@@ -239,7 +256,8 @@
239 'url': 'http://example.com/demo?param=value',256 'url': 'http://example.com/demo?param=value',
240 'value': u'boom, yo'},257 'value': u'boom, yo'},
241 # Then the middleware responses258 # Then the middleware responses
242 ('500 Internal Server Error', [('Content-Type', 'text/html')]),259 ('start error', '500 Internal Server Error',
260 [('Content-Type', 'text/html')], ValueError, ('boom, yo',)),
243 ], self.calls)261 ], self.calls)
244 self.assertEqual(iterated, 'woo')262 self.assertEqual(iterated, 'woo')
245263
@@ -250,3 +268,20 @@
250 self.wrap_and_run(inner, config)268 self.wrap_and_run(inner, config)
251 self.assertEqual('http://example.com/demo?param=value',269 self.assertEqual('http://example.com/demo?param=value',
252 self.calls[0]['url'])270 self.calls[0]['url'])
271
272 def test_start_response_with_just_exc_info_generates_oops(self):
273 def inner(environ, start_response):
274 try:
275 raise ValueError('boom, yo')
276 except ValueError:
277 exc_info = sys.exc_info()
278 start_response('500 FAIL', [], exc_info)
279 yield 'body'
280 config = self.config_for_oopsing()
281 self.wrap_and_run(inner, config)
282 self.assertEqual([{'id': 1,
283 'type': 'ValueError',
284 'url': 'http://example.com/demo?param=value',
285 'value': u'boom, yo'},
286 ('start error', '500 FAIL', [], ValueError, ('boom, yo',))],
287 self.calls)

Subscribers

People subscribed via source and target branches

to all changes: