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# orm/identity.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 

8import weakref 

9 

10from . import attributes 

11from . import util as orm_util 

12from .. import exc as sa_exc 

13from .. import util 

14 

15 

16class IdentityMap(object): 

17 def __init__(self): 

18 self._dict = {} 

19 self._modified = set() 

20 self._wr = weakref.ref(self) 

21 

22 def keys(self): 

23 return self._dict.keys() 

24 

25 def replace(self, state): 

26 raise NotImplementedError() 

27 

28 def add(self, state): 

29 raise NotImplementedError() 

30 

31 def _add_unpresent(self, state, key): 

32 """optional inlined form of add() which can assume item isn't present 

33 in the map""" 

34 self.add(state) 

35 

36 def update(self, dict_): 

37 raise NotImplementedError("IdentityMap uses add() to insert data") 

38 

39 def clear(self): 

40 raise NotImplementedError("IdentityMap uses remove() to remove data") 

41 

42 def _manage_incoming_state(self, state): 

43 state._instance_dict = self._wr 

44 

45 if state.modified: 

46 self._modified.add(state) 

47 

48 def _manage_removed_state(self, state): 

49 del state._instance_dict 

50 if state.modified: 

51 self._modified.discard(state) 

52 

53 def _dirty_states(self): 

54 return self._modified 

55 

56 def check_modified(self): 

57 """return True if any InstanceStates present have been marked 

58 as 'modified'. 

59 

60 """ 

61 return bool(self._modified) 

62 

63 def has_key(self, key): 

64 return key in self 

65 

66 def popitem(self): 

67 raise NotImplementedError("IdentityMap uses remove() to remove data") 

68 

69 def pop(self, key, *args): 

70 raise NotImplementedError("IdentityMap uses remove() to remove data") 

71 

72 def setdefault(self, key, default=None): 

73 raise NotImplementedError("IdentityMap uses add() to insert data") 

74 

75 def __len__(self): 

76 return len(self._dict) 

77 

78 def copy(self): 

79 raise NotImplementedError() 

80 

81 def __setitem__(self, key, value): 

82 raise NotImplementedError("IdentityMap uses add() to insert data") 

83 

84 def __delitem__(self, key): 

85 raise NotImplementedError("IdentityMap uses remove() to remove data") 

86 

87 

88class WeakInstanceDict(IdentityMap): 

89 def __getitem__(self, key): 

90 state = self._dict[key] 

91 o = state.obj() 

92 if o is None: 

93 raise KeyError(key) 

94 return o 

95 

96 def __contains__(self, key): 

97 try: 

98 if key in self._dict: 

99 state = self._dict[key] 

100 o = state.obj() 

101 else: 

102 return False 

103 except KeyError: 

104 return False 

105 else: 

106 return o is not None 

107 

108 def contains_state(self, state): 

109 if state.key in self._dict: 

110 try: 

111 return self._dict[state.key] is state 

112 except KeyError: 

113 return False 

114 else: 

115 return False 

116 

117 def replace(self, state): 

118 if state.key in self._dict: 

119 try: 

120 existing = self._dict[state.key] 

121 except KeyError: 

122 # catch gc removed the key after we just checked for it 

123 pass 

124 else: 

125 if existing is not state: 

126 self._manage_removed_state(existing) 

127 else: 

128 return None 

129 else: 

130 existing = None 

131 

132 self._dict[state.key] = state 

133 self._manage_incoming_state(state) 

134 return existing 

135 

136 def add(self, state): 

137 key = state.key 

138 # inline of self.__contains__ 

139 if key in self._dict: 

140 try: 

141 existing_state = self._dict[key] 

142 except KeyError: 

143 # catch gc removed the key after we just checked for it 

144 pass 

145 else: 

146 if existing_state is not state: 

147 o = existing_state.obj() 

148 if o is not None: 

149 raise sa_exc.InvalidRequestError( 

150 "Can't attach instance " 

151 "%s; another instance with key %s is already " 

152 "present in this session." 

153 % (orm_util.state_str(state), state.key) 

154 ) 

155 else: 

156 return False 

157 self._dict[key] = state 

158 self._manage_incoming_state(state) 

159 return True 

160 

161 def _add_unpresent(self, state, key): 

162 # inlined form of add() called by loading.py 

163 self._dict[key] = state 

164 state._instance_dict = self._wr 

165 

166 def get(self, key, default=None): 

167 if key not in self._dict: 

168 return default 

169 try: 

170 state = self._dict[key] 

171 except KeyError: 

172 # catch gc removed the key after we just checked for it 

173 return default 

174 else: 

175 o = state.obj() 

176 if o is None: 

177 return default 

178 return o 

179 

180 def items(self): 

181 values = self.all_states() 

182 result = [] 

183 for state in values: 

184 value = state.obj() 

185 if value is not None: 

186 result.append((state.key, value)) 

187 return result 

188 

189 def values(self): 

190 values = self.all_states() 

