mirror of
https://github.com/valitydev/salt.git
synced 2024-11-07 08:58:59 +00:00
Add array support to salt.utils.config (with unit tests)
This commit is contained in:
parent
fac5c06c23
commit
e0f4706ef6
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user