Initial revision
[pli.git] / pli / objutils.py
blobca8a769b8fc79c0d93c1b7b9f230b3321bb5cbb9
1 #=======================================================================
3 __version__ = '''0.0.12'''
4 __sub_version__ = '''20040227011349'''
5 __copyright__ = '''(c) Alex A. Naanou 2003'''
8 #-----------------------------------------------------------------------
10 from __future__ import generators
11 import types
14 #-----------------------------------------------------------------------
15 #-----------------------------------------------------ObjectWithAttrs---
16 # XXX might be good to rename this... and to add method interface
17 # checking support...
18 # TODO add update callbacks (__attr_update_callbacks__ = {<attr>: <meth>[, ...]})
19 # TODO add regex attribute naming.... (e.g. if <attr> is a regexp
20 # object use it to match the attr name...)
21 # NOTE: might be good to use a predicate...
22 class ObjectWithAttrs(object):
23 '''
24 a generic object class with attribute creation an update automation.
26 this class checks attribute format.
27 '''
28 # this defines an acl object to be used...
29 __acl__ = None
30 # this will restrict the attribute that can be created for this
31 # object to the ones mentioned in the list (DO NOT SET HERE!).
32 # value: tuple
33 __attr_format__ = None
34 # this defines a tuple of attributes that must be defined on object
35 # init.
36 # value: tuple
37 __essential_attrs__ = None
38 ## # this defines the callbacks for attr update... (RPC only at the
39 ## # moment...)
40 ## # value: dict
41 ## __attr_update_callbacks__ = {}
42 # if this is unset the checks will ignore all attrs that are not in
43 # format...
44 # TODO revise....
45 __strict_attr_format__ = True
46 # this will enable attribute type checking... (change for legacy
47 # support only... though might be good for a speedup)
48 __check_attr_types__ = True
49 # this defines the values that are to be treated as "no-vals" (e.g.
50 # ignored on type checks...)
51 __none_values__ = ('', None)
53 def __init__(self, name, attrs={}):
54 '''
55 create an object with attrs from a dict...
56 '''
57 super(ObjectWithAttrs, self).__init__(name)
58 # check essential attrs....
59 ## if hasattr(self, '__essential_attrs__') and self.__essential_attrs__ != None and \
60 ## False in [ (type(attr) not in (str, unicode) and attr[0] or attr) in attrs \
61 ## for attr in self.__essential_attrs__ ]:
62 ## raise TypeError, 'essential attribute format mismatch.'
63 if hasattr(self, '__essential_attrs__') and self.__essential_attrs__ != None:
64 essential_attrs = [ (type(attr) not in (str, unicode) and attr[0] or attr) \
65 for attr in self.__essential_attrs__ ]
66 err = []
67 if False in [ attr in attrs and self._isattrwritable(attr, \
68 attrs[attr], \
69 strict=hasattr(self, '__strict_attr_format__') and self.__strict_attr_format__, \
70 format=self.__essential_attrs__) \
71 or (err.append(attr), False)[-1] \
72 for attr in essential_attrs ]:
73 raise TypeError, 'essential attribute format mismatch in %s.' % (err,)
74 self.update(attrs)
75 # the isconsisten protocol...
76 def __isconsistent__(self):
77 '''
78 check object consistency...
79 '''
80 return _checkarttrs(self.__dict__)
81 # NOTE: this is not very efficient if more than one attr is added
82 # in a loop (e.g. on object init)...
83 def _isattrwritable(self, name, value, format=None, strict=True, message=None, none_vals=False):
84 '''
85 this predicate will return true if the attribute is writable.
87 NOTE: this function impements error reporting a side-effect.
89 the error message[s] will be appended to the message argument (if present).
91 NOTE: the argument "message" must be of type list (if present).
92 NOTE: the "message" argument will be modified during this functions execution.
93 '''
94 if format == None:
95 if hasattr(self, '__attr_format__') and self.__attr_format__ != None and len(self.__attr_format__) != 0:
96 format = self.__attr_format__
97 ##!!!
98 none_vals = True
99 else:
100 return True
101 # cache the complex format...
102 cformat = {}
103 [ cformat.update({e[0]: e[1:]}) for e in format if type(e) not in (str, unicode) ]
104 ## # NOTE: both of the folowing are quite slow...
105 ## # cache the regex format...
106 ## rformat = []
107 ## # cache the predicate format...
108 pformat = []
109 if hasattr(self, '__check_attr_types__') and self.__check_attr_types__:
110 if name not in format:
111 if name in cformat:
112 # get data...
113 e = cformat[name]
114 etype = len(e) > 0 and type(e[0]) not in (str, unicode) and e[0] or ''
115 # check none_vals
116 if none_vals and hasattr(self, '__none_values__') and \
117 self.__none_values__ and value in self.__none_values__:
118 return True
119 # check type...
120 try:
121 if type(etype) in (str, unicode) or issubclass(type(value), etype):
122 return True
123 except TypeError:
124 # we get here if issubclass failse when
125 # comparing types with a function/lambda
126 pass
127 # check predicate....
128 # XXX (temporary??) the predicate can only be a
129 # function or a lambda...
130 ## if callable(etype):
131 if type(etype) in (types.LambdaType, types.FunctionType):
132 try:
133 if etype(value):
134 return True
135 except Exception, msg:
136 print '>>>', msg
137 # implement the side-effect...
138 if message != None:
139 if type(message) != list:
140 raise TypeError, 'message paramiter must be of type "list".'
141 message += [msg]
142 except:
143 pass
144 elif not strict:
145 return True
146 return False
147 # legacy only....
148 elif name not in format and name not in cformat:
149 return False
150 return True
151 # sloooow _checkarttrs version... (uses _isattrwritable in a
152 # loop...)
153 def _checkarttrs(self, attrs, errors=None, none_vals=False):
155 check if attribute dict given is compatible with format (see self._isattrwritable for details...)
157 NOTE: this function impements error reporting a side-effect.
159 all the the arguments that generate errors will be appended to the errors argument (if present).
161 NOTE: the argument "errors" must be of type list (if present).
162 NOTE: the "errors" argument will be modified during this functions execution.
164 # NOTE: this is very inefficient!!!
165 if errors == None:
166 for name, val in attrs.items():
167 if not self._isattrwritable(name, val, strict=(hasattr(self, '__strict_attr_format__') and self.__strict_attr_format__), none_vals=none_vals):
168 return False
169 elif type(errors) == list:
170 errors[:] = []
171 for name, val in attrs.items():
172 if not self._isattrwritable(name, val, strict=(hasattr(self, '__strict_attr_format__') and self.__strict_attr_format__), none_vals=none_vals):
173 errors += [name]
174 if len(errors) > 0:
175 return False
176 else:
177 raise TypeError, 'errors paramiter must be of type "list".'
178 return True
179 def update(self, attrs):
181 update object attributes.
183 NOTE: in strict mode this will disallow an update of non-existant
184 attributes.
186 # XXX comment the folowing two lines out if __setattr__ is used...
187 err = []
188 if not self._checkarttrs(attrs, errors=err):
189 raise AttributeError, 'can\'t update object %s, attribute format mismatch in %s.' % (self, err)
190 if self.__acl__ != None:
191 acl_setattr = self.__acl__.setattr
192 for n, v in attrs.items():
193 acl_setattr(self, n, v)
194 else:
195 for n, v in attrs.items():
196 setattr(self, n, v)
197 # the attribute interface....
198 ##!! test !!##
199 def __setattr__(self, name, val):
202 if not self._isattrwritable(name, val, strict=(hasattr(self, '__strict_attr_format__') and self.__strict_attr_format__)):
203 raise TypeError, 'attribute "%s" does not comply with the format of %s object.' % (name, self)
204 self.__dict__[name] = val
205 def __delattr__(self, name):
208 if hasattr(self, '__essential_attrs__') and self.__essential_attrs__ != None:
209 if name in self.__essential_attrs__ or \
210 name in [ attr[0] for attr in self.__essential_attrs__ if type(attr) not in (str, unicode) ]:
211 raise TypeError, 'can not remove essential attribute "%s" of %s object.' % (name, self)
212 del self.__dict__[name]
213 # introspection...
214 # TODO make this prittier....
215 def __help__(cls):
216 return cls.getattributetextformat()
217 __help__ = classmethod(__help__)
218 def getattributetextformat(cls):
220 this will return a text definition of the attr format of obj.
222 def _format_str(struct):
223 res = ''
224 for elem in struct:
225 if type(elem) is str:
226 name = elem
227 atype = 'any type'
228 doc = ''
229 elif type(elem) in (tuple, list):
230 name = elem[0]
231 atype = len(elem) > 1 \
232 and (type(elem[1]) not in (str, unicode) and elem[1].__name__ or False) \
233 or 'any type'
234 doc = (len(elem) > 1 and type(elem[1]) in (str, unicode) and elem[1] or '') \
235 + (len(elem) > 2 and type(elem[2]) in (str, unicode) and elem[2] or '')
236 res += ' %-30s : %s\n' % ('%s (%s)' % (name, atype), doc)
237 return res
238 # essential attrs:
239 res = 'Essential Attributes:\n'
240 if hasattr(cls, '__essential_attrs__') and cls.__essential_attrs__ != None:
241 r = _format_str(cls.__essential_attrs__)
242 res += r == '' and ' None\n' or r
243 else:
244 res += ' None\n'
245 res += '\n'
246 # attr format:
247 res += 'Attribute Format:\n'
248 if hasattr(cls, '__attr_format__') and cls.__attr_format__ != None:
249 r = _format_str(cls.__attr_format__)
250 res += r == '' and ' None\n' or r
251 else:
252 res += ' None\n'
253 return res + '\n'
254 getattributetextformat = classmethod(getattributetextformat)
255 def getattributeformat(cls, name=None):
258 def _format_dict(struct, target_name=None):
259 res = {}
260 for elem in struct:
261 if type(elem) is str:
262 name = elem
263 atype = 'any'
264 doc = ''
265 elif type(elem) in (tuple, list):
266 name = elem[0]
267 atype = len(elem) > 1 \
268 and (type(elem[1]) not in (str, unicode) and elem[1].__name__ or False) \
269 or 'any'
270 doc = (len(elem) > 1 and type(elem[1]) in (str, unicode) and elem[1] or '') \
271 + (len(elem) > 2 and type(elem[2]) in (str, unicode) and elem[2] or '')
272 # return type of requested attr...
273 if target_name != None and target_name == name:
274 return [name, atype, doc]
275 res[name] = (atype, doc)
276 if target_name != None:
277 # if name was not found....
278 return []
279 return res
280 # requested name...
281 if name != None:
282 res = []
283 if hasattr(cls, '__essential_attrs__') and cls.__essential_attrs__ != None:
284 res = _format_dict(cls.__essential_attrs__, name)
285 if res == [] and hasattr(cls, '__attr_format__') and cls.__attr_format__ != None:
286 res = _format_dict(cls.__attr_format__, name)
287 return res
288 # empty res...
289 res = {}
290 # essential attrs:
291 if hasattr(cls, '__essential_attrs__') and cls.__essential_attrs__ != None:
292 res['essential'] = _format_dict(cls.__essential_attrs__)
293 # attr format:
294 if hasattr(cls, '__attr_format__') and cls.__attr_format__ != None:
295 res['format'] = _format_dict(cls.__attr_format__)
296 return res
297 getattributeformat = classmethod(getattributeformat)
301 #=======================================================================
302 # vim:set ts=4 sw=4 nowrap :