1 #=======================================================================
3 __version__
= '''0.2.19'''
4 __sub_version__
= '''20040914013450'''
5 __copyright__
= '''(c) Alex A. Naanou 2003'''
8 #-----------------------------------------------------------------------
11 This module defines an interface definition and enforcement mechanism and
16 #-----------------------------------------------------------------------
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):
40 #-----------------------------------------------------------------------
41 #-----------------------------------------------------_BasicInterface---
42 class _BasicInterface(type, mapping
.Mapping
):
44 this is the interface metaclass.
46 # this will define the interface format...
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__
= (
70 def __init__(cls
, name
, bases
, ns
):
74 if not hasattr(cls
, '__format__'):
75 raise InterfaceError
, 'interface %s does not have a format defined.' % cls
76 # check consistency...
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
)
83 def __getitem__(cls
, name
):
87 if not hasattr(cls
, '__format__'):
88 raise InterfaceError
, 'interface %s does not have a format defined.' % cls
89 format
= cls
.__format
__
92 if hasattr(c
, '__format__') \
93 and c
.__format
__ != None \
94 and name
in c
.__format
__:
95 return c
.__format
__[name
]
97 # c was not a dict-like....
100 ##!!! is this correct ?
101 return super(_BasicInterface
, cls
).__getitem
__(name
)
102 ## except AttributeError:
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
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
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
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...
132 del cls
.__format
__[name
]
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
]
141 raise KeyError, str(name
)
142 ## def __contains__(cls, name):
149 for c
in cls
.__mro
__:
150 if hasattr(c
, '__format__') \
151 and c
.__format
__ != None:
152 for k
in c
.__format
__.iterkeys():
157 # interface specific (1st generation):
158 def __isconsistent__(cls
, errors
=None):
161 allowed_props
= cls
.__attribute
_properties
__
164 props
= cls
.getattrproperty(name
)
166 if n
not in allowed_props
:
167 raise InterfaceError
, 'unknown option "%s".' % prop
171 if errors
in ([], None):
174 def getattrproperty(cls
, name
, prop
=None):
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).
185 allowed_props
= cls
.__attribute
_properties
__ + (None,)
186 if prop
not in allowed_props
:
187 raise InterfaceError
, 'unknown option "%s".' % prop
192 raise KeyError, str(name
)
194 res
= cls
[name
].copy()
195 # resolve the 'LIKE' prop...
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()
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
210 # XXX is there a better way to do this??? (use sets???)
211 for d
in visited
[1:]:
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
220 visited
+= [ext_format
.copy()]
222 ext_format
.update(res
)
225 res
.update(ext_format
)
230 ## raise InterfaceError, 'property "%s" is not defined for attr "%s".' % (prop, name)
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
271 the attribute definition format is as follows:
275 <opt-name>: <opt-value>
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
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
306 writable - this if False will prevent the attr from being
308 deleteable - this if False will prevent the attr from being
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
331 #-----------------------------------------------------------------------
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:
340 # get objects interface...
341 if type(obj
.__implemments
__) in (list, tuple):
342 return tuple(obj
.__implemments
__)
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
354 if interface
!= None:
357 format
= logictypes
.DictUnion(*getinterfaces(obj
)[::-1])
359 ## star = format.get('*', False) != False or False
361 if not hasattr(obj
, 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
)
370 res
[k
] = getattr(obj
, k
)
373 for k
in [ n
for n
in object.__getattribute
__(obj
, '__dict__') if n
not in format
]:
374 res
[k
] = getattr(obj
, k
)
378 #----------------------------------------------------------checkvalue---
379 def checkvalue(obj
, name
, value
, interface
=None):
381 this will check the correctness/compatibility of the obj, attr name
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
,)
390 format
= getinterfaces(obj
)
391 # get the effective inteface...
394 # find explicit name definition...
396 format
= i
.getattrproperty(name
)
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)...
405 raise InterfaceError
, 'no definition of attribute "%s" in %s.' % (name
, obj
)
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'])
412 if 'predicate' in format
and not format
['predicate'](value
):
413 raise InterfaceError
, 'predicate failed for "%s" attribute.' % name
417 #-----------------------------------------------------------checkattr---
418 # XXX use a different getattr mechanism as this might be blocked by the
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:
442 format
= logictypes
.DictUnion(*getinterfaces(obj
)[::-1])
443 handler
= format
.get(name
, {}).get('handler', False)
445 return handler(obj
, name
, 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:
462 format
= logictypes
.DictUnion(*getinterfaces(obj
)[::-1])
464 return format
[name
].get('essential', True)
466 return format
['*'].get('essential', True)
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:
482 format
= logictypes
.DictUnion(*getinterfaces(obj
)[::-1])
484 return format
[name
].get('readable', True)
486 return format
['*'].get('readable', True)
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:
502 format
= logictypes
.DictUnion(*getinterfaces(obj
)[::-1])
504 return format
[name
].get('writable', True)
506 return format
['*'].get('writable', True)
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:
522 format
= logictypes
.DictUnion(*getinterfaces(obj
)[::-1])
524 return format
[name
].get('deletable', True) and not format
[name
].get('essential', False)
526 return format
['*'].get('deletable', True) and not format
[name
].get('essential', 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.
538 return checkvalue(obj
, name
, value
, interface
)
543 #--------------------------------------------------------iscompatible---
544 # this will check all the applicable predicates...
545 # XXX use a different getattr mechanism as this might be blocked by the
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.
555 return checkattr(obj
, name
, interface
)
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:
573 format
= logictypes
.DictUnion(*getinterfaces(obj
)[::-1])
577 v
= format
[n
].get('essential', False)
578 if v
not in (None, False):
581 if not hasattr(obj
, n
):
582 raise InterfaceError
, 'essential attribute "%s" missing from %s' % (n
, obj
)
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:
598 format
= logictypes
.DictUnion(*getinterfaces(obj
)[::-1])
599 o_attrs
= vars(obj
).copy()
601 # Q: which one of the folowing is faster??
603 if format
[n
].get('essential', False):
604 raise InterfaceError
, 'essential attribute "%s" missing from %s' % (n
, obj
)
609 if '*' not in format
:
610 raise InterfaceError
, 'excess attributes %s in object %s.' % (o_attrs
.keys(), obj
)
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:
631 format
= logictypes
.DictUnion(*getinterfaces(obj
)[::-1])
632 # if name is present...
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...
641 res
[n
] = format
[n
].get('doc', None)
646 #-----------------------------------------------------------------------
647 #---------------------------------------------------------implemments---
648 def implemments(interface
, depth
=1):
651 f_locals
= sys
._getframe
(depth
).f_locals
652 res
= f_locals
.get('__implemments__', None)
654 f_locals
['__implemments__'] = interface
656 # some sort of interface already exists...
657 if type(res
) is tuple:
658 if type(interface
) is tuple:
659 res
= interface
+ res
661 res
= (interface
,) + res
663 if type(interface
) is tuple:
664 res
= interface
+ (res
,)
666 res
= (interface
, res
)
667 f_locals
['__implemments__'] = res
671 #=======================================================================
672 # vim:set ts=4 sw=4 nowrap :