import shelve import base64 from threading import Lock class PersistentContainer(object): """A wrapper for shelve dictionaries, allowing basic transactions and namespaces Be carefull as this module returns copies but no references. This means it needs careful programming: >>> pc = PersistentContainer("ns","foobar") >>> pc["num"] = 3 # direct access using = operators works >>> pc["list"] = [1,2,3] >>> pc["num"] += 3 # this will work... >>> print pc["num"] 6 >>> pc["list"].append(4) # ...but this wont. >>> print pc["list"] [1, 2, 3] >>> tmp = pc["list"] # instead store a copy >>> tmp.append(4) # alter the copy >>> pc["list"]=tmp # and write it back >>> print pc["list"] [1, 2, 3, 4] """ refs = dict() def __init__(self,namespace,path): """Create a PersistentContainer object namespace -- the namespace in which the variables will be saved path -- path to the (n)dbm-file used to permanently store the values """ self.namespace = namespace self.path = path self.trans_cache = dict() self.trans_del = [] if not path in PersistentContainer.refs: ref_cnt = 0 lock = Lock() dic = shelve.open(path,writeback=False) PersistentContainer.refs[path] = [ref_cnt,lock,dic] ref = PersistentContainer.refs[path] ref[0]+=1 self.transaction = ref[1] self.dic = ref[2] def __del__(self): """delete the object""" assert self.path in PersistentContainer.refs ref = PersistentContainer.refs[self.path] ref[0] -= 1 if ref[0]==0: self.dic.close() del PersistentContainer.refs[self.path] def start_transaction(self,blocking=True): """Start an atomic transaction. blocking -- If True the start of the transaction will wait until the running is finished """ return self.transaction.acquire(blocking) def commit_transaction(self): """Commit the values that changed during the transaction""" self._write() self.transaction.release() def rollback(self): """Discard the values that changed during the transaction""" for k in self.trans_cache.keys(): del self.trans_cache[k] self.transaction.release() def keys(self): """Return the valid keys in the namespace""" ret = [] for k in self.dict.keys(): if k.endswith("."+self.namespace): ret.append(k[:-len("."+self.namespace)]) return ret def is_owned(self): """Return True if there is an transaction running""" return self.transaction.locked() def __getitem__(self, name): key = name+"."+self.namespace key = base64.b64encode(key) if key in self.dic: return self.dic[key] return None def __setitem__(self, name,val): self.trans_cache[name]=val if not self.is_owned(): self._write() def __delitem__(self, name): if name not in self.trans_del: self.trans_del.append(name) if name in self.trans_cache: del self.trans_cache[name] if not self.is_owned(): self._write() def __contains__(self,name): key = name+"."+self.namespace key = base64.b64encode(key) return key in self.dic def _write(self): for name in self.trans_cache.keys(): key = name+"."+self.namespace key = base64.b64encode(key) val = self.trans_cache[name] self.dic[key]=val del self.trans_cache[name] while len(self.trans_del): name = self.trans_del.pop() key = name+"."+self.namespace key = base64.b64encode(key) del self.dic[key] self.dic.sync()