1""" 2$URL: svn+ssh://svn.mems-exchange.org/repos/trunk/durus/persistent.py $ 3$Id: persistent.py 31094 2008-09-15 11:34:19Z dbinger $ 4""" 5from durus.utils import str_to_int8, iteritems, as_bytes 6from sys import stderr 7 8# these must match the constants in _persistent.c 9UNSAVED = 1 10SAVED = 0 11GHOST = -1 12 13 14try: 15 from durus._persistent import PersistentBase, ConnectionBase 16 from durus._persistent import _setattribute, _delattribute 17 from durus._persistent import _getattribute, _hasattribute 18 from durus._persistent import call_if_persistent 19 [ConnectionBase, _hasattribute, call_if_persistent] # silence import checker 20except ImportError: 21 stderr.write('Using Python base classes for persistence.\n') 22 23 _setattribute = object.__setattr__ 24 _delattribute = object.__delattr__ 25 _getattribute = object.__getattribute__ 26 27 def _hasattribute(obj, name): 28 try: 29 _getattribute(obj, name) 30 except AttributeError: 31 return False 32 else: 33 return True 34 35 class ConnectionBase(object): 36 """ 37 The faster implementation of this class is in _persistent.c. 38 """ 39 40 __slots__ = ['transaction_serial'] 41 42 def __new__(klass, *args, **kwargs): 43 instance = object.__new__(klass) 44 instance.transaction_serial = 1 45 return instance 46 47 48 _GHOST_SAFE_ATTRIBUTES = { 49 '__repr__': 1, 50 '__class__': 1, 51 '__setstate__': 1, 52 } 53 54 class PersistentBase(object): 55 """ 56 The faster implementation of this class is in _persistent.c. 57 The __slots__ and methods of this class are the ones that typical 58 applications use very frequently, so we want them to be fast. 59 60 Instance attributes: 61 _p_status: UNSAVED | SAVED | GHOST 62 UNSAVED means that state that is here, self.__dict__, is usable and 63 has not been stored. 64 SAVED means that the state that is here, self.__dict__, is usable 65 and the same as the stored state. 66 GHOST means that the state that is here, self.__dict__, is empty 67 and unusable until it is updated from storage. 68 69 New instances are UNSAVED. 70 UNSAVED -> SAVED 71 happens when the instance state, self.__dict__, is saved. 72 UNSAVED -> GHOST 73 happens on an abort (if the object has previously been saved). 74 SAVED -> UNSAVED 75 happens when changes are made to self.__dict__. 76 SAVED -> GHOST 77 happens when the cache manager wants space. 78 GHOST -> SAVED 79 happens when the instance state is loaded from the storage. 80 GHOST -> UNSAVED 81 this happens when you want to make changes to self.__dict__. 82 The stored state is loaded during this state transition. 83 _p_serial: int 84 On every access, this attribute is set to self._p_connection.serial 85 (if _p_connection is not None). 86 _p_connection: durus.connection.Connection | None 87 The Connection to the Storage that stores this instance. 88 The _p_connection is None when this instance has never been stored. 89 _p_oid: str | None 90 The identifier assigned when the instance was first stored. 91 The _p_oid is None when this instance has never been stored. 92 """ 93 94 __slots__ = ['_p_status', '_p_serial', '_p_connection', '_p_oid'] 95 96 def __new__(klass, *args, **kwargs): 97 instance = object.__new__(klass) 98 instance._p_status = UNSAVED 99 instance._p_serial = 0 100 instance._p_connection = None 101 instance._p_oid = None 102 return instance 103 104 def __getattribute__(self, name): 105 if name[:3] != '_p_' and name not in _GHOST_SAFE_ATTRIBUTES: 106 if self._p_status == GHOST: 107 self._p_load_state() 108 connection = self._p_connection 109 if (connection is not None and 110 self._p_serial != connection.transaction_serial): 111 connection.note_access(self) 112 return _getattribute(self, name) 113 114 def __setattr__(self, name, value): 115 if name[:3] != '_p_' and name not in _GHOST_SAFE_ATTRIBUTES: 116 self._p_note_change() 117 _setattribute(self, name, value) 118 119 def call_if_persistent(f, x): 120 if isinstance(x, PersistentBase): 121 return f(x) 122 else: 123 return None 124 125 126class PersistentObject (PersistentBase): 127 """ 128 All Durus persistent objects should inherit from this class. 129 """ 130 __slots__ = ['__weakref__'] 131 132 def _p_gen_data_slots(self): 133 """Generate the sequence of names of data slots that have values. 134 """ 135 for klass in self.__class__.__mro__: 136 if klass is not PersistentBase: 137 for name in getattr(klass, '__slots__', []): 138 if (name not in ('__weakref__', '__dict__') and 139 _hasattribute(self, name)): 140 yield name 141 142 def __getstate__(self): 143 if self._p_status == GHOST: 144 self._p_load_state() 145 state = {} 146 if _hasattribute(self, '__dict__'): 147 state.update(_getattribute(self, '__dict__')) 148 for name in self._p_gen_data_slots(): 149 state[name] = _getattribute(self, name) 150 return state 151 152 def __setstate__(self, state): 153 if _hasattribute(self, '__dict__'): 154 _getattribute(self, '__dict__').clear() 155 for name in self._p_gen_data_slots(): 156 _delattribute(self, name) 157 if state is not None: 158 for key, value in iteritems(state): 159 _setattribute(self, key, value) 160 161 def __repr__(self): 162 if self._p_oid is None: 163 identifier = '@%x' % id(self) 164 else: 165 identifier = self._p_format_oid() 166 return "<%s %s>" % (self.__class__.__name__, identifier) 167 168 def __delattr__(self, name): 169 self._p_note_change() 170 _delattribute(self, name) 171 172 def _p_load_state(self): 173 assert self._p_status == GHOST 174 self._p_connection.load_state(self) 175 self._p_set_status_saved() 176 177 def _p_note_change(self): 178 if self._p_status != UNSAVED: 179 self._p_set_status_unsaved() 180 self._p_connection.note_change(self) 181 182 def _p_format_oid(self): 183 oid = self._p_oid 184 return str(oid and str_to_int8(as_bytes(oid))) 185 186 def _p_set_status_ghost(self): 187 self.__setstate__({}) 188 self._p_status = GHOST 189 190 def _p_set_status_saved(self): 191 self._p_status = SAVED 192 193 def _p_set_status_unsaved(self): 194 if self._p_status == GHOST: 195 self._p_load_state() 196 self._p_status = UNSAVED 197 198 def _p_is_ghost(self): 199 return self._p_status == GHOST 200 201 def _p_is_unsaved(self): 202 return self._p_status == UNSAVED 203 204 def _p_is_saved(self): 205 return self._p_status == SAVED 206 207 208class Persistent (PersistentObject): 209 """ 210 This is the traditional persistent class of Durus. The state is stored 211 in the __dict__. 212 """ 213 def __getstate__(self): 214 return self.__dict__ 215 216 def __setstate__(self, state): 217 self_dict = _getattribute(self, '__dict__') 218 self_dict.clear() 219 self_dict.update(state) 220 221 222class ComputedAttribute (PersistentObject): 223 """Computed attributes do not have any state that needs to be saved in 224 the database. Instead, their value is computed based on other persistent 225 objects. Although they have no real persistent state, they do have 226 OIDs. That allows synchronize of the cached value between connections 227 (necessary to maintain consistency). If the value becomes invalid in one 228 connection then it must be invalidated in all connections. That is 229 achieved by marking the object as UNSAVED and treating it like a normal 230 persistent object. 231 232 Instance attributes: none 233 """ 234 __slots__ = ['value'] 235 236 def __getstate__(self): 237 return None 238 239 def _p_load_state(self): 240 # don't need to read state from connection, there is none 241 self._p_set_status_saved() 242 243 def invalidate(self): 244 """Forget value and mark object as UNSAVED. On commit it will cause 245 other connections to receive a invalidation notification and forget the 246 value as well. 247 """ 248 self.__setstate__(None) 249 self._p_note_change() 250 251 def get(self, compute): 252 """(compute) -> value 253 254 Compute the value (if necessary) and return it. 'compute' needs 255 to be a function that takes no arguments. 256 """ 257 # we are careful here not to mark object as UNSAVED 258 if _hasattribute(self, 'value'): 259 value = _getattribute(self, 'value') 260 else: 261 value = compute() 262 _setattribute(self, 'value', value) 263 return value 264