Package Pyblio :: Package Stores :: Module filestore
[hide private]
[frames] | no frames]

Source Code for Module Pyblio.Stores.filestore

  1  # This file is part of pybliographer 
  2  #  
  3  # Copyright (C) 1998-2006 Frederic GOBRY 
  4  # Email : gobry@pybliographer.org 
  5  #           
  6  # This program is free software; you can redistribute it and/or 
  7  # modify it under the terms of the GNU General Public License 
  8  # as published by the Free Software Foundation; either version 2  
  9  # of the License, or (at your option) any later version. 
 10  #    
 11  # This program is distributed in the hope that it will be useful, 
 12  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
 13  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 14  # GNU General Public License for more details.  
 15  #  
 16  # You should have received a copy of the GNU General Public License 
 17  # along with this program; if not, write to the Free Software 
 18  # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA. 
 19  #  
 20   
 21  """ 
 22  Provides an in-memory store, which can read and save the database in 
 23  Pyblio's XML format. 
 24   
 25  This store is useful for relatively small databases (up to a few 
 26  thousand entries) and that are processed in batch once for instance, 
 27  as the reading and writing can be slow. 
 28  """ 
 29  import weakref 
 30  from gettext import gettext as _ 
 31   
 32  import os, copy, string 
 33   
 34  from Pyblio import Store, Callback, Attribute, Exceptions, Tools, Query, Sort 
 35  from Pyblio.Stores import resultset 
 36   
 37  from Pyblio.Arrays import KeyArray, match_arrays 
 38   
