Source code for virttest.propcan

"""
Class which allows property and dict-like access to a fixed set of instance
attributes.  Attributes are locked by __slots__, however accessor methods
may be created/removed on instances, or defined by the subclass.  An
INITIALIZED attribute is provided to signel completion of __init__()
for use by accessor methods (i.e. so they know when __init__ may be
setting values).

Subclasses must define a __slots__ class attribute containing the list
of attribute names to reserve.  All additional subclass descendents
must explicitly copy __slots__ from the parent in their definition.

Users of subclass instances are expected to get/set/del attributes
only via the standard object or dict-like interface.  i.e.

instance.attribute = whatever
or
instance['attribute'] = whatever

Internally, methods are free to call the accessor methods.  Only
accessor methods should use the special __dict_*__() and __super_*__() methods.
These are there to allow convenient access to the internal dictionary
values and subclass-defined attributes (such as __slots__).

example:

::

    class A(PropCan):
       # Class with *attributes*
        __slots__ = ('a', 'b')
        # 'a' has defined a set/get/del by definition of method with prefix
        #     set_a, get_a, del_a
        # 'b' doesn't have defined set/get/del then classic set/get/del will be
        #     called instead.


        def __init__(self, a=1, b='b'):
           super(A, self).__init__(a, b)


        def set_a(self, value)
            # If is_instance(obj, A) then obj.a = "val" call this method.
            self.__dict_set__("a", value)


        def get_a(self, value)
            # If is_instance(obj, A) then xx = obj.a call this method.
            return self.__dict_get__("a")


        def del_a(self, value)
            # If is_instance(obj, A) then del obj.a call this method.
            self.__dict_del__("a")


    class B(PropCan):
       # Class without *attributes*
       # ***** Even if class doesn't have attributes there should be
       # defined __slots__ = []. Because it is preferred by new style of class.
       # *****
        __slots__ = []


        def __init__(self):
           super(B, self).__init__()
"""


