Add array support to salt.utils.config (with unit tests)

This commit is contained in:
Pedro Algarvio 2015-07-02 14:37:25 +01:00
parent fac5c06c23
commit e0f4706ef6
2 changed files with 285 additions and 3 deletions

View File

@ -606,8 +606,13 @@ class BaseItem(six.with_metaclass(BaseConfigItemMeta, object)):
'''
Return the argname value looking up on all possible attributes
'''
# Let's see if the value is defined as a public class variable
argvalue = getattr(self, argname, None)
# Let's see if there's a private fuction to get the value
argvalue = getattr(self, '__get_{0}__'.format(argname), None)
if argvalue and callable(argvalue):
argvalue = argvalue()
if argvalue is None:
# Let's see if the value is defined as a public class variable
argvalue = getattr(self, argname, None)
if argvalue is None:
# Let's see if it's defined as a private class variable
argvalue = getattr(self, '__{0}__'.format(argname), None)
@ -657,7 +662,7 @@ class BaseConfigItem(BaseItem):
A list(list, tuple, set) of valid choices.
'''
self.title = title
self.description = description or self.__doc__
self.description = description
self.default = default
if enum is not None:
if not isinstance(enum, (list, tuple, set)):
@ -863,3 +868,80 @@ class NumberConfig(BaseConfigItem):
class IntegerConfig(NumberConfig):
__type__ = 'integer'
class ArrayConfig(BaseConfigItem):
__type__ = 'array'
__serialize_attr_aliases__ = {
'min_items': 'minItems',
'max_items': 'maxItems',
'unique_items': 'uniqueItems',
'additional_items': 'additionalItems'
}
def __init__(self,
items=None,
min_items=None,
max_items=None,
unique_items=None,
additional_items=None,
**kwargs):
'''
:param required:
If the configuration item is required. Defaults to ``False``.
:param title:
A short explanation about the purpose of the data described by this item.
:param description:
A detailed explanation about the purpose of the data described by this item.
:param default:
The default value for this configuration item. May be :data:`.Null` (a special value
to set the default value to null).
:param enum:
A list(list, tuple, set) of valid choices.
:param items:
Either of the following:
* :class:`BaseConfigItem` -- all items of the array must match the field schema;
* a list or a tuple of :class:`fields <.BaseConfigItem>` -- all items of the array must be
valid according to the field schema at the corresponding index (tuple typing);
:param min_items:
Minimum length of the array
:param max_items:
Maximum length of the array
:param unique_items:
Whether all the values in the array must be distinct.
:param additional_items:
If the value of ``items`` is a list or a tuple, and the array length is larger than
the number of fields in ``items``, then the additional items are described
by the :class:`.BaseField` passed using this argument.
:type additional_items: bool or :class:`.BaseConfigItem`
'''
if items:
if isinstance(items, (list, tuple)):
for item in items:
if not isinstance(item, BaseItem):
raise RuntimeError(
'All items passed in the item argument tuple/list must be '
'a subclass of BaseItem/BaseConfigItem, not {0}'.format(type(item))
)
elif not isinstance(items, BaseItem):
raise RuntimeError(
'The items argument passed must be a subclass of '
'BaseConfigItem, not {0}'.format(type(item))
)
self.items = items
self.min_items = min_items
self.max_items = max_items
self.unique_items = unique_items
self.additional_items = additional_items
super(ArrayConfig, self).__init__(**kwargs)
def __get_items__(self):
if isinstance(self.items, BaseItem):
# This is a BaseConfigItem, return it in it's serialized form
return self.items.serialize()
if isinstance(self.items, (tuple, list)):
items = []
for item in self.items:
items.append(item.serialize())
return items

View File

@ -736,6 +736,206 @@ class ConfigTestCase(TestCase):
jsonschema.validate({'item': 3}, TestConf.serialize())
self.assertIn('is not one of', excinfo.exception.message)
def test_array_config(self):
item = config.ArrayConfig(title='Dog Names', description='Name your dogs')
self.assertDictEqual(
item.serialize(), {
'type': 'array',
'title': item.title,
'description': item.description
}
)
string_item = config.StringConfig(title='Dog Name',
description='The dog name')
item = config.ArrayConfig(title='Dog Names',
description='Name your dogs',
items=string_item)
self.assertDictEqual(
item.serialize(), {
'type': 'array',
'title': item.title,
'description': item.description,
'items': {
'type': 'string',
'title': string_item.title,
'description': string_item.description
}
}
)
integer_item = config.IntegerConfig(title='Dog Age',
description='The dog age')
item = config.ArrayConfig(title='Dog Names',
description='Name your dogs',
items=(string_item, integer_item))
self.assertDictEqual(
item.serialize(), {
'type': 'array',
'title': item.title,
'description': item.description,
'items': [
{
'type': 'string',
'title': string_item.title,
'description': string_item.description
},
{
'type': 'integer',
'title': integer_item.title,
'description': integer_item.description
}
]
}
)
item = config.ArrayConfig(title='Dog Names',
description='Name your dogs',
items=(config.StringConfig(),
config.IntegerConfig()),
min_items=1,
max_items=3,
additional_items=False,
unique_items=True)
self.assertDictEqual(
item.serialize(), {
'type': 'array',
'title': item.title,
'description': item.description,
'minItems': item.min_items,
'maxItems': item.max_items,
'uniqueItems': item.unique_items,
'additionalItems': item.additional_items,
'items': [
{
'type': 'string',
},
{
'type': 'integer',
}
]
}
)
@skipIf(HAS_JSONSCHEMA is False, 'The \'jsonschema\' library is missing')
def test_array_config_validation(self):
class TestConf(config.Configuration):
item = config.ArrayConfig(title='Dog Names', description='Name your dogs')
try:
jsonschema.validate({'item': ['Tobias', 'Óscar']}, TestConf.serialize(),
format_checker=jsonschema.FormatChecker())
except jsonschema.exceptions.ValidationError as exc:
self.fail('ValidationError raised: {0}'.format(exc))
with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
jsonschema.validate({'item': 1}, TestConf.serialize(),
format_checker=jsonschema.FormatChecker())
self.assertIn('is not of type', excinfo.exception.message)
class TestConf(config.Configuration):
item = config.ArrayConfig(title='Dog Names',
description='Name your dogs',
items=config.StringConfig())
try:
jsonschema.validate({'item': ['Tobias', 'Óscar']}, TestConf.serialize(),
format_checker=jsonschema.FormatChecker())
except jsonschema.exceptions.ValidationError as exc:
self.fail('ValidationError raised: {0}'.format(exc))
with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
jsonschema.validate({'item': ['Tobias', 'Óscar', 3]}, TestConf.serialize(),
format_checker=jsonschema.FormatChecker())
self.assertIn('is not of type', excinfo.exception.message)
class TestConf(config.Configuration):
item = config.ArrayConfig(title='Dog Names',
description='Name your dogs',
min_items=1,
max_items=2)
try:
jsonschema.validate({'item': ['Tobias', 'Óscar']}, TestConf.serialize(),
format_checker=jsonschema.FormatChecker())
except jsonschema.exceptions.ValidationError as exc:
self.fail('ValidationError raised: {0}'.format(exc))
with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
jsonschema.validate({'item': ['Tobias', 'Óscar', 'Pepe']}, TestConf.serialize(),
format_checker=jsonschema.FormatChecker())
self.assertIn('is too long', excinfo.exception.message)
with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
jsonschema.validate({'item': []}, TestConf.serialize(),
format_checker=jsonschema.FormatChecker())
self.assertIn('is too short', excinfo.exception.message)
class TestConf(config.Configuration):
item = config.ArrayConfig(title='Dog Names',
description='Name your dogs',
items=config.StringConfig(),
uniqueItems=True)
with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
jsonschema.validate({'item': ['Tobias', 'Tobias']}, TestConf.serialize(),
format_checker=jsonschema.FormatChecker())
self.assertIn('has non-unique elements', excinfo.exception.message)
class TestConf(config.Configuration):
item = config.ArrayConfig(items=(config.StringConfig(),
config.IntegerConfig()))
try:
jsonschema.validate({'item': ['Óscar', 4]}, TestConf.serialize(),
format_checker=jsonschema.FormatChecker())
except jsonschema.exceptions.ValidationError as exc:
self.fail('ValidationError raised: {0}'.format(exc))
with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
jsonschema.validate({'item': ['Tobias', 'Óscar']}, TestConf.serialize(),
format_checker=jsonschema.FormatChecker())
self.assertIn('is not of type', excinfo.exception.message)
class TestConf(config.Configuration):
item = config.ArrayConfig(
items=config.ArrayConfig(
items=(config.StringConfig(),
config.IntegerConfig())
)
)
try:
jsonschema.validate({'item': [['Tobias', 8], ['Óscar', 4]]}, TestConf.serialize(),
format_checker=jsonschema.FormatChecker())
except jsonschema.exceptions.ValidationError as exc:
self.fail('ValidationError raised: {0}'.format(exc))
with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
jsonschema.validate({'item': [['Tobias', 8], ['Óscar', '4']]}, TestConf.serialize(),
format_checker=jsonschema.FormatChecker())
self.assertIn('is not of type', excinfo.exception.message)
class TestConf(config.Configuration):
item = config.ArrayConfig(items=config.StringConfig(enum=['Tobias', 'Óscar']))
try:
jsonschema.validate({'item': ['Óscar']}, TestConf.serialize(),
format_checker=jsonschema.FormatChecker())
except jsonschema.exceptions.ValidationError as exc:
self.fail('ValidationError raised: {0}'.format(exc))
try:
jsonschema.validate({'item': ['Tobias']}, TestConf.serialize(),
format_checker=jsonschema.FormatChecker())
except jsonschema.exceptions.ValidationError as exc:
self.fail('ValidationError raised: {0}'.format(exc))
with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
jsonschema.validate({'item': ['Pepe']}, TestConf.serialize(),
format_checker=jsonschema.FormatChecker())
self.assertIn('is not one of', excinfo.exception.message)
if __name__ == '__main__':
from integration import run_tests