annotate flax/TreeDict.py @ 2321:ed0db55f604c

use undefined for 'nothing selected' insetad of null
author drewp@bigasterisk.com
date Thu, 01 Jun 2023 14:20:40 -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()