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/registry.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"""Provides managed registration services on behalf of :func:`.listen` 

9arguments. 

10 

11By "managed registration", we mean that event listening functions and 

12other objects can be added to various collections in such a way that their 

13membership in all those collections can be revoked at once, based on 

14an equivalent :class:`._EventKey`. 

15 

16""" 

17 

18from __future__ import absolute_import 

19 

20import collections 

21import types 

22import weakref 

23 

24from .. import exc 

25from .. import util 

26 

27 

28_key_to_collection = collections.defaultdict(dict) 

29""" 

30Given an original listen() argument, can locate all 

31listener collections and the listener fn contained 

32 

33(target, identifier, fn) -> { 

34 ref(listenercollection) -> ref(listener_fn) 

35 ref(listenercollection) -> ref(listener_fn) 

36 ref(listenercollection) -> ref(listener_fn) 

37 } 

38""" 

39 

40_collection_to_key = collections.defaultdict(dict) 

41""" 

42Given a _ListenerCollection or _ClsLevelListener, can locate 

43all the original listen() arguments and the listener fn contained 

44 

45ref(listenercollection) -> { 

46 ref(listener_fn) -> (target, identifier, fn), 

47 ref(listener_fn) -> (target, identifier, fn), 

48 ref(listener_fn) -> (target, identifier, fn), 

49 } 

50""" 

51 

52 

53def _collection_gced(ref): 

54 # defaultdict, so can't get a KeyError 

55 if not _collection_to_key or ref not in _collection_to_key: 

56 return 

57 listener_to_key = _collection_to_key.pop(ref) 

58 for key in listener_to_key.values(): 

59 if key in _key_to_collection: 

60 # defaultdict, so can't get a KeyError 

61 dispatch_reg = _key_to_collection[key] 

62 dispatch_reg.pop(ref) 

63 if not dispatch_reg: 

64 _key_to_collection.pop(key) 

65 

66 

67def _stored_in_collection(event_key, owner): 

68 key = event_key._key 

69 

70 dispatch_reg = _key_to_collection[key] 

71 

72 owner_ref = owner.ref 

73 listen_ref = weakref.ref(event_key._listen_fn) 

74 

75 if owner_ref in dispatch_reg: 

76 return False 

77 

78 dispatch_reg[owner_ref] = listen_ref 

79 

80 listener_to_key = _collection_to_key[owner_ref] 

81 listener_to_key[listen_ref] = key 

82 

83 return True 

84 

85 

86def _removed_from_collection(event_key, owner): 

87 key = event_key._key 

88 

89 dispatch_reg = _key_to_collection[key] 

90 

91 listen_ref = weakref.ref(event_key._listen_fn) 

92 

93 owner_ref = owner.ref 

94 dispatch_reg.pop(owner_ref, None) 

95 if not dispatch_reg: 

96 del _key_to_collection[key] 

97 

98 if owner_ref in _collection_to_key: 

99 listener_to_key = _collection_to_key[owner_ref] 

100 listener_to_key.pop(listen_ref) 

101 

102 

103def _stored_in_collection_multi(newowner, oldowner, elements): 

104 if not elements: 

105 return 

106 

107 oldowner = oldowner.ref 

108 newowner = newowner.ref 

109 

110 old_listener_to_key = _collection_to_key[oldowner] 

111 new_listener_to_key = _collection_to_key[newowner] 

112 

113 for listen_fn in elements: 

114 listen_ref = weakref.ref(listen_fn) 

115 key = old_listener_to_key[listen_ref] 

116 dispatch_reg = _key_to_collection[key] 

117 if newowner in dispatch_reg: 

118 assert dispatch_reg[newowner] == listen_ref 

119 else: 

120 dispatch_reg[newowner] = listen_ref 

121 

122 new_listener_to_key[listen_ref] = key 

123 

124 

125def _clear(owner, elements): 

126 if not elements: 

127 return 

128 

129 owner = owner.ref 

130 listener_to_key = _collection_to_key[owner] 

131 for listen_fn in elements: 

132 listen_ref = weakref.ref(listen_fn) 

133 key = listener_to_key[listen_ref] 

134 dispatch_reg = _key_to_collection[key] 

135 dispatch_reg.pop(owner, None) 

136 

137 if not dispatch_reg: 

138 del _key_to_collection[key] 

139 

140 

141class _EventKey(object): 

142 """Represent :func:`.listen` arguments. 

143 """ 

144 

