annotate flax/TreeDict.py @ 2405:69ca2b2fc133

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