Merge "Add Field model and tests"
This commit is contained in:
422
nova/objects/fields.py
Normal file
422
nova/objects/fields.py
Normal file
@@ -0,0 +1,422 @@
|
||||
# Copyright 2013 Red Hat, Inc.
|
||||
#
|
||||
# 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 abc
|
||||
import datetime
|
||||
import iso8601
|
||||
|
||||
import netaddr
|
||||
import six
|
||||
|
||||
from nova.network import model as network_model
|
||||
from nova.openstack.common.gettextutils import _
|
||||
from nova.openstack.common import timeutils
|
||||
|
||||
|
||||
class KeyTypeError(TypeError):
|
||||
def __init__(self, expected, value):
|
||||
super(KeyTypeError, self).__init__(
|
||||
_('Key %(key)s must be of type %(expected)s not %(actual)s'
|
||||
) % {'key': repr(value),
|
||||
'expected': expected.__name__,
|
||||
'actual': value.__class__.__name__,
|
||||
})
|
||||
|
||||
|
||||
class ElementTypeError(TypeError):
|
||||
def __init__(self, expected, key, value):
|
||||
super(ElementTypeError, self).__init__(
|
||||
_('Element %(key)s:%(val)s must be of type %(expected)s'
|
||||
' not %(actual)s'
|
||||
) % {'key': key,
|
||||
'val': repr(value),
|
||||
'expected': expected,
|
||||
'actual': value.__class__.__name__,
|
||||
})
|
||||
|
||||
|
||||
class AbstractFieldType(six.with_metaclass(abc.ABCMeta, object)):
|
||||
@abc.abstractmethod
|
||||
def coerce(self, obj, attr, value):
|
||||
"""This is called to coerce (if possible) a value on assignment.
|
||||
|
||||
This method should convert the value given into the designated type,
|
||||
or throw an exception if this is not possible.
|
||||
|
||||
:param:obj: The NovaObject on which an attribute is being set
|
||||
:param:attr: The name of the attribute being set
|
||||
:param:value: The value being set
|
||||
:returns: A properly-typed value
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def from_primitive(self, obj, attr, value):
|
||||
"""This is called to deserialize a value.
|
||||
|
||||
This method should deserialize a value from the form given by
|
||||
to_primitive() to the designated type.
|
||||
|
||||
:param:obj: The NovaObject on which the value is to be set
|
||||
:param:attr: The name of the attribute which will hold the value
|
||||
:param:value: The serialized form of the value
|
||||
:returns: The natural form of the value
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def to_primitive(self, obj, attr, value):
|
||||
"""This is called to serialize a value.
|
||||
|
||||
This method should serialize a value to the form expected by
|
||||
from_primitive().
|
||||
|
||||
:param:obj: The NovaObject on which the value is set
|
||||
:param:attr: The name of the attribute holding the value
|
||||
:param:value: The natural form of the value
|
||||
:returns: The serialized form of the value
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def describe(self):
|
||||
"""Returns a string describing the type of the field."""
|
||||
pass
|
||||
|
||||
|
||||
class FieldType(AbstractFieldType):
|
||||
def coerce(self, obj, attr, value):
|
||||
return value
|
||||
|
||||
def from_primitive(self, obj, attr, value):
|
||||
return value
|
||||
|
||||
def to_primitive(self, obj, attr, value):
|
||||
return value
|
||||
|
||||
def describe(self):
|
||||
return self.__class__.__name__
|
||||
|
||||
|
||||
class UnspecifiedDefault(object):
|
||||
pass
|
||||
|
||||
|
||||
class Field(object):
|
||||
def __init__(self, field_type, nullable=False, default=UnspecifiedDefault):
|
||||
self._type = field_type
|
||||
self._nullable = nullable
|
||||
self._default = default
|
||||
|
||||
@property
|
||||
def nullable(self):
|
||||
return self._nullable
|
||||
|
||||
@property
|
||||
def default(self):
|
||||
return self._default
|
||||
|
||||
def _null(self, obj, attr):
|
||||
if self.nullable:
|
||||
return None
|
||||
elif self._default != UnspecifiedDefault:
|
||||
# NOTE(danms): We coerce the default value each time the field
|
||||
# is set to None as our contract states that we'll let the type
|
||||
# examine the object and attribute name at that time.
|
||||
return self._type.coerce(obj, attr, self._default)
|
||||
else:
|
||||
raise ValueError(_("Field `%s' cannot be None") % attr)
|
||||
|
||||
def coerce(self, obj, attr, value):
|
||||
"""Coerce a value to a suitable type.
|
||||
|
||||
This is called any time you set a value on an object, like:
|
||||
|
||||
foo.myint = 1
|
||||
|
||||
and is responsible for making sure that the value (1 here) is of
|
||||
the proper type, or can be sanely converted.
|
||||
|
||||
This also handles the potentially nullable or defaultable
|
||||
nature of the field and calls the coerce() method on a
|
||||
FieldType to actually do the coercion.
|
||||
|
||||
:param:obj: The object being acted upon
|
||||
:param:attr: The name of the attribute/field being set
|
||||
:param:value: The value being set
|
||||
:returns: The properly-typed value
|
||||
"""
|
||||
if value is None:
|
||||
return self._null(obj, attr)
|
||||
else:
|
||||
return self._type.coerce(obj, attr, value)
|
||||
|
||||
def from_primitive(self, obj, attr, value):
|
||||
"""Deserialize a value from primitive form.
|
||||
|
||||
This is responsible for deserializing a value from primitive
|
||||
into regular form. It calls the from_primitive() method on a
|
||||
FieldType to do the actual deserialization.
|
||||
|
||||
:param:obj: The object being acted upon
|
||||
:param:attr: The name of the attribute/field being deserialized
|
||||
:param:value: The value to be deserialized
|
||||
:returns: The deserialized value
|
||||
"""
|
||||
if value is None:
|
||||
return None
|
||||
else:
|
||||
return self._type.from_primitive(obj, attr, value)
|
||||
|
||||
def to_primitive(self, obj, attr, value):
|
||||
"""Serialize a value to primitive form.
|
||||
|
||||
This is responsible for serializing a value to primitive
|
||||
form. It calls to_primitive() on a FieldType to do the actual
|
||||
serialization.
|
||||
|
||||
:param:obj: The object being acted upon
|
||||
:param:attr: The name of the attribute/field being serialized
|
||||
:param:value: The value to be serialized
|
||||
:returns: The serialized value
|
||||
"""
|
||||
if value is None:
|
||||
return None
|
||||
else:
|
||||
return self._type.to_primitive(obj, attr, value)
|
||||
|
||||
def describe(self):
|
||||
"""Return a short string describing the type of this field."""
|
||||
name = self._type.describe()
|
||||
prefix = self.nullable and 'Nullable' or ''
|
||||
return prefix + name
|
||||
|
||||
|
||||
class String(FieldType):
|
||||
def coerce(self, obj, attr, value):
|
||||
# FIXME(danms): We should really try to avoid the need to do this
|
||||
if isinstance(value, (basestring, int, long, float,
|
||||
datetime.datetime)):
|
||||
return unicode(value)
|
||||
else:
|
||||
raise ValueError(_('A string is required here, not %s'),
|
||||
value.__class__.__name__)
|
||||
|
||||
|
||||
class UUID(FieldType):
|
||||
def coerce(self, obj, attr, value):
|
||||
# FIXME(danms): We should actually verify the UUIDness here
|
||||
return str(value)
|
||||
|
||||
|
||||
class Integer(FieldType):
|
||||
def coerce(self, obj, attr, value):
|
||||
return int(value)
|
||||
|
||||
|
||||
class Boolean(FieldType):
|
||||
def coerce(self, obj, attr, value):
|
||||
return bool(value)
|
||||
|
||||
|
||||
class DateTime(FieldType):
|
||||
def coerce(self, obj, attr, value):
|
||||
if isinstance(value, basestring):
|
||||
value = timeutils.parse_isotime(value)
|
||||
elif not isinstance(value, datetime.datetime):
|
||||
raise ValueError(_('A datetime.datetime is required here'))
|
||||
|
||||
if value.utcoffset() is None:
|
||||
value = value.replace(tzinfo=iso8601.iso8601.Utc())
|
||||
return value
|
||||
|
||||
def from_primitive(self, obj, attr, value):
|
||||
return self.coerce(obj, attr, timeutils.parse_isotime(value))
|
||||
|
||||
def to_primitive(self, obj, attr, value):
|
||||
return timeutils.isotime(value)
|
||||
|
||||
|
||||
class IPV4Address(FieldType):
|
||||
def coerce(self, obj, attr, value):
|
||||
try:
|
||||
return netaddr.IPAddress(value, version=4)
|
||||
except netaddr.AddrFormatError as e:
|
||||
raise ValueError(str(e))
|
||||
|
||||
def from_primitive(self, obj, attr, value):
|
||||
return self.coerce(obj, attr, value)
|
||||
|
||||
def to_primitive(self, obj, attr, value):
|
||||
return str(value)
|
||||
|
||||
|
||||
class IPV6Address(FieldType):
|
||||
def coerce(self, obj, attr, value):
|
||||
try:
|
||||
return netaddr.IPAddress(value, version=6)
|
||||
except netaddr.AddrFormatError as e:
|
||||
raise ValueError(str(e))
|
||||
|
||||
def from_primitive(self, obj, attr, value):
|
||||
return self.coerce(obj, attr, value)
|
||||
|
||||
def to_primitive(self, obj, attr, value):
|
||||
return str(value)
|
||||
|
||||
|
||||
class CompoundFieldType(FieldType):
|
||||
def __init__(self, element_type, **field_args):
|
||||
self._element_type = Field(element_type, **field_args)
|
||||
|
||||
|
||||
class List(CompoundFieldType):
|
||||
def coerce(self, obj, attr, value):
|
||||
if not isinstance(value, list):
|
||||
raise ValueError(_('A list is required here'))
|
||||
for index, element in enumerate(list(value)):
|
||||
value[index] = self._element_type.coerce(
|
||||
obj, '%s[%i]' % (attr, index), element)
|
||||
return value
|
||||
|
||||
def to_primitive(self, obj, attr, value):
|
||||
return [self._element_type.to_primitive(obj, attr, x) for x in value]
|
||||
|
||||
def from_primitive(self, obj, attr, value):
|
||||
return [self._element_type.from_primitive(obj, attr, x) for x in value]
|
||||
|
||||
|
||||
class Dict(CompoundFieldType):
|
||||
def coerce(self, obj, attr, value):
|
||||
if not isinstance(value, dict):
|
||||
raise ValueError(_('A dict is required here'))
|
||||
for key, element in value.items():
|
||||
if not isinstance(key, basestring):
|
||||
raise KeyTypeError(basestring, key)
|
||||
value[key] = self._element_type.coerce(
|
||||
obj, '%s["%s"]' % (attr, key), element)
|
||||
return value
|
||||
|
||||
def to_primitive(self, obj, attr, value):
|
||||
primitive = {}
|
||||
for key, element in value.items():
|
||||
primitive[key] = self._element_type.to_primitive(
|
||||
obj, '%s["%s"]' % (attr, key), element)
|
||||
return primitive
|
||||
|
||||
def from_primitive(self, obj, attr, value):
|
||||
concrete = {}
|
||||
for key, element in value.items():
|
||||
concrete[key] = self._element_type.from_primitive(
|
||||
obj, '%s["%s"]' % (attr, key), element)
|
||||
return concrete
|
||||
|
||||
|
||||
class Object(FieldType):
|
||||
def __init__(self, objtype, **kwargs):
|
||||
self._objtype = objtype
|
||||
super(Object, self).__init__(**kwargs)
|
||||
|
||||
def coerce(self, obj, attr, value):
|
||||
if not isinstance(value, self._objtype):
|
||||
raise ValueError(_('An object of type %s is required here') %
|
||||
self._objtype.obj_name())
|
||||
return value
|
||||
|
||||
def to_primitive(self, obj, attr, value):
|
||||
return value.obj_to_primitive()
|
||||
|
||||
def from_primitive(self, obj, attr, value):
|
||||
# FIXME(danms): Avoid circular import from base.py
|
||||
from nova.objects import base as obj_base
|
||||
return obj_base.NovaObject.obj_from_primitive(value, obj._context)
|
||||
|
||||
def describe(self):
|
||||
return "Object<%s>" % self._objtype.obj_name()
|
||||
|
||||
|
||||
class NetworkModel(FieldType):
|
||||
def coerce(self, obj, attr, value):
|
||||
if isinstance(value, network_model.NetworkInfo):
|
||||
return value
|
||||
elif isinstance(value, basestring):
|
||||
# Hmm, do we need this?
|
||||
return network_model.NetworkInfo.hydrate(value)
|
||||
else:
|
||||
raise ValueError(_('A NetworkModel is required here'))
|
||||
|
||||
def to_primitive(self, obj, attr, value):
|
||||
return value.json()
|
||||
|
||||
def from_primitive(self, obj, attr, value):
|
||||
return network_model.NetworkInfo.hydrate(value)
|
||||
|
||||
|
||||
class AutoTypedField(Field):
|
||||
AUTO_TYPE = None
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(AutoTypedField, self).__init__(self.AUTO_TYPE, **kwargs)
|
||||
|
||||
|
||||
class StringField(AutoTypedField):
|
||||
AUTO_TYPE = String()
|
||||
|
||||
|
||||
class UUIDField(AutoTypedField):
|
||||
AUTO_TYPE = UUID()
|
||||
|
||||
|
||||
class IntegerField(AutoTypedField):
|
||||
AUTO_TYPE = Integer()
|
||||
|
||||
|
||||
class BooleanField(AutoTypedField):
|
||||
AUTO_TYPE = Boolean()
|
||||
|
||||
|
||||
class DateTimeField(AutoTypedField):
|
||||
AUTO_TYPE = DateTime()
|
||||
|
||||
|
||||
class IPV4AddressField(AutoTypedField):
|
||||
AUTO_TYPE = IPV4Address()
|
||||
|
||||
|
||||
class IPV6AddressField(AutoTypedField):
|
||||
AUTO_TYPE = IPV6Address()
|
||||
|
||||
|
||||
class DictOfStringsField(AutoTypedField):
|
||||
AUTO_TYPE = Dict(String())
|
||||
|
||||
|
||||
class DictOfNullableStringsField(AutoTypedField):
|
||||
AUTO_TYPE = Dict(String(), nullable=True)
|
||||
|
||||
|
||||
class ListOfStringsField(AutoTypedField):
|
||||
AUTO_TYPE = List(String())
|
||||
|
||||
|
||||
class ObjectField(AutoTypedField):
|
||||
def __init__(self, objtype, **kwargs):
|
||||
self.AUTO_TYPE = Object(objtype)
|
||||
super(ObjectField, self).__init__(**kwargs)
|
||||
|
||||
|
||||
class ListOfObjectsField(AutoTypedField):
|
||||
def __init__(self, objtype, **kwargs):
|
||||
self.AUTO_TYPE = List(Object(objtype))
|
||||
super(ListOfObjectsField, self).__init__(**kwargs)
|
214
nova/tests/objects/test_fields.py
Normal file
214
nova/tests/objects/test_fields.py
Normal file
@@ -0,0 +1,214 @@
|
||||
# Copyright 2013 Red Hat, Inc.
|
||||
#
|
||||
# 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 datetime
|
||||
import iso8601
|
||||
|
||||
import netaddr
|
||||
|
||||
from nova.network import model as network_model
|
||||
from nova.objects import base as obj_base
|
||||
from nova.objects import fields
|
||||
from nova.openstack.common import timeutils
|
||||
from nova import test
|
||||
|
||||
|
||||
class FakeFieldType(fields.FieldType):
|
||||
def coerce(self, obj, attr, value):
|
||||
return '*%s*' % value
|
||||
|
||||
def to_primitive(self, obj, attr, value):
|
||||
return '!%s!' % value
|
||||
|
||||
def from_primitive(self, obj, attr, value):
|
||||
return value[1:-1]
|
||||
|
||||
|
||||
class TestField(test.NoDBTestCase):
|
||||
def setUp(self):
|
||||
super(TestField, self).setUp()
|
||||
self.field = fields.Field(FakeFieldType())
|
||||
self.coerce_good_values = [('foo', '*foo*')]
|
||||
self.coerce_bad_values = []
|
||||
self.to_primitive_values = [('foo', '!foo!')]
|
||||
self.from_primitive_values = [('!foo!', 'foo')]
|
||||
|
||||
def test_coerce_good_values(self):
|
||||
for in_val, out_val in self.coerce_good_values:
|
||||
self.assertEqual(out_val, self.field.coerce('obj', 'attr', in_val))
|
||||
|
||||
def test_coerce_bad_values(self):
|
||||
for in_val in self.coerce_bad_values:
|
||||
self.assertRaises((TypeError, ValueError),
|
||||
self.field.coerce, 'obj', 'attr', in_val)
|
||||
|
||||
def test_to_primitive(self):
|
||||
for in_val, prim_val in self.to_primitive_values:
|
||||
self.assertEqual(prim_val, self.field.to_primitive('obj', 'attr',
|
||||
in_val))
|
||||
|
||||
def test_from_primitive(self):
|
||||
class ObjectLikeThing:
|
||||
_context = 'context'
|
||||
|
||||
for prim_val, out_val in self.from_primitive_values:
|
||||
self.assertEqual(out_val, self.field.from_primitive(
|
||||
ObjectLikeThing, 'attr', prim_val))
|
||||
|
||||
|
||||
class TestString(TestField):
|
||||
def setUp(self):
|
||||
super(TestField, self).setUp()
|
||||
self.field = fields.StringField()
|
||||
self.coerce_good_values = [('foo', 'foo'), (1, '1'), (1L, '1'),
|
||||
(True, 'True')]
|
||||
self.coerce_bad_values = [None]
|
||||
self.to_primitive_values = self.coerce_good_values[0:1]
|
||||
self.from_primitive_values = self.coerce_good_values[0:1]
|
||||
|
||||
|
||||
class TestInteger(TestField):
|
||||
def setUp(self):
|
||||
super(TestField, self).setUp()
|
||||
self.field = fields.IntegerField()
|
||||
self.coerce_good_values = [(1, 1), ('1', 1)]
|
||||
self.coerce_bad_values = ['foo', None]
|
||||
self.to_primitive_values = self.coerce_good_values[0:1]
|
||||
self.from_primitive_values = self.coerce_good_values[0:1]
|
||||
|
||||
|
||||
class TestBoolean(TestField):
|
||||
def setUp(self):
|
||||
super(TestField, self).setUp()
|
||||
self.field = fields.BooleanField()
|
||||
self.coerce_good_values = [(True, True), (False, False), (1, True),
|
||||
('foo', True), (0, False), ('', False)]
|
||||
self.coerce_bad_values = []
|
||||
self.to_primitive_values = self.coerce_good_values[0:2]
|
||||
self.from_primitive_values = self.coerce_good_values[0:2]
|
||||
|
||||
|
||||
class TestDateTime(TestField):
|
||||
def setUp(self):
|
||||
super(TestDateTime, self).setUp()
|
||||
self.dt = datetime.datetime(1955, 11, 5, tzinfo=iso8601.iso8601.Utc())
|
||||
self.field = fields.DateTimeField()
|
||||
self.coerce_good_values = [(self.dt, self.dt),
|
||||
(timeutils.isotime(self.dt), self.dt)]
|
||||
self.coerce_bad_values = [1, 'foo']
|
||||
self.to_primitive_values = [(self.dt, timeutils.isotime(self.dt))]
|
||||
self.from_primitive_values = [(timeutils.isotime(self.dt), self.dt)]
|
||||
|
||||
|
||||
class TestIPAddressV4(TestField):
|
||||
def setUp(self):
|
||||
super(TestIPAddressV4, self).setUp()
|
||||
self.field = fields.IPV4AddressField()
|
||||
self.coerce_good_values = [('1.2.3.4', netaddr.IPAddress('1.2.3.4')),
|
||||
(netaddr.IPAddress('1.2.3.4'),
|
||||
netaddr.IPAddress('1.2.3.4'))]
|
||||
self.coerce_bad_values = ['1-2', 'foo', '::1']
|
||||
self.to_primitive_values = [(netaddr.IPAddress('1.2.3.4'), '1.2.3.4')]
|
||||
self.from_primitive_values = [('1.2.3.4',
|
||||
netaddr.IPAddress('1.2.3.4'))]
|
||||
|
||||
|
||||
class TestIPAddressV6(TestField):
|
||||
def setUp(self):
|
||||
super(TestIPAddressV6, self).setUp()
|
||||
self.field = fields.IPV6AddressField()
|
||||
self.coerce_good_values = [('::1', netaddr.IPAddress('::1')),
|
||||
(netaddr.IPAddress('::1'),
|
||||
netaddr.IPAddress('::1'))]
|
||||
self.coerce_bad_values = ['1.2', 'foo', '1.2.3.4']
|
||||
self.to_primitive_values = [(netaddr.IPAddress('::1'), '::1')]
|
||||
self.from_primitive_values = [('::1',
|
||||
netaddr.IPAddress('::1'))]
|
||||
|
||||
|
||||
class TestDict(TestField):
|
||||
def setUp(self):
|
||||
super(TestDict, self).setUp()
|
||||
self.field = fields.Field(fields.Dict(FakeFieldType()))
|
||||
self.coerce_good_values = [({'foo': 'bar'}, {'foo': '*bar*'}),
|
||||
({'foo': 1}, {'foo': '*1*'})]
|
||||
self.coerce_bad_values = [{1: 'bar'}, 'foo']
|
||||
self.to_primitive_values = [({'foo': 'bar'}, {'foo': '!bar!'})]
|
||||
self.from_primitive_values = [({'foo': '!bar!'}, {'foo': 'bar'})]
|
||||
|
||||
|
||||
class TestDictOfStrings(TestField):
|
||||
def setUp(self):
|
||||
super(TestDictOfStrings, self).setUp()
|
||||
self.field = fields.DictOfStringsField()
|
||||
self.coerce_good_values = [({'foo': 'bar'}, {'foo': 'bar'}),
|
||||
({'foo': 1}, {'foo': '1'})]
|
||||
self.coerce_bad_values = [{1: 'bar'}, {'foo': None}, 'foo']
|
||||
self.to_primitive_values = [({'foo': 'bar'}, {'foo': 'bar'})]
|
||||
self.from_primitive_values = [({'foo': 'bar'}, {'foo': 'bar'})]
|
||||
|
||||
|
||||
class TestDictOfStringsNone(TestField):
|
||||
def setUp(self):
|
||||
super(TestDictOfStringsNone, self).setUp()
|
||||
self.field = fields.DictOfNullableStringsField()
|
||||
self.coerce_good_values = [({'foo': 'bar'}, {'foo': 'bar'}),
|
||||
({'foo': 1}, {'foo': '1'}),
|
||||
({'foo': None}, {'foo': None})]
|
||||
self.coerce_bad_values = [{1: 'bar'}, 'foo']
|
||||
self.to_primitive_values = [({'foo': 'bar'}, {'foo': 'bar'})]
|
||||
self.from_primitive_values = [({'foo': 'bar'}, {'foo': 'bar'})]
|
||||
|
||||
|
||||
class TestList(TestField):
|
||||
def setUp(self):
|
||||
super(TestList, self).setUp()
|
||||
self.field = fields.Field(fields.List(FakeFieldType()))
|
||||
self.coerce_good_values = [(['foo', 'bar'], ['*foo*', '*bar*'])]
|
||||
self.coerce_bad_values = ['foo']
|
||||
self.to_primitive_values = [(['foo'], ['!foo!'])]
|
||||
self.from_primitive_values = [(['!foo!'], ['foo'])]
|
||||
|
||||
|
||||
class TestObject(TestField):
|
||||
def setUp(self):
|
||||
class TestableObject(obj_base.NovaObject):
|
||||
def __eq__(self, value):
|
||||
# NOTE(danms): Be rather lax about this equality thing to
|
||||
# satisfy the assertEqual() in test_from_primitive(). We
|
||||
# just want to make sure the right type of object is re-created
|
||||
return value.__class__.__name__ == TestableObject.__name__
|
||||
|
||||
class OtherTestableObject(obj_base.NovaObject):
|
||||
pass
|
||||
|
||||
test_inst = TestableObject()
|
||||
super(TestObject, self).setUp()
|
||||
self.field = fields.Field(fields.Object(TestableObject))
|
||||
self.coerce_good_values = [(test_inst, test_inst)]
|
||||
self.coerce_bad_values = [OtherTestableObject(), 1, 'foo']
|
||||
self.to_primitive_values = [(test_inst, test_inst.obj_to_primitive())]
|
||||
self.from_primitive_values = [(test_inst.obj_to_primitive(),
|
||||
test_inst)]
|
||||
|
||||
|
||||
class TestNetworkModel(TestField):
|
||||
def setUp(self):
|
||||
super(TestNetworkModel, self).setUp()
|
||||
model = network_model.NetworkInfo()
|
||||
self.field = fields.Field(fields.NetworkModel())
|
||||
self.coerce_good_values = [(model, model), (model.json(), model)]
|
||||
self.coerce_bad_values = [[], 'foo']
|
||||
self.to_primitive_values = [(model, model.json())]
|
||||
self.from_primitive_values = [(model.json(), model)]
|
Reference in New Issue
Block a user