]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- class level listeners can be added after the fact
authorMike Bayer <mike_mp@zzzcomputing.com>
Tue, 27 Jul 2010 14:51:20 +0000 (10:51 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Tue, 27 Jul 2010 14:51:20 +0000 (10:51 -0400)
- more indirect listener registration allows multiple target types

lib/sqlalchemy/engine/base.py
lib/sqlalchemy/event.py
lib/sqlalchemy/orm/attributes.py

index adba6fa47fa303526ffdaa94bed6a027718381af..8533e19fc2da7be34106b11e107ea942183f7277 100644 (file)
@@ -1847,7 +1847,11 @@ def _proxy_connection_cls(cls, dispatch):
             return orig
         def go(*arg, **kw):
             nested = _exec_recursive(conn, fns[1:], orig)
-            ret = fns[0](conn, nested, *arg, **kw)
+            try:
+                ret = fns[0](conn, nested, *arg, **kw)
+            except IndexError:
+                import pdb
+                pdb.set_trace()
             # TODO: need to get consistent way to check 
             # for "they called the fn, they didn't", or otherwise
             # make some decision here how this is to work
index 7aad1d21c4c7ac8ceb1f4501b5933d5121148e7c..91d1bb82f8f2924d1e854ecdf7705a0737fd1a51 100644 (file)
@@ -12,24 +12,46 @@ from sqlalchemy import util
 
 def listen(fn, identifier, target, *args, **kw):
     """Listen for events, passing to fn."""
-    
-    target.events.listen(fn, identifier, target, *args, **kw)
-
-NO_RESULT = util.symbol('no_result')
 
+    # rationale - the events on ClassManager, Session, and Mapper
+    # will need to accept mapped classes directly as targets and know 
+    # what to do
+    
+    for evt_cls in _registrars[identifier]:
+        evt = evt_cls.accept_with(target)
+        if evt:
+            evt.listen(fn, identifier, target, *args, **kw)
+            break
+    
 class _DispatchMeta(type):
     def __init__(cls, classname, bases, dict_):
         for k in dict_:
             if k.startswith('on_'):
                 setattr(cls, k, EventDescriptor(dict_[k]))
+                _registrars[k].append(cls)
         return type.__init__(cls, classname, bases, dict_)
 
+_registrars = util.defaultdict(list)
+
 class Events(object):
     __metaclass__ = _DispatchMeta
     
     def __init__(self, parent_cls):
         self.parent_cls = parent_cls
     
+    @classmethod
+    def accept_with(cls, target):
+        # Mapper, ClassManager, Session override this to
+        # also accept classes, scoped_sessions, sessionmakers, etc.
+        if hasattr(target, 'events') and (
+                    isinstance(target.events, cls) or \
+                    isinstance(target.events, type) and \
+                    issubclass(target.events, cls)
+                ):
+            return target.events
+        else:
+            return None
+        
     @classmethod
     def listen(cls, fn, identifier, target):
         getattr(target.events, identifier).append(fn, target)
@@ -44,7 +66,7 @@ class Events(object):
         """Populate from the listeners in another :class:`Events` object."""
 
         for ls in other.events:
-            getattr(self, ls.name).extend(ls)
+            getattr(self, ls.name).listeners.extend(ls.listeners)
 
 class _ExecEvent(object):
     _exec_once = False
@@ -86,32 +108,46 @@ class EventDescriptor(object):
     def __init__(self, fn):
         self.__name__ = fn.__name__
         self.__doc__ = fn.__doc__
-        self._clslevel = []
+        self._clslevel = util.defaultdict(list)
     
     def append(self, obj, target):
         assert isinstance(target, type), "Class-level Event targets must be classes."
-        self._clslevel.append((obj, target))
+        for cls in [target] + target.__subclasses__():
+            self._clslevel[cls].append(obj)
     
     def __get__(self, obj, cls):
         if obj is None:
             return self
-        obj.__dict__[self.__name__] = result = Listeners(self.__name__)
-        result.extend([
-            fn for fn, target in 
-            self._clslevel
-            if issubclass(obj.parent_cls, target)
-        ])
+        obj.__dict__[self.__name__] = result = Listeners(self, obj.parent_cls)
         return result
 
-class Listeners(_ExecEvent, list):
+class Listeners(_ExecEvent):
     """Represent a collection of listeners linked
     to an instance of :class:`Events`."""
     
-    def __init__(self, name):
-        self.name = name
+    def __init__(self, parent, target_cls):
+        self.parent_listeners = parent._clslevel[target_cls]
+        self.name = parent.__name__
+        self.listeners = []
+    
+    # I'm not entirely thrilled about the overhead here,
+    # but this allows class-level listeners to be added
+    # at any point.
+    
+    def __len__(self):
+        return len(self.parent_listeners + self.listeners)
+        
+    def __iter__(self):
+        return iter(self.parent_listeners + self.listeners)
+    
+    def __getitem__(self, index):
+        return (self.parent_listeners + self.listeners)[index]
+        
+    def __nonzero__(self):
+        return bool(self.listeners or self.parent_listeners)
         
     def append(self, obj, target):
-        list.append(self, obj)
+        self.listeners.append(obj)
 
 class dispatcher(object):
     def __init__(self, events):
index 7cf7900a8d74ba2aa2edf6fe65cffe87585b7395..2701fc879e0335083a693e2df21067feeeae895c 100644 (file)
@@ -104,6 +104,9 @@ class QueryableAttribute(interfaces.PropComparator):
         self.comparator = comparator
         self.parententity = parententity
 
+    # TODO: this can potentially be moved to AttributeImpl,
+    # have Sphinx document the "events" class directly, implement an 
+    # accept_with() that checks for QueryableAttribute
     class events(event.Events):
         """Events for ORM attributes.