summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/event
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2018-02-06 18:13:27 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2018-02-07 20:55:09 -0500
commit4065352c7e9779cfeae34f01073ef6c43aeae488 (patch)
tree133bcc4e4a26439c94f6086556ceb0d32d8a3481 /lib/sqlalchemy/event
parent00570a6ac9453a48b06ca094de6e0502c3b73fa5 (diff)
downloadsqlalchemy-4065352c7e9779cfeae34f01073ef6c43aeae488.tar.gz
Add flag for class-level disallow of events, apply to OptionEngine
Fixed bug where events associated with an :class:`Engine` at the class level would be doubled when the :meth:`.Engine.execution_options` method were used. To achieve this, the semi-private class :class:`.OptionEngine` no longer accepts events directly at the class level and will raise an error; the class only propagates class-level events from its parent :class:`.Engine`. Instance-level events continue to work as before. The comments present another way of doing this where we would copy events from the parent engine at option time rather than linking the event listeners, but this would be a behavioral change that adding new events to the parent engine would not take effect for an already-created OptionEngine. Change-Id: Id128516f54103fbad9a2210d6571eceb59c8b0cb Fixes: #4181
Diffstat (limited to 'lib/sqlalchemy/event')
-rw-r--r--lib/sqlalchemy/event/attr.py35
1 files changed, 30 insertions, 5 deletions
diff --git a/lib/sqlalchemy/event/attr.py b/lib/sqlalchemy/event/attr.py
index 1068257cb..efa8fab42 100644
--- a/lib/sqlalchemy/event/attr.py
+++ b/lib/sqlalchemy/event/attr.py
@@ -30,7 +30,7 @@ as well as support for subclass propagation (e.g. events assigned to
"""
from __future__ import absolute_import, with_statement
-
+from .. import exc
from .. import util
from ..util import threading
from . import registry
@@ -47,6 +47,20 @@ class RefCollection(util.MemoizedSlots):
return weakref.ref(self, registry._collection_gced)
+class _empty_collection(object):
+ def append(self, element):
+ pass
+
+ def extend(self, other):
+ pass
+
+ def __iter__(self):
+ return iter([])
+
+ def clear(self):
+ pass
+
+
class _ClsLevelDispatch(RefCollection):
"""Class-level events on :class:`._Dispatch` classes."""
@@ -91,6 +105,9 @@ class _ClsLevelDispatch(RefCollection):
target = event_key.dispatch_target
assert isinstance(target, type), \
"Class-level Event targets must be classes."
+ if not getattr(target, '_sa_propagate_class_events', True):
+ raise exc.InvalidRequestError(
+ "Can't assign an event directly to the %s class" % target)
stack = [target]
while stack:
cls = stack.pop(0)
@@ -99,7 +116,7 @@ class _ClsLevelDispatch(RefCollection):
self.update_subclass(cls)
else:
if cls not in self._clslevel:
- self._clslevel[cls] = collections.deque()
+ self._assign_cls_collection(cls)
self._clslevel[cls].appendleft(event_key._listen_fn)
registry._stored_in_collection(event_key, self)
@@ -107,7 +124,9 @@ class _ClsLevelDispatch(RefCollection):
target = event_key.dispatch_target
assert isinstance(target, type), \
"Class-level Event targets must be classes."
-
+ if not getattr(target, '_sa_propagate_class_events', True):
+ raise exc.InvalidRequestError(
+ "Can't assign an event directly to the %s class" % target)
stack = [target]
while stack:
cls = stack.pop(0)
@@ -116,13 +135,19 @@ class _ClsLevelDispatch(RefCollection):
self.update_subclass(cls)
else:
if cls not in self._clslevel:
- self._clslevel[cls] = collections.deque()
+ self._assign_cls_collection(cls)
self._clslevel[cls].append(event_key._listen_fn)
registry._stored_in_collection(event_key, self)
+ def _assign_cls_collection(self, target):
+ if getattr(target, '_sa_propagate_class_events', True):
+ self._clslevel[target] = collections.deque()
+ else:
+ self._clslevel[target] = _empty_collection()
+
def update_subclass(self, target):
if target not in self._clslevel:
- self._clslevel[target] = collections.deque()
+ self._assign_cls_collection(target)
clslevel = self._clslevel[target]
for cls in target.__mro__[1:]:
if cls in self._clslevel: