Add internationalization support to ugit
[ugit.git] / ugitlibs / model.py
blob86d12266bb007eb9025a40ea939aa89885f96c60
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 if observer not in self.__observers:
16 self.__observers.append(observer)
17 def remove_observer(self, observer):
18 if observer in self.__observers:
19 self.__observers.remove(observer)
20 def notify_observers(self, *attr):
21 if not self.__notify: return
22 for observer in self.__observers:
23 observer.notify(*attr)
25 class Model(Observable):
26 '''Creates a generic model object with attributes specified
27 as a name:value dictionary.
29 get_name() and set_name(value) are created automatically
30 for any of the specified attributes.'''
32 def __init__(self, attributes = {}, defaults = {}, notify=True):
33 '''Initializes all attributes and default attribute
34 values. By default we do not call notify unless explicitly
35 told to do so.'''
37 Observable.__init__(self, notify)
39 for attr, value in attributes.iteritems():
40 setattr(self, attr, value)
42 for attr, value in defaults.iteritems():
43 if not hasattr(self, attr):
44 setattr(self, attr, value)
46 # For meta-programmability
47 self.__attributes = list(attributes.keys() + defaults.keys())
48 self.__list_attrs = {}
49 self.__object_attrs = {}
51 def clone(self):
52 return self.__class__().from_dict(self.to_dict())
54 def set_list_attrs(self, list_attrs):
55 self.__list_attrs.update(list_attrs)
57 def set_object_attrs(self, obj_attrs):
58 self.__object_attrs.update(obj_attrs)
60 def getattr(self, attr):
61 return getattr(self, attr)
63 def get_attributes(self):
64 return self.__attributes
67 def __getattr__(self, attr):
68 '''Provides automatic get/set/add/append methods.'''
70 # Base case: we actually have this attribute
71 if attr in self.__dict__:
72 return getattr(self, attr)
74 # Check for the translated variant of the attr
75 realattr = self.__translate(attr, sep='')
76 if realattr in self.__dict__:
77 return getattr(self, realattr)
79 if realattr.startswith('get'):
80 realattr = self.__translate(attr, 'get')
81 return lambda: getattr(self, realattr)
83 elif realattr.startswith('set'):
84 realattr = self.__translate(attr, 'set')
85 return lambda(value): self.set(realattr, value)
87 elif realattr.startswith('add'):
88 self.__array = self.__translate(attr, 'add')
89 return self.__append
91 elif realattr.startswith('append'):
92 self.__array = self.__translate(attr, 'append')
93 return self.__append
95 errmsg = "%s object has no attribute '%s'" \
96 %( self.__class__, attr )
98 raise AttributeError, errmsg
100 def set(self, attr, value, notify=True):
101 '''Sets a model attribute.'''
102 setattr(self, attr, value)
103 if attr not in self.__attributes:
104 self.__attributes.append(attr)
105 if notify: self.notify_observers(attr)
107 def __append(self, *values):
108 '''Appends an arbitrary number of values to
109 an array atribute.'''
111 attr = self.__array
112 array = getattr(self, attr)
114 if array is None:
115 errmsg = "%s object has no attribute '%s'" \
116 %( self.__class__, attr )
117 raise AttributeError, errmsg
119 for value in values:
120 array.append(value)
123 def __translate(self, attr, prefix='', sep='_'):
124 '''Translates an attribute name from the external name
125 used in methods to those used internally. The default
126 settings strip off '_' so that both get_foo() and getFoo()
127 are valid incantations.'''
129 return attr.lstrip(prefix).lstrip(sep).lower()
131 def __get_class(self, objspec):
132 '''Loads a class from a module and returns the class.'''
134 # str("module.submodule:ClassName")
135 ( modname, classname ) = objspec.split(':')
136 modfile = imp.find_module(modname)
137 module = imp.load_module(modname,
138 modfile[0], modfile[1], modfile[2])
140 if classname in module.__dict__:
141 cls = module.__dict__[classname]
142 else:
143 cls = Model
144 warning = 'WARNING: %s not found in %s\n' %(
145 modname, classname )
146 sys.stderr.write(warning)
148 modfile[0].close()
149 return cls
151 def save(self, filename):
152 import simplejson
153 file = open(filename, 'w')
154 simplejson.dump( self.to_dict(), file, indent=4 )
155 file.close()
157 def load(self, filename):
158 import simplejson
159 file = open(filename, 'r')
160 dict = simplejson.load(file)
161 file.close()
162 self.from_dict(dict)
164 def from_dict(self, model):
165 '''Import a complex model from a dictionary. The import/export
166 is clued as to nested Model-objects by setting the
167 __list_attrs or __object_attrs object specifications.'''
169 for attr,val in model.iteritems():
170 setattr(self, attr, self.__attr_from_dict(attr,val))
171 if attr not in self.__attributes:
172 self.__attributes.append(attr)
173 return self
175 def __attr_from_dict(self,attr,val):
177 # A list of Model-objects
178 if is_list(val):
179 if attr in self.__list_attrs:
180 # A list of Model-derived objects
181 listattr = []
182 objspec = self.__list_attrs[attr]
183 cls = self.__get_class(objspec)
184 for item in val:
185 listattr.append(cls().from_dict(item))
186 return listattr
188 # An attribute that maps to a Model-object
189 elif is_dict(val):
190 if attr in self.__object_attrs:
191 # "module.submodule:ClassName"
192 objectspec = self.__object_attrs[attr]
193 cls = self.__get_class(objectspec)
194 return cls().from_dict(val)
196 # Atoms and uninteresting hashes/dictionaries
197 return val
200 def to_dict(self):
201 '''Exports a model to a dictionary.
202 This simplifies serialization.'''
204 attrs = {}
205 for attr in self.__attributes:
206 attrs[attr] = self.__attr_to_dict(attr)
207 return attrs
209 def __attr_to_dict(self, attr):
210 item = getattr(self, attr)
211 return self.__item_to_dict(item)
213 def __item_to_dict(self, item):
215 if is_atom(item): return item
217 elif is_list(item):
218 newlist = []
219 for i in item:
220 newlist.append(self.__item_to_dict(i))
221 return newlist
223 elif is_dict(item):
224 newdict = {}
225 for k,v in item.iteritems():
226 newdict[k] = self.__item_to_dict(v)
227 return newdict
229 elif is_instance(item):
230 return item.to_dict()
232 else:
233 raise NotImplementedError, 'Unknown type:' + str(type(item))
236 __INDENT__ = -4 # Used by __str__
238 def __str__(self):
239 '''A convenient, recursively-defined stringification method.'''
241 Model.__INDENT__ += 4
243 strings = ['']
244 for attr in self.__dict__:
245 if attr.startswith('_'): continue
246 inner = " " * Model.__INDENT__ + attr + ": "
248 value = getattr(self, attr)
250 if type(value) == ListType:
252 indent = " " *(Model.__INDENT__ + 4)
253 strings.append(inner + "[")
254 for val in value:
255 stringval = indent + str(val)
256 strings.append(stringval)
258 indent = " " * Model.__INDENT__
259 strings.append(indent + "]")
261 else:
262 strings.append(inner + str(value))
264 Model.__INDENT__ -= 4
266 return "\n".join(strings)
269 def is_dict(item):
270 return type(item) is DictType
271 def is_list(item):
272 return type(item) is ListType or type(item) is TupleType
273 def is_atom(item):
274 return(type(item) in StringTypes
275 or type(item) is BooleanType
276 or type(item) is IntType
277 or type(item) is LongType
278 or type(item) is FloatType
279 or type(item) is ComplexType)
281 def is_instance(item):
282 return( issubclass(item.__class__, Model)
283 or type(item) is InstanceType )