comparison flax/TreeDict.py @ 0:45b12307c695

Initial revision
author drewp
date Wed, 03 Jul 2002 09:37:57 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:45b12307c695
1 """Persistent Tree Dictionaries
2
3 Incidentally, this is also the Hiss Preferences System. However, PTD is
4 expected to be usable outside of Hiss."""
5
6 __author__ = "David McClosky <dmcc@bigasterisk.com>, " + \
7 "Drew Perttula <drewp@bigasterisk.com>"
8 __cvsid__ = "$Id: TreeDict.py,v 1.1 2003/07/06 08:33:06 dmcc Exp $"
9 __version__ = "$Revision: 1.1 $"[11:-2]
10
11 try:
12 # use gnosis's XML pickler if available
13 import gnosis.xml.pickle as pickle
14
15 # this hack is needed or else xml.pickle will not be able to restore
16 # methods (they will be restored as mysterious classes which contain
17 # the same attributes but no methods,
18 # gnosis.xml.pickle.util._util.originalclassname, to be specific)
19 # the important thing to get from this comment is that we cannot properly
20 # pickle classes without them being "assigned" into gnosis.xml.pickle.
21 # i hope this gets fixed in a later version of pickle.
22 except ImportError:
23 # fallback to standard library pickle
24 import pickle
25
26 def allow_class_to_be_pickled(class_obj):
27 """to be documented"""
28 name = class_obj.__name__
29 setattr(pickle, name, class_obj)
30
31 class TreeDict(dict):
32 """TreeDict is the workhorse for the preferences system. It allows
33 for simple creation and access of preference trees. It could be
34 used to store anything, though."""
35 def __getattr__(self, attr):
36 """Gets an attribute/item, but with a twist. If not present, the
37 attribute will be created, with the value set to a new TreeDict."""
38 if attr.startswith('__'): # if it's special, we let dict handle it
39 return self.dictgetattr(attr)
40 if attr in self:
41 return dict.__getitem__(self, attr)
42 else:
43 newtree = self.__class__()
44 self.set_parent_attrs(newtree, attr) # record ourselves as the
45 # parent
46 dict.__setitem__(self, attr, newtree)
47 return newtree
48 def __setattr__(self, attr, newval):
49 """Sets an attribute/item to a new value."""
50 if attr.startswith('__'): # if it's special, we let dict handle it
51 return dict.__setattr__(self, attr, newval)
52 else:
53 oldval = self[attr] or None
54 dict.__setitem__(self, attr, newval)
55 if isinstance(newval, self.__class__):
56 self.set_parent_attrs(newval, attr)
57 self.changed_callback(attr, oldval, newval)
58 def __delattr__(self, attr):
59 """Deletes an attribute/item"""
60 if attr.startswith('__'): # if it's special, we let dict handle it
61 return dict.__delattr__(self, attr)
62 else: # otherwise, they meant a normal attribute/item
63 dict.__delitem__(self, attr)
64
65 # attr and item access are now the same
66 __getitem__ = __getattr__
67 __setitem__ = __setattr__
68 __delitem__ = __delattr__
69
70 # Original getattr/setattr for storing special attributes
71 def dictgetattr(self, attr):
72 """dict's original __getattribute__ method. This is useful for
73 storing bookkeeping information (like parents) which shouldn't
74 be persistent."""
75 return dict.__getattribute__(self, attr)
76 def dictsetattr(self, attr, newval):
77 """dict's original __setattr__ method. This is useful for
78 storing bookkeeping information (like parents) which shouldn't
79 be persistent."""
80 return dict.__setattr__(self, attr, newval)
81 def set_parent_attrs(self, newtree, key):
82 """Set ourselves as the parent to a new key."""
83 # record that we are the parent of this new object
84 newtree.dictsetattr('__parent', self)
85
86 # we store the key that newtree is in
87 newtree.dictsetattr('__parent_key', key)
88 def get_path(self):
89 """Returns the path to this TreeDict."""
90 try:
91 parent = self.dictgetattr('__parent')
92 key = self.dictgetattr('__parent_key')
93 return parent.get_path() + [key]
94 except AttributeError:
95 return []
96
97 def changed_callback(self, attr, oldval, newval):
98 """Called whenever an attribute is changed. It will be called with
99 three arguments: the attribute that changed, the previous value of
100 the attribute, and the newvalue of the attribute.
101 If the attribute didn't exist before, the old value will be None.
102
103 This should be overridden by subclasses of TreeDict."""
104
105 # Tree operations
106 def tree_update(self, othertree):
107 """Recursive update(). All keys from the othertree are copied over.
108 If both this tree and the other tree have a key which is a TreeDict,
109 we will recurse into it."""
110 for key in othertree: # if the key is in the other tree, merge it into
111 # ours
112 if key in self and isinstance(self[key], self.__class__):
113 self[key].tree_update(othertree[key])
114 else: # copy othertree's branch to ours
115 self[key] = othertree[key]
116
117 # Loading and saving
118 def load(self, filename, clobber=1):
119 """Combine this TreeDict with the TreeDict previously save()d
120 to a file. If clobber=1, this tree will be clear()ed before
121 combining (this is the default)."""
122 newprefs = pickle.load(file(filename))
123 if clobber: self.clear()
124 self.tree_update(newprefs)
125 return self
126 def save(self, filename):
127 """Save this TreeDict to a file. You can later restore the TreeDict
128 by creating a TreeDict and using the load() method:
129
130 treedict.save("tree.xml")
131 newtreedict = TreeDict()
132 newtreedict.load("tree.xml")"""
133 pickle.dump(self, file(filename, 'w'))
134
135 def pprint(self, depth=0):
136 "A simple pretty printing method, useful for debugging"
137 for key in self:
138 print "%s%s =" % ('\t' * depth, key),
139 val = self[key]
140 if isinstance(val, self.__class__):
141 print
142 val.pprint(depth + 1)
143 else:
144 print repr(self[key])
145
146 allow_class_to_be_pickled(TreeDict)
147
148 if __name__ == "__main__":
149 # We subclass TreeDict here so we can demonstrate how to override
150 # changed_callback.
151 RealTreeDict = TreeDict
152 class TreeDict(RealTreeDict):
153 def changed_callback(self, attr, oldval, newval):
154 fullpath = self.get_path() + [attr]
155
156 if 0: # more information
157 print 'changed_callback %s: %s -> %s' % ('.'.join(fullpath),
158 oldval, newval)
159 else: # output looks like the Python code that built the tree
160 print 'changed_callback %s = %r' % ('.'.join(fullpath),
161 newval)
162
163 # store our new class so we can pickle :(
164 # maybe we give someone a list of classes that we use, and it handles
165 # the rest. or, we just record a list of classes seen using
166 # setattr callbacks.
167 allow_class_to_be_pickled(TreeDict)
168
169 True = 1
170 False = 0
171
172 defaults_tree = TreeDict()
173 defaults_tree.auto_scroll_to_bottom = True
174 defaults_tree.aging.enabled = True
175 defaults_tree.aging.fade_exponent = 3
176 defaults_tree.aging.user_colors = TreeDict({'user1' : 'color1',
177 'user2' : 'color2'})
178
179 import time
180 defaults_tree.current_time = time.asctime()
181
182 # on disk
183 new_tree = TreeDict()
184 new_tree.some_extra_pref = "hi mom"
185 new_tree.auto_scroll_to_bottom = False
186 new_tree.aging.user_colors.user1 = 'green'
187
188 defaults_tree.tree_update(new_tree)
189 defaults_tree.pprint()
190
191 # test load / save
192 print "---"
193 defaults_tree.save("persistence_test.xml")
194 loaded_tree = TreeDict().load("persistence_test.xml")
195 loaded_tree.pprint()