Convert Object FIELDS from list to dict

This allows for storing additional per-field metadata, for
example validation rules or policy triggers.

Change-Id: Ie6ad13ecc8e36a881fb2f0b08661fa4ef666b608
Blueprint: validation-cleanup
This commit is contained in:
Kiall Mac Innes 2014-10-10 16:12:50 +01:00
parent 4b3443e036
commit ef3a964e1b
23 changed files with 139 additions and 48 deletions

View File

@ -29,16 +29,22 @@ def get_attrname(name):
def make_class_properties(cls):
"""Build getter and setter methods for all the objects attributes"""
cls.FIELDS = list(cls.FIELDS)
# Prepare an empty dict to gather the merged/final set of fields
fields = {}
# Add each supercls's 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)
fields.update(supercls.FIELDS)
for field in cls.FIELDS:
# Add our fields
fields.update(cls.FIELDS)
# Store the results
cls.FIELDS = fields
for field in cls.FIELDS.keys():
def getter(self, name=field):
return getattr(self, get_attrname(name), None)
@ -63,7 +69,7 @@ class DesignateObjectMetaclass(type):
@six.add_metaclass(DesignateObjectMetaclass)
class DesignateObject(object):
FIELDS = []
FIELDS = {}
@staticmethod
def from_primitive(primitive):
@ -96,7 +102,7 @@ class DesignateObject(object):
self._obj_original_values = dict()
for name, value in kwargs.items():
if name in self.FIELDS:
if name in self.FIELDS.keys():
setattr(self, name, value)
else:
raise TypeError("'%s' is an invalid keyword argument" % name)
@ -115,7 +121,7 @@ class DesignateObject(object):
data = {}
for field in self.FIELDS:
for field in self.FIELDS.keys():
if self.obj_attr_is_set(field):
if isinstance(getattr(self, field), DesignateObject):
data[field] = getattr(self, field).to_primitive()
@ -181,7 +187,7 @@ class DesignateObject(object):
c_obj = self.__class__()
for field in self.FIELDS:
for field in self.FIELDS.keys():
if self.obj_attr_is_set(field):
c_field = copy.deepcopy(getattr(self, field), memodict)
setattr(c_obj, field, c_field)
@ -213,10 +219,10 @@ class DictObjectMixin(object):
setattr(self, key, value)
def __contains__(self, item):
return item in self.FIELDS
return item in self.FIELDS.keys()
def get(self, key, default=NotSpecifiedSentinel):
if key not in self.FIELDS:
if key not in self.FIELDS.keys():
raise AttributeError("'%s' object has no attribute '%s'" % (
self.__class__, key))
@ -230,12 +236,12 @@ class DictObjectMixin(object):
setattr(self, k, v)
def iteritems(self):
for field in self.FIELDS:
for field in self.FIELDS.keys():
if self.obj_attr_is_set(field):
yield field, getattr(self, field)
def __iter__(self):
for field in self.FIELDS:
for field in self.FIELDS.keys():
if self.obj_attr_is_set(field):
yield field, getattr(self, field)
@ -244,7 +250,7 @@ class DictObjectMixin(object):
class ListObjectMixin(object):
"""Mixin class for lists of objects"""
FIELDS = ['objects']
FIELDS = {'objects': {}}
LIST_ITEM_TYPE = DesignateObject
@classmethod
@ -279,7 +285,7 @@ class ListObjectMixin(object):
data = {}
for field in self.FIELDS:
for field in self.FIELDS.keys():
if self.obj_attr_is_set(field):
if field == 'objects':
data[field] = [o.to_primitive() for o in self.objects]
@ -365,7 +371,7 @@ class PersistentObjectMixin(object):
This adds the fields that we use in common for all persistent objects.
"""
FIELDS = ['id', 'created_at', 'updated_at', 'version']
FIELDS = {'id': {}, 'created_at': {}, 'updated_at': {}, 'version': {}}
class SoftDeleteObjectMixin(object):
@ -374,4 +380,4 @@ class SoftDeleteObjectMixin(object):
This adds the fields that we use in common for all soft-deleted objects.
"""
FIELDS = ['deleted', 'deleted_at']
FIELDS = {'deleted': {}, 'deleted_at': {}}

View File

