Hide keyboard shortcuts

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 

7 

8"""Base implementation classes. 

9 

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``. 

16 

17""" 

18from __future__ import absolute_import 

19 

20import weakref 

21 

22from .attr import _ClsLevelDispatch 

23from .attr import _EmptyListener 

24from .attr import _JoinedListener 

25from .. import util 

26 

27 

28_registrars = util.defaultdict(list) 

29 

30 

31def _is_event_name(name): 

32 return not name.startswith("_") and name != "dispatch" 

33 

34 

35class _UnpickleDispatch(object): 

36 """Serializable callable that re-generates an instance of 

37 :class:`_Dispatch` given a particular :class:`.Events` subclass. 

38 

39 """ 

40 

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.") 

49 

50 

51class _Dispatch(object): 

52 """Mirror the event listening definitions of an Events class with 

53 listener collections. 

54 

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. 

60 

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. 

68 

69 """ 

70 

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" 

74 

75 _empty_listener_reg = weakref.WeakKeyDictionary() 

76 

77 def __init__(self, parent, instance_cls=None): 

78 self._parent = parent 

79 self._instance_cls = instance_cls 

80 

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 = {} 

93 

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 

104 

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) 

111 

112 @property 

113 def _listen(self): 

114 return self._events._listen 

115 

116 def _for_class(self, instance_cls): 

117 return self.__class__(self, instance_cls) 

118 

119 def _for_instance(self, instance): 

120 instance_cls = instance.__class__ 

121 return self._for_class(instance_cls) 

122 

123 def _join(self, other): 

124 """Create a 'join' of this :class:`._Dispatch` and another. 

125 

126 This new dispatcher will dispatch events to both 

127 :class:`._Dispatch` objects. 

128 

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 ) 

136 

137 self.__class__._joined_dispatch_cls = cls 

138 return self._joined_dispatch_cls(self, other) 

139 

140 def __reduce__(self): 

141 return _UnpickleDispatch(), (self._instance_cls,) 

142 

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 ) 

152 

153 def _clear(self): 

154 for ls in self._event_descriptors: 

155 ls.for_modify(self).clear() 

156 

157 

158class _EventMeta(type): 

159 """Intercept new Event subclasses and create 

160 associated _Dispatch classes.""" 

161 

162 def __init__(cls, classname, bases, dict_): 

163 _create_dispatcher_class(cls, classname, bases, dict_) 

164 type.__init__(cls, classname, bases, dict_) 

165 

166 

167def _create_dispatcher_class(cls, classname, bases, dict_): 

168 """Create a :class:`._Dispatch` class corresponding to an 

169 :class:`.Events` class.""" 

170 

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 

178 

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 ) 

183 

184 dispatch_cls._event_names = event_names 

185 

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) 

190 

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) 

196 

197 if getattr(cls, "_dispatch_target", None): 

198 cls._dispatch_target.dispatch = dispatcher(cls) 

199 

200 

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] 

206 

207 

208class Events(util.with_metaclass(_EventMeta, object)): 

209 """Define event listening functions for a particular target type.""" 

210 

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 

221 

222 @classmethod 

223 def _accept_with(cls, target): 

224 def dispatch_is(*types): 

225 return all(isinstance(target.dispatch, t) for t in types) 

226 

227 def dispatch_parent_is(t): 

228 return isinstance(target.dispatch.parent, t) 

229 

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 

242 

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) 

246 

247 @classmethod 

248 def _remove(cls, event_key): 

249 event_key.remove() 

250 

251 @classmethod 

252 def _clear(cls): 

253 cls.dispatch._clear() 

254 

255 

256class _JoinedDispatcher(object): 

257 """Represent a connection between two _Dispatch objects.""" 

258 

259 __slots__ = "local", "parent", "_instance_cls" 

260 

261 def __init__(self, local, parent): 

262 self.local = local 

263 self.parent = parent 

264 self._instance_cls = self.local._instance_cls 

265 

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 

273 

274 @property 

275 def _listen(self): 

276 return self.parent._listen 

277 

278 @property 

279 def _events(self): 

280 return self.parent._events 

281 

282 

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. 

288 

289 """ 

290 

291 def __init__(self, events): 

292 self.dispatch = events.dispatch 

293 self.events = events 

294 

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