5 class Observable(object):
6 '''Handles subject/observer notifications.'''
7 def __init__(self
, notify
=True):
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
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 set_list_attrs(self
, list_attrs
):
52 self
.__list
_attrs
.update(list_attrs
)
54 def set_object_attrs(self
, obj_attrs
):
55 self
.__object
_attrs
.update(obj_attrs
)
57 def getattr(self
, attr
):
58 return getattr(self
, attr
)
60 def get_attributes(self
):
61 return self
.__attributes
64 def __getattr__(self
, attr
):
65 '''Provides automatic get/set/add/append methods.'''
67 # Base case: we actually have this attribute
68 if attr
in self
.__dict
__:
69 return getattr(self
, attr
)
71 # Check for the translated variant of the attr
72 realattr
= self
.__translate
(attr
, sep
='')
73 if realattr
in self
.__dict
__:
74 return getattr(self
, realattr
)
76 if realattr
.startswith('get'):
77 realattr
= self
.__translate
(attr
, 'get')
78 return lambda: getattr(self
, realattr
)
80 elif realattr
.startswith('set'):
81 realattr
= self
.__translate
(attr
, 'set')
82 return lambda(value
): self
.set_and_notify(realattr
, value
)
84 elif realattr
.startswith('add'):
85 self
.__array
= self
.__translate
(attr
, 'add')
88 elif realattr
.startswith('append'):
89 self
.__array
= self
.__translate
(attr
, 'append')
92 errmsg
= "%s object has no attribute '%s'" \
93 % ( self
.__class
__, attr
)
95 raise AttributeError, errmsg
97 def set(self
, attr
, value
):
98 '''Sets a model attribute.'''
99 setattr(self
, attr
, value
)
100 if attr
not in self
.__attributes
:
101 self
.__attributes
.append(attr
)
103 def set_and_notify(self
, attr
, value
):
104 '''Sets an attribute and notifies observers.'''
105 self
.set(attr
, value
)
106 self
.notify_observers(attr
)
108 def __append(self
, *values
):
109 '''Appends an arbitrary number of values to
110 an array atribute.'''
113 array
= getattr(self
, attr
)
116 errmsg
= "%s object has no attribute '%s'" \
117 % ( self
.__class
__, attr
)
118 raise AttributeError, errmsg
124 def __translate(self
, attr
, prefix
='', sep
='_'):
125 '''Translates an attribute name from the external name
126 used in methods to those used internally. The default
127 settings strip off '_' so that both get_foo() and getFoo()
128 are valid incantations.'''
130 return attr
.lstrip(prefix
).lstrip(sep
).lower()
132 def __get_class(self
, objspec
):
133 '''Loads a class from a module and returns the class.'''
135 # str("module.submodule:ClassName")
136 ( modname
, classname
) = objspec
.split(':')
137 modfile
= imp
.find_module(modname
)
138 module
= imp
.load_module(modname
,
139 modfile
[0], modfile
[1], modfile
[2])
141 if classname
in module
.__dict
__:
142 cls
= module
.__dict
__[classname
]
145 warning
= 'WARNING: %s not found in %s\n' % (
147 sys
.stderr
.write(warning
)
152 def save(self
, filename
):
154 file = open(filename
, 'w')
155 simplejson
.dump( self
.to_dict(), file, indent
=4 )
158 def load(self
, filename
):
160 file = open(filename
, 'r')
161 dict = simplejson
.load(file)
165 def from_dict(self
, model
):
166 '''Import a complex model from a dictionary. The import/export
167 is clued as to nested Model-objects by setting the
168 __list_attrs or __object_attrs object specifications.'''
170 for attr
,val
in model
.iteritems():
171 setattr(self
, attr
, self
.__attr
_from
_dict
(attr
,val
))
172 if attr
not in self
.__attributes
:
173 self
.__attributes
.append(attr
)
176 def __attr_from_dict(self
,attr
,val
):
178 # A list of Model-objects
180 if attr
in self
.__list
_attrs
:
181 # A list of Model-derived objects
183 objspec
= self
.__list
_attrs
[attr
]
184 cls
= self
.__get
_class
(objspec
)
186 listattr
.append(cls().from_dict(item
))
189 # An attribute that maps to a Model-object
191 if attr
in self
.__object
_attrs
:
192 # "module.submodule:ClassName"
193 objectspec
= self
.__object
_attrs
[attr
]
194 cls
= self
.__get
_class
(objectspec
)
195 return cls().from_dict(val
)
197 # Atoms and uninteresting hashes/dictionaries
202 '''Exports a model to a dictionary.
203 This simplifies serialization.'''
206 for attr
in self
.__attributes
:
207 attrs
[attr
] = self
.__attr
_to
_dict
(attr
)
210 def __attr_to_dict(self
, attr
):
211 item
= getattr(self
, attr
)
212 return self
.__item
_to
_dict
(item
)
214 def __item_to_dict(self
, item
):
216 if is_atom(item
): return item
221 newlist
.append(self
.__item
_to
_dict
(i
))
226 for k
,v
in item
.iteritems():
227 newdict
[k
] = self
.__item
_to
_dict
(v
)
230 elif is_instance(item
):
231 return item
.to_dict()
234 raise NotImplementedError, 'Unknown type:' + str(type(item
))
237 __INDENT__
= -4 # Used by __str__
240 '''A convenient, recursively-defined stringification method.'''
242 Model
.__INDENT
__ += 4
245 for attr
in self
.__dict
__:
246 if attr
.startswith('_'): continue
247 inner
= " " * Model
.__INDENT
__ + attr
+ ": "
249 value
= getattr(self
, attr
)
251 if type(value
) == ListType
:
253 indent
= " " * (Model
.__INDENT
__ + 4)
254 strings
.append(inner
+ "[")
256 stringval
= indent
+ str(val
)
257 strings
.append (stringval
)
259 indent
= " " * Model
.__INDENT
__
260 strings
.append(indent
+ "]")
263 strings
.append(inner
+ str(value
))
265 Model
.__INDENT
__ -= 4
267 return "\n".join(strings
)
270 def is_list(item
): return type(item
) is ListType
or type(item
) is TupleType
271 def is_dict(item
): return type(item
) is DictType
274 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
282 def is_instance(item
):
283 return ( issubclass(item
.__class
__, Model
)
284 or type(item
) is InstanceType
)