identity.py 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. # orm/identity.py
  2. # Copyright (C) 2005-2022 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: https://www.opensource.org/licenses/mit-license.php
  7. import weakref
  8. from . import util as orm_util
  9. from .. import exc as sa_exc
  10. from .. import util
  11. class IdentityMap(object):
  12. def __init__(self):
  13. self._dict = {}
  14. self._modified = set()
  15. self._wr = weakref.ref(self)
  16. def _kill(self):
  17. self._add_unpresent = _killed
  18. def keys(self):
  19. return self._dict.keys()
  20. def replace(self, state):
  21. raise NotImplementedError()
  22. def add(self, state):
  23. raise NotImplementedError()
  24. def _add_unpresent(self, state, key):
  25. """optional inlined form of add() which can assume item isn't present
  26. in the map"""
  27. self.add(state)
  28. def update(self, dict_):
  29. raise NotImplementedError("IdentityMap uses add() to insert data")
  30. def clear(self):
  31. raise NotImplementedError("IdentityMap uses remove() to remove data")
  32. def _manage_incoming_state(self, state):
  33. state._instance_dict = self._wr
  34. if state.modified:
  35. self._modified.add(state)
  36. def _manage_removed_state(self, state):
  37. del state._instance_dict
  38. if state.modified:
  39. self._modified.discard(state)
  40. def _dirty_states(self):
  41. return self._modified
  42. def check_modified(self):
  43. """return True if any InstanceStates present have been marked
  44. as 'modified'.
  45. """
  46. return bool(self._modified)
  47. def has_key(self, key):
  48. return key in self
  49. def popitem(self):
  50. raise NotImplementedError("IdentityMap uses remove() to remove data")
  51. def pop(self, key, *args):
  52. raise NotImplementedError("IdentityMap uses remove() to remove data")
  53. def setdefault(self, key, default=None):
  54. raise NotImplementedError("IdentityMap uses add() to insert data")
  55. def __len__(self):
  56. return len(self._dict)
  57. def copy(self):
  58. raise NotImplementedError()
  59. def __setitem__(self, key, value):
  60. raise NotImplementedError("IdentityMap uses add() to insert data")
  61. def __delitem__(self, key):
  62. raise NotImplementedError("IdentityMap uses remove() to remove data")
  63. class WeakInstanceDict(IdentityMap):
  64. def __getitem__(self, key):
  65. state = self._dict[key]
  66. o = state.obj()
  67. if o is None:
  68. raise KeyError(key)
  69. return o
  70. def __contains__(self, key):
  71. try:
  72. if key in self._dict:
  73. state = self._dict[key]
  74. o = state.obj()
  75. else:
  76. return False
  77. except KeyError:
  78. return False
  79. else:
  80. return o is not None
  81. def contains_state(self, state):
  82. if state.key in self._dict:
  83. try:
  84. return self._dict[state.key] is state
  85. except KeyError:
  86. return False
  87. else:
  88. return False
  89. def replace(self, state):
  90. if state.key in self._dict:
  91. try:
  92. existing = self._dict[state.key]
  93. except KeyError:
  94. # catch gc removed the key after we just checked for it
  95. pass
  96. else:
  97. if existing is not state:
  98. self._manage_removed_state(existing)
  99. else:
  100. return None
  101. else:
  102. existing = None
  103. self._dict[state.key] = state
  104. self._manage_incoming_state(state)
  105. return existing
  106. def add(self, state):
  107. key = state.key
  108. # inline of self.__contains__
  109. if key in self._dict:
  110. try:
  111. existing_state = self._dict[key]
  112. except KeyError:
  113. # catch gc removed the key after we just checked for it
  114. pass
  115. else:
  116. if existing_state is not state:
  117. o = existing_state.obj()
  118. if o is not None:
  119. raise sa_exc.InvalidRequestError(
  120. "Can't attach instance "
  121. "%s; another instance with key %s is already "
  122. "present in this session."
  123. % (orm_util.state_str(state), state.key)
  124. )
  125. else:
  126. return False
  127. self._dict[key] = state
  128. self._manage_incoming_state(state)
  129. return True
  130. def _add_unpresent(self, state, key):
  131. # inlined form of add() called by loading.py
  132. self._dict[key] = state
  133. state._instance_dict = self._wr
  134. def get(self, key, default=None):
  135. if key not in self._dict:
  136. return default
  137. try:
  138. state = self._dict[key]
  139. except KeyError:
  140. # catch gc removed the key after we just checked for it
  141. return default
  142. else:
  143. o = state.obj()
  144. if o is None:
  145. return default
  146. return o
  147. def items(self):
  148. values = self.all_states()
  149. result = []
  150. for state in values:
  151. value = state.obj()
  152. if value is not None:
  153. result.append((state.key, value))
  154. return result
  155. def values(self):
  156. values = self.all_states()
  157. result = []
  158. for state in values:
  159. value = state.obj()
  160. if value is not None:
  161. result.append(value)
  162. return result
  163. def __iter__(self):
  164. return iter(self.keys())
  165. if util.py2k:
  166. def iteritems(self):
  167. return iter(self.items())
  168. def itervalues(self):
  169. return iter(self.values())
  170. def all_states(self):
  171. if util.py2k:
  172. return self._dict.values()
  173. else:
  174. return list(self._dict.values())
  175. def _fast_discard(self, state):
  176. # used by InstanceState for state being
  177. # GC'ed, inlines _managed_removed_state
  178. try:
  179. st = self._dict[state.key]
  180. except KeyError:
  181. # catch gc removed the key after we just checked for it
  182. pass
  183. else:
  184. if st is state:
  185. self._dict.pop(state.key, None)
  186. def discard(self, state):
  187. self.safe_discard(state)
  188. def safe_discard(self, state):
  189. if state.key in self._dict:
  190. try:
  191. st = self._dict[state.key]
  192. except KeyError:
  193. # catch gc removed the key after we just checked for it
  194. pass
  195. else:
  196. if st is state:
  197. self._dict.pop(state.key, None)
  198. self._manage_removed_state(state)
  199. def _killed(state, key):
  200. # external function to avoid creating cycles when assigned to
  201. # the IdentityMap
  202. raise sa_exc.InvalidRequestError(
  203. "Object %s cannot be converted to 'persistent' state, as this "
  204. "identity map is no longer valid. Has the owning Session "
  205. "been closed?" % orm_util.state_str(state),
  206. code="lkrp",
  207. )