@ -17,7 +17,10 @@ from designate.objects import base
class Blacklist(base.DictObjectMixin, base.PersistentObjectMixin,
base.DesignateObject):
FIELDS = ['pattern', 'description']
FIELDS = {
'pattern': {},
'description': {}
}
class BlacklistList(base.ListObjectMixin, base.DesignateObject):

View File

@ -17,9 +17,20 @@ from designate.objects import base
class Domain(base.DictObjectMixin, base.SoftDeleteObjectMixin,
base.PersistentObjectMixin, base.DesignateObject):
FIELDS = ['tenant_id', 'name', 'email', 'ttl', 'refresh', 'retry',
'expire', 'minimum', 'parent_domain_id', 'serial', 'description',
'status']
FIELDS = {
'tenant_id': {},
'name': {},
'email': {},
'ttl': {},
'refresh': {},
'retry': {},
'expire': {},
'minimum': {},
'parent_domain_id': {},
'serial': {},
'description': {},
'status': {}
}
class DomainList(base.ListObjectMixin, base.DesignateObject):

View File

@ -17,7 +17,11 @@ from designate.objects import base
class Quota(base.DictObjectMixin, base.PersistentObjectMixin,
base.DesignateObject):
FIELDS = ['tenant_id', 'resource', 'hard_limit']
FIELDS = {
'tenant_id': {},
'resource': {},
'hard_limit': {}
}
class QuotaList(base.ListObjectMixin, base.DesignateObject):

View File

@ -19,12 +19,24 @@ class Record(base.DictObjectMixin, base.PersistentObjectMixin,
base.DesignateObject):
# TODO(kiall): `hash` is an implementation detail of our SQLA driver,
# so we should remove it.
FIELDS = ['data', 'priority', 'domain_id', 'managed',
'managed_resource_type', 'managed_resource_id',
'managed_plugin_name', 'managed_plugin_type', 'hash',
'description', 'status', 'tenant_id', 'recordset_id',
'managed_tenant_id', 'managed_resource_region',
'managed_extra']
FIELDS = {
'data': {},
'priority': {},
'domain_id': {},
'managed': {},
'managed_resource_type': {},
'managed_resource_id': {},
'managed_plugin_name': {},
'managed_plugin_type': {},
'hash': {},
'description': {},
'status': {},
'tenant_id': {},
'recordset_id': {},
'managed_tenant_id': {},
'managed_resource_region': {},
'managed_extra': {}
}
class RecordList(base.ListObjectMixin, base.DesignateObject):

View File

@ -17,8 +17,15 @@ from designate.objects import base
class RecordSet(base.DictObjectMixin, base.PersistentObjectMixin,
base.DesignateObject):
FIELDS = ['tenant_id', 'domain_id', 'name', 'type', 'ttl', 'description',
'records']
FIELDS = {
'tenant_id': {},
'domain_id': {},
'name': {},
'type': {},
'ttl': {},
'description': {},
'records': {}
}
class RecordSetList(base.ListObjectMixin, base.DesignateObject):

View File

@ -20,7 +20,9 @@ class RRData_A(Record):
A Resource Record Type
Defined in: RFC1035
"""
FIELDS = ['address']
FIELDS = {
'address': {}
}
# The record type is defined in the RFC. This will be used when the record
# is sent by mini-dns.

View File

@ -20,7 +20,9 @@ class RRData_AAAA(Record):
AAAA Resource Record Type
Defined in: RFC3596
"""
FIELDS = ['address']
FIELDS = {
'address': {}
}
# The record type is defined in the RFC. This will be used when the record
# is sent by mini-dns.

View File

@ -20,7 +20,9 @@ class RRData_CNAME(Record):
CNAME Resource Record Type
Defined in: RFC1035
"""
FIELDS = ['cname']
FIELDS = {
'cname': {}
}
# The record type is defined in the RFC. This will be used when the record
# is sent by mini-dns.

View File

@ -21,7 +21,9 @@ class RRData_MX(Record):
Defined in: RFC1035
"""
# priority is maintained separately for MX records and not in 'data'
FIELDS = ['exchange']
FIELDS = {
'exchange': {}
}
# The record type is defined in the RFC. This will be used when the record
# is sent by mini-dns.

View File

@ -20,7 +20,9 @@ class RRData_NS(Record):
NS Resource Record Type
Defined in: RFC1035
"""
FIELDS = ['nsdname']
FIELDS = {
'nsdname': {}
}
# The record type is defined in the RFC. This will be used when the record
# is sent by mini-dns.

View File

@ -20,7 +20,9 @@ class RRData_PTR(Record):
PTR Resource Record Type
Defined in: RFC1035
"""
FIELDS = ['ptrdname']
FIELDS = {
'ptrdname': {}
}
# The record type is defined in the RFC. This will be used when the record
# is sent by mini-dns.