39 -class RODict(Callback.Publisher):
40 """ Read-only dictionnary """ 41
42 - def __init__ (self, _dict):
43 Callback.Publisher.__init__ (self) 44 self._db = _dict 45 return
46
47 - def view(self, criterion):
48 return resultset.View(self, criterion)
49
50 - def itervalues (self):
51 return self._db.itervalues ()
52
53 - def iteritems (self):
54 return self._db.iteritems ()
55
56 - def iterkeys (self):
57 return self._db.iterkeys ()
58 59 __iter__ = iterkeys 60
61 - def __len__ (self):
62 return len(self._db)
63
64 - def _forward (self, * args):
65 """ forward messages. the message name is passed last """ 66 args, msg = args [:-1], args [-1] 67 return apply (self.emit, (msg,) + args)
68 69
70 -class ResultSetStore(dict, Store.ResultSetStore):
71 - def __init__ (self, db):
72 self._db = weakref.ref(db) 73 self._id = 1 74 return
75
76 - def new(self, rsid=None):
77 """ Create an empty result set """ 78 db = self._db() 79 assert db is not None 80 (self._id, rsid) = Tools.id_make(self._id, rsid) 81 # a result set keeps a strong reference on the database, as it 82 # accesses its content pretty naturally 83 rs = resultset.ResultSet(rsid, db) 84 return rs
85
86 - def __iter__ (self):
87 return self.itervalues()
88
89 - def update(self, result_set):
90 self[result_set.id] = result_set
91 92 # -------------------------------------------------- 93
94 -class Database (Query.Queryable, Store.Database, Callback.Publisher):
95
96 - def __init__ (self, schema = None, file = None, 97 create = False):
98 99 Callback.Publisher.__init__ (self) 100 101 self._dict = {} 102 self._rodict = RODict (self._dict) 103 104 self.register('add-item', self._rodict._forward, 'add-item') 105 self.register('delete-item', self._rodict._forward, 'delete-item') 106 self.register('update-item', self._rodict._forward, 'update-item') 107 108 self.file = file 109 110 self.schema = schema 111 112 self.header = None 113 self.rs = ResultSetStore (self) 114 115 self._id = 1 116 self._indexed = False 117 118 if not create: 119 try: 120 self.xmlread(open(file)) 121 122 except IOError, msg: 123 raise Store.StoreError(_("cannot open database: %s") % msg) 124 125 return
126
127 - def _entries_get(self):
128 """ Return the result set that contains all the entries. """ 129 130 return self._rodict
131 132 entries = property(_entries_get, None) 133 134
135 - def add(self, record, key = None):
136 """ Insert a new entry in the database. 137 138 New entries MUST be added with this method, not via an update 139 with a hand-made Key. 140 141 key is only useful for importing an existing database, by 142 proposing a key choice. 143 """ 144 145 self._id, key = Tools.id_make (self._id, key) 146 147 key = Store.Key (key) 148 149 assert not self.has_key (key), \ 150 _("a duplicate key has been generated: %d") % key 151 152 record = copy.copy (record) 153 record.key = key 154 155 record = self.validate (record) 156 157 self._dict [key] = record 158 159 if self._indexed: 160 self._idxadd(key, record) 161 162 self.emit ('add-item', key) 163 164 return key
165 166
167 - def __delitem__(self, k):
168 169 del self._dict [k] 170 self.emit ('delete-item', k) 171 172 if self._indexed: 173 self._idxdel(k) 174 return
175 176
177 - def has_key(self, k):
178 return self._dict.has_key(k)
179 180
181 - def __setitem__ (self, key, value):
182 183 # Ensure the key is not added, only updated. 184 assert self.has_key (key), \ 185 _("use self.add () to add a new entry") 186 187 value = copy.deepcopy (value) 188 value.key = key 189 190 value = self.validate (value) 191 192 self._dict [key] = value 193 194 if self._indexed: 195 self._idxdel(key) 196 self._idxadd(key, value) 197 198 self.emit ('update-item', key) 199 return
200 201
202 - def __getitem__ (self, key):
203 return self._dict [key]
204 205
206 - def save(self):
207 208 if self.file is None: 209 return 210 211 try: 212 os.unlink (self.file + '.bak') 213 except OSError: 214 pass 215 216 if os.path.exists (self.file): 217 os.rename (self.file, self.file + '.bak') 218 219 fd = open (self.file, 'w') 220 self.xmlwrite (fd) 221 fd.close () 222 223 return
224
225 - def _idxadd(self, key, val):
226 227 for attribs in val.values(): 228 for attrib in attribs: 229 230 for idx in attrib.index(): 231 self._idx_b.setdefault(key, {})[idx] = True 232 233 try: 234 self._idx_f[idx].add(key) 235 except KeyError: 236 a = KeyArray() 237 a.add(key) 238 239 self._idx_f[idx] = a 240 return
241
242 - def _idxdel(self, key):
243 244 try: 245 ws = self._idx_b[key] 246 247 except KeyError: 248 return 249 250 del self._idx_b[key] 251 252 for w in ws: 253 del self._idx_f[w][key] 254 255 return
256
257 - def index(self):
258 """ Turn on indexing of the db content. """ 259 260 if self._indexed: 261 return 262 263 self._idx_f = {} 264 self._idx_b = {} 265 266 for key, rec in self.entries.iteritems(): 267 self._idxadd(key, rec) 268 269 self._indexed = True 270 return
271
272 - def _q_anyword(self, query):
273 if self._indexed: 274 word = query.word.lower() 275 276 try: 277 return self._idx_f[word] 278 except KeyError: 279 return KeyArray() 280 281 return Query.Queryable._q_anyword(self, query)
282
283 -def dbdestroy(path, nobackup=False):
284 os.unlink(path) 285 if nobackup: 286 try: 287 os.unlink (path + '.bak') 288 except OSError: 289 pass 290 return
291
292 -def dbcreate(path, schema, args={}):
293 # Ensure we are the ones creating the file 294 try: 295 fd = os.open(path, os.O_CREAT|os.O_EXCL|os.O_WRONLY, 0666) 296 except OSError, msg: 297 raise Store.StoreError (_("cannot create database '%s': %s") % ( 298 path, msg)) 299 os.close(fd) 300 db = Database (schema=schema, file=path, create=True) 301 db.save () 302 return db
303
304 -def dbopen(path, args={}):
305 return Database(file=path)
306
307 -def dbimport(target, source, args={}):
308 db = Database(file=source) 309 db.file = target 310 return db
311 312 description = _("Flat XML file storage") 313