.. changelog::
:version: 0.9.8
+ .. change::
+ :tags: bug, orm
+ :versions: 1.0.0
+ :tickets: 3199
+
+ Fixed bug that affected many classes of event, particularly
+ ORM events but also engine events, where the usual logic of
+ "de duplicating" a redundant call to :func:`.event.listen`
+ with the same arguments would fail, for those events where the
+ listener function is wrapped. An assertion would be hit within
+ registry.py. This assertion has now been integrated into the
+ deduplication check, with the added bonus of a simpler means
+ of checking deduplication across the board.
+
.. change::
:tags: bug, mssql
:versions: 1.0.0
registry._stored_in_collection_multi(self, other, to_associate)
def insert(self, event_key, propagate):
- if event_key._listen_fn not in self.listeners:
- event_key.prepend_to_list(self, self.listeners)
+ if event_key.prepend_to_list(self, self.listeners):
if propagate:
self.propagate.add(event_key._listen_fn)
def append(self, event_key, propagate):
- if event_key._listen_fn not in self.listeners:
- event_key.append_to_list(self, self.listeners)
+ if event_key.append_to_list(self, self.listeners):
if propagate:
self.propagate.add(event_key._listen_fn)
listen_ref = weakref.ref(event_key._listen_fn)
if owner_ref in dispatch_reg:
- assert dispatch_reg[owner_ref] == listen_ref
- else:
- dispatch_reg[owner_ref] = listen_ref
+ return False
+
+ dispatch_reg[owner_ref] = listen_ref
listener_to_key = _collection_to_key[owner_ref]
listener_to_key[listen_ref] = key
+ return True
+
def _removed_from_collection(event_key, owner):
key = event_key._key
def _listen_fn(self):
return self.fn_wrap or self.fn
- def append_value_to_list(self, owner, list_, value):
- _stored_in_collection(self, owner)
- list_.append(value)
-
def append_to_list(self, owner, list_):
- _stored_in_collection(self, owner)
- list_.append(self._listen_fn)
+ if _stored_in_collection(self, owner):
+ list_.append(self._listen_fn)
+ return True
+ else:
+ return False
def remove_from_list(self, owner, list_):
_removed_from_collection(self, owner)
list_.remove(self._listen_fn)
def prepend_to_list(self, owner, list_):
- _stored_in_collection(self, owner)
- list_.appendleft(self._listen_fn)
+ if _stored_in_collection(self, owner):
+ list_.appendleft(self._listen_fn)
+ return True
+ else:
+ return False
dispatch = event.dispatcher(TargetEvents)
return Target
+ def _wrapped_fixture(self):
+ class TargetEvents(event.Events):
+ @classmethod
+ def _listen(cls, event_key):
+ fn = event_key.fn
+
+ def adapt(value):
+ fn("adapted " + value)
+ event_key = event_key.with_wrapper(adapt)
+
+ event_key.base_listen()
+
+ def event_one(self, value):
+ pass
+
+ class Target(object):
+ dispatch = event.dispatcher(TargetEvents)
+ return Target
+
def test_clslevel(self):
Target = self._fixture()
"deque mutated during iteration",
t1.dispatch.event_one
)
+
+ def test_double_event_nonwrapped(self):
+ Target = self._fixture()
+
+ listen_one = Mock()
+ t1 = Target()
+ event.listen(t1, "event_one", listen_one)
+ event.listen(t1, "event_one", listen_one)
+
+ t1.dispatch.event_one("t1")
+
+ # doubles are eliminated
+ eq_(listen_one.mock_calls, [call("t1")])
+
+ # only one remove needed
+ event.remove(t1, "event_one", listen_one)
+ t1.dispatch.event_one("t2")
+
+ eq_(listen_one.mock_calls, [call("t1")])
+
+ def test_double_event_wrapped(self):
+ # this is issue #3199
+ Target = self._wrapped_fixture()
+
+ listen_one = Mock()
+ t1 = Target()
+
+ event.listen(t1, "event_one", listen_one)
+ event.listen(t1, "event_one", listen_one)
+
+ t1.dispatch.event_one("t1")
+
+ # doubles are eliminated
+ eq_(listen_one.mock_calls, [call("adapted t1")])
+
+ # only one remove needed
+ event.remove(t1, "event_one", listen_one)
+ t1.dispatch.event_one("t2")
+
+ eq_(listen_one.mock_calls, [call("adapted t1")])