*** empty log message ***
[pli.git] / pli / interface / interface.py
blob057aa6835a82093f8860088a1848ff50d5f8c2f6
1 #=======================================================================
3 __version__ = '''0.2.19'''
4 __sub_version__ = '''20040909170101'''
5 __copyright__ = '''(c) Alex A. Naanou 2003'''
8 #-----------------------------------------------------------------------
10 __doc__ = '''\
11 This module defines an interface definition and enforcement mechanism and
12 classes.
13 '''
16 #-----------------------------------------------------------------------
18 import inspect
19 import types
21 import pli.functional as func
22 import pli.logictypes as logictypes
23 import pli.pattern.mixin.mapping as mapping
26 #-----------------------------------------------------------------------
27 #------------------------------------------------------InterfaceError---
28 class InterfaceError(Exception):
29 '''
30 '''
31 pass
35 #-----------------------------------------------------------------------
36 #----------------------------------------------------------_Interface---
37 class _Interface(type, mapping.Mapping):
38 '''
39 this is the interface metaclass.
40 '''
41 # this will define the interface format...
42 __format__ = None
43 # this if False will prevent the modification of the interface
44 # after it's definition... (default: False)
45 __interface_writable__ = False
46 # this if True will enable element deletion from base interfaces if
47 # it was not defined locally... (default: False)
48 __contagious_delete__ = False
50 # WARNING: do not change this unless you know what you are doing!
51 __attribute_properties__ = (
52 'type',
53 'default',
54 'predicate',
55 'essential',
56 'doc',
57 'handler',
58 'readable',
59 'writable',
60 'deleteable',
61 # special options...
62 'LIKE',
65 def __init__(cls, name, bases, ns):
66 '''
67 '''
68 # sanity checks....
69 if not hasattr(cls, '__format__'):
70 raise InterfaceError, 'interface %s does not have a format defined.' % cls
71 # check consistency...
72 errs = []
73 if not cls.__isconsistent__(errs):
74 errs[:] = dict.fromkeys([ e.__class__.__name__ + ': '+ str(e) for e in errs ]).keys()
75 raise InterfaceError, 'inconsistent interface definition for %s in:\n %s.' % (cls, '\n '.join(errs))
76 super(_Interface, cls).__init__(name, bases, ns)
77 # mapping specific:
78 def __getitem__(cls, name):
79 '''
80 '''
81 # sanity checks....
82 if not hasattr(cls, '__format__'):
83 raise InterfaceError, 'interface %s does not have a format defined.' % cls
84 format = cls.__format__
85 try:
86 for c in cls.__mro__:
87 if hasattr(c, '__format__') \
88 and c.__format__ != None \
89 and name in c.__format__:
90 return c.__format__[name]
91 except TypeError:
92 # c was not a dict-like....
93 pass
94 try:
95 ##!!! is this correct ?
96 return super(_Interface, cls).__getitem__(name)
97 ## except AttributeError:
98 ##!!!!
99 except:
100 raise KeyError, str(name)
101 def __setitem__(cls, name, value):
104 NOTE: this will only modify the current class (no base interface will change).
106 if hasattr(cls, '__interface_writable__') and not cls.__interface_writable__:
107 raise InterfaceError, 'the interface %s is not modifiable.' % cls
108 if '__format__' in cls.__dict__ and cls.__format__ != None:
109 cls.__format__[name] = value
110 return
111 cls.__format__ = {name: value}
112 def __delitem__(cls, name):
115 if hasattr(cls, '__interface_writable__') and not cls.__interface_writable__:
116 raise InterfaceError, 'the interface %s is not modifiable.' % cls
117 if name in cls:
118 # if name is not local to this interface...
119 if ('__format__' not in cls.__dict__ or name not in cls.__dict__['__format__']) \
120 and (not hasattr(cls, '__contagious_delete__') or not cls.__contagious_delete__):
121 raise InterfaceError, 'the interface %s is not modifiable.' % cls
122 # delete...
123 # this is safe as-is as we get here in two cases:
124 # 1. the name is local
125 # 2. we can delete the name form its container...
126 try:
127 del cls.__format__[name]
128 except KeyError:
129 for c in cls.__mro__[1:]:
130 if hasattr(c, '__format__') \
131 and c.__format__ != None \
132 and name in c.__format__:
133 del c.__format__[name]
134 return
135 else:
136 raise KeyError, str(name)
137 ## def __contains__(cls, name):
138 ## '''
139 ## '''
140 def __iter__(cls):
143 visited = []
144 for c in cls.__mro__:
145 if hasattr(c, '__format__') \
146 and c.__format__ != None:
147 for k in c.__format__.iterkeys():
148 if k in visited:
149 continue
150 visited += [k]
151 yield k
153 def __isconsistent__(cls, errors=None):
156 allowed_props = cls.__attribute_properties__
157 for name in cls:
158 try:
159 props = cls.getattrproperty(name)
160 for n in props:
161 if n not in allowed_props:
162 raise InterfaceError, 'unknown option "%s".' % prop
163 except Exception, e:
164 if errors != None:
165 errors += [e]
166 if errors in ([], None):
167 return True
168 return False
169 # interface specific (1st generation):
170 def getattrproperty(cls, name, prop=None):
173 returns:
174 None : if prop is given but not found.
175 val : if prop is given and found (might also be None).
176 dict : if name is found and prop not given.
178 NOTE: if a property is not defined for the attr None will be returned (this is the same as if its value was none).
180 ##!! REVISE !!##
181 allowed_props = cls.__attribute_properties__ + (None,)
182 if prop not in allowed_props:
183 raise InterfaceError, 'unknown option "%s".' % prop
184 if name not in cls:
185 if '*' in cls:
186 res = {}
187 else:
188 raise KeyError, str(name)
189 else:
190 res = cls[name].copy()
191 # resolve the 'LIKE' prop...
192 visited = [res]
193 while 'LIKE' in res:
194 if type(res['LIKE']) is str:
195 ext_format = cls[res['LIKE']].copy()
196 elif type(res['LIKE']) is dict:
197 ext_format = res['LIKE'].copy()
198 else:
199 raise TypeError, 'the argument of "LIKE" attribute option must '\
200 'either be of type str or dict (got: %s).' % type(res['LIKE'])
201 if res['LIKE'] == name or ext_format in visited:
202 # check for conflicts in the chain.... (a conflict is
203 # when a name is present more than once with different
204 # values).
205 v = visited[0]
206 # XXX is there a better way to do this??? (use sets???)
207 for d in visited[1:]:
208 for k in d:
209 if k != 'LIKE' and k in v and d[k] != v[k]:
210 raise InterfaceError, 'LIKE loop conflict in %s for attribute "%s".' % (cls, name)
211 if k not in allowed_props:
212 raise InterfaceError, 'unknown option "%s".' % k
213 v[k] = d[k]
214 del res['LIKE']
215 break
216 visited += [ext_format.copy()]
217 del res['LIKE']
218 ext_format.update(res)
219 # revise...
220 ## res = ext_format
221 res.update(ext_format)
222 if prop != None:
223 if prop in res:
224 return res[prop]
225 else:
226 ## raise InterfaceError, 'property "%s" is not defined for attr "%s".' % (prop, name)
227 return None
228 return res
229 # interface methods (2nd generation):
230 # TODO exception safe??????
231 def isessential(cls, name):
234 return cls.getattrproperty(name, 'essential') == True
235 def isreadable(cls, name):
238 return cls.getattrproperty(name, 'readable') in (True, None)
239 def iswritable(cls, name):
242 return cls.getattrproperty(name, 'writable') in (True, None)
243 def isdeletable(cls, name):
246 return cls.getattrproperty(name, 'deleteable') in (True, None) \
247 and cls.getattrproperty(name, 'essential') != True
250 #-----------------------------------------------------------Interface---
251 class Interface(object):
253 this is the basic interface class.
254 this provides a basic mechanism to define object attribute format.
256 NOTE: this only provides meens to define attribute format, as
257 methods are also attributes they can be checked using the
258 predicate mechanism.
261 the attribute definition format is as follows:
263 <attr-name> :
265 <opt-name>: <opt-value>
266 [...]
268 [...]
272 supported options:
273 type - value type or superclass.
274 default - this is the default value of the option.
275 predicate - this will get the option value as argument and
276 test its compliance (if the will return False
277 InterfaceError will be raised).
278 essential - this if true will guarantee the options'
279 existence in the created object.
281 doc - this is the attr documentation
283 handler - this is the alternative attribute handler.
284 this will take the option value and its old value
285 as arguments and its' return will replace the
286 original value.
288 readable - this if False will prevent the attr from being
289 read.
290 writable - this if False will prevent the attr from being
291 written.
292 deleteable - this if False will prevent the attr from being
293 removed.
295 special options:
296 LIKE - this states the attribute template. if this is
297 given all options not declared for this attribute
298 will be taken from the template.
299 the value is either the name of the attribute in
300 the current interface or a dict of the interface
301 attr definition format.
304 special attribute names:
305 '*' - this matches any attribute not explicitly given.
309 __metaclass__ = _Interface
311 __format__ = None
315 #-----------------------------------------------------------------------
316 # utility functions
317 #-------------------------------------------------------getinterfaces---
318 def getinterfaces(obj):
320 this will return a tuuple containing the supported by the object interfaces.
322 if not hasattr(obj, '__implemments__') or obj.__implemments__ is None:
323 return ()
324 # get objects interface...
325 if type(obj.__implemments__) in (list, tuple):
326 return tuple(obj.__implemments__)
327 else:
328 return (obj.__implemments__,)
331 #-------------------------------------------------------------getdata---
332 def getdata(obj, interface=None):
334 this will return a dict containing the data taken from the object in compliance
335 to the interface.
337 if interface != None:
338 format = interface
339 else:
340 format = logictypes.DictUnion(*getinterfaces(obj)[::-1])
341 res = {}
342 for k in format:
343 if not hasattr(obj, k):
344 format_k = format[k]
345 if 'default' in format_k:
346 res[k] = format_k['default']
347 elif format_k.get('essential', False):
348 raise InterfaceError, 'object %s does not have an essential attribute "%s".' % (obj, k)
349 else:
350 res[k] = getattr(obj, k)
351 return res
354 #----------------------------------------------------------checkvalue---
355 def checkvalue(obj, name, value, interface=None):
357 this will check the correctness/compatibility of the obj, attr name
358 and value triplit.
359 if all is well will return True else raise an exception.
361 NOTE: if the inteface is not given, the objects interface(s) is used.
363 if interface != None:
364 format = (interface,)
365 else:
366 format = getinterfaces(obj)
367 # get the effective inteface...
368 star = None
369 for i in format:
370 # find explicit name definition...
371 if name in i:
372 format = i.getattrproperty(name)
373 break
374 # find first '*'...
375 if star == None and '*' in i:
376 star = i.getattrproperty('*')
377 # if no explicit definition is found...
378 if type(format) is not dict:
379 # attr must be defined in interface (explicit or implicit)...
380 if star == None:
381 raise InterfaceError, 'no definition of attribute "%s" in %s.' % (name, obj)
382 format = star
383 # attr type...
384 if 'type' in format and not issubclass(type(value), format['type']):
385 raise InterfaceError, 'attribute type mismatch. "%s" attribute ' \
386 'must be of type %s (got: %s).' % (name, type(value), format['type'])
387 # attr predicate...
388 if 'predicate' in format and not format['predicate'](value):
389 raise InterfaceError, 'predicate failed for "%s" attribute.' % name
390 return True
393 #-----------------------------------------------------------checkattr---
394 # XXX use a different getattr mechanism as this might be blocked by the
395 # interface....
396 def checkattr(obj, name, interface=None):
398 this will check the correctness/compatibility of the obj, attr name pair.
399 if all is well will return True else raise an exception.
401 NOTE: the value to be used is taken from the objects attribute.
402 NOTE: if the inteface is not given, the objects interface(s) is used.
404 return checkvalue(obj, name, getattr(obj, name), interface)
408 #-----------------------------------------------------------------------
409 # attribute level functions...
410 #---------------------------------------------------------isessential---
411 def isessential(obj, name, interface=None):
413 this will return True if the name is essential, else False.
415 NOTE: if the object does not support interfaces and no explicit
416 interface was given this will return False.
417 NOTE: if the interface is not given, the objects interface(s) is used.
419 if interface != None:
420 format = interface
421 else:
422 format = logictypes.DictUnion(*getinterfaces(obj)[::-1])
423 if name in format:
424 return format[name].get('essential', True)
425 elif '*' in format:
426 return format['*'].get('essential', True)
427 return False
430 #----------------------------------------------------------isreadable---
431 def isreadable(obj, name, interface=None):
433 this will return True if the name is readable, else False.
435 NOTE: if the object does not support interfaces and no explicit
436 interface was given this will return True.
437 NOTE: if the interface is not given, the objects interface(s) is used.
439 if interface != None:
440 format = interface
441 else:
442 format = logictypes.DictUnion(*getinterfaces(obj)[::-1])
443 if name in format:
444 return format[name].get('readable', True)
445 elif '*' in format:
446 return format['*'].get('readable', True)
447 return False
450 #----------------------------------------------------------iswritable---
451 def iswritable(obj, name, interface=None):
453 this will return True if the name is writable, else False.
455 NOTE: if the object does not support interfaces and no explicit
456 interface was given this will return True.
457 NOTE: if the interface is not given, the objects interface(s) is used.
459 if interface != None:
460 format = interface
461 else:
462 format = logictypes.DictUnion(*getinterfaces(obj)[::-1])
463 if name in format:
464 return format[name].get('writable', True)
465 elif '*' in format:
466 return format['*'].get('writable', True)
467 return False
470 #---------------------------------------------------------isdeletable---
471 def isdeletable(obj, name, interface=None):
473 this will return True if the name is deletable, else False.
475 NOTE: if the object does not support interfaces and no explicit
476 interface was given this will return True.
477 NOTE: if the interface is not given, the objects interface(s) is used.
479 if interface != None:
480 format = interface
481 else:
482 format = logictypes.DictUnion(*getinterfaces(obj)[::-1])
483 if name in format:
484 return format[name].get('deletable', True) and not format[name].get('essential', False)
485 elif '*' in format:
486 return format['*'].get('deletable', True) and not format[name].get('essential', False)
487 return False
490 #---------------------------------------------------isvaluecompatible---
491 def isvaluecompatible(obj, name, value, interface=None):
493 this will return True if the obj, name and value triplet is valid, else False.
495 NOTE: if the interface is not given, the objects interface(s) is used.
497 try:
498 return checkvalue(obj, name, value, interface)
499 except:
500 return False
503 #--------------------------------------------------------iscompatible---
504 # this will check all the applicable predicates...
505 # XXX use a different getattr mechanism as this might be blocked by the
506 # interface....
507 def iscompatible(obj, name, interface=None):
509 this will return True if the obj, name pair is valid, else False.
511 NOTE: the value to be used is taken from the objects attribute.
512 NOTE: if the inteface is not given, the objects interface(s) is used.
514 try:
515 return checkattr(obj, name, interface)
516 except:
517 return False
521 #-----------------------------------------------------------------------
522 # object level functions...
523 #-----------------------------------------------------checkessentials---
524 def checkessentials(obj, interface=None):
526 this will check if obj contains all the essential attributes defined by the interface.
528 NOTE: if the inteface is not given, the objects interface(s) is used.
530 if interface != None:
531 format = interface
532 else:
533 format = logictypes.DictUnion(*getinterfaces(obj)[::-1])
534 res = {}
535 for n in format:
536 if n not in res:
537 v = format[n].get('essential', False)
538 if v not in (None, False):
539 res[n] = v
540 for n in res:
541 if not hasattr(obj, n):
542 raise InterfaceError, 'essential attribute "%s" missing from %s' % (n, obj)
543 checkattr(obj, n)
544 return True
547 #---------------------------------------------------------checkobject---
548 def checkobject(obj, interface=None):
550 this will check if the object complies to the interface will return
551 True if yes, else False.
553 NOTE: if the inteface is not given, the objects interface(s) is used.
555 if interface != None:
556 format = interface
557 else:
558 format = logictypes.DictUnion(*getinterfaces(obj)[::-1])
559 o_attrs = vars(obj).copy()
560 for n in format:
561 # Q: which one of the folowing is faster??
562 if n not in o_attrs:
563 if format[n].get('essential', False):
564 raise InterfaceError, 'essential attribute "%s" missing from %s' % (n, obj)
565 else:
566 chackattr(obj, n)
567 del o_attrs[n]
568 if len(o_attrs) > 0:
569 if '*' not in format:
570 raise InterfaceError, 'excess attributes %s in object %s.' % (o_attrs.keys(), obj)
571 for n in o_attrs:
572 chackattr(obj, n)
573 return True
577 #-----------------------------------------------------------------------
578 #--------------------------------------------------------------getdoc---
579 # Q: do we need type information here???
580 def getdoc(obj, name=None, interface=None):
582 this will return a dict containing the attr name and the coresponding
583 doc defined in the interface.
585 NOTE: if name is not present this will return all the docs for each attr defined...
586 NOTE: if the inteface is not given, the objects interface(s) is used.
588 if interface != None:
589 format = interface
590 else:
591 format = logictypes.DictUnion(*getinterfaces(obj)[::-1])
592 # if name is present...
593 if name != None:
594 if name in format:
595 return {name: format[name].get('doc', None)}
596 raise InterfaceError, 'attribute "%s" is not defined in the interface for %s.' % (name, obj)
597 # if name is not present...
598 res = {}
599 for n in format:
600 if n not in res:
601 res[n] = format[n].get('doc', None)
602 return res
606 #=======================================================================
607 # vim:set ts=4 sw=4 nowrap :