[docs]class PropCanInternal(object): """ Semi-private methods for use only by PropCanBase subclasses (NOT instances) """ # The following methods are intended for use by accessor-methods # where they may need to bypass the special attribute/key handling def __dict_get__(self, key): """ Get a key unconditionally, w/o checking for accessor method or __slots__ """ return dict.__getitem__(self, key) def __dict_set__(self, key, value): """ Set a key unconditionally, w/o checking for accessor method or __slots__ """ dict.__setitem__(self, key, value) def __dict_del__(self, key): """ Del key unconditionally, w/o checking for accessor method or __slots__ """ return dict.__delitem__(self, key) def __super_get__(self, key): """ Get attribute unconditionally, w/o checking accessor method or __slots__ """ return object.__getattribute__(self, key) def __super_set__(self, key, value): """ Set attribute unconditionally, w/o checking accessor method or __slots__ """ object.__setattr__(self, key, value) def __super_del__(self, key): """ Del attribute unconditionally, w/o checking accessor method or __slots__ """ object.__delattr__(self, key)
[docs]class classproperty(property): def __get__(self, obj, type_): data = self.fget.__get__(None, type_)() return data def __set__(self, obj, value): cls = type(obj) return self.fset.__get__(None, cls)(value)
[docs]class PropCanBase(dict, PropCanInternal): """ Objects with optional accessor methods and dict-like access to fixed set of keys """ # get_*(), set_*(), del_*() accessor methods called from subclass # __init__ sometimes need special handling, this is the signal. INITIALIZED = False # Help debugging by making all slot values available in all subclasses # cache the value on first call ___all_slots__ = None @classproperty @classmethod def __all_slots__(cls): if not cls.___all_slots__: all_slots = [] for cls_slots in [getattr(_cls, '__slots__', []) for _cls in cls.__mro__]: all_slots += cls_slots cls.___all_slots__ = tuple(all_slots) return cls.___all_slots__ def __new__(cls, *args, **dargs): if not hasattr(cls, '__slots__'): raise NotImplementedError("Class '%s' must define __slots__ " "property" % str(cls)) newone = super(PropCanBase, cls).__new__(cls, *args, **dargs) cls.___all_slots__ = tuple() return newone def __init__(self, *args, **dargs): """ Initialize contents directly or by way of accessors :param args: Initial values for __slots__ keys, same as dict. :param dargs: Initial values for __slots__ keys, same as dict. """ # Params are initialized here, not in super super(PropCanBase, self).__init__() # No need to re-invent dict argument processing values = dict(*args, **dargs) for key in self.__all_slots__: value = values.get(key, "@!@!@!SENTINEL!@!@!@") if value is not "@!@!@!SENTINEL!@!@!@": # Call accessor methods if present self[key] = value # Let accessor methods know initialization is complete self.__super_set__('INITIALIZED', True) def __getitem__(self, key): try: accessor = super(PropCanBase, self).__getattribute__('get_%s' % key) except AttributeError: return super(PropCanBase, self).__getitem__(key) return accessor() def __setitem__(self, key, value): self.__canhaz__(key, KeyError) try: accessor = super(PropCanBase, self).__getattribute__('set_%s' % key) except AttributeError: return super(PropCanBase, self).__setitem__(key, value) return accessor(value) def __delitem__(self, key): try: accessor = super(PropCanBase, self).__getattribute__('del_%s' % key) except AttributeError: return super(PropCanBase, self).__delitem__(key) return accessor() def __get__(self, key): try: # Attempt to call accessor methods first whenever possible self.__canhaz__(key, KeyError) return self.__getitem__(key) except KeyError: # Allow subclasses to define attributes if required return super(PropCanBase, self).__getattribute__(key) def __set__(self, key, value): self.__canhaz__(key) try: return self.__setitem__(key, value) except KeyError, detail: # Prevent subclass instances from defining normal attributes raise AttributeError(str(detail)) def __getattr__(self, key): try: # Attempt to call accessor methods first whenever possible self.__canhaz__(key, KeyError) return self.__getitem__(key) except KeyError: # Allow subclasses to define attributes if required return super(PropCanBase, self).__getattribute__(key) def __setattr__(self, key, value): self.__canhaz__(key) try: return self.__setitem__(key, value) except KeyError, detail: # Prevent subclass instances from defining normal attributes raise AttributeError(str(detail)) def __delattr__(self, key): self.__canhaz__(key) try: return self.__delitem__(key) except KeyError, detail: # Prevent subclass instances from deleting normal attributes raise AttributeError(str(detail)) def __canhaz__(self, key, excpt=AttributeError): """ Quickly determine if an accessor or instance attribute name is defined. """ slots = self.__all_slots__ keys = slots + ('get_%s' % key, 'set_%s' % key, 'del_%s' % key) if key not in keys: raise excpt("Key '%s' not found in super class attributes or in %s" % (str(key), str(keys)))
[docs] def copy(self): """ Copy properties by value, not by reference. """ return self.__class__(dict(self))
[docs] def update(self, other=None, excpt=AttributeError, **kwargs): """ Update properties in __all_slots__ with another dict. """ _tmp_dict = dict() _other_dict = dict() if other: _other_dict = dict(other) try: _tmp_dict.update(_other_dict) _tmp_dict.update(kwargs) except TypeError, detail: raise excpt(detail) for item in _tmp_dict.keys(): self[item] = _tmp_dict[item]
[docs]class PropCan(PropCanBase): """ Special value handling on retrieval of None values """ def __len__(self): length = 0 for key in self.__all_slots__: # special None/False value handling if self.__contains__(key): length += 1 return length def __contains__(self, key): try: value = self.__dict_get__(key) except (KeyError, AttributeError): return False # Avoid inf. recursion if value == self if issubclass(type(value), type(self)) or value: return True return False def __eq__(self, other): # special None/False value handling return dict([(key, value) for key, value in self.items()]) == other def __ne__(self, other): return not self.__eq__(other)
[docs] def keys(self): # special None/False value handling return [key for key in self.__all_slots__ if self.__contains__(key)]
[docs] def values(self): # special None/False value handling return [self[key] for key in self.keys()]
[docs] def items(self): return tuple([(key, self[key]) for key in self.keys()])
has_key = __contains__
[docs] def set_if_none(self, key, value): """ Set the value of key, only if it's not set or None """ if key not in self: self[key] = value
[docs] def set_if_value_not_none(self, key, value): """ Set the value of key, only if value is not None """ if value: self[key] = value