Package ivs :: Package inout :: Module database
[hide private]
[frames] | no frames]

Source Code for Module ivs.inout.database

  1  # -*- coding: utf-8 -*- 
  2  """ 
  3  A simple interface to work with a database saved on the hard disk.  
  4   
  5  Author: Robin Lombaert 
  6   
  7  """ 
  8   
  9  import os 
 10  import cPickle 
 11  import time 
 12   
 13   
14 -class Database(dict):
15 16 ''' 17 A database class. 18 19 The class creates and manages a dictionary saved to the hard disk. 20 21 It functions as a python dictionary with the extra option of synchronizing 22 the database instance with the dictionary saved on the hard disk. 23 24 No changes will be made to the hard disk copy, unless Database.sync() is 25 called. 26 27 Note that changes made on a deeper level than the (key,value) pairs of the 28 Database (for instance in the case where value is a dict() type itself) 29 will not be automatically taken into account when calling the sync() 30 method. The key for which the value has been changed on a deeper level has 31 to be added to the Database.__changed list by calling addChangedKey(key) 32 manually. 33 34 Running the Database.sync() method will not read the database from the hard 35 disk if no changes were made or if changes were made on a deeper level 36 only. In order to get the most recent version of the Database, without 37 having made any changes, use the .read() method. Note that if changes were 38 made on a deeper level, they will be lost. 39 40 Example: 41 42 >>> import os 43 >>> from ivs.inout import database 44 >>> filename = 'mytest.db' 45 >>> db = database.Database(filename) 46 No database present at mytest.db. Creating a new one. 47 >>> db['test'] = 1 48 >>> db['test2'] = 'robin' 49 >>> db.sync() 50 >>> db2 = database.Database(filename) 51 >>> print db2['test'] 52 1 53 >>> print db2['test2'] 54 robin 55 >>> db2['test'] = 2 56 >>> db2.sync() 57 >>> db.sync() 58 >>> print db['test'] 59 1 60 >>> db.read() 61 >>> print db['test'] 62 2 63 >>> del db2['test2'] 64 >>> db2.sync() 65 >>> print db['test2'] 66 robin 67 >>> db.read() 68 >>> print db['test2'] 69 Traceback (most recent call last): 70 File "<stdin>", line 1, in <module> 71 KeyError: 'test2' 72 >>> test_dict = dict() 73 >>> db['test'] = test_dict 74 >>> db.sync() 75 >>> db2.read() 76 >>> print db2['test'] 77 {} 78 >>> db['test']['test'] = 1 79 >>> db.sync() 80 >>> db2.read() 81 >>> print db2['test'] 82 {} 83 >>> db.addChangedKey('test') 84 >>> db.sync() 85 >>> db2.read() 86 >>> print db2['test'] 87 {'test': 1} 88 >>> db.setdefault('test','defkey') 89 {'test': 1} 90 >>> db.setdefault('test3','defval') 91 'defval' 92 >>> db.sync() 93 >>> db2.read() 94 >>> print db2['test3'] 95 defval 96 >>> os.system('rm %s'%filename) 97 0 98 ''' 99 100
101 - def __init__(self,db_path):
102 103 ''' 104 Initializing a Database class. 105 106 Upon initialization, the class will read the dictionary saved at the 107 db_path given as a dictionary. 108 109 Note that cPickle is used to write and read these dictionaries. 110 111 If no database exists at db_path, a new dictionary will be created. 112 113 @param db_path: The path to the database on the hard disk. 114 @type db_path: string 115 116 ''' 117 118 super(Database, self).__init__() 119 self.db_path = db_path 120 self.read() 121 self.__changed = [] 122 self.__deleted = []
123 124 125
126 - def __delitem__(self,key):
127 128 ''' 129 Delete a key from the database. 130 131 This deletion is also done in the hard disk version of the database 132 when the sync() method is called. 133 134 This method can be called by using syntax: 135 del db[key] 136 137 @param key: a dict key that will be deleted from the Database in memory 138 @type key: a type valid for a dict key 139 140 ''' 141 142 self.__deleted.append(key) 143 return super(Database,self).__delitem__(key)
144 145 146 147
148 - def __setitem__(self,key,value):
149 150 ''' 151 Set a dict key with value. 152 153 This change is only added to the database saved on the hard disk when 154 the sync() method is called. 155 156 The key is added to the Database.__changed list. 157 158 This method can be called by using syntax: 159 db[key] = value 160 161 @param key: a dict key that will be added to the Database in memory 162 @type key: a type valid for a dict key 163 @param key: value of the key to be added 164 @type value: any 165 166 ''' 167 168 self.__changed.append(key) 169 return super(Database,self).__setitem__(key,value)
170 171 172
173 - def setdefault(self,key,*args):
174 175 ''' 176 Return key's value, if present. Otherwise add key with value default 177 and return. 178 179 Database.__changed is updated with the key if it is not present yet. 180 181 @param key: the key to be returned and/or added. 182 @type key: any valid dict() key 183 @param args: A default value added to the dict() if the key is not 184 present. If not specified, default defaults to None. 185 @type args: any type 186 @return: key's value or default 187 188 ''' 189 190 if not self.has_key(key): 191 self.__changed.append(key) 192 return super(Database,self).setdefault(key,*args)
193 194 195
196 - def pop(self,key,*args):
197 198 ''' 199 If database has key, remove it from the database and return it, else 200 return default. 201 202 If both default is not given and key is not in the database, a KeyError 203 is raised. 204 205 If deletion is successful, this change is only added to the database 206 saved on the hard disk when the sync() method is called. 207 208 The key is added to the Database.__deleted list, if present originally. 209 210 @param key: a dict key that will be removed from the Database in memory 211 @type key: a type valid for a dict key 212 @param args: value of the key to be returned if key not in Database 213 @type args: any 214 @return: value for key, or default 215 216 ''' 217 218 if self.has_key(key): 219 self.__deleted.append(key) 220 return super(Database,self).pop(key,*args)
221 222
223 - def popitem(self):
224 225 ''' 226 Remove and return an arbitrary (key, value) pair from the database. 227 228 A KeyError is raised if the database has an empty dictionary. 229 230 If removal is successful, this change is only added to the database 231 saved on the hard disk when the sync() method is called. 232 233 The removed key is added to the Database.__deleted list. 234 235 @return: (key, value) pair from Database 236 237 ''' 238 239 (key,value) = super(Database,self).popitem() 240 self.__deleted.append(key) 241 return (key,value)
242 243 244
245 - def update(self,*args,**kwargs):
246 247 ''' 248 249 Update the database with new entries, as with a dictionary. 250 251 This update is not synched to the hard disk! Instead Database.__changed 252 includes the changed keys so that the next sync will save these changes 253 to the hard disk. 254 255 @param args: A dictionary type object to update the Database. 256 @type args: dict() 257 @keyword kwargs: Any extra keywords are added as keys with their values. 258 @type kwargs: any type that is allowed as a dict key type. 259 260 ''' 261 262 self.__changed.extend(kwargs.keys()) 263 self.__changed.extend(args[0].keys()) 264 return super(Database,self).update(*args,**kwargs)
265 266 267
268 - def read(self):
269 270 ''' 271 Read the database from the hard disk. 272 273 Whenever called, the database in memory is updated with the version 274 saved on the hard disk. 275 276 Any changes made outside the session of this Database() instance will 277 be applied to the database in memory! 278 279 Any changes made to existing keys in current memory before calling 280 read() will be undone! Use sync() instead of read if you want to keep 281 current changes inside the session. 282 283 If no database is present at the path given to Database() upon 284 initialisation, a new Database is made by saving an empty dict() at the 285 requested location. 286 287 Reading and saving of the database is done by cPickle-ing the dict(). 288 289 ''' 290 291 try: 292 dbfile = open(self.db_path,'r') 293 while True: 294 try: 295 db = cPickle.load(dbfile) 296 break 297 except ValueError: 298 print 'Loading database failed: ValueError ~ insecure '+\ 299 'string pickle. Waiting 10 seconds and trying again.' 300 time.sleep(10) 301 dbfile.close() 302 self.clear() 303 super(Database,self).update(db) 304 except IOError: 305 print 'No database present at %s. Creating a new one.'%self.db_path 306 self.__save()
307 308 309
310 - def sync(self):
311 312 ''' 313 314 Update the database on the harddisk and in the memory. 315 316 The database is read anew, ie updated with the hard disk version to 317 account for any changes made by a different program. Next, the changes 318 made to the database in memory are applied, before saving the database 319 to the hard disk again. 320 321 Any items deleted from the database in memory will also be deleted from 322 the version saved on the hard disk! 323 324 The keys that are changed explicitly are all listed in self.__changed, 325 to which entries can be added manually using the addChangedKey method, 326 or automatically by calling .update(), .__setitem__() or .setdefault(). 327 328 ''' 329 330 if self.__changed or self.__deleted: 331 current_db = dict([(k,v) 332 for k,v in self.items() 333 if k in set(self.__changed)]) 334 self.read() 335 self.__deleted = list(set(self.__deleted)) 336 while self.__deleted: 337 try: 338 super(Database,self).__delitem__(self.__deleted.pop()) 339 except KeyError: 340 pass 341 super(Database,self).update(current_db) 342 self.__save() 343 self.__changed = []
344 345 346
347 - def __save(self):
348 349 ''' 350 351 Save a database. 352 353 Only called by Database() internally. Use sync() to save the Database 354 to the hard disk. 355 356 Reading and saving of the database is done by cPickle-ing the dict(). 357 358 ''' 359 360 dbfile = open(self.db_path,'w') 361 cPickle.dump(self,dbfile) 362 dbfile.close()
363 364 365
366 - def addChangedKey(self,key):
367 368 ''' 369 Add a key to the list of changed keys in the database. 370 371 This is useful if a change was made to an entry on a deeper level, 372 meaning that the __set__() method of Database() is not called directly. 373 374 If the key is not added to this list manually, it will not make it into 375 the database on the hard disk when calling the sync() method. 376 377 @param key: the key you want to include in the next sync() call. 378 @type key: string 379 380 ''' 381 382 self.__changed.append(key)
383 384 385
386 - def getDeletedKeys(self):
387 388 ''' 389 Return a list of all keys that have been deleted from the database in 390 memory. 391 392 @return: list of keys 393 @rtype: list 394 ''' 395 396 return self.__deleted
397 398 399
400 - def getChangedKeys(self):
401 402 ''' 403 Return a list of all keys that have been changed in the database in 404 memory. 405 406 @return: list of keys 407 @rtype: list 408 ''' 409 410 return self.__changed
411 412 if __name__ == "__main__": 413 import doctest 414 doctest.testmod() 415