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
1=== modified file 'oops_wsgi/middleware.py'
2--- oops_wsgi/middleware.py 2011-08-18 00:45:58 +0000
3+++ oops_wsgi/middleware.py 2011-08-22 07:04:24 +0000
4@@ -85,8 +85,23 @@
5 state['write'] = start_response(status, headers)
6 write = state['write']
7 write(bytes)
8- def oops_start_response(status, headers):
9- state['response'] = (status, headers)
10+ def oops_start_response(status, headers, exc_info=None):
11+ if exc_info is not None:
12+ # The app is explicitly signalling an error (rather than
13+ # returning a page describing the error). Capture that and then
14+ # forward to the containing element verbatim. In future we might
15+ # choose to add the OOPS id to the headers (but we can't fiddle
16+ # the body as it is being generated by the contained app.
17+ report = config.create(
18+ dict(exc_info=exc_info, url=construct_url(environ)))
19+ ids = config.publish(report)
20+ try:
21+ state['write'] = start_response(status, headers, exc_info)
22+ return state['write']
23+ finally:
24+ del exc_info
25+ else:
26+ state['response'] = (status, headers)
27 return oops_write
28 try:
29 iterator = iter(app(environ, oops_start_response))
30@@ -112,7 +127,8 @@
31 # server figure it out.
32 raise
33 start_response('500 Internal Server Error',
34- [('Content-Type', content_type)])
35+ [('Content-Type', content_type)], exc_info=exc_info)
36+ del exc_info
37 if error_render is not None:
38 yield error_render(report)
39 else:
40
41=== modified file 'oops_wsgi/tests/test_middleware.py'
42--- oops_wsgi/tests/test_middleware.py 2011-08-18 22:44:58 +0000
43+++ oops_wsgi/tests/test_middleware.py 2011-08-22 07:04:24 +0000
44@@ -20,6 +20,7 @@
45 from doctest import ELLIPSIS
46 import gc
47 import socket
48+import sys
49 from textwrap import dedent
50
51 from oops import (
52@@ -52,7 +53,21 @@
53 environ['HTTP_HOST'] = 'example.com'
54 environ['PATH_INFO'] = '/demo'
55 environ['QUERY_STRING'] = 'param=value'
56- def start_response(status, headers):
57+ def start_response(status, headers, exc_info=None):
58+ if exc_info:
59+ # If an exception is raised after we have written (via the app
60+ # calling our oops_write) or after we have yielded data, we
61+ # must supply exc_info in the call to start_response, per the
62+ # wsgi spec. The easiest way to do that is to always supply
63+ # exc_info when reporting an exception - that will DTRT if
64+ # start_response upstream had already written data. Thus
65+ # our test start_response which lives above the oops middleware
66+ # captures the fact exc_info was called and all our tests that
67+ # expect oopses raised expect the exc_info to be present.
68+ self.calls.append(
69+ ('start error', status, headers, exc_info[0],
70+ exc_info[1].args))
71+ return self.calls.append
72 if failing_start:
73 raise socket.error(errno.EPIPE, 'Connection closed')
74 self.calls.append((status, headers))
75@@ -191,7 +206,8 @@
76 'url': 'http://example.com/demo?param=value',
77 'value': u'boom, yo'},
78 # Then the middleware responses
79- ('500 Internal Server Error', [('Content-Type', 'text/html')]),
80+ ('start error', '500 Internal Server Error',
81+ [('Content-Type', 'text/html')], ValueError, ('boom, yo',)),
82 ], self.calls)
83 self.assertThat(iterated, DocTestMatches(dedent("""\
84 <html>
85@@ -219,7 +235,8 @@
86 'url': 'http://example.com/demo?param=value',
87 'value': u'boom, yo'},
88 # Then the middleware responses
89- ('500 Internal Server Error', [('Content-Type', 'text/json')]),
90+ ('start error', '500 Internal Server Error',
91+ [('Content-Type', 'text/json')], ValueError, ('boom, yo',)),
92 ], self.calls)
93 self.assertThat(iterated, DocTestMatches(dedent("""\
94 {"oopsid" : "..."}"""), ELLIPSIS))
95@@ -239,7 +256,8 @@
96 'url': 'http://example.com/demo?param=value',
97 'value': u'boom, yo'},
98 # Then the middleware responses
99- ('500 Internal Server Error', [('Content-Type', 'text/html')]),
100+ ('start error', '500 Internal Server Error',
101+ [('Content-Type', 'text/html')], ValueError, ('boom, yo',)),
102 ], self.calls)
103 self.assertEqual(iterated, 'woo')
104
105@@ -250,3 +268,20 @@
106 self.wrap_and_run(inner, config)
107 self.assertEqual('http://example.com/demo?param=value',
108 self.calls[0]['url'])
109+
110+ def test_start_response_with_just_exc_info_generates_oops(self):
111+ def inner(environ, start_response):
112+ try:
113+ raise ValueError('boom, yo')
114+ except ValueError:
115+ exc_info = sys.exc_info()
116+ start_response('500 FAIL', [], exc_info)
117+ yield 'body'
118+ config = self.config_for_oopsing()
119+ self.wrap_and_run(inner, config)
120+ self.assertEqual([{'id': 1,
121+ 'type': 'ValueError',
122+ 'url': 'http://example.com/demo?param=value',
123+ 'value': u'boom, yo'},
124+ ('start error', '500 FAIL', [], ValueError, ('boom, yo',))],
125+ self.calls)

Subscribers

People subscribed via source and target branches

to all changes: