From eae99acbf9eed71967ff12961e495f27708d1f39 Mon Sep 17 00:00:00 2001 From: dobesv Date: Wed, 9 Jul 2014 15:15:08 -0700 Subject: Allow the last callback called to add a callback This fixes a bug in the finished and response callbacks where if the last/only callback adds another callback, the newly added callback won't be called afterwards. This is because when it tries to add the callback, it is added to a new list instance because the callbacks list is empty at that time; the check for whether the callbacks list was created didn't previously distinguish between an empty list and not a list. However, if it is not the last callback in the list, the callbacks list will not be empty and the new callback will be added to the same list and the newly added callback *will* be called. Because the code as written appears to be trying to support callbacks adding callbacks, this push request modifies the code so that a callback may add another callback whether it is the last one or not. An alternative approach would be to modify the code so that callbacks cannot add new callbacks, which also would be reasonable. But I think it's a bug that the behavior depends currently on whether you are in the last/only callback when you try to add another one. --- pyramid/request.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyramid/request.py b/pyramid/request.py index 6318049ee..7e8b8c07d 100644 --- a/pyramid/request.py +++ b/pyramid/request.py @@ -72,7 +72,7 @@ class CallbackMethodsMixin(object): """ callbacks = self.response_callbacks - if not callbacks: + if callbacks == (): callbacks = [] callbacks.append(callback) self.response_callbacks = callbacks @@ -132,7 +132,7 @@ class CallbackMethodsMixin(object): """ callbacks = self.finished_callbacks - if not callbacks: + if callbacks == (): callbacks = [] callbacks.append(callback) self.finished_callbacks = callbacks -- cgit v1.2.3 From 63e6e14a83a10331635bdced1ff6258e4c18989c Mon Sep 17 00:00:00 2001 From: Dobes Vandermeer Date: Wed, 23 Jul 2014 09:49:44 -0700 Subject: Add a test case for a response callback adding a response callback when it is the only callback. --- pyramid/tests/test_request.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/pyramid/tests/test_request.py b/pyramid/tests/test_request.py index ed41b62ff..ec206dad3 100644 --- a/pyramid/tests/test_request.py +++ b/pyramid/tests/test_request.py @@ -144,6 +144,29 @@ class TestRequest(unittest.TestCase): self.assertEqual(response.called2, True) self.assertEqual(inst.response_callbacks, []) + def test__process_response_callback_adding_response_callback(self): + """ + When a response callback adds another callback, that new callback should still be called. + + See https://github.com/Pylons/pyramid/pull/1373 + """ + inst = self._makeOne() + def callback1(request, response): + request.called1 = True + response.called1 = True + request.add_response_callback(callback2) + def callback2(request, response): + request.called2 = True + response.called2 = True + inst.add_response_callback(callback1) + response = DummyResponse() + inst._process_response_callbacks(response) + self.assertEqual(inst.called1, True) + self.assertEqual(inst.called2, True) + self.assertEqual(response.called1, True) + self.assertEqual(response.called2, True) + self.assertEqual(inst.response_callbacks, []) + def test_add_finished_callback(self): inst = self._makeOne() self.assertEqual(inst.finished_callbacks, ()) -- cgit v1.2.3 From 05ceab495c4390c0e3af5ae6458d4c808eb08e67 Mon Sep 17 00:00:00 2001 From: Dobes Vandermeer Date: Tue, 29 Jul 2014 17:54:23 -0700 Subject: Use None for the default value of the callbacks list. --- pyramid/request.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyramid/request.py b/pyramid/request.py index 7e8b8c07d..fa35dc5f4 100644 --- a/pyramid/request.py +++ b/pyramid/request.py @@ -32,8 +32,8 @@ class TemplateContext(object): pass class CallbackMethodsMixin(object): - response_callbacks = () - finished_callbacks = () + response_callbacks = None + finished_callbacks = None def add_response_callback(self, callback): """ Add a callback to the set of callbacks to be called by the @@ -72,7 +72,7 @@ class CallbackMethodsMixin(object): """ callbacks = self.response_callbacks - if callbacks == (): + if callbacks is None: callbacks = [] callbacks.append(callback) self.response_callbacks = callbacks @@ -132,7 +132,7 @@ class CallbackMethodsMixin(object): """ callbacks = self.finished_callbacks - if callbacks == (): + if callbacks is None: callbacks = [] callbacks.append(callback) self.finished_callbacks = callbacks -- cgit v1.2.3 From c73eb9be17a17c6e0b19ce6b08a579a6d524c584 Mon Sep 17 00:00:00 2001 From: Dobes Vandermeer Date: Tue, 29 Jul 2014 18:05:27 -0700 Subject: Use a deque for the request finished / response callbacks. --- pyramid/request.py | 9 +++++---- pyramid/tests/test_request.py | 25 ++++++++++++++----------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/pyramid/request.py b/pyramid/request.py index fa35dc5f4..bc2889310 100644 --- a/pyramid/request.py +++ b/pyramid/request.py @@ -1,3 +1,4 @@ +from collections import deque import json from zope.interface import implementer @@ -73,14 +74,14 @@ class CallbackMethodsMixin(object): callbacks = self.response_callbacks if callbacks is None: - callbacks = [] + callbacks = deque() callbacks.append(callback) self.response_callbacks = callbacks def _process_response_callbacks(self, response): callbacks = self.response_callbacks while callbacks: - callback = callbacks.pop(0) + callback = callbacks.popleft() callback(self, response) def add_finished_callback(self, callback): @@ -133,14 +134,14 @@ class CallbackMethodsMixin(object): callbacks = self.finished_callbacks if callbacks is None: - callbacks = [] + callbacks = deque() callbacks.append(callback) self.finished_callbacks = callbacks def _process_finished_callbacks(self): callbacks = self.finished_callbacks while callbacks: - callback = callbacks.pop(0) + callback = callbacks.popleft() callback(self) @implementer(IRequest) diff --git a/pyramid/tests/test_request.py b/pyramid/tests/test_request.py index ec206dad3..48af98f59 100644 --- a/pyramid/tests/test_request.py +++ b/pyramid/tests/test_request.py @@ -1,3 +1,4 @@ +from collections import deque import unittest from pyramid import testing @@ -119,13 +120,13 @@ class TestRequest(unittest.TestCase): def test_add_response_callback(self): inst = self._makeOne() - self.assertEqual(inst.response_callbacks, ()) + self.assertEqual(inst.response_callbacks, None) def callback(request, response): """ """ inst.add_response_callback(callback) - self.assertEqual(inst.response_callbacks, [callback]) + self.assertEqual(list(inst.response_callbacks), [callback]) inst.add_response_callback(callback) - self.assertEqual(inst.response_callbacks, [callback, callback]) + self.assertEqual(list(inst.response_callbacks), [callback, callback]) def test__process_response_callbacks(self): inst = self._makeOne() @@ -135,14 +136,15 @@ class TestRequest(unittest.TestCase): def callback2(request, response): request.called2 = True response.called2 = True - inst.response_callbacks = [callback1, callback2] + inst.add_response_callback(callback1) + inst.add_response_callback(callback2) response = DummyResponse() inst._process_response_callbacks(response) self.assertEqual(inst.called1, True) self.assertEqual(inst.called2, True) self.assertEqual(response.called1, True) self.assertEqual(response.called2, True) - self.assertEqual(inst.response_callbacks, []) + self.assertEqual(len(inst.response_callbacks), 0) def test__process_response_callback_adding_response_callback(self): """ @@ -165,17 +167,17 @@ class TestRequest(unittest.TestCase): self.assertEqual(inst.called2, True) self.assertEqual(response.called1, True) self.assertEqual(response.called2, True) - self.assertEqual(inst.response_callbacks, []) + self.assertEqual(len(inst.response_callbacks), 0) def test_add_finished_callback(self): inst = self._makeOne() - self.assertEqual(inst.finished_callbacks, ()) + self.assertEqual(inst.finished_callbacks, None) def callback(request): """ """ inst.add_finished_callback(callback) - self.assertEqual(inst.finished_callbacks, [callback]) + self.assertEqual(list(inst.finished_callbacks), [callback]) inst.add_finished_callback(callback) - self.assertEqual(inst.finished_callbacks, [callback, callback]) + self.assertEqual(list(inst.finished_callbacks), [callback, callback]) def test__process_finished_callbacks(self): inst = self._makeOne() @@ -183,11 +185,12 @@ class TestRequest(unittest.TestCase): request.called1 = True def callback2(request): request.called2 = True - inst.finished_callbacks = [callback1, callback2] + inst.add_finished_callback(callback1) + inst.add_finished_callback(callback2) inst._process_finished_callbacks() self.assertEqual(inst.called1, True) self.assertEqual(inst.called2, True) - self.assertEqual(inst.finished_callbacks, []) + self.assertEqual(len(inst.finished_callbacks), 0) def test_resource_url(self): self._registerResourceURL() -- cgit v1.2.3 From 39a03eef73b14bd05540af70e705d9f02d77f850 Mon Sep 17 00:00:00 2001 From: Dobes Vandermeer Date: Wed, 30 Jul 2014 09:42:28 -0700 Subject: Fix a few tests that assume response_callbacks was a list --- pyramid/tests/test_router.py | 6 +++--- pyramid/tests/test_session.py | 2 +- pyramid/tests/test_testing.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyramid/tests/test_router.py b/pyramid/tests/test_router.py index 838e52db0..c6c6eea1c 100644 --- a/pyramid/tests/test_router.py +++ b/pyramid/tests/test_router.py @@ -522,7 +522,7 @@ class TestRouter(unittest.TestCase): def view(context, request): def callback(request, response): response.called_back = True - request.response_callbacks = [callback] + request.add_response_callback(callback) return response environ = self._makeEnviron() self._registerView(view, '', IViewClassifier, IRequest, IContext) @@ -545,7 +545,7 @@ class TestRouter(unittest.TestCase): def view(context, request): def callback(request): request.environ['called_back'] = True - request.finished_callbacks = [callback] + request.add_finished_callback(callback) return response environ = self._makeEnviron() self._registerView(view, '', IViewClassifier, IRequest, IContext) @@ -567,7 +567,7 @@ class TestRouter(unittest.TestCase): def view(context, request): def callback(request): request.environ['called_back'] = True - request.finished_callbacks = [callback] + request.add_finished_callback(callback) raise NotImplementedError environ = self._makeEnviron() self._registerView(view, '', IViewClassifier, IRequest, IContext) diff --git a/pyramid/tests/test_session.py b/pyramid/tests/test_session.py index 35c234e99..b013ffa66 100644 --- a/pyramid/tests/test_session.py +++ b/pyramid/tests/test_session.py @@ -521,7 +521,7 @@ class Test_manage_accessed(unittest.TestCase): result = wrapper(session, 'a') self.assertEqual(result, 1) callbacks = request.response_callbacks - self.assertEqual(len(callbacks), 0) + if callbacks is not None: self.assertEqual(len(callbacks), 0) class Test_manage_changed(unittest.TestCase): def _makeOne(self, wrapped): diff --git a/pyramid/tests/test_testing.py b/pyramid/tests/test_testing.py index 2d0548b33..dfcad2a0c 100644 --- a/pyramid/tests/test_testing.py +++ b/pyramid/tests/test_testing.py @@ -217,7 +217,7 @@ class TestDummyRequest(unittest.TestCase): def test_add_response_callback(self): request = self._makeOne() request.add_response_callback(1) - self.assertEqual(request.response_callbacks, [1]) + self.assertEqual(list(request.response_callbacks), [1]) def test_registry_is_config_registry_when_setup_is_called_after_ctor(self): # see https://github.com/Pylons/pyramid/issues/165 -- cgit v1.2.3