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.
124 lines
3.3 KiB
124 lines
3.3 KiB
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()
|
|
|
|
|