Merge lp:~james-w/python-timeline-django/clean-env into lp:python-timeline-django

Proposed by James Westby
Status: Merged
Approved by: James Westby
Approved revision: 24
Merged at revision: 23
Proposed branch: lp:~james-w/python-timeline-django/clean-env
Merge into: lp:python-timeline-django
Diff against target: 164 lines (+79/-11)
4 files modified
timeline_django/hooks.py (+12/-7)
timeline_django/tests/test_hooks.py (+6/-0)
timeline_django/tests/test_wsgi.py (+48/-3)
timeline_django/wsgi.py (+13/-1)
To merge this branch: bzr merge lp:~james-w/python-timeline-django/clean-env
Reviewer Review Type Date Requested Status
Natalia Bidart (community) Approve
Review via email: mp+232704@code.launchpad.net

Commit message

Be more careful about leaking objects.

1. Register signals with a dispatch_uid so that if register is called
   twice there aren't double receivers. There is a very slim chance that
   someone could two want two sets of receivers with timeline_factories
   that get different timelines. In that case this should be rewritten
   to not use dispatch_uid and offer a disconnect method that can be
   used in tests etc.

2. Re-set the environ stored in the thread local after the wsgi response
   is delivered. In a case where some requests go through wsgi and some
   don't (e.g. tests) the environ would leak in to those that don't use
   wsgi. This could lead to an ever-growing timeline.

Description of the change

Hi,

This fixes the problems that timeline was causing in the sca test suite.

Thanks,

James

To post a comment you must log in.
Revision history for this message
Natalia Bidart (nataliabidart) wrote :

Looks good!

review: Approve
24. By James Westby

