summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/build/changelog/changelog_09.rst14
-rw-r--r--lib/sqlalchemy/event/attr.py6
-rw-r--r--lib/sqlalchemy/event/registry.py26
-rw-r--r--test/base/test_events.py59
4 files changed, 90 insertions, 15 deletions
diff --git a/doc/build/changelog/changelog_09.rst b/doc/build/changelog/changelog_09.rst
index 91e66bded..56be5b38e 100644
--- a/doc/build/changelog/changelog_09.rst
+++ b/doc/build/changelog/changelog_09.rst
@@ -14,6 +14,20 @@
: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
:tickets: 3151
diff --git a/lib/sqlalchemy/event/attr.py b/lib/sqlalchemy/event/attr.py
index dba1063cf..be2a82208 100644
--- a/lib/sqlalchemy/event/attr.py
+++ b/lib/sqlalchemy/event/attr.py
@@ -319,14 +319,12 @@ class _ListenerCollection(RefCollection, _CompoundListener):
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)
diff --git a/lib/sqlalchemy/event/registry.py b/lib/sqlalchemy/event/registry.py
index ba2f671a3..217cf7d44 100644
--- a/lib/sqlalchemy/event/registry.py
+++ b/lib/sqlalchemy/event/registry.py
@@ -71,13 +71,15 @@ def _stored_in_collection(event_key, owner):
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
@@ -229,18 +231,20 @@ class _EventKey(object):
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
diff --git a/test/base/test_events.py b/test/base/test_events.py
index 30b728cd3..913e1d3f5 100644
--- a/test/base/test_events.py
+++ b/test/base/test_events.py
@@ -996,6 +996,25 @@ class RemovalTest(fixtures.TestBase):
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()
@@ -1194,3 +1213,43 @@ class RemovalTest(fixtures.TestBase):
"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")])