You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

241 lines
6.6KB

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