2 # Copyright (c) 2008 David Aguilar
5 from cStringIO
import StringIO
6 from types
import DictType
7 from types
import ListType
8 from types
import TupleType
9 from types
import StringTypes
10 from types
import BooleanType
11 from types
import IntType
12 from types
import LongType
13 from types
import FloatType
14 from types
import ComplexType
15 from types
import InstanceType
17 class Observable(object):
18 """Handles subject/observer notifications."""
19 def __init__(self
,*args
,**kwargs
):
24 def set_notify(self
, notify
=True):
25 self
.__notify
= notify
26 def add_observer(self
, observer
):
27 if observer
not in self
.__observers
:
28 self
.__observers
.append(observer
)
29 def remove_observer(self
, observer
):
30 if observer
in self
.__observers
:
31 self
.__observers
.remove(observer
)
32 def notify_observers(self
, *param
):
33 if not self
.__notify
: return
34 for observer
in self
.__observers
:
35 observer
.notify(*param
)
37 class Model(Observable
):
38 """Creates a generic model object with params specified
39 as a name:value dictionary.
41 get_name() and set_name(value) are created automatically
42 for any of the parameters specified in the **kwargs"""
44 def __init__(self
, *args
, **kwargs
):
45 Observable
.__init
__(self
)
47 self
.from_dict(kwargs
)
51 """init() is called by the built-in constructor.
52 Subclasses should implement this if necessary."""
55 def create(self
,**kwargs
):
56 return self
.from_dict(kwargs
)
58 def get_param_names(self
):
59 return tuple(self
.__params
)
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 has_param(self
,param
):
68 return param
in self
.__params
70 def get_param(self
,param
):
71 return getattr(self
, param
)
73 def __getattr__(self
, param
):
74 """Provides automatic get/set/add/append methods."""
76 # Base case: we actually have this param
77 if param
in self
.__dict
__:
78 return getattr(self
, param
)
80 # Check for the translated variant of the param
81 realparam
= self
.__translate
(param
, sep
='')
82 if realparam
in self
.__dict
__:
83 return getattr(self
, realparam
)
85 if realparam
.startswith("get"):
86 param
= self
.__translate
(param
, "get")
87 return lambda: getattr(self
, param
)
89 elif realparam
.startswith("set"):
90 param
= self
.__translate
(param
, "set")
91 return lambda v
: self
.set_param(param
, v
,
94 elif (realparam
.startswith("add") 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 AttributeError("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
)
130 self
.notify_observers(param
)
132 def copy_params(self
, model
, params
=None):
134 params
= self
.get_param_names()
136 self
.set_param(param
, model
.get_param(param
))
138 def __translate(self
, param
, prefix
='', sep
='_'):
139 """Translates an param name from the external name
140 used in methods to those used internally. The default
141 settings strip off '_' so that both get_foo() and getFoo()
142 are valid incantations."""
143 return param
[len(prefix
):].lstrip(sep
).lower()
145 def save(self
, filename
):
149 file = open(filename
, 'w')
150 simplejson
.dump(self
.to_dict(), file, indent
=4)
153 def load(self
, filename
):
157 file = open(filename
, 'r')
158 ddict
= simplejson
.load(file)
160 if "__class__" in ddict
:
161 # load params in-place.
162 del ddict
["__class__"]
163 return self
.from_dict(ddict
)
166 def instance(filename
):
170 file = open(filename
, 'r')
171 ddict
= simplejson
.load(file)
173 if "__class__" in ddict
:
174 cls
= Model
.str_to_class(ddict
["__class__"])
175 del ddict
["__class__"]
176 return cls().from_dict(ddict
)
178 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():
193 self
.set_param(param
,
194 self
.__obj
_from
_value
(val
),
199 def __obj_from_value(self
, val
):
204 # Possibly nested lists
206 return [ self
.__obj
_from
_value
(v
) for v
in val
]
209 # A param that maps to a Model-object
210 if "__class__" in val
:
211 clsstr
= val
["__class__"]
212 cls
= Model
.str_to_class(clsstr
)
214 return cls().from_dict(val
)
216 for k
, v
in val
.iteritems():
217 newdict
[k
] = self
.__obj
_from
_value
(v
)
225 Exports a model to a dictionary.
226 This simplifies serialization.
228 params
= {"__class__": Model
.class_to_str(self
)}
229 for param
in self
.__params
:
230 params
[param
] = self
.__obj
_to
_value
(getattr(self
, param
))
233 def __obj_to_value(self
, item
):
238 newlist
= [ self
.__obj
_to
_value
(i
) for i
in item
]
243 for k
,v
in item
.iteritems():
244 newdict
[k
] = self
.__obj
_to
_value
(v
)
247 elif is_instance(item
):
248 return item
.to_dict()
251 raise NotImplementedError("Unknown type:" + str(type(item
)))
259 Model
.__INDENT
__ += i
260 return '\t' * Model
.__INDENT
__
263 """A convenient, recursively-defined stringification method."""
265 # This avoid infinite recursion on cyclical structures
266 if self
in Model
.__STRSTACK
__:
267 return 'REFERENCE' # TODO: implement references?
269 Model
.__STRSTACK
__.append(self
)
273 if Model
.__PREINDENT
__:
274 io
.write(Model
.INDENT())
276 io
.write(self
.__class
__.__name
__ + '(')
280 for param
in self
.__params
:
281 if param
.startswith('_'):
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 #############################################################################
356 return issubclass(item
.__class
__, Model
)
358 return type(item
) is DictType
360 return type(item
) is ListType
or type(item
) is TupleType
362 return(type(item
) in StringTypes
363 or type(item
) is BooleanType
364 or type(item
) is IntType
365 or type(item
) is LongType
366 or type(item
) is FloatType
367 or type(item
) is ComplexType
)
368 def is_instance(item
):
369 return(is_model(item
) or type(item
) is InstanceType
)