1*cda5da8dSAndroid Build Coastguard Worker"""Manage shelves of pickled objects. 2*cda5da8dSAndroid Build Coastguard Worker 3*cda5da8dSAndroid Build Coastguard WorkerA "shelf" is a persistent, dictionary-like object. The difference 4*cda5da8dSAndroid Build Coastguard Workerwith dbm databases is that the values (not the keys!) in a shelf can 5*cda5da8dSAndroid Build Coastguard Workerbe essentially arbitrary Python objects -- anything that the "pickle" 6*cda5da8dSAndroid Build Coastguard Workermodule can handle. This includes most class instances, recursive data 7*cda5da8dSAndroid Build Coastguard Workertypes, and objects containing lots of shared sub-objects. The keys 8*cda5da8dSAndroid Build Coastguard Workerare ordinary strings. 9*cda5da8dSAndroid Build Coastguard Worker 10*cda5da8dSAndroid Build Coastguard WorkerTo summarize the interface (key is a string, data is an arbitrary 11*cda5da8dSAndroid Build Coastguard Workerobject): 12*cda5da8dSAndroid Build Coastguard Worker 13*cda5da8dSAndroid Build Coastguard Worker import shelve 14*cda5da8dSAndroid Build Coastguard Worker d = shelve.open(filename) # open, with (g)dbm filename -- no suffix 15*cda5da8dSAndroid Build Coastguard Worker 16*cda5da8dSAndroid Build Coastguard Worker d[key] = data # store data at key (overwrites old data if 17*cda5da8dSAndroid Build Coastguard Worker # using an existing key) 18*cda5da8dSAndroid Build Coastguard Worker data = d[key] # retrieve a COPY of the data at key (raise 19*cda5da8dSAndroid Build Coastguard Worker # KeyError if no such key) -- NOTE that this 20*cda5da8dSAndroid Build Coastguard Worker # access returns a *copy* of the entry! 21*cda5da8dSAndroid Build Coastguard Worker del d[key] # delete data stored at key (raises KeyError 22*cda5da8dSAndroid Build Coastguard Worker # if no such key) 23*cda5da8dSAndroid Build Coastguard Worker flag = key in d # true if the key exists 24*cda5da8dSAndroid Build Coastguard Worker list = d.keys() # a list of all existing keys (slow!) 25*cda5da8dSAndroid Build Coastguard Worker 26*cda5da8dSAndroid Build Coastguard Worker d.close() # close it 27*cda5da8dSAndroid Build Coastguard Worker 28*cda5da8dSAndroid Build Coastguard WorkerDependent on the implementation, closing a persistent dictionary may 29*cda5da8dSAndroid Build Coastguard Workeror may not be necessary to flush changes to disk. 30*cda5da8dSAndroid Build Coastguard Worker 31*cda5da8dSAndroid Build Coastguard WorkerNormally, d[key] returns a COPY of the entry. This needs care when 32*cda5da8dSAndroid Build Coastguard Workermutable entries are mutated: for example, if d[key] is a list, 33*cda5da8dSAndroid Build Coastguard Worker d[key].append(anitem) 34*cda5da8dSAndroid Build Coastguard Workerdoes NOT modify the entry d[key] itself, as stored in the persistent 35*cda5da8dSAndroid Build Coastguard Workermapping -- it only modifies the copy, which is then immediately 36*cda5da8dSAndroid Build Coastguard Workerdiscarded, so that the append has NO effect whatsoever. To append an 37*cda5da8dSAndroid Build Coastguard Workeritem to d[key] in a way that will affect the persistent mapping, use: 38*cda5da8dSAndroid Build Coastguard Worker data = d[key] 39*cda5da8dSAndroid Build Coastguard Worker data.append(anitem) 40*cda5da8dSAndroid Build Coastguard Worker d[key] = data 41*cda5da8dSAndroid Build Coastguard Worker 42*cda5da8dSAndroid Build Coastguard WorkerTo avoid the problem with mutable entries, you may pass the keyword 43*cda5da8dSAndroid Build Coastguard Workerargument writeback=True in the call to shelve.open. When you use: 44*cda5da8dSAndroid Build Coastguard Worker d = shelve.open(filename, writeback=True) 45*cda5da8dSAndroid Build Coastguard Workerthen d keeps a cache of all entries you access, and writes them all back 46*cda5da8dSAndroid Build Coastguard Workerto the persistent mapping when you call d.close(). This ensures that 47*cda5da8dSAndroid Build Coastguard Workersuch usage as d[key].append(anitem) works as intended. 48*cda5da8dSAndroid Build Coastguard Worker 49*cda5da8dSAndroid Build Coastguard WorkerHowever, using keyword argument writeback=True may consume vast amount 50*cda5da8dSAndroid Build Coastguard Workerof memory for the cache, and it may make d.close() very slow, if you 51*cda5da8dSAndroid Build Coastguard Workeraccess many of d's entries after opening it in this way: d has no way to 52*cda5da8dSAndroid Build Coastguard Workercheck which of the entries you access are mutable and/or which ones you 53*cda5da8dSAndroid Build Coastguard Workeractually mutate, so it must cache, and write back at close, all of the 54*cda5da8dSAndroid Build Coastguard Workerentries that you access. You can call d.sync() to write back all the 55*cda5da8dSAndroid Build Coastguard Workerentries in the cache, and empty the cache (d.sync() also synchronizes 56*cda5da8dSAndroid Build Coastguard Workerthe persistent dictionary on disk, if feasible). 57*cda5da8dSAndroid Build Coastguard Worker""" 58*cda5da8dSAndroid Build Coastguard Worker 59*cda5da8dSAndroid Build Coastguard Workerfrom pickle import DEFAULT_PROTOCOL, Pickler, Unpickler 60*cda5da8dSAndroid Build Coastguard Workerfrom io import BytesIO 61*cda5da8dSAndroid Build Coastguard Worker 62*cda5da8dSAndroid Build Coastguard Workerimport collections.abc 63*cda5da8dSAndroid Build Coastguard Worker 64*cda5da8dSAndroid Build Coastguard Worker__all__ = ["Shelf", "BsdDbShelf", "DbfilenameShelf", "open"] 65*cda5da8dSAndroid Build Coastguard Worker 66*cda5da8dSAndroid Build Coastguard Workerclass _ClosedDict(collections.abc.MutableMapping): 67*cda5da8dSAndroid Build Coastguard Worker 'Marker for a closed dict. Access attempts raise a ValueError.' 68*cda5da8dSAndroid Build Coastguard Worker 69*cda5da8dSAndroid Build Coastguard Worker def closed(self, *args): 70*cda5da8dSAndroid Build Coastguard Worker raise ValueError('invalid operation on closed shelf') 71*cda5da8dSAndroid Build Coastguard Worker __iter__ = __len__ = __getitem__ = __setitem__ = __delitem__ = keys = closed 72*cda5da8dSAndroid Build Coastguard Worker 73*cda5da8dSAndroid Build Coastguard Worker def __repr__(self): 74*cda5da8dSAndroid Build Coastguard Worker return '<Closed Dictionary>' 75*cda5da8dSAndroid Build Coastguard Worker 76*cda5da8dSAndroid Build Coastguard Worker 77*cda5da8dSAndroid Build Coastguard Workerclass Shelf(collections.abc.MutableMapping): 78*cda5da8dSAndroid Build Coastguard Worker """Base class for shelf implementations. 79*cda5da8dSAndroid Build Coastguard Worker 80*cda5da8dSAndroid Build Coastguard Worker This is initialized with a dictionary-like object. 81*cda5da8dSAndroid Build Coastguard Worker See the module's __doc__ string for an overview of the interface. 82*cda5da8dSAndroid Build Coastguard Worker """ 83*cda5da8dSAndroid Build Coastguard Worker 84*cda5da8dSAndroid Build Coastguard Worker def __init__(self, dict, protocol=None, writeback=False, 85*cda5da8dSAndroid Build Coastguard Worker keyencoding="utf-8"): 86*cda5da8dSAndroid Build Coastguard Worker self.dict = dict 87*cda5da8dSAndroid Build Coastguard Worker if protocol is None: 88*cda5da8dSAndroid Build Coastguard Worker protocol = DEFAULT_PROTOCOL 89*cda5da8dSAndroid Build Coastguard Worker self._protocol = protocol 90*cda5da8dSAndroid Build Coastguard Worker self.writeback = writeback 91*cda5da8dSAndroid Build Coastguard Worker self.cache = {} 92*cda5da8dSAndroid Build Coastguard Worker self.keyencoding = keyencoding 93*cda5da8dSAndroid Build Coastguard Worker 94*cda5da8dSAndroid Build Coastguard Worker def __iter__(self): 95*cda5da8dSAndroid Build Coastguard Worker for k in self.dict.keys(): 96*cda5da8dSAndroid Build Coastguard Worker yield k.decode(self.keyencoding) 97*cda5da8dSAndroid Build Coastguard Worker 98*cda5da8dSAndroid Build Coastguard Worker def __len__(self): 99*cda5da8dSAndroid Build Coastguard Worker return len(self.dict) 100*cda5da8dSAndroid Build Coastguard Worker 101*cda5da8dSAndroid Build Coastguard Worker def __contains__(self, key): 102*cda5da8dSAndroid Build Coastguard Worker return key.encode(self.keyencoding) in self.dict 103*cda5da8dSAndroid Build Coastguard Worker 104*cda5da8dSAndroid Build Coastguard Worker def get(self, key, default=None): 105*cda5da8dSAndroid Build Coastguard Worker if key.encode(self.keyencoding) in self.dict: 106*cda5da8dSAndroid Build Coastguard Worker return self[key] 107*cda5da8dSAndroid Build Coastguard Worker return default 108*cda5da8dSAndroid Build Coastguard Worker 109*cda5da8dSAndroid Build Coastguard Worker def __getitem__(self, key): 110*cda5da8dSAndroid Build Coastguard Worker try: 111*cda5da8dSAndroid Build Coastguard Worker value = self.cache[key] 112*cda5da8dSAndroid Build Coastguard Worker except KeyError: 113*cda5da8dSAndroid Build Coastguard Worker f = BytesIO(self.dict[key.encode(self.keyencoding)]) 114*cda5da8dSAndroid Build Coastguard Worker value = Unpickler(f).load() 115*cda5da8dSAndroid Build Coastguard Worker if self.writeback: 116*cda5da8dSAndroid Build Coastguard Worker self.cache[key] = value 117*cda5da8dSAndroid Build Coastguard Worker return value 118*cda5da8dSAndroid Build Coastguard Worker 119*cda5da8dSAndroid Build Coastguard Worker def __setitem__(self, key, value): 120*cda5da8dSAndroid Build Coastguard Worker if self.writeback: 121*cda5da8dSAndroid Build Coastguard Worker self.cache[key] = value 122*cda5da8dSAndroid Build Coastguard Worker f = BytesIO() 123*cda5da8dSAndroid Build Coastguard Worker p = Pickler(f, self._protocol) 124*cda5da8dSAndroid Build Coastguard Worker p.dump(value) 125*cda5da8dSAndroid Build Coastguard Worker self.dict[key.encode(self.keyencoding)] = f.getvalue() 126*cda5da8dSAndroid Build Coastguard Worker 127*cda5da8dSAndroid Build Coastguard Worker def __delitem__(self, key): 128*cda5da8dSAndroid Build Coastguard Worker del self.dict[key.encode(self.keyencoding)] 129*cda5da8dSAndroid Build Coastguard Worker try: 130*cda5da8dSAndroid Build Coastguard Worker del self.cache[key] 131*cda5da8dSAndroid Build Coastguard Worker except KeyError: 132*cda5da8dSAndroid Build Coastguard Worker pass 133*cda5da8dSAndroid Build Coastguard Worker 134*cda5da8dSAndroid Build Coastguard Worker def __enter__(self): 135*cda5da8dSAndroid Build Coastguard Worker return self 136*cda5da8dSAndroid Build Coastguard Worker 137*cda5da8dSAndroid Build Coastguard Worker def __exit__(self, type, value, traceback): 138*cda5da8dSAndroid Build Coastguard Worker self.close() 139*cda5da8dSAndroid Build Coastguard Worker 140*cda5da8dSAndroid Build Coastguard Worker def close(self): 141*cda5da8dSAndroid Build Coastguard Worker if self.dict is None: 142*cda5da8dSAndroid Build Coastguard Worker return 143*cda5da8dSAndroid Build Coastguard Worker try: 144*cda5da8dSAndroid Build Coastguard Worker self.sync() 145*cda5da8dSAndroid Build Coastguard Worker try: 146*cda5da8dSAndroid Build Coastguard Worker self.dict.close() 147*cda5da8dSAndroid Build Coastguard Worker except AttributeError: 148*cda5da8dSAndroid Build Coastguard Worker pass 149*cda5da8dSAndroid Build Coastguard Worker finally: 150*cda5da8dSAndroid Build Coastguard Worker # Catch errors that may happen when close is called from __del__ 151*cda5da8dSAndroid Build Coastguard Worker # because CPython is in interpreter shutdown. 152*cda5da8dSAndroid Build Coastguard Worker try: 153*cda5da8dSAndroid Build Coastguard Worker self.dict = _ClosedDict() 154*cda5da8dSAndroid Build Coastguard Worker except: 155*cda5da8dSAndroid Build Coastguard Worker self.dict = None 156*cda5da8dSAndroid Build Coastguard Worker 157*cda5da8dSAndroid Build Coastguard Worker def __del__(self): 158*cda5da8dSAndroid Build Coastguard Worker if not hasattr(self, 'writeback'): 159*cda5da8dSAndroid Build Coastguard Worker # __init__ didn't succeed, so don't bother closing 160*cda5da8dSAndroid Build Coastguard Worker # see http://bugs.python.org/issue1339007 for details 161*cda5da8dSAndroid Build Coastguard Worker return 162*cda5da8dSAndroid Build Coastguard Worker self.close() 163*cda5da8dSAndroid Build Coastguard Worker 164*cda5da8dSAndroid Build Coastguard Worker def sync(self): 165*cda5da8dSAndroid Build Coastguard Worker if self.writeback and self.cache: 166*cda5da8dSAndroid Build Coastguard Worker self.writeback = False 167*cda5da8dSAndroid Build Coastguard Worker for key, entry in self.cache.items(): 168*cda5da8dSAndroid Build Coastguard Worker self[key] = entry 169*cda5da8dSAndroid Build Coastguard Worker self.writeback = True 170*cda5da8dSAndroid Build Coastguard Worker self.cache = {} 171*cda5da8dSAndroid Build Coastguard Worker if hasattr(self.dict, 'sync'): 172*cda5da8dSAndroid Build Coastguard Worker self.dict.sync() 173*cda5da8dSAndroid Build Coastguard Worker 174*cda5da8dSAndroid Build Coastguard Worker 175*cda5da8dSAndroid Build Coastguard Workerclass BsdDbShelf(Shelf): 176*cda5da8dSAndroid Build Coastguard Worker """Shelf implementation using the "BSD" db interface. 177*cda5da8dSAndroid Build Coastguard Worker 178*cda5da8dSAndroid Build Coastguard Worker This adds methods first(), next(), previous(), last() and 179*cda5da8dSAndroid Build Coastguard Worker set_location() that have no counterpart in [g]dbm databases. 180*cda5da8dSAndroid Build Coastguard Worker 181*cda5da8dSAndroid Build Coastguard Worker The actual database must be opened using one of the "bsddb" 182*cda5da8dSAndroid Build Coastguard Worker modules "open" routines (i.e. bsddb.hashopen, bsddb.btopen or 183*cda5da8dSAndroid Build Coastguard Worker bsddb.rnopen) and passed to the constructor. 184*cda5da8dSAndroid Build Coastguard Worker 185*cda5da8dSAndroid Build Coastguard Worker See the module's __doc__ string for an overview of the interface. 186*cda5da8dSAndroid Build Coastguard Worker """ 187*cda5da8dSAndroid Build Coastguard Worker 188*cda5da8dSAndroid Build Coastguard Worker def __init__(self, dict, protocol=None, writeback=False, 189*cda5da8dSAndroid Build Coastguard Worker keyencoding="utf-8"): 190*cda5da8dSAndroid Build Coastguard Worker Shelf.__init__(self, dict, protocol, writeback, keyencoding) 191*cda5da8dSAndroid Build Coastguard Worker 192*cda5da8dSAndroid Build Coastguard Worker def set_location(self, key): 193*cda5da8dSAndroid Build Coastguard Worker (key, value) = self.dict.set_location(key) 194*cda5da8dSAndroid Build Coastguard Worker f = BytesIO(value) 195*cda5da8dSAndroid Build Coastguard Worker return (key.decode(self.keyencoding), Unpickler(f).load()) 196*cda5da8dSAndroid Build Coastguard Worker 197*cda5da8dSAndroid Build Coastguard Worker def next(self): 198*cda5da8dSAndroid Build Coastguard Worker (key, value) = next(self.dict) 199*cda5da8dSAndroid Build Coastguard Worker f = BytesIO(value) 200*cda5da8dSAndroid Build Coastguard Worker return (key.decode(self.keyencoding), Unpickler(f).load()) 201*cda5da8dSAndroid Build Coastguard Worker 202*cda5da8dSAndroid Build Coastguard Worker def previous(self): 203*cda5da8dSAndroid Build Coastguard Worker (key, value) = self.dict.previous() 204*cda5da8dSAndroid Build Coastguard Worker f = BytesIO(value) 205*cda5da8dSAndroid Build Coastguard Worker return (key.decode(self.keyencoding), Unpickler(f).load()) 206*cda5da8dSAndroid Build Coastguard Worker 207*cda5da8dSAndroid Build Coastguard Worker def first(self): 208*cda5da8dSAndroid Build Coastguard Worker (key, value) = self.dict.first() 209*cda5da8dSAndroid Build Coastguard Worker f = BytesIO(value) 210*cda5da8dSAndroid Build Coastguard Worker return (key.decode(self.keyencoding), Unpickler(f).load()) 211*cda5da8dSAndroid Build Coastguard Worker 212*cda5da8dSAndroid Build Coastguard Worker def last(self): 213*cda5da8dSAndroid Build Coastguard Worker (key, value) = self.dict.last() 214*cda5da8dSAndroid Build Coastguard Worker f = BytesIO(value) 215*cda5da8dSAndroid Build Coastguard Worker return (key.decode(self.keyencoding), Unpickler(f).load()) 216*cda5da8dSAndroid Build Coastguard Worker 217*cda5da8dSAndroid Build Coastguard Worker 218*cda5da8dSAndroid Build Coastguard Workerclass DbfilenameShelf(Shelf): 219*cda5da8dSAndroid Build Coastguard Worker """Shelf implementation using the "dbm" generic dbm interface. 220*cda5da8dSAndroid Build Coastguard Worker 221*cda5da8dSAndroid Build Coastguard Worker This is initialized with the filename for the dbm database. 222*cda5da8dSAndroid Build Coastguard Worker See the module's __doc__ string for an overview of the interface. 223*cda5da8dSAndroid Build Coastguard Worker """ 224*cda5da8dSAndroid Build Coastguard Worker 225*cda5da8dSAndroid Build Coastguard Worker def __init__(self, filename, flag='c', protocol=None, writeback=False): 226*cda5da8dSAndroid Build Coastguard Worker import dbm 227*cda5da8dSAndroid Build Coastguard Worker Shelf.__init__(self, dbm.open(filename, flag), protocol, writeback) 228*cda5da8dSAndroid Build Coastguard Worker 229*cda5da8dSAndroid Build Coastguard Worker 230*cda5da8dSAndroid Build Coastguard Workerdef open(filename, flag='c', protocol=None, writeback=False): 231*cda5da8dSAndroid Build Coastguard Worker """Open a persistent dictionary for reading and writing. 232*cda5da8dSAndroid Build Coastguard Worker 233*cda5da8dSAndroid Build Coastguard Worker The filename parameter is the base filename for the underlying 234*cda5da8dSAndroid Build Coastguard Worker database. As a side-effect, an extension may be added to the 235*cda5da8dSAndroid Build Coastguard Worker filename and more than one file may be created. The optional flag 236*cda5da8dSAndroid Build Coastguard Worker parameter has the same interpretation as the flag parameter of 237*cda5da8dSAndroid Build Coastguard Worker dbm.open(). The optional protocol parameter specifies the 238*cda5da8dSAndroid Build Coastguard Worker version of the pickle protocol. 239*cda5da8dSAndroid Build Coastguard Worker 240*cda5da8dSAndroid Build Coastguard Worker See the module's __doc__ string for an overview of the interface. 241*cda5da8dSAndroid Build Coastguard Worker """ 242*cda5da8dSAndroid Build Coastguard Worker 243*cda5da8dSAndroid Build Coastguard Worker return DbfilenameShelf(filename, flag, protocol, writeback) 244