views: add a bookmarks view
[git-cola.git] / ugit / model.py
blob5295241f8bee5514225390633aba62093ef4872e
1 #!/usr/bin/env python
2 import os
3 import imp
4 from cStringIO import StringIO
5 from types import DictType
6 from types import ListType
7 from types import TupleType
8 from types import StringTypes
9 from types import BooleanType
10 from types import IntType
11 from types import LongType
12 from types import FloatType
13 from types import ComplexType
14 from types import InstanceType
16 class Observable(object):
17 '''Handles subject/observer notifications.'''
18 def __init__(self,*args,**kwargs):
19 self.__observers = []
20 self.__notify = True
21 def get_notify(self):
22 return self.__notify
23 def set_notify(self, notify=True):
24 self.__notify = notify
25 def add_observer(self, observer):
26 if observer not in self.__observers:
27 self.__observers.append(observer)
28 def remove_observer(self, observer):
29 if observer in self.__observers:
30 self.__observers.remove(observer)
31 def notify_observers(self, *param):
32 if not self.__notify: return
33 for observer in self.__observers:
34 observer.notify(*param)
36 class Model(Observable):
37 '''Creates a generic model object with params specified
38 as a name:value dictionary.
40 get_name() and set_name(value) are created automatically
41 for any of the parameters specified in the **kwargs'''
43 def __init__(self, *args, **kwargs):
44 Observable.__init__(self)
45 self.__params = []
46 self.__list_params = {}
47 self.__object_params = {}
48 # For meta-programmability
49 self.from_dict(kwargs)
50 self.init()
52 # for subclasses
53 def init(self): pass
55 def create(self,**kwargs):
56 return self.from_dict(kwargs)
58 def get_param_names(self):
59 return tuple(self.__params)
61 def notify_all(self):
62 self.notify_observers(*self.get_param_names())
64 def clone(self, *args, **kwargs):
65 return self.__class__(*args, **kwargs).from_dict(self.to_dict())
67 def set_list_params(self, **list_params):
68 self.__list_params.update(list_params)
70 def set_object_params(self, **obj_params):
71 self.__object_params.update(obj_params)
73 def has_param(self,param):
74 return param in self.__params
76 def get_param(self,param):
77 return getattr(self, param)
79 def __getattr__(self, param):
80 '''Provides automatic get/set/add/append methods.'''
82 # Base case: we actually have this param
83 if param in self.__dict__:
84 return getattr(self, param)
86 # Check for the translated variant of the param
87 realparam = self.__translate(param, sep='')
88 if realparam in self.__dict__:
89 return getattr(self, realparam)
91 if realparam.startswith('get'):
92 param = self.__translate(param, 'get')
93 return lambda: getattr(self, param)
95 elif realparam.startswith('set'):
96 param = self.__translate(param, 'set')
97 return lambda v: self.set_param(param, v,
98 check_params=True)
100 elif (realparam.startswith('add')
101 or realparam.startswith('append')):
103 if realparam.startswith('add'):
104 param = self.__translate(realparam, 'add')
105 else:
106 param = self.__translate(realparam, 'append')
108 def array_append(*values):
109 array = getattr(self, param)
110 if array is None:
111 classnm = self.__class__.__name__
112 errmsg = ("%s object has no array named '%s'"
113 %( classnm, param ))
114 raise AttributeError(errmsg)
115 else:
116 array.extend(values)
117 # Cache the function definition
118 setattr(self, realparam, array_append)
119 return array_append
121 errmsg = ("%s object has no parameter '%s'"
122 % (self.__class__.__name__, param))
124 raise AttributeError(errmsg)
126 def set_param(self, param, value, notify=True, check_params=False):
127 '''Set param with optional notification and validity checks.'''
129 param = param.lower()
130 if check_params and param not in self.__params:
131 raise Exception("Parameter '%s' not available for %s"
132 % (param, self.__class__.__name__))
133 elif param not in self.__params:
134 self.__params.append(param)
136 setattr(self, param, value)
137 if notify: self.notify_observers(param)
139 def copy_params(self, model, params=None):
140 if params is None:
141 params = self.get_param_names()
142 for param in params:
143 self.set_param(param, model.get_param(param))
145 def __translate(self, param, prefix='', sep='_'):
146 '''Translates an param name from the external name
147 used in methods to those used internally. The default
148 settings strip off '_' so that both get_foo() and getFoo()
149 are valid incantations.'''
150 return param.lstrip(prefix).lstrip(sep).lower()
152 def save(self, filename):
153 try:
154 import simplejson
155 except ImportError:
156 print "Unable to save."
157 print "You do not have simplejson installed."
158 print "try: sudo apt-get install simplejson"
159 return
160 file = open(filename, 'w')
161 simplejson.dump(self.to_dict(), file, indent=4)
162 file.close()
164 def load(self, filename):
165 try:
166 import simplejson
167 except ImportError:
168 print "Unable to load."
169 print "You do not have simplejson installed."
170 print "try: sudo apt-get install simplejson"
171 return
172 file = open(filename, 'r')
173 dict = simplejson.load(file)
174 file.close()
175 self.from_dict(dict)
177 def from_dict(self, source_dict):
178 '''Import a complex model from a dictionary. The import/export
179 is clued as to nested Model-objects by setting the
180 __list_params or __object_params object specifications.'''
182 if '__META__' in source_dict:
183 metadict = source_dict['__META__']
184 for param, clstr in metadict.iteritems():
185 cls = Model.str_to_class(clstr)
186 self.set_object_param(param, cls)
188 for param, val in source_dict.iteritems():
189 self.set_param(
190 param,
191 self.__param_from_dict(param, val),
192 notify=False)
193 self.__params.sort()
194 return self
196 def __param_from_dict(self,param,val):
197 # A list of Model-objects
198 if is_list(val):
199 if param in self.__list_params:
200 # A list of Model-derived objects
201 listparam = []
202 cls = self.__list_params[param]
203 for item in val:
204 listparam.append(cls().from_dict(item))
205 return listparam
207 # A param that maps to a Model-object
208 elif is_dict(val):
209 if param in self.__object_params:
210 # "module.submodule:ClassName"
211 cls = self.__object_params[param]
212 return cls().from_dict(val)
214 # Atoms and uninteresting hashes/dictionaries
215 return val
217 def to_dict(self):
218 '''Exports a model to a dictionary.
219 This simplifies serialization.'''
220 params = {}
221 for param in self.__params:
222 params[param] = self.__param_to_dict(param)
223 value = getattr(self, param)
224 if is_instance(value):
225 clstr = Model.class_to_str(value)
226 params.setdefault('__META__', {})
227 params['__META__'][param] = clstr
228 return params
230 def __param_to_dict(self, param):
231 item = getattr(self, param)
232 return self.__item_to_dict(item)
234 def __item_to_dict(self, item):
235 if is_atom(item):
236 return item
238 elif is_list(item):
239 newlist = []
240 for i in item:
241 newlist.append(self.__item_to_dict(i))
242 return newlist
244 elif is_dict(item):
245 newdict = {}
246 for k,v in item.iteritems():
247 newdict[k] = self.__item_to_dict(v)
248 return newdict
250 elif is_instance(item):
251 return item.to_dict()
253 else:
254 raise NotImplementedError, 'Unknown type:' + str(type(item))
256 __INDENT__ = 0
257 __PREINDENT__ = True
258 __STRSTACK__ = []
260 @staticmethod
261 def INDENT(i=0):
262 Model.__INDENT__ += i
263 return '\t' * Model.__INDENT__
265 def __str__(self):
266 '''A convenient, recursively-defined stringification method.'''
268 # This avoid infinite recursion on cyclical structures
269 if self in Model.__STRSTACK__:
270 return 'REFERENCE'
271 else:
272 Model.__STRSTACK__.append(self)
274 io = StringIO()
276 if Model.__PREINDENT__:
277 io.write(Model.INDENT())
279 io.write(self.__class__.__name__ + '(')
281 Model.INDENT(1)
283 for param in self.__params:
284 if param.startswith('_'): continue
285 io.write('\n')
287 inner = Model.INDENT() + param + " = "
288 value = getattr(self, param)
290 if type(value) == ListType:
291 indent = Model.INDENT(1)
292 io.write(inner + "[\n")
293 for val in value:
294 if is_model(val):
295 io.write(str(val)+'\n')
296 else:
297 io.write(indent)
298 io.write(str(val))
299 io.write(",\n")
301 io.write(Model.INDENT(-1))
302 io.write('],')
303 else:
304 Model.__PREINDENT__ = False
305 io.write(inner)
306 io.write(str(value))
307 io.write(',')
308 Model.__PREINDENT__ = True
310 io.write('\n' + Model.INDENT(-1) + ')')
311 value = io.getvalue()
312 io.close()
314 Model.__STRSTACK__.remove(self)
315 return value
317 @staticmethod
318 def str_to_class(clstr):
319 items = clstr.split('.')
320 modules = items[:-1]
321 classname = items[-1]
322 path = None
323 module = None
324 for mod in modules:
325 search = imp.find_module(mod, path)
326 try:
327 module = imp.load_module(mod, *search)
328 if hasattr(module, '__path__'):
329 path = module.__path__
330 finally:
331 if search and search[0]:
332 search[0].close()
333 if module:
334 return getattr(module, classname)
335 else:
336 raise Exception("No class found for: %s" % clstr)
338 @staticmethod
339 def class_to_str(instance):
340 modname = instance.__module__
341 classname = instance.__class__.__name__
342 return '%s.%s' % (modname, classname)
345 #############################################################################
346 def is_model(item): return issubclass(item.__class__, Model)
347 def is_dict(item):
348 return type(item) is DictType
349 def is_list(item):
350 return type(item) is ListType or type(item) is TupleType
351 def is_atom(item):
352 return(type(item) in StringTypes
353 or type(item) is BooleanType
354 or type(item) is IntType
355 or type(item) is LongType
356 or type(item) is FloatType
357 or type(item) is ComplexType)
358 def is_instance(item):
359 return(is_model(item)
360 or type(item) is InstanceType)