191 result = [] 

192 for state in values: 

193 value = state.obj() 

194 if value is not None: 

195 result.append(value) 

196 

197 return result 

198 

199 def __iter__(self): 

200 return iter(self.keys()) 

201 

202 if util.py2k: 

203 

204 def iteritems(self): 

205 return iter(self.items()) 

206 

207 def itervalues(self): 

208 return iter(self.values()) 

209 

210 def all_states(self): 

211 if util.py2k: 

212 return self._dict.values() 

213 else: 

214 return list(self._dict.values()) 

215 

216 def _fast_discard(self, state): 

217 # used by InstanceState for state being 

218 # GC'ed, inlines _managed_removed_state 

219 try: 

220 st = self._dict[state.key] 

221 except KeyError: 

222 # catch gc removed the key after we just checked for it 

223 pass 

224 else: 

225 if st is state: 

226 self._dict.pop(state.key, None) 

227 

228 def discard(self, state): 

229 self.safe_discard(state) 

230 

231 def safe_discard(self, state): 

232 if state.key in self._dict: 

233 try: 

234 st = self._dict[state.key] 

235 except KeyError: 

236 # catch gc removed the key after we just checked for it 

237 pass 

238 else: 

239 if st is state: 

240 self._dict.pop(state.key, None) 

241 self._manage_removed_state(state) 

242 

243 def prune(self): 

244 return 0 

245 

246 

247class StrongInstanceDict(IdentityMap): 

248 """A 'strong-referencing' version of the identity map. 

249 

250 .. deprecated 1.1:: 

251 The strong 

252 reference identity map is legacy. See the 

253 recipe at :ref:`session_referencing_behavior` for 

254 an event-based approach to maintaining strong identity 

255 references. 

256 

257 

258 """ 

259 

260 if util.py2k: 

261 

262 def itervalues(self): 

263 return self._dict.itervalues() 

264 

265 def iteritems(self): 

266 return self._dict.iteritems() 

267 

268 def __iter__(self): 

269 return iter(self.dict_) 

270 

271 def __getitem__(self, key): 

272 return self._dict[key] 

273 

274 def __contains__(self, key): 

275 return key in self._dict 

276 

277 def get(self, key, default=None): 

278 return self._dict.get(key, default) 

279 

280 def values(self): 

281 return self._dict.values() 

282 

283 def items(self): 

284 return self._dict.items() 

285 

286 def all_states(self): 

287 return [attributes.instance_state(o) for o in self.values()] 

288 

289 def contains_state(self, state): 

290 return ( 

291 state.key in self 

292 and attributes.instance_state(self[state.key]) is state 

293 ) 

294 

295 def replace(self, state): 

296 if state.key in self._dict: 

297 existing = self._dict[state.key] 

298 existing = attributes.instance_state(existing) 

299 if existing is not state: 

300 self._manage_removed_state(existing) 

301 else: 

302 return 

303 else: 

304 existing = None 

305 

306 self._dict[state.key] = state.obj() 

307 self._manage_incoming_state(state) 

308 return existing 

309 

310 def add(self, state): 

311 if state.key in self: 

312 if attributes.instance_state(self._dict[state.key]) is not state: 

313 raise sa_exc.InvalidRequestError( 

314 "Can't attach instance " 

315 "%s; another instance with key %s is already " 

316 "present in this session." 

317 % (orm_util.state_str(state), state.key) 

318 ) 

319 return False 

320 else: 

321 self._dict[state.key] = state.obj() 

322 self._manage_incoming_state(state) 

323 return True 

324 

325 def _add_unpresent(self, state, key): 

326 # inlined form of add() called by loading.py 

327 self._dict[key] = state.obj() 

328 state._instance_dict = self._wr 

329 

330 def _fast_discard(self, state): 

331 # used by InstanceState for state being 

332 # GC'ed, inlines _managed_removed_state 

333 try: 

334 obj = self._dict[state.key] 

335 except KeyError: 

336 # catch gc removed the key after we just checked for it 

337 pass 

338 else: 

339 if attributes.instance_state(obj) is state: 

340 self._dict.pop(state.key, None) 

341 

342 def discard(self, state): 

343 self.safe_discard(state) 

344 

345 def safe_discard(self, state): 

346 if state.key in self._dict: 

347 obj = self._dict[state.key] 

348 st = attributes.instance_state(obj) 

349 if st is state: 

350 self._dict.pop(state.key, None) 

351 self._manage_removed_state(state) 

352 

353 def prune(self): 

354 """prune unreferenced, non-dirty states.""" 

355 

356 ref_count = len(self) 

357 dirty = [s.obj() for s in self.all_states() if s.modified] 

358 

359 # work around http://bugs.python.org/issue6149 

360 keepers = weakref.WeakValueDictionary() 

361 keepers.update(self) 

362 

363 self._dict.clear() 

364 self._dict.update(keepers) 

365 self.modified = bool(dirty) 

366 return ref_count - len(self)