base.py 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. # event/base.py
  2. # Copyright (C) 2005-2017 the SQLAlchemy authors and contributors
  3. # <see AUTHORS file>
  4. #
  5. # This module is part of SQLAlchemy and is released under
  6. # the MIT License: http://www.opensource.org/licenses/mit-license.php
  7. """Base implementation classes.
  8. The public-facing ``Events`` serves as the base class for an event interface;
  9. its public attributes represent different kinds of events. These attributes
  10. are mirrored onto a ``_Dispatch`` class, which serves as a container for
  11. collections of listener functions. These collections are represented both
  12. at the class level of a particular ``_Dispatch`` class as well as within
  13. instances of ``_Dispatch``.
  14. """
  15. from __future__ import absolute_import
  16. import weakref
  17. from .. import util
  18. from .attr import _JoinedListener, \
  19. _EmptyListener, _ClsLevelDispatch
  20. _registrars = util.defaultdict(list)
  21. def _is_event_name(name):
  22. return not name.startswith('_') and name != 'dispatch'
  23. class _UnpickleDispatch(object):
  24. """Serializable callable that re-generates an instance of
  25. :class:`_Dispatch` given a particular :class:`.Events` subclass.
  26. """
  27. def __call__(self, _instance_cls):
  28. for cls in _instance_cls.__mro__:
  29. if 'dispatch' in cls.__dict__:
  30. return cls.__dict__['dispatch'].\
  31. dispatch_cls._for_class(_instance_cls)
  32. else:
  33. raise AttributeError("No class with a 'dispatch' member present.")
  34. class _Dispatch(object):
  35. """Mirror the event listening definitions of an Events class with
  36. listener collections.
  37. Classes which define a "dispatch" member will return a
  38. non-instantiated :class:`._Dispatch` subclass when the member
  39. is accessed at the class level. When the "dispatch" member is
  40. accessed at the instance level of its owner, an instance
  41. of the :class:`._Dispatch` class is returned.
  42. A :class:`._Dispatch` class is generated for each :class:`.Events`
  43. class defined, by the :func:`._create_dispatcher_class` function.
  44. The original :class:`.Events` classes remain untouched.
  45. This decouples the construction of :class:`.Events` subclasses from
  46. the implementation used by the event internals, and allows
  47. inspecting tools like Sphinx to work in an unsurprising
  48. way against the public API.
  49. """
  50. # in one ORM edge case, an attribute is added to _Dispatch,
  51. # so __dict__ is used in just that case and potentially others.
  52. __slots__ = '_parent', '_instance_cls', '__dict__', '_empty_listeners'
  53. _empty_listener_reg = weakref.WeakKeyDictionary()
  54. def __init__(self, parent, instance_cls=None):
  55. self._parent = parent
  56. self._instance_cls = instance_cls
  57. if instance_cls:
  58. try:
  59. self._empty_listeners = self._empty_listener_reg[instance_cls]
  60. except KeyError:
  61. self._empty_listeners = \
  62. self._empty_listener_reg[instance_cls] = dict(
  63. (ls.name, _EmptyListener(ls, instance_cls))
  64. for ls in parent._event_descriptors
  65. )
  66. else:
  67. self._empty_listeners = {}
  68. def __getattr__(self, name):
  69. # assign EmptyListeners as attributes on demand
  70. # to reduce startup time for new dispatch objects
  71. try:
  72. ls = self._empty_listeners[name]
  73. except KeyError:
  74. raise AttributeError(name)
  75. else:
  76. setattr(self, ls.name, ls)
  77. return ls
  78. @property
  79. def _event_descriptors(self):
  80. for k in self._event_names:
  81. yield getattr(self, k)
  82. def _for_class(self, instance_cls):
  83. return self.__class__(self, instance_cls)
  84. def _for_instance(self, instance):
  85. instance_cls = instance.__class__
  86. return self._for_class(instance_cls)
  87. @property
  88. def _listen(self):
  89. return self._events._listen
  90. def _join(self, other):
  91. """Create a 'join' of this :class:`._Dispatch` and another.
  92. This new dispatcher will dispatch events to both
  93. :class:`._Dispatch` objects.
  94. """
  95. if '_joined_dispatch_cls' not in self.__class__.__dict__:
  96. cls = type(
  97. "Joined%s" % self.__class__.__name__,
  98. (_JoinedDispatcher, ), {'__slots__': self._event_names}
  99. )
  100. self.__class__._joined_dispatch_cls = cls
  101. return self._joined_dispatch_cls(self, other)
  102. def __reduce__(self):
  103. return _UnpickleDispatch(), (self._instance_cls, )
  104. def _update(self, other, only_propagate=True):
  105. """Populate from the listeners in another :class:`_Dispatch`
  106. object."""
  107. for ls in other._event_descriptors:
  108. if isinstance(ls, _EmptyListener):
  109. continue
  110. getattr(self, ls.name).\
  111. for_modify(self)._update(ls, only_propagate=only_propagate)
  112. def _clear(self):
  113. for ls in self._event_descriptors:
  114. ls.for_modify(self).clear()
  115. class _EventMeta(type):
  116. """Intercept new Event subclasses and create
  117. associated _Dispatch classes."""
  118. def __init__(cls, classname, bases, dict_):
  119. _create_dispatcher_class(cls, classname, bases, dict_)
  120. return type.__init__(cls, classname, bases, dict_)
  121. def _create_dispatcher_class(cls, classname, bases, dict_):
  122. """Create a :class:`._Dispatch` class corresponding to an
  123. :class:`.Events` class."""
  124. # there's all kinds of ways to do this,
  125. # i.e. make a Dispatch class that shares the '_listen' method
  126. # of the Event class, this is the straight monkeypatch.
  127. if hasattr(cls, 'dispatch'):
  128. dispatch_base = cls.dispatch.__class__
  129. else:
  130. dispatch_base = _Dispatch
  131. event_names = [k for k in dict_ if _is_event_name(k)]
  132. dispatch_cls = type("%sDispatch" % classname,
  133. (dispatch_base, ), {'__slots__': event_names})
  134. dispatch_cls._event_names = event_names
  135. dispatch_inst = cls._set_dispatch(cls, dispatch_cls)
  136. for k in dispatch_cls._event_names:
  137. setattr(dispatch_inst, k, _ClsLevelDispatch(cls, dict_[k]))
  138. _registrars[k].append(cls)
  139. for super_ in dispatch_cls.__bases__:
  140. if issubclass(super_, _Dispatch) and super_ is not _Dispatch:
  141. for ls in super_._events.dispatch._event_descriptors:
  142. setattr(dispatch_inst, ls.name, ls)
  143. dispatch_cls._event_names.append(ls.name)
  144. if getattr(cls, '_dispatch_target', None):
  145. cls._dispatch_target.dispatch = dispatcher(cls)
  146. def _remove_dispatcher(cls):
  147. for k in cls.dispatch._event_names:
  148. _registrars[k].remove(cls)
  149. if not _registrars[k]:
  150. del _registrars[k]
  151. class Events(util.with_metaclass(_EventMeta, object)):
  152. """Define event listening functions for a particular target type."""
  153. @staticmethod
  154. def _set_dispatch(cls, dispatch_cls):
  155. # this allows an Events subclass to define additional utility
  156. # methods made available to the target via
  157. # "self.dispatch._events.<utilitymethod>"
  158. # @staticemethod to allow easy "super" calls while in a metaclass
  159. # constructor.
  160. cls.dispatch = dispatch_cls(None)
  161. dispatch_cls._events = cls
  162. return cls.dispatch
  163. @classmethod
  164. def _accept_with(cls, target):
  165. # Mapper, ClassManager, Session override this to
  166. # also accept classes, scoped_sessions, sessionmakers, etc.
  167. if hasattr(target, 'dispatch') and (
  168. isinstance(target.dispatch, cls.dispatch.__class__) or
  169. (
  170. isinstance(target.dispatch, type) and
  171. isinstance(target.dispatch, cls.dispatch.__class__)
  172. ) or
  173. (
  174. isinstance(target.dispatch, _JoinedDispatcher) and
  175. isinstance(target.dispatch.parent, cls.dispatch.__class__)
  176. )
  177. ):
  178. return target
  179. else:
  180. return None
  181. @classmethod
  182. def _listen(cls, event_key, propagate=False, insert=False, named=False):
  183. event_key.base_listen(propagate=propagate, insert=insert, named=named)
  184. @classmethod
  185. def _remove(cls, event_key):
  186. event_key.remove()
  187. @classmethod
  188. def _clear(cls):
  189. cls.dispatch._clear()
  190. class _JoinedDispatcher(object):
  191. """Represent a connection between two _Dispatch objects."""
  192. __slots__ = 'local', 'parent', '_instance_cls'
  193. def __init__(self, local, parent):
  194. self.local = local
  195. self.parent = parent
  196. self._instance_cls = self.local._instance_cls
  197. def __getattr__(self, name):
  198. # assign _JoinedListeners as attributes on demand
  199. # to reduce startup time for new dispatch objects
  200. ls = getattr(self.local, name)
  201. jl = _JoinedListener(self.parent, ls.name, ls)
  202. setattr(self, ls.name, jl)
  203. return jl
  204. @property
  205. def _listen(self):
  206. return self.parent._listen
  207. class dispatcher(object):
  208. """Descriptor used by target classes to
  209. deliver the _Dispatch class at the class level
  210. and produce new _Dispatch instances for target
  211. instances.
  212. """
  213. def __init__(self, events):
  214. self.dispatch_cls = events.dispatch
  215. self.events = events
  216. def __get__(self, obj, cls):
  217. if obj is None:
  218. return self.dispatch_cls
  219. obj.__dict__['dispatch'] = disp = self.dispatch_cls._for_instance(obj)
  220. return disp