persist.py (7953B)
1 # standard imports 2 import datetime 3 4 # local imports 5 from .state import ( 6 State, 7 split_elements, 8 ) 9 from .error import ( 10 StateItemExists, 11 StateLockedKey, 12 StateExists, 13 ) 14 15 16 class PersistedState(State): 17 """Adapter for persisting state changes and synchronising states between memory and persisted backend. 18 19 :param factory: A function capable of returning a persisted store from a single path argument. 20 :type factory: function 21 :param bits: Number of pure states. Passed to the superclass. 22 :type bits: int 23 :param logger: Logger to capture logging output, or None for no logging. 24 :type logger: object 25 """ 26 27 def __init__(self, factory, bits, logger=None, verifier=None, check_alias=True, event_callback=None, default_state=None): 28 super(PersistedState, self).__init__(bits, logger=logger, verifier=verifier, check_alias=check_alias, event_callback=event_callback, default_state=default_state) 29 self.__store_factory = factory 30 self.__stores = {} 31 self.__ensure_store(self.base_state_name) 32 33 34 # Create state store container if missing. 35 def __ensure_store(self, k): 36 k = k.upper() 37 if self.__stores.get(k) == None: 38 self.__stores[k] = self.__store_factory(k) 39 40 41 def put(self, key, contents=None, state=None): 42 """Persist a key or key/content pair. 43 44 See shep.state.State.put 45 """ 46 k = self.to_name(state) 47 48 self.__ensure_store(k) 49 50 self.__stores[k].put(key, contents) 51 52 super(PersistedState, self).put(key, state=state, contents=contents) 53 54 self.register_modify(key) 55 56 57 def set(self, key, or_state): 58 """Persist a new state for a key or key/content. 59 60 See shep.state.State.set 61 """ 62 from_state = self.state(key) 63 if from_state & or_state == or_state: 64 return 65 k_from = self.name(from_state) 66 67 to_state = super(PersistedState, self).set(key, or_state) 68 k_to = self.name(to_state) 69 self.__ensure_store(k_to) 70 71 contents = None 72 try: 73 contents = self.__stores[k_from].get(key) 74 self.__stores[k_to].put(key, contents) 75 self.__stores[k_from].remove(key) 76 except StateLockedKey as e: 77 super(PersistedState, self).unset(key, or_state, allow_base=True) 78 raise e 79 80 #self.sync(to_state) 81 82 return to_state 83 84 85 def unset(self, key, not_state, allow_base=False): 86 """Persist a new state for a key or key/content. 87 88 See shep.state.State.unset 89 """ 90 from_state = self.state(key) 91 k_from = self.name(from_state) 92 93 to_state = super(PersistedState, self).unset(key, not_state, allow_base=allow_base) 94 95 k_to = self.name(to_state) 96 self.__ensure_store(k_to) 97 98 contents = self.__stores[k_from].get(key) 99 self.__stores[k_to].put(key, contents) 100 self.__stores[k_from].remove(key) 101 102 return to_state 103 104 105 def change(self, key, bits_set, bits_unset): 106 """Persist a new state for a key or key/content. 107 108 See shep.state.State.unset 109 """ 110 from_state = self.state(key) 111 k_from = self.name(from_state) 112 113 to_state = super(PersistedState, self).change(key, bits_set, bits_unset) 114 115 k_to = self.name(to_state) 116 self.__ensure_store(k_to) 117 118 contents = self.__stores[k_from].get(key) 119 self.__stores[k_to].put(key, contents) 120 self.__stores[k_from].remove(key) 121 122 self.register_modify(key) 123 124 return to_state 125 126 127 def move(self, key, to_state): 128 """Persist a new state for a key or key/content. 129 130 See shep.state.State.move 131 """ 132 from_state = self.state(key) 133 to_state = super(PersistedState, self).move(key, to_state) 134 return self.__movestore(key, from_state, to_state) 135 136 137 def __ensure_parts(self, state): 138 if self.is_pure(state): 139 return 140 state_name = self.name(state) 141 parts = split_elements(state_name) 142 for k in parts: 143 try: 144 self.add(k) 145 except StateExists: 146 pass 147 self.__ensure_store(k) 148 149 150 # common procedure for safely moving a persisted resource from one state to another. 151 def __movestore(self, key, from_state, to_state): 152 k_from = self.name(from_state) 153 k_to = self.name(to_state) 154 155 self.__ensure_store(k_to) 156 157 contents = self.__stores[k_from].get(key) 158 self.__stores[k_to].put(key, contents) 159 self.__stores[k_from].remove(key) 160 161 self.__ensure_parts(to_state) 162 163 self.register_modify(key) 164 165 self.sync(to_state) 166 167 return to_state 168 169 170 def sync(self, state=None, not_state=None, ignore_auto=True): 171 """Reload resources for a single state in memory from the persisted state store. 172 173 :param state: State to load 174 :type state: int 175 :raises StateItemExists: A content key is already recorded with a different state in memory than in persisted store. 176 # :todo: if sync state is none, sync all 177 """ 178 179 states_numeric = [] 180 if state == None: 181 states_numeric = list(self.all(numeric=True, ignore_auto=ignore_auto)) 182 else: 183 states_numeric = [state] 184 185 states = [] 186 for state in states_numeric: 187 if not_state != None: 188 if state & not_state == 0: 189 states.append(self.name(state)) 190 else: 191 states.append(self.name(state)) 192 193 ks = [] 194 for k in states: 195 ks.append(k) 196 197 for k in ks: 198 self.__ensure_store(k) 199 for o in self.__stores[k].list(): 200 state = self.from_name(k) 201 try: 202 super(PersistedState, self).put(o[0], state=state, contents=o[1]) 203 except StateItemExists as e: 204 pass 205 206 207 def list(self, state): 208 """List all content keys for a particular state. 209 210 This method will return from memory, and will not sync the persisted state first. 211 212 See shep.state.State.list 213 """ 214 k = self.name(state) 215 self.__ensure_store(k) 216 return super(PersistedState, self).list(state) 217 218 219 def path(self, state, key=None): 220 """Return a file path or URL pointing to the persisted state. 221 222 If the key is omitted, the URL to the state item's container must be returned, and None if no such container exists. 223 224 :param state: State to locate 225 :type state: int 226 :param key: Content key to locate 227 :type key: str 228 :rtype: str 229 :returns: Locator pointng to persisted state 230 :todo: rename to "location" 231 """ 232 k = self.name(state) 233 self.__ensure_store(k) 234 return self.__stores[k].path(k=key) 235 236 237 def next(self, key=None): 238 """Advance and persist to the next pure state. 239 240 See shep.state.State.next 241 """ 242 from_state = self.state(key) 243 to_state = super(PersistedState, self).next(key) 244 return self.__movestore(key, from_state, to_state) 245 246 247 def replace(self, key, contents): 248 """Replace contents associated by content key. 249 250 See shep.state.State.replace 251 """ 252 state = self.state(key) 253 k = self.name(state) 254 r = self.__stores[k].replace(key, contents) 255 super(PersistedState, self).replace(key, contents) 256 return r 257 258 259 def modified(self, key): 260 state = self.state(key) 261 k = self.name(state) 262 return self.__stores[k].modified(key) 263 264 265 def add(self, key): 266 self.__ensure_store(key) 267 return super(PersistedState, self).add(key) 268 269 270 def alias(self, key, *args): 271 self.__ensure_store(key) 272 super(PersistedState, self).alias(key, *args)