Welcome, guest | Sign In | My Account | Store | Cart
#!/usr/bin/env python
#
#   Copyright 2010-  Hui Zhang
#   E-mail: hui.zh012@gmail.com
#
#   Distributed under the terms of the GPL (GNU Public License)
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program; if not, write to the Free Software
#   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

__all__
= ["Attribute", "attribute", ]

from weakref import ref
from functools import partial

class BaseAttribute(object):
   
"""        
        usage:
            >>>class A(object):
                   val = Attriubte('val',
                                 factory=int,
                                 validator=lambda v: v in range(3,7),
                                 onchanged=somfunc,
                                 readonly=True,
                                 otw=False,
                                 ...   # for more please check the code
                                 )
            >>> a = A()
            >>> print a.val
            0
            >>> a.val=1; print a.val
            1
            >>> del a.val; print a.val
            0
                         
            notes:
                'del a.val' does not delete the attr from instance.
                it just clean the assigned value for this attr.

        parameters:
       
        * name :
            the attr's name, default is ''
        * readonly :
            define if the attr could be assigned.
            notes: readonly does not impact behavior of 'del a.val'
        * delable :
            define if the value could be deleted

        * default :
            the default value.
        * factory, boundfactory
            define the initializer for the attr.
            -factory takes no parametor
            -boundfactory would get the instance as the only parameter.

        * validator, boundvalidator, skip_valid_err
            validate the value assigned to the attr.
            if validate failed,
                - the value would not be assigned
                - a exception would be raised if skip_valid_err is False.
            The validation would not be applied for the value initialized by:
                default, factory, boundfactory
           
        * onchanged
            define a hook while attr value changed.
            the following 4 args would be given to the hook:
                (instance, Attribute instance, old value, new value)
               
        * otw
            one time write.
            e.g:
                class A(object):
                    v = Attribute(otw=True)
                   
                class B(A):
                    def __init__(self, value):
                        self.v = value
                       
                b = B(99)
                print b.v -> 99
                b.v = 100 -> exception raised

        [initialization:]
            initializers are prioritized as "
default, factory, boundfactory"
            if no any defined and value assinged, a exception would be raised while you try to read this attr.
       
    """

   
undefined = object()
   
   
def __init__(self, name='', **kwargs):
       
self.name = name
       
self.dict = kwargs.copy()
       
       
self.skip_valid_err = self.getarg('skip_valid_err', True)
       
       
self.build_initer()
       
self.build_assigner()
       
self.build_deleter()
       
        onchanged
= self.getarg('onchanged', None)
       
if onchanged:
           
self.onchanged(onchanged)

   
## read, write and erase the value from/to the storage
   
## to be implemented by sub classes
   
def _read_(self, instance):
       
""" read the value from the storage
            raise exception if the value is not initialized
        """


   
def _write_(self, instance, value):
       
""" store the value to the storage
        """

       
   
def _earase_(self, instance):
       
""" erase the value from the storage
        """

           
   
def __get__(self, instance, owner):
       
if instance is None:
           
return self
       
else:
           
try:
               
return self._read_(instance)
           
except:
               
if self.initer:
                   
self._write_(instance, self.initer(instance))
                   
return self._read_(instance)
               
               
raise AttributeError("Attribute is used before initialized.")
   
   
def __set__(self, instance, value):
       
self.assigner(instance, value)
       
   
def __delete__(self, instance):
       
self.deleter(instance)

   
## build the initer, assigner and deleter
   
def getarg(self, name, default):
       
if name in self.dict:
           
return self.dict.pop(name)
       
else:
           
return default
   
   
def build_initer(self):
       
self.initer = None
       
        _dummy
= object
       
       
default = self.getarg('default', _dummy)
       
if default is not _dummy:
           
self.initer = lambda instance: default
           
return

        factory
= self.getarg('factory', _dummy)
       
if factory is not _dummy:
           
self.initer = lambda instance: factory()
           
