*** empty log message ***
[pli.git] / pli / interface / interface.py
blob5bf4203acf8d9481b474fa523ef1f0c79417338d
1 #=======================================================================
3 __version__ = '''0.2.19'''
4 __sub_version__ = '''20040914013450'''
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
20 import sys
22 import pli.functional as func
23 import pli.logictypes as logictypes
24 import pli.pattern.mixin.mapping as mapping
27 #-----------------------------------------------------------------------
28 # TODO write an InterfaceUnion class.... (e.g. an interface composed of
29 # several interfaces, that will support the "interface" interface)
32 #------------------------------------------------------InterfaceError---
33 class InterfaceError(Exception):
34 '''
35 '''
36 pass
40 #-----------------------------------------------------------------------
41 #-----------------------------------------------------_BasicInterface---
42 class _BasicInterface(type, mapping.Mapping):
43 '''
44 this is the interface metaclass.
45 '''
46 # this will define the interface format...
47 __format__ = None
48 # this if False will prevent the modification of the interface
49 # after it's definition... (default: False)
50 __interface_writable__ = False
51 # this if True will enable element deletion from base interfaces if
52 # it was not defined locally... (default: False)
53 __contagious_delete__ = False
55 # WARNING: do not change this unless you know what you are doing!
56 __attribute_properties__ = (
57 'type',
58 'default',
59 'predicate',
60 'essential',
61 'doc',
62 'handler',
63 'readable',
64 'writable',
65 'deleteable',
66 # special options...
67 'LIKE',
70 def __init__(cls, name, bases, ns):
71 '''
72 '''
73 # sanity checks....
74 if not hasattr(cls, '__format__'):
75 raise InterfaceError, 'interface %s does not have a format defined.' % cls
76 # check consistency...
77 errs = []
78 if not cls.__isconsistent__(errs):
79 errs[:] = dict.fromkeys([ e.__class__.__name__ + ': '+ str(e) for e in errs ]).keys()
80 raise InterfaceError, 'inconsistent interface definition for %s in:\n %s.' % (cls, '\n '.join(errs))
81 super(_BasicInterface, cls).__init__(name, bases, ns)
82 # mapping specific:
83 def __getitem__(cls, name):
84 '''
85 '''
86 # sanity checks....
87 if not hasattr(cls, '__format__'):
88 raise InterfaceError, 'interface %s does not have a format defined.' % cls
89 format = cls.__format__
90 try:
91 for c in cls.__mro__:
92 if hasattr(c, '__format__') \
93 and c.__format__ != None \
94 and name in c.__format__:
95 return c.__format__[name]
96 except TypeError:
97 # c was not a dict-like....
98 pass
99 try:
100 ##!!! is this correct ?
101 return super(_BasicInterface, cls).__getitem__(name)
102 ## except AttributeError:
103 ##!!!!
104 except:
105 raise KeyError, str(name)
106 def __setitem__(cls, name, value):
109 NOTE: this will only modify the current class (no base interface will change).
111 if hasattr(cls, '__interface_writable__') and not cls.__interface_writable__:
112 raise InterfaceError, 'the interface %s is not modifiable.' % cls
113 if '__format__' in cls.__dict__ and cls.__format__ != None:
114 cls.__format__[name] = value
115 return
116 cls.__format__ = {name: value}
117 def __delitem__(cls, name):
120 if hasattr(cls, '__interface_writable__') and not cls.__interface_writable__:
121 raise InterfaceError, 'the interface %s is not modifiable.' % cls
122 if name in cls:
123 # if name is not local to this interface...
124 if ('__format__' not in cls.__dict__ or name not in cls.__dict__['__format__']) \
125 and (not hasattr(cls, '__contagious_delete__') or not cls.__contagious_delete__):
126 raise InterfaceError, 'the interface %s is not modifiable.' % cls
127 # delete...
128 # this is safe as-is as we get here in two cases:
129 # 1. the name is local
130 # 2. we can delete the name form its container...
131 try:
132 del cls.__format__[name]
133 except KeyError:
134 for c in cls.__mro__[1:]:
135 if hasattr(c, '__format__') \
136 and c.__format__ != None \
137 and name in c.__format__:
138 del c.__format__[name]
139 return
140 else:
141 raise KeyError, str(name)
142 ## def __contains__(cls, name):
143 ## '''
144 ## '''
145 def __iter__(cls):
148 visited = []
149 for c in cls.__mro__:
150 if hasattr(c, '__format__') \
151 and c.__format__ != None:
152 for k in c.__format__.iterkeys():
153 if k in visited:
154 continue
155 visited += [k]
156 yield k
157 # interface specific (1st generation):
158 def __isconsistent__(cls, errors=None):
161 allowed_props = cls.__attribute_properties__
162 for name in cls:
163 try:
164 props = cls.getattrproperty(name)
165 for n in props:
166 if n not in allowed_props:
167 raise InterfaceError, 'unknown option "%s".' % prop
168 except Exception, e:
169 if errors != None:
170 errors += [e]
171 if errors in ([], None):
172 return True
173 return False
174 def getattrproperty(cls, name, prop=None):
177 returns:
178 None : if prop is given but not found.
179 val : if prop is given and found (might also be None).
180 dict : if name is found and prop not given.
182 NOTE: if a property is not defined for the attr None will be returned (this is the same as if its value was none).
184 ##!! REVISE !!##
185 allowed_props = cls.__attribute_properties__ + (None,)
186 if prop not in allowed_props:
187 raise InterfaceError, 'unknown option "%s".' % prop
188 if name not in cls:
189 if '*' in cls:
190 res = {}
191 else:
192 raise KeyError, str(name)
193 else:
194 res = cls[name].copy()
195 # resolve the 'LIKE' prop...
196 visited = [res]
197 while 'LIKE' in res:
198 if type(res['LIKE']) is str:
199 ext_format = cls[res['LIKE']].copy()
200 elif type(res['LIKE']) is dict:
201 ext_format = res['LIKE'].copy()
202 else:
203 raise TypeError, 'the argument of "LIKE" attribute option must '\
204 'either be of type str or dict (got: %s).' % type(res['LIKE'])
205 if res['LIKE'] == name or ext_format in visited:
206 # check for conflicts in the chain.... (a conflict is
207 # when a name is present more than once with different
208 # values).
209 v = visited[0]
210 # XXX is there a better way to do this??? (use sets???)
211 for d in visited[1:]:
212 for k in d:
213 if k != 'LIKE' and k in v and d[k] != v[k]:
214 raise InterfaceError, 'LIKE loop conflict in %s for attribute "%s".' % (cls, name)
215 if k not in allowed_props:
216 raise InterfaceError, 'unknown option "%s".' % k
217 v[k] = d[k]
218 del res['LIKE']
219 break
220 visited += [ext_format.copy()]
221 del res['LIKE']
222 ext_format.update(res)
223 # revise...
224 ## res = ext_format
225 res.update(ext_format)
226 if prop != None:
227 if prop in res:
228 return res[prop]
229 else:
230 ## raise InterfaceError, 'property "%s" is not defined for attr "%s".' % (prop, name)
231 return None
232 return res
235 #----------------------------------------------------------_Interface---
236 class _Interface(_BasicInterface):
239 # interface methods (2nd generation):
240 # TODO exception safe??????
241 def isessential(cls, name):
244 return cls.getattrproperty(name, 'essential') == True
245 def isreadable(cls, name):
248 return cls.getattrproperty(name, 'readable') in (True, None)
249 def iswritable(cls, name):
252 return cls.getattrproperty(name, 'writable') in (True, None)
253 def isdeletable(cls, name):
256 return cls.getattrproperty(name, 'deleteable') in (True, None) \
257 and cls.getattrproperty(name, 'essential') != True
260 #-----------------------------------------------------------Interface---
261 class Interface(object):
263 this is the basic interface class.
264 this provides a basic mechanism to define object attribute format.
266 NOTE: this only provides meens to define attribute format, as
267 methods are also attributes they can be checked using the
268 predicate mechanism.
271 the attribute definition format is as follows:
273 <attr-name> :
275 <opt-name>: <opt-value>
276 [...]
278 [...]
282 supported options:
283 type - value type or superclass.
284 default - this is the default value of the option.
285 predicate - this will get the option value as argument and
286 test its compliance (if the will return False
287 InterfaceError will be raised).
288 essential - this if true will guarantee the options'
289 existence in the created object.
291 doc - this is the attr documentation
293 handler - this is the alternative attribute handler.
294 this will take the object, attr name and option
295 value as arguments and its' return will replace
296 the original value.
297 NOTE: this can be called when the object is not
298 fully initialized, thus no assumptions about
299 object state should be made.
300 NOTE: it is not recommended for this to have side
301 effects as the handler call is not garaneed
302 to precede an attribute write.
304 readable - this if False will prevent the attr from being
305 read.
306 writable - this if False will prevent the attr from being
307 written.
308 deleteable - this if False will prevent the attr from being
309 removed.
311 special options:
312 LIKE - this states the attribute template. if this is
313 given all options not declared for this attribute
314 will be taken from the template.
315 the value is either the name of the attribute in
316 the current interface or a dict of the interface
317 attr definition format.
320 special attribute names:
321 '*' - this matches any attribute not explicitly given.
325 __metaclass__ = _Interface
327 __format__ = None
331 #-----------------------------------------------------------------------
332 # utility functions
333 #-------------------------------------------------------getinterfaces---
334 def getinterfaces(obj):
336 this will return a tuuple containing the supported by the object interfaces.
338 if not hasattr(obj, '__implemments__') or obj.__implemments__ is None:
339 return ()
340 # get objects interface...
341 if type(obj.__implemments__) in (list, tuple):
342 return tuple(obj.__implemments__)
343 else:
344 return (obj.__implemments__,)
347 #-------------------------------------------------------------getdata---
348 # XXX should types be checked here???
349 def getdata(obj, interface=None):
351 this will return a dict containing the data taken from the object in compliance
352 to the interface.
354 if interface != None:
355 format = interface
356 else:
357 format = logictypes.DictUnion(*getinterfaces(obj)[::-1])
358 res = {}
359 ## star = format.get('*', False) != False or False
360 for k in format:
361 if not hasattr(obj, k):
362 if k == '*':
363 continue
364 format_k = format[k]
365 if 'default' in format_k:
366 res[k] = format_k['default']
367 elif format_k.get('essential', False):
368 raise InterfaceError, 'object %s does not have an essential attribute "%s".' % (obj, k)
369 else:
370 res[k] = getattr(obj, k)
371 if '*' in format:
372 ##!!! HACK !!!##
373 for k in [ n for n in object.__getattribute__(obj, '__dict__') if n not in format ]:
374 res[k] = getattr(obj, k)
375 return res
378 #----------------------------------------------------------checkvalue---
379 def checkvalue(obj, name, value, interface=None):
381 this will check the correctness/compatibility of the obj, attr name
382 and value triplit.
383 if all is well will return True else raise an exception.
385 NOTE: if the inteface is not given, the objects interface(s) is used.
387 if interface != None:
388 format = (interface,)
389 else:
390 format = getinterfaces(obj)
391 # get the effective inteface...
392 star = None
393 for i in format:
394 # find explicit name definition...
395 if name in i:
396 format = i.getattrproperty(name)
397 break
398 # find first '*'...
399 if star == None and '*' in i:
400 star = i.getattrproperty('*')
401 # if no explicit definition is found...
402 if type(format) is not dict:
403 # attr must be defined in interface (explicit or implicit)...
404 if star == None:
405 raise InterfaceError, 'no definition of attribute "%s" in %s.' % (name, obj)
406 format = star
407 # attr type...
408 if 'type' in format and not issubclass(type(value), format['type']):
409 raise InterfaceError, 'attribute type mismatch. "%s" attribute ' \
410 'must be of type %s (got: %s).' % (name, type(value), format['type'])
411 # attr predicate...
412 if 'predicate' in format and not format['predicate'](value):
413 raise InterfaceError, 'predicate failed for "%s" attribute.' % name
414 return True
417 #-----------------------------------------------------------checkattr---
418 # XXX use a different getattr mechanism as this might be blocked by the
419 # interface....
420 def checkattr(obj, name, interface=None):
422 this will check the correctness/compatibility of the obj, attr name pair.
423 if all is well will return True else raise an exception.
425 NOTE: the value to be used is taken from the objects attribute.
426 NOTE: if the inteface is not given, the objects interface(s) is used.
428 return checkvalue(obj, name, getattr(obj, name), interface)
432 #-----------------------------------------------------------------------
433 # attribute level functions...
434 #------------------------------------------------------------_setattr---
435 def getvalue(obj, name, value, interface=None):
437 this will create an attribute value...
439 if interface != None:
440 format = interface
441 else:
442 format = logictypes.DictUnion(*getinterfaces(obj)[::-1])
443 handler = format.get(name, {}).get('handler', False)
444 if handler != False:
445 return handler(obj, name, value)
446 return value
450 #---------------------------------------------------------isessential---
451 def isessential(obj, name, interface=None):
453 this will return True if the name is essential, else False.
455 NOTE: if the object does not support interfaces and no explicit
456 interface was given this will return False.
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('essential', True)
465 elif '*' in format:
466 return format['*'].get('essential', True)
467 return False
470 #----------------------------------------------------------isreadable---
471 def isreadable(obj, name, interface=None):
473 this will return True if the name is readable, 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('readable', True)
485 elif '*' in format:
486 return format['*'].get('readable', True)
487 return False
490 #----------------------------------------------------------iswritable---
491 def iswritable(obj, name, interface=None):
493 this will return True if the name is writable, else False.
495 NOTE: if the object does not support interfaces and no explicit
496 interface was given this will return True.
497 NOTE: if the interface is not given, the objects interface(s) is used.
499 if interface != None:
500 format = interface
501 else:
502 format = logictypes.DictUnion(*getinterfaces(obj)[::-1])
503 if name in format:
504 return format[name].get('writable', True)
505 elif '*' in format:
506 return format['*'].get('writable', True)
507 return False
510 #---------------------------------------------------------isdeletable---
511 def isdeletable(obj, name, interface=None):
513 this will return True if the name is deletable, else False.
515 NOTE: if the object does not support interfaces and no explicit
516 interface was given this will return True.
517 NOTE: if the interface is not given, the objects interface(s) is used.
519 if interface != None:
520 format = interface
521 else:
522 format = logictypes.DictUnion(*getinterfaces(obj)[::-1])
523 if name in format:
524 return format[name].get('deletable', True) and not format[name].get('essential', False)
525 elif '*' in format:
526 return format['*'].get('deletable', True) and not format[name].get('essential', False)
527 return False
530 #---------------------------------------------------isvaluecompatible---
531 def isvaluecompatible(obj, name, value, interface=None):
533 this will return True if the obj, name and value triplet is valid, else False.
535 NOTE: if the interface is not given, the objects interface(s) is used.
537 try:
538 return checkvalue(obj, name, value, interface)
539 except:
540 return False
543 #--------------------------------------------------------iscompatible---
544 # this will check all the applicable predicates...
545 # XXX use a different getattr mechanism as this might be blocked by the
546 # interface....
547 def iscompatible(obj, name, interface=None):
549 this will return True if the obj, name pair is valid, else False.
551 NOTE: the value to be used is taken from the objects attribute.
552 NOTE: if the inteface is not given, the objects interface(s) is used.
554 try:
555 return checkattr(obj, name, interface)
556 except:
557 return False
561 #-----------------------------------------------------------------------
562 # object level functions...
563 #-----------------------------------------------------checkessentials---
564 def checkessentials(obj, interface=None):
566 this will check if obj contains all the essential attributes defined by the interface.
568 NOTE: if the inteface is not given, the objects interface(s) is used.
570 if interface != None:
571 format = interface
572 else:
573 format = logictypes.DictUnion(*getinterfaces(obj)[::-1])
574 res = {}
575 for n in format:
576 if n not in res:
577 v = format[n].get('essential', False)
578 if v not in (None, False):
579 res[n] = v
580 for n in res:
581 if not hasattr(obj, n):
582 raise InterfaceError, 'essential attribute "%s" missing from %s' % (n, obj)
583 checkattr(obj, n)
584 return True
587 #---------------------------------------------------------checkobject---
588 def checkobject(obj, interface=None):
590 this will check if the object complies to the interface will return
591 True if yes, else False.
593 NOTE: if the inteface is not given, the objects interface(s) is used.
595 if interface != None:
596 format = interface
597 else:
598 format = logictypes.DictUnion(*getinterfaces(obj)[::-1])
599 o_attrs = vars(obj).copy()
600 for n in format:
601 # Q: which one of the folowing is faster??
602 if n not in o_attrs:
603 if format[n].get('essential', False):
604 raise InterfaceError, 'essential attribute "%s" missing from %s' % (n, obj)
605 else:
606 chackattr(obj, n)
607 del o_attrs[n]
608 if len(o_attrs) > 0:
609 if '*' not in format:
610 raise InterfaceError, 'excess attributes %s in object %s.' % (o_attrs.keys(), obj)
611 for n in o_attrs:
612 chackattr(obj, n)
613 return True
617 #-----------------------------------------------------------------------
618 #--------------------------------------------------------------getdoc---
619 # Q: do we need type information here???
620 def getdoc(obj, name=None, interface=None):
622 this will return a dict containing the attr name and the coresponding
623 doc defined in the interface.
625 NOTE: if name is not present this will return all the docs for each attr defined...
626 NOTE: if the inteface is not given, the objects interface(s) is used.
628 if interface != None:
629 format = interface
630 else:
631 format = logictypes.DictUnion(*getinterfaces(obj)[::-1])
632 # if name is present...
633 if name != None:
634 if name in format:
635 return {name: format[name].get('doc', None)}
636 raise InterfaceError, 'attribute "%s" is not defined in the interface for %s.' % (name, obj)
637 # if name is not present...
638 res = {}
639 for n in format:
640 if n not in res:
641 res[n] = format[n].get('doc', None)
642 return res
646 #-----------------------------------------------------------------------
647 #---------------------------------------------------------implemments---
648 def implemments(interface, depth=1):
651 f_locals = sys._getframe(depth).f_locals
652 res = f_locals.get('__implemments__', None)
653 if res == None:
654 f_locals['__implemments__'] = interface
655 return
656 # some sort of interface already exists...
657 if type(res) is tuple:
658 if type(interface) is tuple:
659 res = interface + res
660 else:
661 res = (interface,) + res
662 else:
663 if type(interface) is tuple:
664 res = interface + (res,)
665 else:
666 res = (interface, res)
667 f_locals['__implemments__'] = res
671 #=======================================================================
672 # vim:set ts=4 sw=4 nowrap :