diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2010-07-27 10:51:20 -0400 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2010-07-27 10:51:20 -0400 |
commit | 448b7db97763df0c9671030e0968e6edc09afeec (patch) | |
tree | cfd1be350385194697dd552d252ccafe3d77fdee /lib/sqlalchemy/event.py | |
parent | 430fd2c9db8f30927c2de1e2d6133f306e82fb23 (diff) | |
download | sqlalchemy-448b7db97763df0c9671030e0968e6edc09afeec.tar.gz |
- class level listeners can be added after the fact
- more indirect listener registration allows multiple target types
Diffstat (limited to 'lib/sqlalchemy/event.py')
-rw-r--r-- | lib/sqlalchemy/event.py | 70 |
1 files changed, 53 insertions, 17 deletions
diff --git a/lib/sqlalchemy/event.py b/lib/sqlalchemy/event.py index 7aad1d21c..91d1bb82f 100644 --- a/lib/sqlalchemy/event.py +++ b/lib/sqlalchemy/event.py @@ -12,18 +12,27 @@ 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 @@ -31,6 +40,19 @@ class Events(object): 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): |