root/trunk/dabo/dPref.py

Revision 4715, 15.4 kB (checked in by ed, 1 week ago)

Changed the default for SQLite to not create the database file if it doesn't already exist. This setting is controlled by dabo.createDbFiles, which defaults to False.

Added dException.DBFileDoesNotExistException class. This is raised if you try to connect to an SQLite file that does not exist and dabo.createDbFiles is False. This exception can be caught in the calling routines and handled appropriately.

dConnection now takes an optional constructor parameter 'forceCreate'; if that is True (default=False), the db file will be created no matter what the system setting is.

dbSQLite.getConnection() takes that same parameter, and passes it to the connection constructor.

dPref, which is supposed to create the DaboPreferences?.db file no matter what, has been modified to pass forceCreate=True to the connection.

  • Property svn:eol-style set to native
Line 
1 # -*- coding: utf-8 -*-
2 import os
3 import warnings
4 import datetime
5 from decimal import Decimal
6 import dabo
7 from dabo.dLocalize import _
8 import dabo.lib.utils as utils
9
10 try:
11     from pysqlite2 import dbapi2 as sqlite
12 except ImportError:
13     try:
14         import sqlite3 as sqlite
15     except ImportError:
16         # pkm: We can't use the errorLog to warn of this problem, because errorLog
17         #      descends from dObject, which needs to load dPref.py first.
18         warnings.warn("Class dPref requires package 'pysqlite2'.")
19         #dabo.errorLog.write("This class requires SQLite")
20
21 # We don't want to deal with these as preferences.
22 regularAtts = ("AutoPersist", "__base__", "__bases__", "__basicsize__", "__call__",
23         "__cmp__", "_deletionCache", "__dictoffset__", "__flags__", "__itemsize__",
24         "__members__", "__methods__", "__mro__", "__name__", "__subclasses__",
25         "__weakrefoffset__", "_autoPersist", "_cache", "_cursor", "_cxn", "get",
26         "_getAttributeNames", "_key", "_noneType", "_parent", "_persistAll", "_typeDict", "mro")
27
28
29 class dPref(object):
30     """dPref is a class that is used to automatically manage preferences. It requires
31     SQLite in order to function; without that installed, you cannot use this class. It
32     automatically supports nesting of preferences; if you have a dPref object named
33     'basePref', and then issue the statement 'basePref.subPref.something=True', a
34     new dPref object named 'subPref' will be created, and can be referred to using
35     'basePref.subPref'.
36     
37     Normally you should specify the initial key for your prefs. This will ensure that
38     your preference names do not conflict with other dabo preferences. This is much like
39     the approach to modules in the Python namespace. Failure to specify a base
40     key would put all of your prefs into the 'root' namespace, where collisions can more
41     easily happen, and thus is not allowed.
42     
43     All preference assignments are automatically persisted to the database unless
44     the 'AutoPersist' property on this object or one of its 'ancestors' is set to False.
45     When that is False, you must call the persist() method manually, or your settings
46     will not be saved. Calling 'persist()' will write any values of that object and all of its
47     child objects to the database.
48     """
49     def __init__(self, key=None, crs=None, cxn=None, appName="Dabo"):
50         if key is None:
51             self._key = ""
52         else:
53             self._key = key
54         self._cache = {}
55         self._deletionCache = {}
56         self._autoPersist = True
57         # Do we save even without a base key? This should only
58         # be changed by framework tools designed to access the
59         # the preference database.
60         self._persistAll = False
61         super(dPref, self).__init__()
62         self._parent = None
63         self._noneType = type(None)
64         self._typeDict = {int: "int", float: "float", long: "long", str: "str", unicode: "unicode",
65                 bool: "bool", list: "list", tuple: "tuple", datetime.date: "date", dict: "dict",
66                 datetime.datetime: "datetime", Decimal: "decimal", self._noneType: "none"}
67         if crs is None:
68             prefdir = utils.getUserAppDataDirectory(appName)
69             if prefdir is None:
70                 # pkm: This happened to me on a webserver where the user is www-data who doesn't have
71                 #      a home directory. I actually don't care about preferences in this case but I
72                 #      wasn't able to set dApp.PreferenceManager to None unfortunately, so we'll just
73                 #      punt and put the preference db in the working directory (up to your webapp to
74                 #      chdir() accordingly)..
75                 prefdir = os.getcwd()
76             db = os.path.join(prefdir, "DaboPreferences.db")
77             self._cxn = dabo.db.dConnection(connectInfo={"DbType": "SQLite", "Database": db},
78                     forceCreate=True)
79             self._cursor = self._cxn.getDaboCursor()
80             self._cursor.IsPrefCursor = True
81             # Make sure that the table exists
82             if not  "daboprefs" in self._cursor.getTables():
83                 self._cursor.execute("create table daboprefs (ckey text not null, ctype text not null, cvalue text not null)")
84                 self._cursor.commitTransaction()
85         else:
86             self._cursor = crs
87             self._cxn = cxn
88        
89        
90     def __getattr__(self, att):
91         if att in regularAtts:
92             if self.__dict__.has_key(att):
93                 return self.__dict__[att]
94             else:
95                 return None
96         if self.__dict__.has_key(att):
97             ret = self.__dict__[att]
98         elif self._cache.has_key(att):
99             ret = self._cache[att]
100         else:
101             # See if it's in the database
102             key = self._getKey()
103             if key:
104                 param = "%s.%s" % (key, att)
105             else:
106                 param = att
107             crs = self._cursor
108             try:
109                 crs.execute("select ctype, cvalue from daboprefs where ckey = ? ", (param, ))
110                 rec = crs.getCurrentRecord()
111             except StandardError, e:
112                 print "QUERY ERR", e
113                 rec = {}
114             if rec:
115                 ret = self._decodeType(rec)
116             else:
117                 ret = dPref(crs=self._cursor, cxn = self._cxn)
118                 ret._parent = self
119                 ret._key = att
120             self._cache[att] = ret
121         return ret
122
123
124     def __setattr__(self, att, val):
125         if att in regularAtts:
126             super(dPref, self).__setattr__(att, val)
127             #self.__dict__[att] = val
128             return
129         persist = False
130         try:
131             curr = self._cache[att]
132             exists = True
133         except KeyError:
134             exists = False
135             persist = self.AutoPersist
136         if exists:
137             persist = (curr != val) and self.AutoPersist
138         if persist:
139             self._persist(att, val)
140         self._cache[att] = val
141    
142    
143     def get(self, att):
144         """If the specified name is a subkey, it is returned. If it is a value, the value is
145         returned. If it doesn't exist, a new subkey is created with that name.
146         """
147         return self.__getattr__(att)
148        
149    
150     def _getKey(self):
151         """The key is a concatenation of this object's name and the names of its
152         ancestors, separated with periods.
153         """
154         ret = self._key
155         if not ret:
156             ret = ""
157         else:
158             if self._parent is not None:
159                 ret = ".".join((self._parent._getKey(), ret))
160         return ret
161    
162    
163     def _encodeType(self, val, typ):
164         """Takes various value types and converts them to a string formats that
165         can be converted back when needed.
166         """
167         if typ in ("str", "unicode"):
168             ret = val
169         elif typ == "date":
170             ret = str((val.year, val.month, val.day))
171         elif typ == "datetime":
172             ret = str((val.year, val.month, val.day, val.hour, val.minute, val.second, val.microsecond))
173         elif typ == "decimal":
174             ret = str(val)
175         else:
176             ret = unicode(val)
177         return ret
178        
179    
180     def _decodeType(self, rec):
181         """Take a record containing a cvalue and ctype, and convert the type
182         as needed.
183         """
184         val = rec["cvalue"]
185         typ = rec["ctype"]
186         ret = None
187         if typ in ("str", "unicode"):
188             ret = val
189         elif typ == "int":
190             ret = int(val)
191         elif typ == "float":
192             ret = float(val)
193         elif typ == "long":
194             ret = long(val)
195         elif typ == "bool":
196             ret = (val == "True")
197         elif typ in ("list", "tuple", "dict"):
198             ret = eval(val)
199         elif typ == "date":
200             ret = eval("datetime.date%s" % val)
201         elif typ == "datetime":
202             ret = eval("datetime.datetime%s" % val)
203         elif typ == "decimal":
204             ret = Decimal(val)
205         elif typ == "none":
206             ret = None
207 #       if ret is None:
208 #           print "NONE", rec
209         return ret
210
211        
212     def _persist(self, att, val):
213         """Writes the value of the particular att to the database with the proper key."""
214         # Make sure that we have a valid key
215         baseKey = self._getKey()
216         if not baseKey:
217             if not self._persistAll:
218                 dabo.errorLog.write(_("No base key set; preference will not be persisted."))
219                 return
220             else:
221                 key = att
222         else:
223             key = "%s.%s" % (baseKey, att)
224         crs = self._cursor
225         try:
226             typ = self._typeDict[type(val)]
227         except KeyError:
228             dabo.errorLog.write(_("BAD TYPE: %s") % type(val))
229             typ = "?"
230         # Convert it to a string that can be properly converted back
231         val = self._encodeType(val, typ)
232
233         sql = "update daboprefs set ctype = ?, cvalue = ? where ckey = ? "
234         prm = (typ, val, key)
235         crs.execute(sql, prm)
236         # Use the dbapi-level 'rowcount' attribute to get the number
237         # of affected rows.
238         if not crs.rowcount:
239             sql = "insert into daboprefs (ckey, ctype, cvalue) values (?, ?, ?)"
240             prm = (key, typ, val)
241             crs.execute(sql, prm)
242         self._cursor.commitTransaction()
243    
244    
245     def persist(self):
246         """Manually save preferences to the database."""
247         for key, val in self._cache.items():
248             if isinstance(val, dPref):
249                 # Child pref; tell it to persist itself
250                 val.persist()
251             else:
252                 self._persist(key, val)
253         # Handle the cached deletions
254         for key in self._deletionCache.keys():
255             self._cursor.execute("delete from daboprefs where ckey like ? ", (key, ))
256         self._deletionCache = {}
257         self._cursor.commitTransaction()
258    
259    
260     def deletePref(self, att, nested=False):
261         """Deletes a particular preference from both the database
262         and the cache. If 'nested' is True, and the att is a node containing
263         sub-prefs, that node and all its children will be deleted.
264         """
265         basekey = self._getKey()
266         if basekey:
267             key = "%s.%s" % (basekey, att)
268         else:
269             key = att
270         crs = self._cursor
271         if nested:
272             key += "%"
273         if self._autoPersist:
274             if nested:
275                 crs.execute("delete from daboprefs where ckey like ? ", (key, ))
276             else:
277                 crs.execute("delete from daboprefs where ckey = ? ", (key, ))
278             if self._cache.has_key(att):
279                 del self._cache[att]
280         else:
281             self._deletionCache[key] = None
282             if self._cache.has_key(att):
283                 del self._cache[att]
284         self._cursor.commitTransaction()
285
286    
287     def deleteAllPrefs(self):
288         """Deletes all preferences for this object, and all sub-prefs as well."""
289         basekey = self._getKey()
290         if not basekey:
291             return
292         key = "%s.%%" % basekey
293         if self._autoPersist:
294             crs = self._cursor
295             crs.execute("delete from daboprefs where ckey like ? ", (key, ))
296             for key, val in self._cache.items():
297                 if isinstance(val, dPref):
298                     # In case there are any other references to it hanging around,
299                     # clear its cache.
300                     val.flushCache()
301             self._cache = {}       
302         else:
303             # Update the caches
304             self._cache = {}
305             self._deletionCache[key] = None
306         self._cursor.commitTransaction()
307
308    
309     def deleteByValue(self, val):
310         """Removes any preferences at or below this object whose value
311         matches the passed value.
312         """
313         crs = self._cursor
314         sql = """delete from daboprefs
315                 where ckey like ?
316                 and cvalue = ?"""
317         prm = ("%s%%" % self._getKey(), val)
318         crs.execute(sql, prm)
319         crs.commitTransaction()
320
321
322     def flushCache(self):
323         """Clear the cache, forcing fresh reads from the database."""
324         for key, val in self._cache.items():
325             if isinstance(val, dPref):
326                 val.flushCache()
327             else:
328                 del self._cache[key]
329     cancel = flushCache
330    
331    
332     def getPrefs(self, returnNested=False, key=None, asDataSet=False):
333         """Returns all the preferences set for this object. If returnNested is True,
334         returns any sub-preferences too.
335         """
336         crs = self._cursor
337         if key is None:
338             key = self._getKey()
339         elif key.startswith("."):
340             # It's relative to this key
341             key = ".".join((self._getKey(), key[1:]))
342         key = key.replace("_", r"\_")
343         param = "%(key)s%%" % locals()
344         sql = "select * from daboprefs where ckey like ? escape '\\' "
345         crs.execute(sql, (param, ))
346         ds = crs.getDataSet()
347         if not returnNested:
348             # Filter out all the results that are not first-level prefs
349             keylen = len(key)+1
350             ds = [rec for rec in ds
351                     if len(rec["ckey"][keylen:].split(".")) == 1]
352         if asDataSet:
353             return ds
354         ret = {}
355         for rec in ds:
356             ret[rec["ckey"]] = self._decodeType(rec)
357         return ret
358    
359    
360     def getPrefKeys(self, spec=None):
361         """Return a list of all preference keys for this key."""
362         crs = self._cursor
363         key = self._getKey()
364         if spec is not None:
365             key = ".".join((key, spec))
366         keylen = len(key) + 1
367         keydots = len(key.split("."))
368         sql = "select ckey from daboprefs where ckey like ?"
369         crs.execute(sql, ("%s.%%" % key, ))
370         rs = crs.getDataSet()
371         tmpDict = {}
372         for rec in rs:
373             tmpDict[rec["ckey"][keylen:keylen+len(rec["ckey"].split(".")[keydots])]] = None
374         ret = tmpDict.keys()
375         # Now add any cached entries
376         addl = [kk for kk in self._cache.keys()
377             if kk not in ret and kk.startswith(key) and not isinstance(kk, dPref)]
378         ret += addl
379         return ret
380
381
382     def hasKey(self, key):
383         """Provides a way to test for a key without automatically adding it."""
384         return key in self.getPrefKeys()
385
386
387     def getSubPrefKeys(self, spec=None):
388         """Return a list of all 'child' keys for this key."""
389         crs = self._cursor
390         key = self._getKey()
391         if spec is not None:
392             key = ".".join((key, spec))
393         keylen = len(key) + 1
394         keydots = len(key.split("."))
395         sql = "select ckey from daboprefs where ckey like ?"
396         crs.execute(sql, ("%s.%%" % key, ))
397         rs = crs.getDataSet()
398         retList = [rec["ckey"].split(".")[keydots] for rec in rs
399                 if len(rec["ckey"].split(".")) > 2]
400         tmp = {}
401         for itm in retList:
402             tmp[itm] = None
403         return tmp.keys()
404    
405    
406     def getValue(self, key):
407         """Given a key, returns the corresponding value, or a
408         dPref object if it exists as a sub key. If there is no match
409         for 'key', None is returned.
410         """
411         ret = self.__getattr__(key)
412         if isinstance(ret, dPref):
413             ret = None
414         return ret
415
416
417     def addKey(self, key, typ, val):
418         """Adds a new key to the base key."""
419         newTyp = self._typeDict[typ]
420         sql = "insert into daboprefs (ckey, ctype, cvalue) values (?, ?, ?)"
421         prm = (key, newTyp, val)
422         self._cursor.execute(sql, prm)
423         self._cursor.commitTransaction()
424    
425    
426     def setValue(self, key, val):
427         """Given a key and value, sets the preference to that value."""
428         self.__setattr__(key, val)
429    
430    
431     def getPrefTree(self, spec=None):
432         """Returns a tree-like series of nested preference keys."""
433         crs = self._cursor
434         key = self._getKey()
435         if spec is not None:
436             key = ".".join((key, spec))
437         keylen = len(key) + 1
438         keydots = len(key.split("."))
439         sql = "select ckey from daboprefs where ckey like ? order by ckey"
440         if key:
441             param = "%s.%%" % key
442         else:
443             param = "%"
444         crs.execute(sql, (param,))
445         rs = crs.getDataSet()
446         vs = [itm.values()[0] for itm in rs]
447
448         def uniqKeys(dct, val):
449             dct[val] = None
450        
451         def mkTree(vals):
452             ret = []
453             # Get all the first-tier keys
454             # get all the first level values
455             lev0 = [val.split(".", 1)  for val in vals]
456             lev0keys = [itm[0] for itm in lev0]
457             keys = {}
458             [uniqKeys(keys, itm[0]) for itm in lev0]
459             for key in keys:
460                 keylist = [key]
461                 try:
462                     kids = [itm[1] for itm in lev0
463                             if itm[0] == key]
464                 except IndexError:
465                     kids = None
466                 if kids:
467                     keylist.append(mkTree(kids))
468                 ret.append(keylist)
469             return ret
470        
471         return mkTree(vs)
472        
473    
474     # Property definitions start here
475     def _getAutoPersist(self):
476         ret = self._autoPersist
477         if ret and self._parent is not None:
478             # Make sure all parents are also auto-persist
479             ret = ret and self._parent.AutoPersist
480         return ret
481
482     def _setAutoPersist(self, val):
483         self._autoPersist = val
484
485
486     def _getFullPath(self):
487         return self._getKey()
488
489
490     AutoPersist = property(_getAutoPersist, _setAutoPersist, None,
491             _("Do property assignments automatically save themselves? Default=True  (bool)"))
492    
493     FullPath = property(_getFullPath, None, None,
494             _("""The fully-qualified path to this object, consisting of all ancestor
495             names along with this name, joined by periods (read-only) (str)"""))
496    
497
498
499            
500        
501
502 if __name__ == "__main__":
503     a = dPref(key="TESTING")
504     a.testValue = "First Level"
505     a.anotherValue = "Another First"
506     a.b.testValue = "Second Level"
507     a.b.anotherValue = "Another Second"
508     a.b.c.CrazyMan = "Ed"
509
510     print a.getPrefKeys()
511     print a.b.getPrefKeys()
512     print a.b.c.getPrefKeys()
513
514     a.deletePref("b.c")
515     print a.getPrefs(True)
516     a.deletePref("b.c", True)
517     print a.getPrefs(True)
518    
519     print "Just 'a' prefs:"
520     print a.getPrefs()
521     print
522     print "'a' prefs and all sub-prefs:"
523     print a.getPrefs(True)
524
525     zz=a.getSubPrefKeys()
526     print "SUB PREFS", zz
527     zz = a.getPrefKeys()
528     print "PREF KEYS", zz
529    
530     a.AutoPersist = False
531     a.b.shouldntStay = "XXXXXXXXXX"
532    
533     print "BEFORE FLUSH", a.b.getPrefKeys()
534     a.flushCache()
535     print "AFTER FLUSH", a.b.getPrefKeys()
536    
537     print "DELETE ONE"
538     a.deletePref("anotherValue")
539     print a.getPrefs(True)
540     print "DELETE ALL"
541     a.deleteAllPrefs()