1 #=======================================================================
3 __version__
= '''0.2.01'''
4 __sub_version__
= '''20040829143248'''
5 __copyright__
= '''(c) Alex A. Naanou 2003'''
8 #-----------------------------------------------------------------------
11 This module defines an interface definition and enforcement mechanism and
16 #-----------------------------------------------------------------------
18 import pli
.pattern
.mixin
.mapping
as mapping
19 import pli
.logictypes
as logictypes
22 #-----------------------------------------------------------------------
23 #------------------------------------------------------InterfaceError---
24 class InterfaceError(Exception):
31 #-----------------------------------------------------------------------
32 #----------------------------------------------------------_Interface---
33 class _Interface(type, mapping
.Mapping
):
36 # this will define the interface format...
38 # this if False will prevent the modification of the interface
39 # after it's definition... (default: False)
40 __interface_writable__
= False
41 # this if True will enable element deletion from base interfaces if
42 # it was not defined locally... (default: False)
43 __contagious_delete__
= False
45 # WARNING: do not change this unless you know what you are doing!
46 __attribute_properties__
= (
60 def __init__(cls
, name
, bases
, ns
):
64 if not hasattr(cls
, '__format__'):
65 raise InterfaceError
, 'interface %s does not have a format defined.' % cls
66 # check consistency...
68 if not cls
.__isconsistent
__(errs
):
69 errs
[:] = dict.fromkeys([ e
.__class
__.__name
__ + ': '+ str(e
) for e
in errs
]).keys()
70 raise InterfaceError
, 'inconsistent interface definition for %s in:\n %s.' % (cls
, '\n '.join(errs
))
71 super(_Interface
, cls
).__init
__(name
, bases
, ns
)
73 def __getitem__(cls
, name
):
77 if not hasattr(cls
, '__format__'):
78 raise InterfaceError
, 'interface %s does not have a format defined.' % cls
79 format
= cls
.__format
__
80 ## if format != None and name in format:
81 ## return format[name]
83 ## for c in cls.__mro__[1:]:
85 if hasattr(c
, '__format__') \
86 and c
.__format
__ != None \
87 and name
in c
.__format
__:
88 return c
.__format
__[name
]
90 # c was not a dict-like....
93 return super(_Interface
, cls
).__getitem
__(name
)
94 except AttributeError:
95 raise KeyError, str(name
)
96 def __setitem__(cls
, name
, value
):
99 NOTE: this will only modify the current class (no base interface will change).
101 if hasattr(cls
, '__interface_writable__') and not cls
.__interface
_writable
__:
102 raise InterfaceError
, 'the interface %s is not modifiable.' % cls
103 if '__format__' in cls
.__dict
__ and cls
.__format
__ != None:
104 cls
.__format
__[name
] = value
106 cls
.__format
__ = {name
: value
}
107 def __delitem__(cls
, name
):
110 if hasattr(cls
, '__interface_writable__') and not cls
.__interface
_writable
__:
111 raise InterfaceError
, 'the interface %s is not modifiable.' % cls
113 # if name is not local to this interface...
114 if ('__format__' not in cls
.__dict
__ or name
not in cls
.__dict
__['__format__']) \
115 and (not hasattr(cls
, '__contagious_delete__') or not cls
.__contagious
_delete
__):
116 raise InterfaceError
, 'the interface %s is not modifiable.' % cls
118 # this is safe as-is as we get here in two cases:
119 # 1. the name is local
120 # 2. we can delete the name form its container...
122 del cls
.__format
__[name
]
124 for c
in cls
.__mro
__[1:]:
125 if hasattr(c
, '__format__') \
126 and c
.__format
__ != None \
127 and name
in c
.__format
__:
128 del c
.__format
__[name
]
131 raise KeyError, str(name
)
132 ## def __contains__(cls, name):
139 for c
in cls
.__mro
__:
140 if hasattr(c
, '__format__') \
141 and c
.__format
__ != None:
142 for k
in c
.__format
__.iterkeys():
148 def __isconsistent__(cls
, errors
=None):
153 props
= cls
.getattrproperty(name
)
157 if errors
in ([], None):
160 # interface specific (1st generation):
161 def getattrproperty(cls
, name
, prop
=None):
165 None : if prop is given but not found.
166 val : if prop is given and found (might also be None).
167 dict : if name is found and prop not given.
169 NOTE: if a property is not defined for the attr None will be returned (this is the same as if its value was none).
171 allowed_props
= cls
.__attribute
_properties
__ + (None,)
172 if prop
not in allowed_props
:
173 raise InterfaceError
, 'unknown option "%s".' % prop
178 raise KeyError, str(name
)
180 res
= cls
[name
].copy()
181 # resolve the 'LIKE' prop...
184 if type(res
['LIKE']) is str:
185 ext_format
= cls
[res
['LIKE']].copy()
186 elif type(res
['LIKE']) is dict:
187 ext_format
= res
['LIKE'].copy()
189 raise TypeError, 'the argument of "LIKE" attribute option must '\
190 'either be of type str or dict (got: %s).' % type(res
['LIKE'])
191 if res
['LIKE'] == name
or ext_format
in visited
:
192 # check for conflicts in the chain.... (a conflict is
193 # when a name is present more than once with different
196 # XXX is there a better way to do this??? (use sets???)
197 for d
in visited
[1:]:
199 if k
!= 'LIKE' and k
in v
and d
[k
] != v
[k
]:
200 raise InterfaceError
, 'LIKE loop conflict in %s for attribute "%s".' % (cls
, name
)
201 if k
not in allowed_props
:
202 raise InterfaceError
, 'unknown option "%s".' % k
206 visited
+= [ext_format
.copy()]
208 ext_format
.update(res
)
211 res
.update(ext_format
)
216 ## raise InterfaceError, 'property "%s" is not defined for attr "%s".' % (prop, name)
219 # interface methods (2nd generation):
220 # TODO exception safe??????
221 def isessential(cls
, name
):
224 return cls
.getattrproperty(name
, 'essential') == True
225 def isreadable(cls
, name
):
228 return cls
.getattrproperty(name
, 'readable') in (True, None)
229 def iswritable(cls
, name
):
232 return cls
.getattrproperty(name
, 'writable') in (True, None)
233 def isdeletable(cls
, name
):
236 return cls
.getattrproperty(name
, 'deleteable') in (True, None) \
237 and cls
.getattrproperty(name
, 'essential') != True
242 #-----------------------------------------------------------Interface---
243 class Interface(object):
245 XXX write more docs...
247 the attribute definition format is as follows:
251 <opt-name>: <opt-value>
259 type - value type or superclass.
260 default - this is the default value of the option.
261 predicate - this will get the option value as argument and
262 test its compliance (if the will return False
263 InterfaceError will be raised).
264 essential - this if true will guarantee the options'
265 existence in the created object.
267 doc - this is the attr documentation
269 handler - this is the alternative attribute handler.
270 this will take the option value and its old value
271 as arguments and its' return will replace the
274 readable - this if False will prevent the attr from being
276 writable - this if False will prevent the attr from being
278 deleteable - this if False will prevent the attr from being
282 LIKE - this states the attribute template. if this is
283 given all options not declared for this attribute
284 will be taken from the template.
285 the value is either the name of the attribute in
286 the current interface or a dict of the interface
287 attr definition format.
290 special attribute names:
291 '*' - this matches any attribute not explicitly given.
295 __metaclass__
= _Interface
301 #-----------------------------------------------------------------------
303 #-------------------------------------------------------getinterfaces---
304 def getinterfaces(obj
):
306 this will return a tuuple containing the supported by the object interfaces.
308 if not hasattr(obj
, '__implemments__') or obj
.__implemments
__ is None:
310 # get objects interface...
311 if type(obj
.__implemments
__) in (list, tuple):
312 return tuple(obj
.__implemments
__)
314 return (obj
.__implemments
__,)
317 #----------------------------------------------------------checkvalue---
318 def checkvalue(obj
, name
, value
, interface
=None):
321 if interface
!= None:
322 format
= (interface
,)
324 format
= getinterfaces(obj
)
325 # get the effective inteface...
328 # find explicit name definition...
330 format
= i
.getattrproperty(name
)
333 if star
!= None and '*' in i
:
334 star
= i
.getattrproperty('*')
335 # if no explicit definition is found...
336 if type(format
) is not dict:
337 # attr must be defined in interface (explicit or implicit)...
339 raise InterfaceError
, 'no definition of attribute "%s" in %s.' % (name
, obj
)
342 if 'type' in format
and not issubclass(type(value
), format
['type']):
343 raise InterfaceError
, 'attribute type mismatch. "%s" attribute ' \
344 'must be of type %s (got: %s).' % (name
, type(value
), format
['type'])
346 if 'predicate' in format
and not format
['predicate'](value
):
347 raise InterfaceError
, 'predicate failed for "%s" attribute.' % name
351 #-----------------------------------------------------------checkattr---
352 # XXX use a different getattr mechanism as this might be blocked by the
354 def checkattr(obj
, name
, interface
=None):
357 return checkvalue(obj
, name
, getattr(obj
, name
), interface
)
361 #-----------------------------------------------------------------------
362 # attribute level functions...
363 # Q: should there be additional logic to determine for example
364 # attr readability for an object not supporting interfaces...
365 #---------------------------------------------------------isessential---
366 def isessential(obj
, name
, interface
=None):
369 NOTE: if the object does not support interfaces and no explicit
370 interface was given this will return False.
372 if interface
!= None:
373 format
= (interface
,)
375 format
= getinterfaces(obj
)
376 return True in [ i
.isessential(name
) for i
in fromat
]
379 #----------------------------------------------------------isreadable---
380 def isreadable(obj
, name
, interface
=None):
383 NOTE: if the object does not support interfaces and no explicit
384 interface was given this will return True.
386 if interface
!= None:
387 format
= (interface
,)
389 format
= getinterfaces(obj
)
390 return False not in [ i
.isreadable(name
) for i
in fromat
]
393 #----------------------------------------------------------iswritable---
394 def iswritable(obj
, name
, interface
=None):
397 NOTE: if the object does not support interfaces and no explicit
398 interface was given this will return True.
400 if interface
!= None:
401 format
= (interface
,)
403 format
= getinterfaces(obj
)
404 return False not in [ i
.iswritable(name
) for i
in fromat
]
407 #---------------------------------------------------------isdeletable---
408 def isdeletable(obj
, name
, interface
=None):
411 NOTE: if the object does not support interfaces and no explicit
412 interface was given this will return True.
414 if interface
!= None:
415 format
= (interface
,)
417 format
= getinterfaces(obj
)
418 return False not in [ i
.isdeletable(name
) for i
in fromat
]
421 ###----------------------------------------------------istypecompatible---
423 ##def istypecompatible(obj, name, interface=None):
429 ###-----------------------------------------------------------isdefault---
430 ##def isdefault(obj, name, interface=None):
436 #---------------------------------------------------isvaluecompatible---
437 def isvaluecompatible(obj
, name
, value
, interface
=None):
441 return checkvalue(obj
, name
, value
, interface
)
446 #--------------------------------------------------------iscompatible---
447 # this will check all the applicable predicates...
448 # XXX use a different getattr mechanism as this might be blocked by the
450 def iscompatible(obj
, name
, interface
=None):
454 return checkattr(obj
, name
, interface
)
460 #-----------------------------------------------------------------------
461 # object level functions...
462 #-----------------------------------------------------checkessentials---
463 def checkessentials(obj
, interface
=None):
466 if interface
!= None:
469 format
= logictypes
.DictUnion(*getinterfaces(obj
)[::-1])
473 v
= format
[n
].get('essential', False)
474 if v
not in (None, False):
477 if not hasattr(obj
, n
):
478 raise InterfaceError
, 'essential attribute "%s" missing from %s' % (n
, obj
)
483 #---------------------------------------------------------checkobject---
484 def checkobject(obj
, interface
=None):
487 if interface
!= None:
490 format
= logictypes
.DictUnion(*getinterfaces(obj
)[::-1])
491 o_attrs
= vars(obj
).copy()
493 # Q: which one of the folowing is faster??
495 ## if isessential(obj, n):
496 if format
[n
].get('essential', False):
497 raise InterfaceError
, 'essential attribute "%s" missing from %s' % (n
, obj
)
502 if '*' not in format
:
503 raise InterfaceError
, 'excess attributes %s in object %s.' % (o_attrs
.keys(), obj
)
510 #-----------------------------------------------------------------------
511 #--------------------------------------------------------------getdoc---
513 def getdoc(obj
, name
=None, interface
=None):
515 this will return a dict containing the attr name and the coresponding
516 doc defined in the interface.
517 if name is not present this will return all the docs for each attr defined...
519 if interface
!= None:
522 format
= logictypes
.DictUnion(*getinterfaces(obj
)[::-1])
523 # if name is present...
526 return {name
: format
[n
].get('essential', None)}
527 raise InterfaceError
, 'attribute "%s" is not defined in the interface for %s.' % (name
, obj
)
528 # if name is not present...
530 # this is bad as getattrproperty is called for each attr in each
531 # interface... (see below for a more eficient variant...)
532 #for i in format[::-1]:
533 # res.update(dict([ (n, i.getattrproperty(n, 'doc')) for n in i ]))
534 # NOTE: this is faster in the above variant... (complexity is less
535 # for cases where there are overlaping interfaces...)
536 # though this might be slightly slower due to the nested
541 res
[n
] = i
.getattrproperty(n
, 'doc')
546 #-----------------------------------------------------------------------
547 #-------------------------------------------------ObjectWithInterface---
548 class ObjectWithInterface(object):
551 # this defines the objects' interface.
552 # NOTE: if this is None interface support will be disabled.
553 __implemments__
= None
556 def __new__(cls
, *p
, **n
):
559 obj
= object.__new
__(cls
, *p
, **n
)
560 interface
= obj
.__implemments
__
561 if interface
!= None:
562 obj
.__dict
__.update(dict([ (n
, v
['default']) \
564 in type(interface
) is tuple \
565 and logictypes
.DictUnion(*[ i
.__format
__ for i
in interface
]).iteritems() \
566 or interface
.__format
__.iteritems() \
567 if 'default' in v
and n
!= '*' ]))
569 def __getattribute__(self
, name
):
572 if name
== '__implemments__' \
573 or object.__getattribute
__(self
, '__implemments__') == None \
574 or isreadable(self
, name
):
575 return super(ObjectWithInterface
, self
).__getattribute
__(name
)
576 raise InterfaceError
, 'can\'t read attribute "%s".' % name
577 def __setattr__(self
, name
, value
):
580 if object.__getattribute
__(self
, '__implemments__') == None \
581 or (iswritable(self
, name
) and isvaluecompatible(self
, name
, value
)):
582 return super(ObjectWithInterface
, self
).__setattr
__(name
, value
)
583 raise InterfaceError
, 'can\'t write value "%s" to attribute "%s".' % (value
, name
)
584 def __delattr__(self
, name
):
587 if object.__getattribute
__(self
, '__implemments__') != None \
588 and not isdeletable(self
, name
):
589 raise InterfaceError
, 'can\'t delete an essential attribute "%s".' % name
590 super(ObjectWithInterface
, self
).__delattr
__(name
)
593 #------------------------------------------------------InterfaceProxy---
594 # TODO move this to pli.pattern.proxy (???)
595 class InterfaceProxy(object):
598 __implemments__
= None
602 def __init__(self
, source
):
605 self
.__source
__ = source
606 def __getattr__(self
, name
):
609 if self
.__implemments
__ == None \
610 or name
== '__implemments__' or isreadable(self
, name
):
611 return getattr(self
.__source
__, name
)
612 raise InterfaceError
, 'can\'t read attribute "%s".' % name
613 def __setattr__(self
, name
, value
):
616 if name
in ('__source__', '__implemments__'):
617 return super(InterfaceProxy
, self
).__setattr
__(name
, value
)
618 if self
.__implemments
__ == None \
619 or (iswritable(self
, name
) and isvaluecompatible(self
, name
, value
)):
620 return setattr(self
.__source
__, name
, value
)
621 raise InterfaceError
, 'can\'t write value "%s" to attribute "%s".' % (value
, name
)
622 def __delattr__(self
, name
):
625 if self
.__implemments
__ != None and isessential(self
, name
):
626 raise InterfaceError
, 'can\'t delete an essential attribute "%s".' % name
627 delattr(self
.__source
__, name
)
631 #=======================================================================
632 if __name__
== '__main__':
634 class ITest(Interface
):
637 'ess': {'essential': True},
638 'xxx': {'doc':'ITest'},
641 class ITestTest(Interface
):
643 'bbb': {'type': int, 'LIKE': 'ccc', 'default': 0},
644 'ccc': {'LIKE': 'xxx', 'default': 0, 'readable': True},
645 ## 'ccc': {'default': 5, 'readable': True},
646 'xxx': {'doc': 'ITestTest', 'LIKE': 'bbb', 'default': 0},
649 class IDTest0(ITest
, ITestTest
):
652 class IDTest1(ITestTest
, ITest
):
657 print ITestTest
['bbb']
661 print 'yyy' in IDTest1
663 print IDTest0
.getattrproperty('xxx')
665 print IDTest1
.getattrproperty('xxx', 'doc')
666 print IDTest1
.getattrproperty('xxx')
674 __implemments__
= IDTest1
681 print checkattr(a
, 'ccc')
682 print checkvalue(a
, 'ccc', 0)
684 print getdoc(a
, 'ccc')
686 print checkessentials(a
)
689 #=======================================================================
690 # vim:set ts=4 sw=4 nowrap :