Mercurial > code > home > repos > light9
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() |