designate/designate/objects/base.py

370 lines
12 KiB
Python

# Copyright (c) 2014 Rackspace Hosting
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import copy
import six
from designate.openstack.common import importutils
class NotSpecifiedSentinel:
pass
def get_attrname(name):
"""Return the mangled name of the attribute's underlying storage."""
return '_%s' % name
def make_class_properties(cls):
"""Build getter and setter methods for all the objects attributes"""
cls.FIELDS = list(cls.FIELDS)
for supercls in cls.mro()[1:-1]:
if not hasattr(supercls, 'FIELDS'):
continue
for field in supercls.FIELDS:
if field not in cls.FIELDS:
cls.FIELDS.append(field)
for field in cls.FIELDS:
def getter(self, name=field):
return getattr(self, get_attrname(name), None)
def setter(self, value, name=field):
if (self.obj_attr_is_set(name) and value != getattr(self, name)
or not self.obj_attr_is_set(name)):
self._obj_changes.add(name)
if (self.obj_attr_is_set(name) and value != getattr(self, name)
and name not in self._obj_original_values.keys()):
self._obj_original_values[name] = getattr(self, name)
return setattr(self, get_attrname(name), value)
setattr(cls, field, property(getter, setter))
class DesignateObjectMetaclass(type):
def __init__(cls, names, bases, dict_):
make_class_properties(cls)
@six.add_metaclass(DesignateObjectMetaclass)
class DesignateObject(object):
FIELDS = []
@staticmethod
def from_primitive(primitive):
"""
Construct an object from primitive types
This is used while deserializing the object.
"""
cls = importutils.import_class(primitive['designate_object.name'])
return cls._obj_from_primitive(primitive)
@classmethod
def _obj_from_primitive(cls, primitive):
instance = cls()
for field, value in primitive['designate_object.data'].items():
if isinstance(value, dict) and 'designate_object.name' in value:
setattr(instance, field, DesignateObject.from_primitive(value))
else:
setattr(instance, field, value)
instance._obj_changes = set(primitive['designate_object.changes'])
instance._obj_original_values = \
primitive['designate_object.original_values']
return instance
def __init__(self, **kwargs):
self._obj_changes = set()
self._obj_original_values = dict()
for name, value in kwargs.items():
if name in self.FIELDS:
setattr(self, name, value)
else:
raise TypeError("'%s' is an invalid keyword argument" % name)
def to_primitive(self):
"""
Convert the object to primitive types so that the object can be
serialized.
NOTE: Currently all the designate objects contain primitive types that
do not need special handling. If this changes we need to modify this
function.
"""
class_name = self.__class__.__name__
if self.__module__:
class_name = self.__module__ + '.' + self.__class__.__name__
data = {}
for field in self.FIELDS:
if self.obj_attr_is_set(field):
if isinstance(getattr(self, field), DesignateObject):
data[field] = getattr(self, field).to_primitive()
else:
data[field] = getattr(self, field)
return {
'designate_object.name': class_name,
'designate_object.data': data,
'designate_object.changes': sorted(self._obj_changes),
'designate_object.original_values': dict(self._obj_original_values)
}
def obj_attr_is_set(self, name):
"""
Return True or False depending of if a particular attribute has had
an attribute's value explicitly set.
"""
return hasattr(self, get_attrname(name))
def obj_what_changed(self):
"""Returns a set of fields that have been modified."""
return set(self._obj_changes)
def obj_get_changes(self):
"""Returns a dict of changed fields and their new values."""
changes = {}
for key in self.obj_what_changed():
changes[key] = getattr(self, key)
return changes
def obj_reset_changes(self, fields=None):
"""Reset the list of fields that have been changed."""
if fields:
self._obj_changes -= set(fields)
for field in fields:
self._obj_original_values.pop(field, None)
else:
self._obj_changes.clear()
self._obj_original_values = dict()
def obj_get_original_value(self, field):
"""Returns the original value of a field."""
if field in self._obj_original_values.keys():
return self._obj_original_values[field]
elif self.obj_attr_is_set(field):
return getattr(self, field)
else:
raise KeyError(field)
def __deepcopy__(self, memodict=None):
"""
Efficiently make a deep copy of this object.
"Efficiently" is used here a relative term, this will be faster
than allowing python to naively deepcopy the object.
"""
memodict = memodict or {}
c_obj = self.__class__()
for field in self.FIELDS:
if self.obj_attr_is_set(field):
c_field = copy.deepcopy(getattr(self, field), memodict)
setattr(c_obj, field, c_field)
c_obj._obj_changes = set(self._obj_changes)
return c_obj
def __eq__(self, other):
if self.__class__ != other.__class__:
return False
return self.to_primitive() == other.to_primitive()
def __ne__(self, other):
return not(self.__eq__(other))
class DictObjectMixin(object):
"""
Mixin to allow DesignateObjects to behave like dictionaries
Eventually, this should be removed.
"""
def __getitem__(self, key):
return getattr(self, key)
def __setitem__(self, key, value):
setattr(self, key, value)
def __contains__(self, item):
return item in self.FIELDS
def get(self, key, default=NotSpecifiedSentinel):
if key not in self.FIELDS:
raise AttributeError("'%s' object has no attribute '%s'" % (
self.__class__, key))
if default != NotSpecifiedSentinel and not self.obj_attr_is_set(key):
return default
else:
return getattr(self, key)
def update(self, values):
for k, v in values.iteritems():
setattr(self, k, v)
def iteritems(self):
for field in self.FIELDS:
if self.obj_attr_is_set(field):
yield field, getattr(self, field)
def __iter__(self):
for field in self.FIELDS:
if self.obj_attr_is_set(field):
yield field, getattr(self, field)
items = lambda self: list(self.iteritems())
class ListObjectMixin(object):
"""Mixin class for lists of objects"""
FIELDS = ['objects']
LIST_ITEM_TYPE = DesignateObject
@classmethod
def _obj_from_primitive(cls, primitive):
instance = cls()
for field, value in primitive['designate_object.data'].items():
if field == 'objects':
instance.objects = [DesignateObject.from_primitive(v) for v in
value]
elif isinstance(value, dict) and 'designate_object.name' in value:
setattr(instance, field, DesignateObject.from_primitive(value))
else:
setattr(instance, field, value)
instance._obj_changes = set(primitive['designate_object.changes'])
instance._obj_original_values = \
primitive['designate_object.original_values']
return instance
def __init__(self, *args, **kwargs):
super(ListObjectMixin, self).__init__(*args, **kwargs)
if 'objects' not in kwargs:
self.objects = []
self.obj_reset_changes(['objects'])
def to_primitive(self):
class_name = self.__class__.__name__
if self.__module__:
class_name = self.__module__ + '.' + self.__class__.__name__
data = {}
for field in self.FIELDS:
if self.obj_attr_is_set(field):
if field == 'objects':
data[field] = [o.to_primitive() for o in self.objects]
elif isinstance(getattr(self, field), DesignateObject):
data[field] = getattr(self, field).to_primitive()
else:
data[field] = getattr(self, field)
return {
'designate_object.name': class_name,
'designate_object.data': data,
'designate_object.changes': list(self._obj_changes),
'designate_object.original_values': dict(self._obj_original_values)
}
def __iter__(self):
"""List iterator interface"""
return iter(self.objects)
def __len__(self):
"""List length"""
return len(self.objects)
def __getitem__(self, index):
"""List index access"""
if isinstance(index, slice):
new_obj = self.__class__()
new_obj.objects = self.objects[index]
new_obj.obj_reset_changes()
return new_obj
return self.objects[index]
def __setitem__(self, index, value):
"""Set list index value"""
self.objects[index] = value
def __contains__(self, value):
"""List membership test"""
return value in self.objects
def append(self, value):
"""Append a value to the list"""
return self.objects.append(value)
def extend(self, values):
"""Extend the list by appending all the items in the given list"""
return self.objects.extend(values)
def pop(self, index):
"""Pop a value from the list"""
return self.objects.pop(index)
def insert(self, index, value):
"""Insert a value into the list at the given index"""
return self.objects.insert(index)
def remove(self, value):
"""Remove a value from the list"""
return self.objects.remove(value)
def index(self, value):
"""List index of value"""
return self.objects.index(value)
def count(self, value):
"""List count of value occurrences"""
return self.objects.count(value)
def sort(self, cmp=None, key=None, reverse=False):
self.objects.sort(cmp=cmp, key=key, reverse=reverse)
def obj_what_changed(self):
changes = set(self._obj_changes)
for item in self.objects:
if item.obj_what_changed():
changes.add('objects')
return changes
class PersistentObjectMixin(object):
"""
Mixin class for Persistent objects.
This adds the fields that we use in common for all persisent objects.
"""
FIELDS = ['id', 'created_at', 'updated_at', 'version']