return
       
        boundfactory
= self.getarg('boundfactory', _dummy)
       
if boundfactory is not _dummy:
           
self.initer = lambda instance: boundfactory(instance)
           
return
       
   
def build_assigner(self):
       
readonly = self.getarg('readonly', False)
       
if readonly:
           
def assigner(instance, value):
               
raise AttributeError("Cannot set value to read-only attribute")
           
self.assigner = assigner
           
return
       
       
self.assigner = self._write_
       
        otw
= self.getarg('otw', False)
       
if otw:
           
self.set_otw()

        validator
= self.getarg('validator', None)
       
if validator:
           
self.validator(validator)
       
        boundvalidator
= self.getarg('boundvalidator', None)
       
if boundvalidator:
           
self.boundvalidator(boundvalidator)
   
   
def build_deleter(self):
        delable
= self.getarg('delable', True)
       
if not delable:
           
def deleter(instance):
               
raise AttributeError("Cannot delete attribute")
           
self.deleter = deleter
       
else:
           
self.deleter = self._earase_
   
   
## handler wrappers
   
def set_otw(self):
        oldassinger
= self.assigner
       
def assigner(instance, value):
           
try:
               
self._read_(instance)
                inited
= True
           
except:
                inited
= False
           
if inited:
               
raise AttributeError('Attribute could only be written once.')
            oldassinger
(instance, value)
       
self.assigner = assigner

   
# be able to use as decorator
   
def onchanged(self, hook):
       
def wrap(func):
           
def newfunc(*args):
                instance
= args[0]
               
try:
                    oldval
= self.__get__(instance, None)
               
except:
                    oldval
= BaseAttribute.undefined
                   
                func
(*args)
               
               
try:
                    newval
= self.__get__(instance, None)
               
except:
                    newval
= BaseAttribute.undefined
               
               
if oldval != newval:
                    hook
(instance, self, oldval, newval)
           
return newfunc
       
       
self.assigner = wrap(self.assigner)
       
self.deleter = wrap(self.deleter)
       
return self

   
def validator(self, hook):
        oldassinger
= self.assigner
       
def assigner(instance, value):
           
if hook(value):
                oldassinger
(instance, value)
           
elif not self.skip_valid_err:
               
raise AttributeError("The value for attribute is not valid")
       
self.assigner = assigner
       
return self

   
def boundvalidator(self, hook):
        oldassinger
= self.assigner
       
def assigner(instance, value):
           
if hook(instance, value):
                oldassinger
(instance, value)
           
elif not self.skip_valid_err:
               
raise AttributeError("The value for attribute is not valid")
       
self.assigner = assigner
       
return self

class Attribute(BaseAttribute):
   
def __init__(self, name='', **kwargs):
       
super(Attribute, self).__init__(name, **kwargs)
       
self.__values = weakkeymap() # Recipe 577580: weak reference map
       
   
def _read_(self, instance):
       
return self.__values.get(instance)
   
   
def _write_(self, instance, value):
       
self.__values.set(instance, value)
       
   
def _earase_(self, instance):
       
self.__values.remove(instance)

def attribute(*args, **kwargs):
   
assert 'boundfactory' not in kwargs \
           
and ( not args
                 
or (len(args)==1 and not kwargs)
                 
)
   
   
if args:
       
return Attribute(args[0].func_name,
                         boundfactory
=args[0])
   
else:
       
def deco(func):
           
return Attribute(func.func_name,
                             boundfactory
=func,
                             
**kwargs)
       
return deco
   
## demo code
#class A(object):
#    @attribute(skip_valid_err=False)
#    def value(self):
#        return 10000
#    
#    @value.boundvalidator
#    def value(self, v):
#        return v < 20
#
#    value.validator(lambda v: v>5)
#    
#    @value.onchanged
#    def value(self, *args):
#        print args
#    
#a = A()
#print a.value
#a.value = 9
#print a.value
#a.value = 100

History