View File

@ -20,8 +20,15 @@ class RRData_SOA(Record):
SOA Resource Record Type
Defined in: RFC1035
"""
FIELDS = ['mname', 'rname', 'serial', 'refresh', 'retry', 'expire',
'minimum']
FIELDS = {
'mname': {},
'rname': {},
'serial': {},
'refresh': {},
'retry': {},
'expire': {},
'minimum': {}
}
# The record type is defined in the RFC. This will be used when the record
# is sent by mini-dns.

View File

@ -20,7 +20,9 @@ class RRData_SPF(Record):
SPF Resource Record Type
Defined in: RFC4408
"""
FIELDS = ['txt-data']
FIELDS = {
'txt-data': {}
}
# The record type is defined in the RFC. This will be used when the record
# is sent by mini-dns.

View File

@ -21,7 +21,11 @@ class RRData_SRV(Record):
Defined in: RFC2782
"""
# priority is maintained separately for SRV records and not in 'data'
FIELDS = ['weight', 'port', 'target']
FIELDS = {
'weight': {},
'port': {},
'target': {}
}
# The record type is defined in the RFC. This will be used when the record
# is sent by mini-dns.

View File

@ -20,7 +20,11 @@ class RRData_SSHFP(Record):
SSHFP Resource Record Type
Defined in: RFC4255
"""
FIELDS = ['algorithm', 'fp_type', 'fingerprint']
FIELDS = {
'algorithm': {},
'fp_type': {},
'fingerprint': {}
}
# The record type is defined in the RFC. This will be used when the record
# is sent by mini-dns.

View File

@ -20,7 +20,9 @@ class RRData_TXT(Record):
TXT Resource Record Type
Defined in: RFC1035
"""
FIELDS = ['txt-data']
FIELDS = {
'txt-data': {}
}
# The record type is defined in the RFC. This will be used when the record
# is sent by mini-dns.

View File

@ -17,7 +17,9 @@ from designate.objects import base
class Server(base.DictObjectMixin, base.PersistentObjectMixin,
base.DesignateObject):
FIELDS = ['name']
FIELDS = {
'name': {}
}
class ServerList(base.ListObjectMixin, base.DesignateObject):

View File

@ -16,7 +16,11 @@ from designate.objects import base
class Tenant(base.DictObjectMixin, base.DesignateObject):
FIELDS = ['id', 'domain_count', 'domains']
FIELDS = {
'id': {},
'domain_count': {},
'domains': {}
}
class TenantList(base.ListObjectMixin, base.DesignateObject):

View File

@ -17,7 +17,10 @@ from designate.objects import base
class Tld(base.DictObjectMixin, base.PersistentObjectMixin,
base.DesignateObject):
FIELDS = ['name', 'description']
FIELDS = {
'name': {},
'description': {}
}
class TldList(base.ListObjectMixin, base.DesignateObject):

View File

@ -17,7 +17,11 @@ from designate.objects import base
class TsigKey(base.DictObjectMixin, base.PersistentObjectMixin,
base.DesignateObject):
FIELDS = ['name', 'algorithm', 'secret']
FIELDS = {
'name': {},
'algorithm': {},
'secret': {}
}
class TsigKeyList(base.ListObjectMixin, base.DesignateObject):

View File

@ -46,7 +46,7 @@ cfg.CONF.register_opts(options.database_opts, group='storage:sqlalchemy')
def _set_object_from_model(obj, model, **extra):
"""Update a DesignateObject with the values from a SQLA Model"""
for fieldname in obj.FIELDS:
for fieldname in obj.FIELDS.keys():
if hasattr(model, fieldname):
if fieldname in extra.keys():
obj[fieldname] = extra[fieldname]

View File

@ -27,7 +27,11 @@ LOG = logging.getLogger(__name__)
class TestObject(objects.DesignateObject):
PATH = 'designate.tests.test_objects.test_base.TestObject'
FIELDS = ['id', 'name', 'nested']
FIELDS = {
'id': {},
'name': {},
'nested': {},
}
class TestObjectDict(objects.DictObjectMixin, TestObject):