Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/sqlalchemy/event/base.py : 83%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# event/base.py
2# Copyright (C) 2005-2020 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
8"""Base implementation classes.
10The public-facing ``Events`` serves as the base class for an event interface;
11its public attributes represent different kinds of events. These attributes
12are mirrored onto a ``_Dispatch`` class, which serves as a container for
13collections of listener functions. These collections are represented both
14at the class level of a particular ``_Dispatch`` class as well as within
15instances of ``_Dispatch``.
17"""
18from __future__ import absolute_import
20import weakref
22from .attr import _ClsLevelDispatch
23from .attr import _EmptyListener
24from .attr import _JoinedListener
25from .. import util
28_registrars = util.defaultdict(list)
31def _is_event_name(name):
32 return not name.startswith("_") and name != "dispatch"
35class _UnpickleDispatch(object):
36 """Serializable callable that re-generates an instance of
37 :class:`_Dispatch` given a particular :class:`.Events` subclass.
39 """
41 def __call__(self, _instance_cls):
42 for cls in _instance_cls.__mro__:
43 if "dispatch" in cls.__dict__:
44 return cls.__dict__["dispatch"].dispatch._for_class(
45 _instance_cls
46 )
47 else:
48 raise AttributeError("No class with a 'dispatch' member present.")
51class _Dispatch(object):
52 """Mirror the event listening definitions of an Events class with
53 listener collections.
55 Classes which define a "dispatch" member will return a
56 non-instantiated :class:`._Dispatch` subclass when the member
57 is accessed at the class level. When the "dispatch" member is
58 accessed at the instance level of its owner, an instance
59 of the :class:`._Dispatch` class is returned.
61 A :class:`._Dispatch` class is generated for each :class:`.Events`
62 class defined, by the :func:`._create_dispatcher_class` function.
63 The original :class:`.Events` classes remain untouched.
64 This decouples the construction of :class:`.Events` subclasses from
65 the implementation used by the event internals, and allows
66 inspecting tools like Sphinx to work in an unsurprising
67 way against the public API.
69 """
71 # In one ORM edge case, an attribute is added to _Dispatch,
72 # so __dict__ is used in just that case and potentially others.
73 __slots__ = "_parent", "_instance_cls", "__dict__", "_empty_listeners"
75 _empty_listener_reg = weakref.WeakKeyDictionary()
77 def __init__(self, parent, instance_cls=None):
78 self._parent = parent
79 self._instance_cls = instance_cls
81 if instance_cls:
82 try:
83 self._empty_listeners = self._empty_listener_reg[instance_cls]
84 except KeyError:
85 self._empty_listeners = self._empty_listener_reg[
86 instance_cls
87 ] = {
88 ls.name: _EmptyListener(ls, instance_cls)
89 for ls in parent._event_descriptors
90 }
91 else:
92 self._empty_listeners = {}
94 def __getattr__(self, name):
95 # Assign EmptyListeners as attributes on demand
96 # to reduce startup time for new dispatch objects.
97 try:
98 ls = self._empty_listeners[name]
99 except KeyError:
100 raise AttributeError(name)
101 else:
102 setattr(self, ls.name, ls)
103 return ls
105 @property
106 def _event_descriptors(self):
107 for k in self._event_names:
108 # Yield _ClsLevelDispatch related
109 # to relevant event name.
110 yield getattr(self, k)
112 @property
113 def _listen(self):
114 return self._events._listen
116 def _for_class(self, instance_cls):
117 return self.__class__(self, instance_cls)
119 def _for_instance(self, instance):
120 instance_cls = instance.__class__
121 return self._for_class(instance_cls)
123 def _join(self, other):
124 """Create a 'join' of this :class:`._Dispatch` and another.
126 This new dispatcher will dispatch events to both
127 :class:`._Dispatch` objects.
129 """
130 if "_joined_dispatch_cls" not in self.__class__.__dict__:
131 cls = type(
132 "Joined%s" % self.__class__.__name__,
133 (_JoinedDispatcher,),
134 {"__slots__": self._event_names},
135 )
137 self.__class__._joined_dispatch_cls = cls
138 return self._joined_dispatch_cls(self, other)
140 def __reduce__(self):
141 return _UnpickleDispatch(), (self._instance_cls,)
143 def _update(self, other, only_propagate=True):
144 """Populate from the listeners in another :class:`_Dispatch`
145 object."""
146 for ls in other._event_descriptors:
147 if isinstance(ls, _EmptyListener):
148 continue
149 getattr(self, ls.name).for_modify(self)._update(
150 ls, only_propagate=only_propagate
151 )
153 def _clear(self):
154 for ls in self._event_descriptors:
155 ls.for_modify(self).clear()
158class _EventMeta(type):
159 """Intercept new Event subclasses and create
160 associated _Dispatch classes."""
162 def __init__(cls, classname, bases, dict_):
163 _create_dispatcher_class(cls, classname, bases, dict_)
164 type.__init__(cls, classname, bases, dict_)
167def _create_dispatcher_class(cls, classname, bases, dict_):
168 """Create a :class:`._Dispatch` class corresponding to an
169 :class:`.Events` class."""
171 # there's all kinds of ways to do this,
172 # i.e. make a Dispatch class that shares the '_listen' method
173 # of the Event class, this is the straight monkeypatch.
174 if hasattr(cls, "dispatch"):
175 dispatch_base = cls.dispatch.__class__
176 else:
177 dispatch_base = _Dispatch
179 event_names = [k for k in dict_ if _is_event_name(k)]
180 dispatch_cls = type(
181 "%sDispatch" % classname, (dispatch_base,), {"__slots__": event_names}
182 )
184 dispatch_cls._event_names = event_names
186 dispatch_inst = cls._set_dispatch(cls, dispatch_cls)
187 for k in dispatch_cls._event_names:
188 setattr(dispatch_inst, k, _ClsLevelDispatch(cls, dict_[k]))
189 _registrars[k].append(cls)
191 for super_ in dispatch_cls.__bases__:
192 if issubclass(super_, _Dispatch) and super_ is not _Dispatch:
193 for ls in super_._events.dispatch._event_descriptors:
194 setattr(dispatch_inst, ls.name, ls)
195 dispatch_cls._event_names.append(ls.name)
197 if getattr(cls, "_dispatch_target", None):
198 cls._dispatch_target.dispatch = dispatcher(cls)
201def _remove_dispatcher(cls):
202 for k in cls.dispatch._event_names:
203 _registrars[k].remove(cls)
204 if not _registrars[k]:
205 del _registrars[k]
208class Events(util.with_metaclass(_EventMeta, object)):
209 """Define event listening functions for a particular target type."""
211 @staticmethod
212 def _set_dispatch(cls, dispatch_cls):
213 # This allows an Events subclass to define additional utility
214 # methods made available to the target via
215 # "self.dispatch._events.<utilitymethod>"
216 # @staticemethod to allow easy "super" calls while in a metaclass
217 # constructor.
218 cls.dispatch = dispatch_cls(None)
219 dispatch_cls._events = cls
220 return cls.dispatch
222 @classmethod
223 def _accept_with(cls, target):
224 def dispatch_is(*types):
225 return all(isinstance(target.dispatch, t) for t in types)
227 def dispatch_parent_is(t):
228 return isinstance(target.dispatch.parent, t)
230 # Mapper, ClassManager, Session override this to
231 # also accept classes, scoped_sessions, sessionmakers, etc.
232 if hasattr(target, "dispatch"):
233 if (
234 dispatch_is(cls.dispatch.__class__)
235 or dispatch_is(type, cls.dispatch.__class__)
236 or (
237 dispatch_is(_JoinedDispatcher)
238 and dispatch_parent_is(cls.dispatch.__class__)
239 )
240 ):
241 return target
243 @classmethod
244 def _listen(cls, event_key, propagate=False, insert=False, named=False):
245 event_key.base_listen(propagate=propagate, insert=insert, named=named)
247 @classmethod
248 def _remove(cls, event_key):
249 event_key.remove()
251 @classmethod
252 def _clear(cls):
253 cls.dispatch._clear()
256class _JoinedDispatcher(object):
257 """Represent a connection between two _Dispatch objects."""
259 __slots__ = "local", "parent", "_instance_cls"
261 def __init__(self, local, parent):
262 self.local = local
263 self.parent = parent
264 self._instance_cls = self.local._instance_cls
266 def __getattr__(self, name):
267 # Assign _JoinedListeners as attributes on demand
268 # to reduce startup time for new dispatch objects.
269 ls = getattr(self.local, name)
270 jl = _JoinedListener(self.parent, ls.name, ls)
271 setattr(self, ls.name, jl)
272 return jl
274 @property
275 def _listen(self):
276 return self.parent._listen
278 @property
279 def _events(self):
280 return self.parent._events
283class dispatcher(object):
284 """Descriptor used by target classes to
285 deliver the _Dispatch class at the class level
286 and produce new _Dispatch instances for target
287 instances.
289 """
291 def __init__(self, events):
292 self.dispatch = events.dispatch
293 self.events = events
295 def __get__(self, obj, cls):
296 if obj is None:
297 return self.dispatch
298 obj.__dict__["dispatch"] = disp = self.dispatch._for_instance(obj)
299 return disp