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 self
.__observers
.append(observer
)
16 def notify_observers(self
, *attr
):
17 if not self
.__notify
: return
18 for observer
in self
.__observers
:
19 observer
.notify(*attr
)
22 class Model(Observable
):
23 '''Creates a generic model object with attributes specified
24 as a name:value dictionary.
26 get_name() and set_name(value) are created automatically
27 for any of the specified attributes.'''
29 def __init__(self
, attributes
= {}, defaults
= {}, notify
=True):
30 '''Initializes all attributes and default attribute
31 values. By default we do not call notify unless explicitly
34 Observable
.__init
__(self
, notify
)
36 for attr
, value
in attributes
.iteritems():
37 setattr(self
, attr
, value
)
39 for attr
, value
in defaults
.iteritems():
40 if not hasattr(self
, attr
):
41 setattr(self
, attr
, value
)
43 # For meta-programmability
44 self
.__attributes
= list(attributes
.keys() + defaults
.keys())
45 self
.__list
_attrs
= {}
46 self
.__object
_attrs
= {}
48 def set_list_attrs(self
, list_attrs
):
49 self
.__list
_attrs
.update(list_attrs
)
51 def set_object_attrs(self
, obj_attrs
):
52 self
.__object
_attrs
.update(obj_attrs
)
54 def getattr(self
, attr
):
55 return getattr(self
, attr
)
57 def get_attributes(self
):
58 return self
.__attributes
61 def __getattr__(self
, attr
):
62 '''Provides automatic get/set/add/append methods.'''
64 # Base case: we actually have this attribute
65 if attr
in self
.__dict
__:
66 return getattr(self
, attr
)
68 # Check for the translated variant of the attr
69 realattr
= self
.__translate
(attr
, sep
='')
70 if realattr
in self
.__dict
__:
71 return getattr(self
, realattr
)
73 if realattr
.startswith('get'):
74 realattr
= self
.__translate
(attr
, 'get')
75 return lambda: getattr(self
, realattr
)
77 elif realattr
.startswith('set'):
78 realattr
= self
.__translate
(attr
, 'set')
79 return lambda(value
): self
.set_and_notify(realattr
, value
)
81 elif realattr
.startswith('add'):
82 self
.__array
= self
.__translate
(attr
, 'add')
85 elif realattr
.startswith('append'):
86 self
.__array
= self
.__translate
(attr
, 'append')
89 errmsg
= "%s object has no attribute '%s'" \
90 % ( self
.__class
__, attr
)
92 raise AttributeError, errmsg
94 def set(self
, attr
, value
):
95 '''Sets a model attribute.'''
96 setattr(self
, attr
, value
)
97 if attr
not in self
.__attributes
:
98 self
.__attributes
.append(attr
)
100 def set_and_notify(self
, attr
, value
):
101 '''Sets an attribute and notifies observers.'''
102 self
.set(attr
, value
)
103 self
.notify_observers(attr
)
105 def __append(self
, *values
):
106 '''Appends an arbitrary number of values to
107 an array atribute.'''
110 array
= getattr(self
, attr
)
113 errmsg
= "%s object has no attribute '%s'" \
114 % ( self
.__class
__, attr
)
115 raise AttributeError, errmsg
121 def __translate(self
, attr
, prefix
='', sep
='_'):
122 '''Translates an attribute name from the external name
123 used in methods to those used internally. The default
124 settings strip off '_' so that both get_foo() and getFoo()
125 are valid incantations.'''
127 return attr
.lstrip(prefix
).lstrip(sep
).lower()
129 def __get_class(self
, objspec
):
130 '''Loads a class from a module and returns the class.'''
132 # str("module.submodule:ClassName")
133 ( modname
, classname
) = objspec
.split(':')
134 modfile
= imp
.find_module(modname
)
135 module
= imp
.load_module(modname
,
136 modfile
[0], modfile
[1], modfile
[2])
138 if classname
in module
.__dict
__:
139 cls
= module
.__dict
__[classname
]
142 warning
= 'WARNING: %s not found in %s\n' % (
144 sys
.stderr
.write(warning
)
149 def save(self
, filename
):
151 file = open(filename
, 'w')
152 simplejson
.dump( self
.to_dict(), file, indent
=4 )
155 def load(self
, filename
):
157 file = open(filename
, 'r')
158 dict = simplejson
.load(file)
162 def from_dict(self
, model
):
163 '''Import a complex model from a dictionary. The import/export
164 is clued as to nested Model-objects by setting the
165 __list_attrs or __object_attrs object specifications.'''
167 for attr
,val
in model
.iteritems():
168 setattr(self
, attr
, self
.__attr
_from
_dict
(attr
,val
))
169 if attr
not in self
.__attributes
:
170 self
.__attributes
.append(attr
)
173 def __attr_from_dict(self
,attr
,val
):
175 # A list of Model-objects
177 if attr
in self
.__list
_attrs
:
178 # A list of Model-derived objects
180 objspec
= self
.__list
_attrs
[attr
]
181 cls
= self
.__get
_class
(objspec
)
183 listattr
.append(cls().from_dict(item
))
186 # An attribute that maps to a Model-object
188 if attr
in self
.__object
_attrs
:
189 # "module.submodule:ClassName"
190 objectspec
= self
.__object
_attrs
[attr
]
191 cls
= self
.__get
_class
(objectspec
)
192 return cls().from_dict(val
)
194 # Atoms and uninteresting hashes/dictionaries
199 '''Exports a model to a dictionary.
200 This simplifies serialization.'''
203 for attr
in self
.__attributes
:
204 attrs
[attr
] = self
.__attr
_to
_dict
(attr
)
207 def __attr_to_dict(self
, attr
):
208 item
= getattr(self
, attr
)
209 return self
.__item
_to
_dict
(item
)
211 def __item_to_dict(self
, item
):
213 if is_atom(item
): return item
218 newlist
.append(self
.__item
_to
_dict
(i
))
223 for k
,v
in item
.iteritems():
224 newdict
[k
] = self
.__item
_to
_dict
(v
)
227 elif is_instance(item
):
228 return item
.to_dict()
231 raise NotImplementedError, 'Unknown type:' + str(type(item
))
234 __INDENT__
= -4 # Used by __str__
237 '''A convenient, recursively-defined stringification method.'''
239 Model
.__INDENT
__ += 4
242 for attr
in self
.__dict
__:
243 if attr
.startswith('_'): continue
244 inner
= " " * Model
.__INDENT
__ + attr
+ ": "
246 value
= getattr(self
, attr
)
248 if type(value
) == ListType
:
250 indent
= " " * (Model
.__INDENT
__ + 4)
251 strings
.append(inner
+ "[")
253 stringval
= indent
+ str(val
)
254 strings
.append (stringval
)
256 indent
= " " * Model
.__INDENT
__
257 strings
.append(indent
+ "]")
260 strings
.append(inner
+ str(value
))
262 Model
.__INDENT
__ -= 4
264 return "\n".join(strings
)
267 def is_list(item
): return type(item
) is ListType
or type(item
) is TupleType
268 def is_dict(item
): return type(item
) is DictType
271 type(item
) in StringTypes
272 or type(item
) is BooleanType
273 or type(item
) is IntType
274 or type(item
) is LongType
275 or type(item
) is FloatType
276 or type(item
) is ComplexType
279 def is_instance(item
):
280 return ( issubclass(item
.__class
__, Model
)
281 or type(item
) is InstanceType
)