Make things look nicer on MacOS
[ugit.git] / py / model.py
blob241f68f88980531e20e42b7791ffc244a3f6259f
1 #!/usr/bin/env python
2 import imp
3 from types import *
5 class Observable(object):
6 '''Handles subject/observer notifications.'''
7 def __init__(self, notify=True):
8 self.__observers = []
9 self.__notify = notify
10 def get_notify(self):
11 return self.__notify
12 def set_notify(self, notify=True):
13 self.__notify = notify
14 def add_observer(self,observer):
15 self.__observers.append(observer)
16 def notify_observers(self, *attr):
17 if not self.__notify: return
18 for observer in self.__observers:
19 observer.notify(*attr)
22 class Model(Observable):
23 '''Creates a generic model object with attributes specified
24 as a name:value dictionary.
26 get_name() and set_name(value) are created automatically
27 for any of the specified attributes.'''
29 def __init__(self, attributes = {}, defaults = {}, notify=True):
30 '''Initializes all attributes and default attribute
31 values. By default we do not call notify unless explicitly
32 told to do so.'''
34 Observable.__init__(self, notify)
36 for attr, value in attributes.iteritems():
37 setattr(self, attr, value)
39 for attr, value in defaults.iteritems():
40 if not hasattr(self, attr):
41 setattr(self, attr, value)
43 # For meta-programmability
44 self.__attributes = list(attributes.keys() + defaults.keys())
45 self.__list_attrs = {}
46 self.__object_attrs = {}
48 def set_list_attrs(self, list_attrs):
49 self.__list_attrs.update(list_attrs)
51 def set_object_attrs(self, obj_attrs):
52 self.__object_attrs.update(obj_attrs)
54 def getattr(self, attr):
55 return getattr(self, attr)
57 def get_attributes(self):
58 return self.__attributes
61 def __getattr__(self, attr):
62 '''Provides automatic get/set/add/append methods.'''
64 # Base case: we actually have this attribute
65 if attr in self.__dict__:
66 return getattr(self, attr)
68 # Check for the translated variant of the attr
69 realattr = self.__translate(attr, sep='')
70 if realattr in self.__dict__:
71 return getattr(self, realattr)
73 if realattr.startswith('get'):
74 realattr = self.__translate(attr, 'get')
75 return lambda: getattr(self, realattr)
77 elif realattr.startswith('set'):
78 realattr = self.__translate(attr, 'set')
79 return lambda(value): self.set_and_notify(realattr, value)
81 elif realattr.startswith('add'):
82 self.__array = self.__translate(attr, 'add')
83 return self.__append
85 elif realattr.startswith('append'):
86 self.__array = self.__translate(attr, 'append')
87 return self.__append
89 errmsg = "%s object has no attribute '%s'" \
90 % ( self.__class__, attr )
92 raise AttributeError, errmsg
94 def set(self, attr, value):
95 '''Sets a model attribute.'''
96 setattr(self, attr, value)
97 if attr not in self.__attributes:
98 self.__attributes.append(attr)
100 def set_and_notify(self, attr, value):
101 '''Sets an attribute and notifies observers.'''
102 self.set(attr, value)
103 self.notify_observers(attr)
105 def __append(self, *values):
106 '''Appends an arbitrary number of values to
107 an array atribute.'''
109 attr = self.__array
110 array = getattr(self, attr)
112 if array is None:
113 errmsg = "%s object has no attribute '%s'" \
114 % ( self.__class__, attr )
115 raise AttributeError, errmsg
117 for value in values:
118 array.append(value)
121 def __translate(self, attr, prefix='', sep='_'):
122 '''Translates an attribute name from the external name
123 used in methods to those used internally. The default
124 settings strip off '_' so that both get_foo() and getFoo()
125 are valid incantations.'''
127 return attr.lstrip(prefix).lstrip(sep).lower()
129 def __get_class(self, objspec):
130 '''Loads a class from a module and returns the class.'''
132 # str("module.submodule:ClassName")
133 ( modname, classname ) = objspec.split(':')
134 modfile = imp.find_module(modname)
135 module = imp.load_module(modname,
136 modfile[0], modfile[1], modfile[2])
138 if classname in module.__dict__:
139 cls = module.__dict__[classname]
140 else:
141 cls = Model
142 warning = 'WARNING: %s not found in %s\n' % (
143 modname, classname )
144 sys.stderr.write(warning)
146 modfile[0].close()
147 return cls
149 def save(self, filename):
150 import simplejson
151 file = open(filename, 'w')
152 simplejson.dump( self.to_dict(), file, indent=4 )
153 file.close()
155 def load(self, filename):
156 import simplejson
157 file = open(filename, 'r')
158 dict = simplejson.load(file)
159 file.close()
160 self.from_dict(dict)
162 def from_dict(self, model):
163 '''Import a complex model from a dictionary. The import/export
164 is clued as to nested Model-objects by setting the
165 __list_attrs or __object_attrs object specifications.'''
167 for attr,val in model.iteritems():
168 setattr(self, attr, self.__attr_from_dict(attr,val))
169 if attr not in self.__attributes:
170 self.__attributes.append(attr)
171 return self
173 def __attr_from_dict(self,attr,val):
175 # A list of Model-objects
176 if is_list(val):
177 if attr in self.__list_attrs:
178 # A list of Model-derived objects
179 listattr = []
180 objspec = self.__list_attrs[attr]
181 cls = self.__get_class(objspec)
182 for item in val:
183 listattr.append(cls().from_dict(item))
184 return listattr
186 # An attribute that maps to a Model-object
187 elif is_dict(val):
188 if attr in self.__object_attrs:
189 # "module.submodule:ClassName"
190 objectspec = self.__object_attrs[attr]
191 cls = self.__get_class(objectspec)
192 return cls().from_dict(val)
194 # Atoms and uninteresting hashes/dictionaries
195 return val
198 def to_dict(self):
199 '''Exports a model to a dictionary.
200 This simplifies serialization.'''
202 attrs = {}
203 for attr in self.__attributes:
204 attrs[attr] = self.__attr_to_dict(attr)
205 return attrs
207 def __attr_to_dict(self, attr):
208 item = getattr(self, attr)
209 return self.__item_to_dict(item)
211 def __item_to_dict(self, item):
213 if is_atom(item): return item
215 elif is_list(item):
216 newlist = []
217 for i in item:
218 newlist.append(self.__item_to_dict(i))
219 return newlist
221 elif is_dict(item):
222 newdict = {}
223 for k,v in item.iteritems():
224 newdict[k] = self.__item_to_dict(v)
225 return newdict
227 elif is_instance(item):
228 return item.to_dict()
230 else:
231 raise NotImplementedError, 'Unknown type:' + str(type(item))
234 __INDENT__ = -4 # Used by __str__
236 def __str__(self):
237 '''A convenient, recursively-defined stringification method.'''
239 Model.__INDENT__ += 4
241 strings = ['']
242 for attr in self.__dict__:
243 if attr.startswith('_'): continue
244 inner = " " * Model.__INDENT__ + attr + ": "
246 value = getattr(self, attr)
248 if type(value) == ListType:
250 indent = " " * (Model.__INDENT__ + 4)
251 strings.append(inner + "[")
252 for val in value:
253 stringval = indent + str(val)
254 strings.append (stringval)
256 indent = " " * Model.__INDENT__
257 strings.append(indent + "]")
259 else:
260 strings.append(inner + str(value))
262 Model.__INDENT__ -= 4
264 return "\n".join(strings)
267 def is_list(item): return type(item) is ListType or type(item) is TupleType
268 def is_dict(item): return type(item) is DictType
269 def is_atom(item):
270 return (
271 type(item) in StringTypes
272 or type(item) is BooleanType
273 or type(item) is IntType
274 or type(item) is LongType
275 or type(item) is FloatType
276 or type(item) is ComplexType
279 def is_instance(item):
280 return ( issubclass(item.__class__, Model)
281 or type(item) is InstanceType )