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
):
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
)
46 # For meta-programmability
47 self
.from_dict(kwargs
)
53 def create(self
,**kwargs
):
54 return self
.from_dict(kwargs
)
56 def get_param_names(self
):
57 return tuple(self
.__params
)
60 self
.notify_observers(*self
.get_param_names())
62 def clone(self
, *args
, **kwargs
):
63 return self
.__class
__(*args
, **kwargs
).from_dict(self
.to_dict())
65 def has_param(self
,param
):
66 return param
in self
.__params
68 def get_param(self
,param
):
69 return getattr(self
, param
)
71 def __getattr__(self
, param
):
72 """Provides automatic get/set/add/append methods."""
74 # Base case: we actually have this param
75 if param
in self
.__dict
__:
76 return getattr(self
, param
)
78 # Check for the translated variant of the param
79 realparam
= self
.__translate
(param
, sep
='')
80 if realparam
in self
.__dict
__:
81 return getattr(self
, realparam
)
83 if realparam
.startswith("get"):
84 param
= self
.__translate
(param
, "get")
85 return lambda: getattr(self
, param
)
87 elif realparam
.startswith("set"):
88 param
= self
.__translate
(param
, "set")
89 return lambda v
: self
.set_param(param
, v
,
92 elif (realparam
.startswith("add")
93 or realparam
.startswith("append")):
95 if realparam
.startswith("add"):
96 param
= self
.__translate
(realparam
, "add")
98 param
= self
.__translate
(realparam
, "append")
100 def array_append(*values
):
101 array
= getattr(self
, param
)
103 classnm
= self
.__class
__.__name
__
104 errmsg
= ("%s object has no array named '%s'"
106 raise AttributeError(errmsg
)
109 # Cache the function definition
110 setattr(self
, realparam
, array_append
)
113 errmsg
= ("%s object has no parameter '%s'"
114 % (self
.__class
__.__name
__, param
))
116 raise AttributeError(errmsg
)
118 def set_param(self
, param
, value
, notify
=True, check_params
=False):
119 """Set param with optional notification and validity checks."""
121 param
= param
.lower()
122 if check_params
and param
not in self
.__params
:
123 raise Exception("Parameter '%s' not available for %s"
124 % (param
, self
.__class
__.__name
__))
125 elif param
not in self
.__params
:
126 self
.__params
.append(param
)
128 setattr(self
, param
, value
)
129 if notify
: self
.notify_observers(param
)
131 def copy_params(self
, model
, params
=None):
133 params
= self
.get_param_names()
135 self
.set_param(param
, model
.get_param(param
))
137 def __translate(self
, param
, prefix
='', sep
='_'):
138 """Translates an param name from the external name
139 used in methods to those used internally. The default
140 settings strip off '_' so that both get_foo() and getFoo()
141 are valid incantations."""
142 return param
[len(prefix
):].lstrip(sep
).lower()
144 def save(self
, filename
):
148 file = open(filename
, 'w')
149 simplejson
.dump(self
.to_dict(), file, indent
=4)
152 def load(self
, filename
):
156 file = open(filename
, 'r')
157 ddict
= simplejson
.load(file)
159 if "__class__" in ddict
:
160 # load params in-place.
161 del ddict
["__class__"]
162 return self
.from_dict(ddict
)
165 def instance(filename
):
169 file = open(filename
, 'r')
170 ddict
= simplejson
.load(file)
172 if "__class__" in ddict
:
173 cls
= Model
.str_to_class(ddict
["__class__"])
174 del ddict
["__class__"]
175 return cls().from_dict(ddict
)
177 return Model().from_dict(ddict
)
180 def from_dict(self
, source_dict
):
181 """Import a complex model from a dictionary.
182 We store class information in the __class__ variable.
183 If it looks like a duck, it's a duck."""
185 if "__class__" in source_dict
:
186 clsstr
= source_dict
["__class__"]
187 del source_dict
["__class__"]
188 cls
= Model
.str_to_class(clsstr
)
189 return cls().from_dict(source_dict
)
191 # Not initiating a clone: load parameters in-place
192 for param
, val
in source_dict
.iteritems():
195 self
.__obj
_from
_value
(val
),
200 def __obj_from_value(self
, val
):
205 # Possibly nested lists
207 return [ self
.__obj
_from
_value
(v
) for v
in val
]
210 # A param that maps to a Model-object
211 if "__class__" in val
:
212 clsstr
= val
["__class__"]
213 cls
= Model
.str_to_class(clsstr
)
215 return cls().from_dict(val
)
217 for k
, v
in val
.iteritems():
218 newdict
[k
] = self
.__obj
_from
_value
(v
)
226 """Exports a model to a dictionary.
227 This simplifies serialization."""
228 params
= {"__class__": Model
.class_to_str(self
)}
229 for param
in self
.__params
:
231 self
.__obj
_to
_value
(getattr(self
, param
))
234 def __obj_to_value(self
, item
):
239 newlist
= [ self
.__obj
_to
_value
(i
) for i
in item
]
244 for k
,v
in item
.iteritems():
245 newdict
[k
] = self
.__obj
_to
_value
(v
)
248 elif is_instance(item
):
249 return item
.to_dict()
252 raise NotImplementedError("Unknown type:" + str(type(item
)))
260 Model
.__INDENT
__ += i
261 return '\t' * Model
.__INDENT
__
264 """A convenient, recursively-defined stringification method."""
266 # This avoid infinite recursion on cyclical structures
267 if self
in Model
.__STRSTACK
__:
270 Model
.__STRSTACK
__.append(self
)
274 if Model
.__PREINDENT
__:
275 io
.write(Model
.INDENT())
277 io
.write(self
.__class
__.__name
__ + '(')
281 for param
in self
.__params
:
282 if param
.startswith('_'): continue
285 inner
= Model
.INDENT() + param
+ " = "
286 value
= getattr(self
, param
)
288 if type(value
) == ListType
:
289 indent
= Model
.INDENT(1)
290 io
.write(inner
+ "[\n")
293 io
.write(str(val
)+'\n')
299 io
.write(Model
.INDENT(-1))
302 Model
.__PREINDENT
__ = False
306 Model
.__PREINDENT
__ = True
308 io
.write('\n' + Model
.INDENT(-1) + ')')
309 value
= io
.getvalue()
312 Model
.__STRSTACK
__.remove(self
)
316 def str_to_class(clstr
):
317 items
= clstr
.split('.')
319 classname
= items
[-1]
323 search
= imp
.find_module(mod
, path
)
325 module
= imp
.load_module(mod
, *search
)
326 if hasattr(module
, "__path__"):
327 path
= module
.__path
__
329 if search
and search
[0]:
332 return getattr(module
, classname
)
334 raise Exception("No class found for: %s" % clstr
)
337 def class_to_str(instance
):
338 modname
= instance
.__module
__
339 classname
= instance
.__class
__.__name
__
340 return "%s.%s" % (modname
, classname
)
343 #############################################################################
349 print "Unable to import simplejson." % action
350 print "You do not have simplejson installed."
351 print "try: sudo apt-get install simplejson"
354 #############################################################################
355 def is_model(item
): return issubclass(item
.__class
__, Model
)
357 return type(item
) is DictType
359 return type(item
) is ListType
or type(item
) is TupleType
361 return(type(item
) in StringTypes
362 or type(item
) is BooleanType
363 or type(item
) is IntType
364 or type(item
) is LongType
365 or type(item
) is FloatType
366 or type(item
) is ComplexType
)
367 def is_instance(item
):
368 return(is_model(item
)
369 or type(item
) is InstanceType
)