145 __slots__ = ( 

146 "target", 

147 "identifier", 

148 "fn", 

149 "fn_key", 

150 "fn_wrap", 

151 "dispatch_target", 

152 ) 

153 

154 def __init__(self, target, identifier, fn, dispatch_target, _fn_wrap=None): 

155 self.target = target 

156 self.identifier = identifier 

157 self.fn = fn 

158 if isinstance(fn, types.MethodType): 

159 self.fn_key = id(fn.__func__), id(fn.__self__) 

160 else: 

161 self.fn_key = id(fn) 

162 self.fn_wrap = _fn_wrap 

163 self.dispatch_target = dispatch_target 

164 

165 @property 

166 def _key(self): 

167 return (id(self.target), self.identifier, self.fn_key) 

168 

169 def with_wrapper(self, fn_wrap): 

170 if fn_wrap is self._listen_fn: 

171 return self 

172 else: 

173 return _EventKey( 

174 self.target, 

175 self.identifier, 

176 self.fn, 

177 self.dispatch_target, 

178 _fn_wrap=fn_wrap, 

179 ) 

180 

181 def with_dispatch_target(self, dispatch_target): 

182 if dispatch_target is self.dispatch_target: 

183 return self 

184 else: 

185 return _EventKey( 

186 self.target, 

187 self.identifier, 

188 self.fn, 

189 dispatch_target, 

190 _fn_wrap=self.fn_wrap, 

191 ) 

192 

193 def listen(self, *args, **kw): 

194 once = kw.pop("once", False) 

195 once_unless_exception = kw.pop("_once_unless_exception", False) 

196 named = kw.pop("named", False) 

197 

198 target, identifier, fn = ( 

199 self.dispatch_target, 

200 self.identifier, 

201 self._listen_fn, 

202 ) 

203 

204 dispatch_collection = getattr(target.dispatch, identifier) 

205 

206 adjusted_fn = dispatch_collection._adjust_fn_spec(fn, named) 

207 

208 self = self.with_wrapper(adjusted_fn) 

209 

210 stub_function = getattr( 

211 self.dispatch_target.dispatch._events, self.identifier 

212 ) 

213 if hasattr(stub_function, "_sa_warn"): 

214 stub_function._sa_warn() 

215 

216 if once or once_unless_exception: 

217 self.with_wrapper( 

218 util.only_once( 

219 self._listen_fn, retry_on_exception=once_unless_exception 

220 ) 

221 ).listen(*args, **kw) 

222 else: 

223 self.dispatch_target.dispatch._listen(self, *args, **kw) 

224 

225 def remove(self): 

226 key = self._key 

227 

228 if key not in _key_to_collection: 

229 raise exc.InvalidRequestError( 

230 "No listeners found for event %s / %r / %s " 

231 % (self.target, self.identifier, self.fn) 

232 ) 

233 dispatch_reg = _key_to_collection.pop(key) 

234 

235 for collection_ref, listener_ref in dispatch_reg.items(): 

236 collection = collection_ref() 

237 listener_fn = listener_ref() 

238 if collection is not None and listener_fn is not None: 

239 collection.remove(self.with_wrapper(listener_fn)) 

240 

241 def contains(self): 

242 """Return True if this event key is registered to listen. 

243 """ 

244 return self._key in _key_to_collection 

245 

246 def base_listen( 

247 self, propagate=False, insert=False, named=False, retval=None 

248 ): 

249 

250 target, identifier = self.dispatch_target, self.identifier 

251 

252 dispatch_collection = getattr(target.dispatch, identifier) 

253 

254 if insert: 

255 dispatch_collection.for_modify(target.dispatch).insert( 

256 self, propagate 

257 ) 

258 else: 

259 dispatch_collection.for_modify(target.dispatch).append( 

260 self, propagate 

261 ) 

262 

263 @property 

264 def _listen_fn(self): 

265 return self.fn_wrap or self.fn 

266 

267 def append_to_list(self, owner, list_): 

268 if _stored_in_collection(self, owner): 

269 list_.append(self._listen_fn) 

270 return True 

271 else: 

272 return False 

273 

274 def remove_from_list(self, owner, list_): 

275 _removed_from_collection(self, owner) 

276 list_.remove(self._listen_fn) 

277 

278 def prepend_to_list(self, owner, list_): 

279 if _stored_in_collection(self, owner): 

280 list_.appendleft(self._listen_fn) 

281 return True 

282 else: 

283 return False