Add missing space, thanks Natalia.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'timeline_django/hooks.py'
--- timeline_django/hooks.py 2014-03-18 16:47:46 +0000
+++ timeline_django/hooks.py 2014-08-29 14:46:17 +0000
@@ -14,7 +14,6 @@
14# GNU Lesser General Public License version 3 (see the file LICENSE).14# GNU Lesser General Public License version 3 (see the file LICENSE).
1515
16from datetime import timedelta16from datetime import timedelta
17import threading
1817
19import django.core.signals18import django.core.signals
20import django.db.backends.signals19import django.db.backends.signals
@@ -87,9 +86,15 @@
8786
88 def connect_to_signals(self):87 def connect_to_signals(self):
89 """Connect the callbacks to their corresponding signals."""88 """Connect the callbacks to their corresponding signals."""
90 django.core.signals.request_started.connect(self.request_started, weak=False)89 django.core.signals.request_started.connect(
91 django.core.signals.request_finished.connect(self.request_finished, weak=False)90 self.request_started, weak=False, dispatch_uid="timeline_django")
92 django.core.signals.got_request_exception.connect(self.got_request_exception, weak=False)91 django.core.signals.request_finished.connect(
93 django.db.backends.signals.connection_created.connect(self.connection_created, weak=False)92 self.request_finished, weak=False, dispatch_uid="timeline_django")
94 signals.wsgi_request_started.connect(self.wsgi_request_started, weak=False)93 django.core.signals.got_request_exception.connect(
95 signals.wsgi_response_started.connect(self.wsgi_response_started, weak=False)94 self.got_request_exception, weak=False, dispatch_uid="timeline_django")
95 django.db.backends.signals.connection_created.connect(
96 self.connection_created, weak=False, dispatch_uid="timeline_django")
97 signals.wsgi_request_started.connect(
98 self.wsgi_request_started, weak=False, dispatch_uid="timeline_django")
99 signals.wsgi_response_started.connect(
100 self.wsgi_response_started, weak=False, dispatch_uid="timeline_django")
96101
=== modified file 'timeline_django/tests/test_hooks.py'
--- timeline_django/tests/test_hooks.py 2014-03-18 16:47:46 +0000
+++ timeline_django/tests/test_hooks.py 2014-08-29 14:46:17 +0000
@@ -252,28 +252,34 @@
252 signal, receiver = self.register_signal(django.core.signals, 'request_started')252 signal, receiver = self.register_signal(django.core.signals, 'request_started')
253 self.assertEqual(1, len(signal.receivers))253 self.assertEqual(1, len(signal.receivers))
254 self.assertEqual(receiver.request_started, signal.receivers[0][1])254 self.assertEqual(receiver.request_started, signal.receivers[0][1])
255 self.assertEqual("timeline_django", signal.receivers[0][0][0])
255256
256 def test_register_signals_registers_request_finished(self):257 def test_register_signals_registers_request_finished(self):
257 signal, receiver = self.register_signal(django.core.signals, 'request_finished')258 signal, receiver = self.register_signal(django.core.signals, 'request_finished')
258 self.assertEqual(1, len(signal.receivers))259 self.assertEqual(1, len(signal.receivers))
259 self.assertEqual(receiver.request_finished, signal.receivers[0][1])260 self.assertEqual(receiver.request_finished, signal.receivers[0][1])
261 self.assertEqual("timeline_django", signal.receivers[0][0][0])
260262
261 def test_register_signals_registers_got_request_exception(self):263 def test_register_signals_registers_got_request_exception(self):
262 signal, receiver = self.register_signal(django.core.signals, 'got_request_exception')264 signal, receiver = self.register_signal(django.core.signals, 'got_request_exception')
263 self.assertEqual(1, len(signal.receivers))265 self.assertEqual(1, len(signal.receivers))
264 self.assertEqual(receiver.got_request_exception, signal.receivers[0][1])266 self.assertEqual(receiver.got_request_exception, signal.receivers[0][1])
267 self.assertEqual("timeline_django", signal.receivers[0][0][0])
265268
266 def test_register_signals_registers_connection_created(self):269 def test_register_signals_registers_connection_created(self):
267 signal, receiver = self.register_signal(django.db.backends.signals, 'connection_created')270 signal, receiver = self.register_signal(django.db.backends.signals, 'connection_created')
268 self.assertEqual(1, len(signal.receivers))271 self.assertEqual(1, len(signal.receivers))
269 self.assertEqual(receiver.connection_created, signal.receivers[0][1])272 self.assertEqual(receiver.connection_created, signal.receivers[0][1])
273 self.assertEqual("timeline_django", signal.receivers[0][0][0])
270274
271 def test_register_signals_registers_wsgi_request_started(self):275 def test_register_signals_registers_wsgi_request_started(self):
272 signal, receiver = self.register_signal(signals, 'wsgi_request_started')276 signal, receiver = self.register_signal(signals, 'wsgi_request_started')
273 self.assertEqual(1, len(signal.receivers))277 self.assertEqual(1, len(signal.receivers))
274 self.assertEqual(receiver.wsgi_request_started, signal.receivers[0][1])278 self.assertEqual(receiver.wsgi_request_started, signal.receivers[0][1])
279 self.assertEqual("timeline_django", signal.receivers[0][0][0])
275280
276 def test_register_signals_registers_wsgi_response_started(self):281 def test_register_signals_registers_wsgi_response_started(self):
277 signal, receiver = self.register_signal(signals, 'wsgi_response_started')282 signal, receiver = self.register_signal(signals, 'wsgi_response_started')
278 self.assertEqual(1, len(signal.receivers))283 self.assertEqual(1, len(signal.receivers))
279 self.assertEqual(receiver.wsgi_response_started, signal.receivers[0][1])284 self.assertEqual(receiver.wsgi_response_started, signal.receivers[0][1])
285 self.assertEqual("timeline_django", signal.receivers[0][0][0])
280286
=== modified file 'timeline_django/tests/test_wsgi.py'
--- timeline_django/tests/test_wsgi.py 2013-09-03 00:25:21 +0000
+++ timeline_django/tests/test_wsgi.py 2014-08-29 14:46:17 +0000
@@ -37,12 +37,22 @@
3737
38 def test_wsgi_sets_timeline(self):38 def test_wsgi_sets_timeline(self):
39 environ = {}39 environ = {}
40 start_response = lambda status, headers:None40 start_response = lambda status, headers: None
41 def do_check(environ, start_response):
42 self.assertThat(wsgi.get_environ(), Is(environ))
43 yield "foo"
44 app = wsgi.make_app(do_check)
45 out = app(environ, start_response)
46 self.assertEqual(["foo"], list(out))
47
48 def test_wsgi_unsets_timeline(self):
49 environ = {}
50 start_response = lambda status, headers: None
41 app = lambda environ, start_response: ["foo"]51 app = lambda environ, start_response: ["foo"]
42 app = wsgi.make_app(app)52 app = wsgi.make_app(app)
43 out = app(environ, start_response)53 out = app(environ, start_response)
44 self.assertEqual(["foo"], out)54 self.assertEqual(["foo"], list(out))
45 self.assertThat(wsgi.get_environ(), Is(environ))55 self.assertThat(wsgi.get_environ(), Is(None))
4656
47 def patch_signal(self, name):57 def patch_signal(self, name):
48 signal = Signal()58 signal = Signal()
@@ -93,3 +103,38 @@
93 wrapped_app(environ, None)103 wrapped_app(environ, None)
94 # Check that the saved environ matches the one passed to the app104 # Check that the saved environ matches the one passed to the app
95 self.assertEqual([environ], called_environs)105 self.assertEqual([environ], called_environs)
106
107
108class GeneratorTrackerTests(TestCase):
109
110 def test_consumes_generator(self):
111 app = wsgi.generator_tracker(lambda: True, ["foo"])
112 self.assertEqual(["foo"], list(app))
113
114 def test_calls_on_finish(self):
115 calls = []
116 def generator():
117 calls.append("consume")
118 yield "foo"
119 def on_finish():
120 calls.append("on_finish")
121 app = wsgi.generator_tracker(on_finish, generator())
122 self.assertEqual(["foo"], list(app))
123 self.assertEqual(["consume", "on_finish"], calls)
124
125 def test_calls_close(self):
126 calls = []
127 class Body(object):
128
129 def __iter__(self):
130 calls.append("consume")
131 yield "foo"
132
133 def close(self):
134 calls.append("close")
135
136 def on_finish():
137 calls.append("on_finish")
138 app = wsgi.generator_tracker(on_finish, Body())
139 self.assertEqual(["foo"], list(app))
140 self.assertEqual(["consume", "close", "on_finish"], calls)
96141
=== modified file 'timeline_django/wsgi.py'
--- timeline_django/wsgi.py 2013-09-03 00:25:21 +0000
+++ timeline_django/wsgi.py 2014-08-29 14:46:17 +0000
@@ -52,5 +52,17 @@
52 signals.wsgi_response_started.send(sender=save_environ,52 signals.wsgi_response_started.send(sender=save_environ,
53 environ=environ)53 environ=environ)
54 return start_response(*args, **kwargs)54 return start_response(*args, **kwargs)
55 return application(environ, signalled_start_response)55 def remove_env():
56 _thread_local.environ = None
57 return generator_tracker(remove_env, application(environ, signalled_start_response))
56 return save_environ58 return save_environ
59
60
61def generator_tracker(on_finish, app_body):
62 try:
63 for bytes in app_body:
64 yield bytes
65 finally:
66 if hasattr(app_body, 'close'):
67 app_body.close()
68 on_finish()

Subscribers

People subscribed via source and target branches