Renamed 'ugitlibs' to just plain 'ugit'
[git-cola.git] / ugit / model.py
blobb72938f7639835a058e2aec05766924b4553e42e
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)
51 def get_param_names(self):
52 return tuple(self.__params)
54 def notify_all(self):
55 self.notify_observers(*self.get_param_names())
57 def create(self,**kwargs):
58 return self.from_dict(kwargs)
60 def clone(self, *args, **kwargs):
61 return self.__class__(*args, **kwargs).from_dict(self.to_dict())
63 def set_list_params(self, **list_params):
64 self.__list_params.update(list_params)
66 def set_object_params(self, **obj_params):
67 self.__object_params.update(obj_params)
69 def has_param(self,param):
70 return param in self.__params
72 def get_param(self,param):
73 return getattr(self, param)
75 def __getattr__(self, param):
76 '''Provides automatic get/set/add/append methods.'''
78 # Base case: we actually have this param
79 if param in self.__dict__:
80 return getattr(self, param)
82 # Check for the translated variant of the param
83 realparam = self.__translate(param, sep='')
84 if realparam in self.__dict__:
85 return getattr(self, realparam)
87 if realparam.startswith('get'):
88 param = self.__translate(param, 'get')
89 return lambda: getattr(self, param)
91 elif realparam.startswith('set'):
92 param = self.__translate(param, 'set')
93 return lambda v: self.set_param(param, v,
94 check_params=True)
96 elif realparam.startswith('add'):
97 self.__array = self.__translate(param, 'add')
98 return self.__append
100 elif realparam.startswith('append'):
101 self.__array = self.__translate(param, 'append')
102 return self.__append
104 errmsg = ("%s object has no parameter '%s'"
105 % (self.__class__.__name__, param))
107 raise AttributeError(errmsg)
109 def set_param(self, param, value, notify=True, check_params=False):
110 '''Set param with optional notification and validity checks.'''
112 param = param.lower()
113 if check_params and param not in self.__params:
114 raise Exception("Parameter '%s' not available for %s"
115 % (param, self.__class__.__name__))
116 elif param not in self.__params:
117 self.__params.append(param)
119 setattr(self, param, value)
120 if notify: self.notify_observers(param)
122 def copy_params(self, model, params=None):
123 if params is None:
124 params = self.get_param_names()
125 for param in params:
126 self.set_param(param, model.get_param(param))
128 def __append(self, *values):
129 '''Appends an arbitrary number of values to
130 an array atribute.'''
131 array = getattr(self, self.__array)
132 if array is None:
133 errmsg = "%s object has no parameter '%s'" \
134 %( self.__class__.__name__, self.__array )
135 raise AttributeError(errmsg)
136 else:
137 array.extend(values)
139 def __translate(self, param, prefix='', sep='_'):
140 '''Translates an param name from the external name
141 used in methods to those used internally. The default
142 settings strip off '_' so that both get_foo() and getFoo()
143 are valid incantations.'''
144 return param.lstrip(prefix).lstrip(sep).lower()
146 def __get_class(self, objspec):
147 '''Loads a class from a module and returns the class.'''
149 # str("module.submodule:ClassName")
150 ( modname, classname ) = objspec.split(':')
151 modfile = imp.find_module(modname)
152 module = imp.load_module(modname,
153 modfile[0], modfile[1], modfile[2])
155 if classname in module.__dict__:
156 cls = module.__dict__[classname]
157 else:
158 cls = Model
159 warning = ('WARNING: %s not found in %s\n'
160 %(modname, classname))
161 sys.stderr.write(warning)
163 modfile[0].close()
164 return cls
166 def save(self, filename):
167 import simplejson
168 file = open(filename, 'w')
169 simplejson.dump(self.to_dict(), file, indent=4)
170 file.close()
172 def load(self, filename):
173 import simplejson
174 file = open(filename, 'r')
175 dict = simplejson.load(file)
176 file.close()
177 self.from_dict(dict)
179 def from_dict(self, source_dict):
180 '''Import a complex model from a dictionary. The import/export
181 is clued as to nested Model-objects by setting the
182 __list_params or __object_params object specifications.'''
183 for param,val in source_dict.iteritems():
184 self.set_param(param, self.__param_from_dict(param, val),
185 notify=False)
186 return self
188 def __param_from_dict(self,param,val):
190 # A list of Model-objects
191 if is_list(val):
192 if param in self.__list_params:
193 # A list of Model-derived objects
194 listparam = []
195 cls = self.__list_params[param]
196 for item in val:
197 listparam.append(cls().from_dict(item))
198 return listparam
200 # A param that maps to a Model-object
201 elif is_dict(val):
202 if param in self.__object_params:
203 # "module.submodule:ClassName"
204 cls = self.__object_params[param]
205 return cls().from_dict(val)
207 # Atoms and uninteresting hashes/dictionaries
208 return val
210 def to_dict(self):
211 '''Exports a model to a dictionary.
212 This simplifies serialization.'''
213 params = {}
214 for param in self.__params:
215 params[param] = self.__param_to_dict(param)
216 return params
218 def __param_to_dict(self, param):
219 item = getattr(self, param)
220 return self.__item_to_dict(item)
222 def __item_to_dict(self, item):
224 if is_atom(item): return item
226 elif is_list(item):
227 newlist = []
228 for i in item:
229 newlist.append(self.__item_to_dict(i))
230 return newlist
232 elif is_dict(item):
233 newdict = {}
234 for k,v in item.iteritems():
235 newdict[k] = self.__item_to_dict(v)
236 return newdict
238 elif is_instance(item):
239 return item.to_dict()
241 else:
242 raise NotImplementedError, 'Unknown type:' + str(type(item))
244 __INDENT__ = 0
246 @staticmethod
247 def INDENT(i=None):
248 if i is not None:
249 Model.__INDENT__ += i
250 return '\t' * Model.__INDENT__
252 def __str__(self):
253 '''A convenient, recursively-defined stringification method.'''
255 io = StringIO()
256 io.write(Model.INDENT())
257 io.write(self.__class__.__name__ + '(')
259 Model.INDENT(1)
261 for param in self.__params:
262 if param.startswith('_'): continue
263 io.write('\n')
265 inner = Model.INDENT() + param + " = "
266 value = getattr(self, param)
268 if type(value) == ListType:
269 indent = Model.INDENT(1)
270 io.write(inner + "[\n")
271 for val in value:
272 if is_model(val):
273 io.write(str(val)+'\n')
274 else:
275 io.write(indent)
276 io.write(str(val))
277 io.write(",\n")
279 io.write(Model.INDENT(-1))
280 io.write('],')
281 else:
282 io.write(inner)
283 io.write(str(value))
284 io.write(',')
286 io.write('\n' + Model.INDENT(-1) + ')')
287 value = io.getvalue()
288 io.close()
289 return value
291 #############################################################################
293 def is_model(item): return issubclass(item.__class__, Model)
294 def is_dict(item):
295 return type(item) is DictType
296 def is_list(item):
297 return type(item) is ListType or type(item) is TupleType
298 def is_atom(item):
299 return(type(item) in StringTypes
300 or type(item) is BooleanType
301 or type(item) is IntType
302 or type(item) is LongType
303 or type(item) is FloatType
304 or type(item) is ComplexType)
305 def is_instance(item):
306 return(is_model(item)
307 or type(item) is InstanceType)