1 #=======================================================================
3 __version__
= '''0.2.19'''
4 __sub_version__
= '''20040909170101'''
5 __copyright__
= '''(c) Alex A. Naanou 2003'''
8 #-----------------------------------------------------------------------
11 This module defines an interface definition and enforcement mechanism and
16 #-----------------------------------------------------------------------
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):
35 #-----------------------------------------------------------------------
36 #----------------------------------------------------------_Interface---
37 class _Interface(type, mapping
.Mapping
):
39 this is the interface metaclass.
41 # this will define the interface format...
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__
= (
65 def __init__(cls
, name
, bases
, ns
):
69 if not hasattr(cls
, '__format__'):
70 raise InterfaceError
, 'interface %s does not have a format defined.' % cls
71 # check consistency...
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
)
78 def __getitem__(cls
, name
):
82 if not hasattr(cls
, '__format__'):
83 raise InterfaceError
, 'interface %s does not have a format defined.' % cls
84 format
= cls
.__format
__
87 if hasattr(c
, '__format__') \
88 and c
.__format
__ != None \
89 and name
in c
.__format
__:
90 return c
.__format
__[name
]
92 # c was not a dict-like....
95 ##!!! is this correct ?
96 return super(_Interface
, cls
).__getitem
__(name
)
97 ## except AttributeError:
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
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
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
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...
127 del cls
.__format
__[name
]
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
]
136 raise KeyError, str(name
)
137 ## def __contains__(cls, name):
144 for c
in cls
.__mro
__:
145 if hasattr(c
, '__format__') \
146 and c
.__format
__ != None:
147 for k
in c
.__format
__.iterkeys():
153 def __isconsistent__(cls
, errors
=None):
156 allowed_props
= cls
.__attribute
_properties
__
159 props
= cls
.getattrproperty(name
)
161 if n
not in allowed_props
:
162 raise InterfaceError
, 'unknown option "%s".' % prop
166 if errors
in ([], None):
169 # interface specific (1st generation):
170 def getattrproperty(cls
, name
, prop
=None):
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).
181 allowed_props
= cls
.__attribute
_properties
__ + (None,)
182 if prop
not in allowed_props
:
183 raise InterfaceError
, 'unknown option "%s".' % prop
188 raise KeyError, str(name
)
190 res
= cls
[name
].copy()
191 # resolve the 'LIKE' prop...
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()
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
206 # XXX is there a better way to do this??? (use sets???)
207 for d
in visited
[1:]:
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
216 visited
+= [ext_format
.copy()]
218 ext_format
.update(res
)
221 res
.update(ext_format
)
226 ## raise InterfaceError, 'property "%s" is not defined for attr "%s".' % (prop, name)
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
261 the attribute definition format is as follows:
265 <opt-name>: <opt-value>
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
288 readable - this if False will prevent the attr from being
290 writable - this if False will prevent the attr from being
292 deleteable - this if False will prevent the attr from being
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
315 #-----------------------------------------------------------------------
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:
324 # get objects interface...
325 if type(obj
.__implemments
__) in (list, tuple):
326 return tuple(obj
.__implemments
__)
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
337 if interface
!= None:
340 format
= logictypes
.DictUnion(*getinterfaces(obj
)[::-1])
343 if not hasattr(obj
, 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
)
350 res
[k
] = getattr(obj
, k
)
354 #----------------------------------------------------------checkvalue---
355 def checkvalue(obj
, name
, value
, interface
=None):
357 this will check the correctness/compatibility of the obj, attr name
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
,)
366 format
= getinterfaces(obj
)
367 # get the effective inteface...
370 # find explicit name definition...
372 format
= i
.getattrproperty(name
)
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)...
381 raise InterfaceError
, 'no definition of attribute "%s" in %s.' % (name
, obj
)
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'])
388 if 'predicate' in format
and not format
['predicate'](value
):
389 raise InterfaceError
, 'predicate failed for "%s" attribute.' % name
393 #-----------------------------------------------------------checkattr---
394 # XXX use a different getattr mechanism as this might be blocked by the
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:
422 format
= logictypes
.DictUnion(*getinterfaces(obj
)[::-1])
424 return format
[name
].get('essential', True)
426 return format
['*'].get('essential', True)
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:
442 format
= logictypes
.DictUnion(*getinterfaces(obj
)[::-1])
444 return format
[name
].get('readable', True)
446 return format
['*'].get('readable', True)
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:
462 format
= logictypes
.DictUnion(*getinterfaces(obj
)[::-1])
464 return format
[name
].get('writable', True)
466 return format
['*'].get('writable', True)
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:
482 format
= logictypes
.DictUnion(*getinterfaces(obj
)[::-1])
484 return format
[name
].get('deletable', True) and not format
[name
].get('essential', False)
486 return format
['*'].get('deletable', True) and not format
[name
].get('essential', 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.
498 return checkvalue(obj
, name
, value
, interface
)
503 #--------------------------------------------------------iscompatible---
504 # this will check all the applicable predicates...
505 # XXX use a different getattr mechanism as this might be blocked by the
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.
515 return checkattr(obj
, name
, interface
)
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:
533 format
= logictypes
.DictUnion(*getinterfaces(obj
)[::-1])
537 v
= format
[n
].get('essential', False)
538 if v
not in (None, False):
541 if not hasattr(obj
, n
):
542 raise InterfaceError
, 'essential attribute "%s" missing from %s' % (n
, obj
)
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:
558 format
= logictypes
.DictUnion(*getinterfaces(obj
)[::-1])
559 o_attrs
= vars(obj
).copy()
561 # Q: which one of the folowing is faster??
563 if format
[n
].get('essential', False):
564 raise InterfaceError
, 'essential attribute "%s" missing from %s' % (n
, obj
)
569 if '*' not in format
:
570 raise InterfaceError
, 'excess attributes %s in object %s.' % (o_attrs
.keys(), obj
)
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:
591 format
= logictypes
.DictUnion(*getinterfaces(obj
)[::-1])
592 # if name is present...
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...
601 res
[n
] = format
[n
].get('doc', None)
606 #=======================================================================
607 # vim:set ts=4 sw=4 nowrap :