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
= {}
52 return self
.__class
__().from_dict(self
.to_dict())
54 def set_list_attrs(self
, list_attrs
):
55 self
.__list
_attrs
.update(list_attrs
)
57 def set_object_attrs(self
, obj_attrs
):
58 self
.__object
_attrs
.update(obj_attrs
)
60 def getattr(self
, attr
):
61 return getattr(self
, attr
)
63 def get_attributes(self
):
64 return self
.__attributes
67 def __getattr__(self
, attr
):
68 '''Provides automatic get/set/add/append methods.'''
70 # Base case: we actually have this attribute
71 if attr
in self
.__dict
__:
72 return getattr(self
, attr
)
74 # Check for the translated variant of the attr
75 realattr
= self
.__translate
(attr
, sep
='')
76 if realattr
in self
.__dict
__:
77 return getattr(self
, realattr
)
79 if realattr
.startswith('get'):
80 realattr
= self
.__translate
(attr
, 'get')
81 return lambda: getattr(self
, realattr
)
83 elif realattr
.startswith('set'):
84 realattr
= self
.__translate
(attr
, 'set')
85 return lambda(value
): self
.set(realattr
, value
)
87 elif realattr
.startswith('add'):
88 self
.__array
= self
.__translate
(attr
, 'add')
91 elif realattr
.startswith('append'):
92 self
.__array
= self
.__translate
(attr
, 'append')
95 errmsg
= "%s object has no attribute '%s'" \
96 %( self
.__class
__, attr
)
98 raise AttributeError, errmsg
100 def set(self
, attr
, value
, notify
=True):
101 '''Sets a model attribute.'''
102 setattr(self
, attr
, value
)
103 if attr
not in self
.__attributes
:
104 self
.__attributes
.append(attr
)
105 if notify
: self
.notify_observers(attr
)
107 def __append(self
, *values
):
108 '''Appends an arbitrary number of values to
109 an array atribute.'''
112 array
= getattr(self
, attr
)
115 errmsg
= "%s object has no attribute '%s'" \
116 %( self
.__class
__, attr
)
117 raise AttributeError, errmsg
123 def __translate(self
, attr
, prefix
='', sep
='_'):
124 '''Translates an attribute name from the external name
125 used in methods to those used internally. The default
126 settings strip off '_' so that both get_foo() and getFoo()
127 are valid incantations.'''
129 return attr
.lstrip(prefix
).lstrip(sep
).lower()
131 def __get_class(self
, objspec
):
132 '''Loads a class from a module and returns the class.'''
134 # str("module.submodule:ClassName")
135 ( modname
, classname
) = objspec
.split(':')
136 modfile
= imp
.find_module(modname
)
137 module
= imp
.load_module(modname
,
138 modfile
[0], modfile
[1], modfile
[2])
140 if classname
in module
.__dict
__:
141 cls
= module
.__dict
__[classname
]
144 warning
= 'WARNING: %s not found in %s\n' %(
146 sys
.stderr
.write(warning
)
151 def save(self
, filename
):
153 file = open(filename
, 'w')
154 simplejson
.dump( self
.to_dict(), file, indent
=4 )
157 def load(self
, filename
):
159 file = open(filename
, 'r')
160 dict = simplejson
.load(file)
164 def from_dict(self
, model
):
165 '''Import a complex model from a dictionary. The import/export
166 is clued as to nested Model-objects by setting the
167 __list_attrs or __object_attrs object specifications.'''
169 for attr
,val
in model
.iteritems():
170 setattr(self
, attr
, self
.__attr
_from
_dict
(attr
,val
))
171 if attr
not in self
.__attributes
:
172 self
.__attributes
.append(attr
)
175 def __attr_from_dict(self
,attr
,val
):
177 # A list of Model-objects
179 if attr
in self
.__list
_attrs
:
180 # A list of Model-derived objects
182 objspec
= self
.__list
_attrs
[attr
]
183 cls
= self
.__get
_class
(objspec
)
185 listattr
.append(cls().from_dict(item
))
188 # An attribute that maps to a Model-object
190 if attr
in self
.__object
_attrs
:
191 # "module.submodule:ClassName"
192 objectspec
= self
.__object
_attrs
[attr
]
193 cls
= self
.__get
_class
(objectspec
)
194 return cls().from_dict(val
)
196 # Atoms and uninteresting hashes/dictionaries
201 '''Exports a model to a dictionary.
202 This simplifies serialization.'''
205 for attr
in self
.__attributes
:
206 attrs
[attr
] = self
.__attr
_to
_dict
(attr
)
209 def __attr_to_dict(self
, attr
):
210 item
= getattr(self
, attr
)
211 return self
.__item
_to
_dict
(item
)
213 def __item_to_dict(self
, item
):
215 if is_atom(item
): return item
220 newlist
.append(self
.__item
_to
_dict
(i
))
225 for k
,v
in item
.iteritems():
226 newdict
[k
] = self
.__item
_to
_dict
(v
)
229 elif is_instance(item
):
230 return item
.to_dict()
233 raise NotImplementedError, 'Unknown type:' + str(type(item
))
236 __INDENT__
= -4 # Used by __str__
239 '''A convenient, recursively-defined stringification method.'''
241 Model
.__INDENT
__ += 4
244 for attr
in self
.__dict
__:
245 if attr
.startswith('_'): continue
246 inner
= " " * Model
.__INDENT
__ + attr
+ ": "
248 value
= getattr(self
, attr
)
250 if type(value
) == ListType
:
252 indent
= " " *(Model
.__INDENT
__ + 4)
253 strings
.append(inner
+ "[")
255 stringval
= indent
+ str(val
)
256 strings
.append(stringval
)
258 indent
= " " * Model
.__INDENT
__
259 strings
.append(indent
+ "]")
262 strings
.append(inner
+ str(value
))
264 Model
.__INDENT
__ -= 4
266 return "\n".join(strings
)
270 return type(item
) is DictType
272 return type(item
) is ListType
or type(item
) is TupleType
274 return(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
)
281 def is_instance(item
):
282 return( issubclass(item
.__class
__, Model
)
283 or type(item
) is InstanceType
)