mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
ArrayConfig must have items passed. Added DictConfig with tests.
This commit is contained in:
parent
884212532a
commit
f36123c4df
2 changed files with 434 additions and 38 deletions
|
@ -1026,21 +1026,24 @@ class ArrayConfig(BaseConfigItem):
|
|||
super(ArrayConfig, self).__init__(**kwargs)
|
||||
|
||||
def __validate_attributes__(self):
|
||||
if self.items is not None:
|
||||
if isinstance(self.items, (list, tuple)):
|
||||
for item in self.items:
|
||||
if not isinstance(item, (Configuration, BaseItem)):
|
||||
raise RuntimeError(
|
||||
'All items passed in the item argument tuple/list must be '
|
||||
'a subclass of Configuration, BaseItem or BaseConfigItem, '
|
||||
'not {0}'.format(type(item))
|
||||
)
|
||||
elif not isinstance(self.items, (Configuration, BaseItem)):
|
||||
raise RuntimeError(
|
||||
'The items argument passed must be a subclass of '
|
||||
'Configuration, BaseItem or BaseConfigItem, not '
|
||||
'{0}'.format(type(self.items))
|
||||
)
|
||||
if not self.items:
|
||||
raise RuntimeError(
|
||||
'The passed items must not be empty'
|
||||
)
|
||||
if isinstance(self.items, (list, tuple)):
|
||||
for item in self.items:
|
||||
if not isinstance(item, (Configuration, BaseItem)):
|
||||
raise RuntimeError(
|
||||
'All items passed in the item argument tuple/list must be '
|
||||
'a subclass of Configuration, BaseItem or BaseConfigItem, '
|
||||
'not {0}'.format(type(item))
|
||||
)
|
||||
elif not isinstance(self.items, (Configuration, BaseItem)):
|
||||
raise RuntimeError(
|
||||
'The items argument passed must be a subclass of '
|
||||
'Configuration, BaseItem or BaseConfigItem, not '
|
||||
'{0}'.format(type(self.items))
|
||||
)
|
||||
|
||||
def __get_items__(self):
|
||||
if isinstance(self.items, (Configuration, BaseItem)):
|
||||
|
@ -1054,6 +1057,141 @@ class ArrayConfig(BaseConfigItem):
|
|||
return items
|
||||
|
||||
|
||||
class DictConfig(BaseConfigItem):
|
||||
|
||||
__type__ = 'object'
|
||||
|
||||
__serialize_attr_aliases__ = {
|
||||
'min_properties': 'minProperties',
|
||||
'max_properties': 'maxProperties',
|
||||
'pattern_properties': 'patternProperties',
|
||||
'additional_properties': 'additionalProperties'
|
||||
}
|
||||
|
||||
properties = None
|
||||
pattern_properties = None
|
||||
additional_properties = None
|
||||
min_properties = None
|
||||
max_properties = None
|
||||
|
||||
def __init__(self,
|
||||
properties=None,
|
||||
pattern_properties=None,
|
||||
additional_properties=None,
|
||||
min_properties=None,
|
||||
max_properties=None,
|
||||
**kwargs):
|
||||
'''
|
||||
:param required:
|
||||
If the configuration item is required. Defaults to ``False``.
|
||||
:type required:
|
||||
boolean
|
||||
:param title:
|
||||
A short explanation about the purpose of the data described by this item.
|
||||
:type title:
|
||||
str
|
||||
: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 properties:
|
||||
A dictionary containing fields
|
||||
:param pattern_properties:
|
||||
A dictionary whose keys are regular expressions (ECMA 262).
|
||||
Properties match against these regular expressions, and for any that match,
|
||||
the property is described by the corresponding field schema.
|
||||
:type pattern_properties: dict[str -> :class:`.Configuration` or
|
||||
:class:`.BaseItem` or :class:`.BaseItemConfig`]
|
||||
:param additional_properties:
|
||||
Describes properties that are not described by the ``properties`` or ``pattern_properties``.
|
||||
:type additional_properties: bool or :class:`.Configuration` or :class:`.BaseItem`
|
||||
or :class:`.BaseItemConfig`
|
||||
:param min_properties:
|
||||
A minimum number of properties.
|
||||
:type min_properties: int
|
||||
:param max_properties:
|
||||
A maximum number of properties
|
||||
:type max_properties: int
|
||||
'''
|
||||
if properties is not None:
|
||||
self.properties = properties
|
||||
if pattern_properties is not None:
|
||||
self.pattern_properties = pattern_properties
|
||||
if additional_properties is not None:
|
||||
self.additional_properties = additional_properties
|
||||
if min_properties is not None:
|
||||
self.min_properties = min_properties
|
||||
if max_properties is not None:
|
||||
self.max_properties = max_properties
|
||||
super(DictConfig, self).__init__(**kwargs)
|
||||
|
||||
def __validate_attributes__(self):
|
||||
if not self.properties and not self.pattern_properties:
|
||||
raise RuntimeError(
|
||||
'One of properties or pattern properties must be passed'
|
||||
)
|
||||
if self.properties is not None:
|
||||
if not isinstance(self.properties, dict):
|
||||
raise RuntimeError(
|
||||
'The passed properties must be passed as a dict not '
|
||||
'\'{0}\''.format(type(self.properties))
|
||||
)
|
||||
for key, prop in self.properties.items():
|
||||
if not isinstance(prop, (Configuration, BaseItem)):
|
||||
raise RuntimeError(
|
||||
'The passed property who\'s key is \'{0}\' must be of type '
|
||||
'Configuration, BaseItem or BaseConfigItem, not '
|
||||
'\'{1}\''.format(key, type(prop))
|
||||
)
|
||||
if self.pattern_properties is not None:
|
||||
if not isinstance(self.pattern_properties, dict):
|
||||
raise RuntimeError(
|
||||
'The passed pattern_properties must be passed as a dict '
|
||||
'not \'{0}\''.format(type(self.pattern_properties))
|
||||
)
|
||||
for key, prop in self.pattern_properties.items():
|
||||
if not isinstance(prop, (Configuration, BaseItem)):
|
||||
raise RuntimeError(
|
||||
'The passed pattern_property who\'s key is \'{0}\' must '
|
||||
'be of type Configuration, BaseItem or BaseConfigItem, '
|
||||
'not \'{1}\''.format(key, type(prop))
|
||||
)
|
||||
if self.additional_properties is not None:
|
||||
if not isinstance(self.additional_properties, (bool, Configuration, BaseItem)):
|
||||
raise RuntimeError(
|
||||
'The passed additional_properties must be of type bool, '
|
||||
'Configuration, BaseItem or BaseConfigItem, not \'{0}\''.format(
|
||||
type(self.pattern_properties)
|
||||
)
|
||||
)
|
||||
|
||||
def __get_properties__(self):
|
||||
if self.properties is None:
|
||||
return
|
||||
properties = OrderedDict()
|
||||
for key, prop in self.properties.items():
|
||||
properties[key] = prop.serialize()
|
||||
return properties
|
||||
|
||||
def __get_pattern_properties__(self):
|
||||
if self.pattern_properties is None:
|
||||
return
|
||||
pattern_properties = OrderedDict()
|
||||
for key, prop in self.pattern_properties.items():
|
||||
pattern_properties[key] = prop.serialize()
|
||||
return pattern_properties
|
||||
|
||||
def __get_additional_properties__(self):
|
||||
if self.additional_properties is None:
|
||||
return
|
||||
if isinstance(self.additional_properties, bool):
|
||||
return self.additional_properties
|
||||
return self.additional_properties.serialize()
|
||||
|
||||
|
||||
class OneOfConfig(BaseItem):
|
||||
|
||||
__type__ = 'oneOf'
|
||||
|
|
|
@ -797,15 +797,6 @@ class ConfigTestCase(TestCase):
|
|||
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',
|
||||
|
@ -915,20 +906,6 @@ class ConfigTestCase(TestCase):
|
|||
|
||||
@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',
|
||||
|
@ -948,6 +925,7 @@ class ConfigTestCase(TestCase):
|
|||
class TestConf(config.Configuration):
|
||||
item = config.ArrayConfig(title='Dog Names',
|
||||
description='Name your dogs',
|
||||
items=config.StringConfig(),
|
||||
min_items=1,
|
||||
max_items=2)
|
||||
|
||||
|
@ -1029,6 +1007,286 @@ class ConfigTestCase(TestCase):
|
|||
format_checker=jsonschema.FormatChecker())
|
||||
self.assertIn('is not one of', excinfo.exception.message)
|
||||
|
||||
def test_dict_config(self):
|
||||
item = config.DictConfig(
|
||||
title='Poligon',
|
||||
description='Describe the Poligon',
|
||||
properties={
|
||||
'sides': config.IntegerConfig()
|
||||
}
|
||||
)
|
||||
self.assertDictEqual(
|
||||
item.serialize(), {
|
||||
'type': 'object',
|
||||
'title': item.title,
|
||||
'description': item.description,
|
||||
'properties': {
|
||||
'sides': {'type': 'integer'}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
item = config.DictConfig(
|
||||
title='Poligon',
|
||||
description='Describe the Poligon',
|
||||
properties={
|
||||
'sides': config.IntegerConfig()
|
||||
},
|
||||
min_properties=1,
|
||||
max_properties=2
|
||||
)
|
||||
self.assertDictEqual(
|
||||
item.serialize(), {
|
||||
'type': 'object',
|
||||
'title': item.title,
|
||||
'description': item.description,
|
||||
'properties': {
|
||||
'sides': {'type': 'integer'}
|
||||
},
|
||||
'minProperties': 1,
|
||||
'maxProperties': 2
|
||||
}
|
||||
)
|
||||
|
||||
item = config.DictConfig(
|
||||
title='Poligon',
|
||||
description='Describe the Poligon',
|
||||
pattern_properties={
|
||||
's*': config.IntegerConfig()
|
||||
},
|
||||
min_properties=1,
|
||||
max_properties=2
|
||||
)
|
||||
self.assertDictEqual(
|
||||
item.serialize(), {
|
||||
'type': 'object',
|
||||
'title': item.title,
|
||||
'description': item.description,
|
||||
'patternProperties': {
|
||||
's*': {'type': 'integer'}
|
||||
},
|
||||
'minProperties': 1,
|
||||
'maxProperties': 2
|
||||
}
|
||||
)
|
||||
|
||||
item = config.DictConfig(
|
||||
title='Poligon',
|
||||
description='Describe the Poligon',
|
||||
properties={
|
||||
'color': config.StringConfig(enum=['red', 'green', 'blue'])
|
||||
},
|
||||
pattern_properties={
|
||||
's*': config.IntegerConfig()
|
||||
},
|
||||
min_properties=1,
|
||||
max_properties=2
|
||||
)
|
||||
self.assertDictEqual(
|
||||
item.serialize(), {
|
||||
'type': 'object',
|
||||
'title': item.title,
|
||||
'description': item.description,
|
||||
'properties': {
|
||||
'color': {
|
||||
'type': 'string',
|
||||
'enum': ['red', 'green', 'blue']
|
||||
}
|
||||
},
|
||||
'patternProperties': {
|
||||
's*': {'type': 'integer'}
|
||||
},
|
||||
'minProperties': 1,
|
||||
'maxProperties': 2
|
||||
}
|
||||
)
|
||||
|
||||
item = config.DictConfig(
|
||||
title='Poligon',
|
||||
description='Describe the Poligon',
|
||||
properties={
|
||||
'color': config.StringConfig(enum=['red', 'green', 'blue'])
|
||||
},
|
||||
pattern_properties={
|
||||
's*': config.IntegerConfig()
|
||||
},
|
||||
additional_properties=True,
|
||||
min_properties=1,
|
||||
max_properties=2
|
||||
)
|
||||
self.assertDictEqual(
|
||||
item.serialize(), {
|
||||
'type': 'object',
|
||||
'title': item.title,
|
||||
'description': item.description,
|
||||
'properties': {
|
||||
'color': {
|
||||
'type': 'string',
|
||||
'enum': ['red', 'green', 'blue']
|
||||
}
|
||||
},
|
||||
'patternProperties': {
|
||||
's*': {'type': 'integer'}
|
||||
},
|
||||
'minProperties': 1,
|
||||
'maxProperties': 2,
|
||||
'additionalProperties': True
|
||||
}
|
||||
)
|
||||
item = config.DictConfig(
|
||||
title='Poligon',
|
||||
description='Describe the Poligon',
|
||||
properties={
|
||||
'sides': config.IntegerConfig()
|
||||
},
|
||||
additional_properties=config.OneOfConfig(items=[config.BooleanConfig(),
|
||||
config.StringConfig()])
|
||||
)
|
||||
self.assertDictEqual(
|
||||
item.serialize(), {
|
||||
'type': 'object',
|
||||
'title': item.title,
|
||||
'description': item.description,
|
||||
'properties': {
|
||||
'sides': {'type': 'integer'}
|
||||
},
|
||||
'additionalProperties': {
|
||||
'oneOf': [
|
||||
{'type': 'boolean'},
|
||||
{'type': 'string'}
|
||||
]
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@skipIf(HAS_JSONSCHEMA is False, 'The \'jsonschema\' library is missing')
|
||||
def test_dict_config_validation(self):
|
||||
class TestConf(config.Configuration):
|
||||
item = config.DictConfig(
|
||||
title='Poligon',
|
||||
description='Describe the Poligon',
|
||||
properties={
|
||||
'sides': config.IntegerConfig()
|
||||
}
|
||||
)
|
||||
|
||||
try:
|
||||
jsonschema.validate({'item': {'sides': 1}}, TestConf.serialize())
|
||||
except jsonschema.exceptions.ValidationError as exc:
|
||||
self.fail('ValidationError raised: {0}'.format(exc))
|
||||
|
||||
with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
|
||||
jsonschema.validate({'item': {'sides': '1'}}, TestConf.serialize())
|
||||
self.assertIn('is not of type', excinfo.exception.message)
|
||||
|
||||
with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
|
||||
jsonschema.validate({'item': 2}, TestConf.serialize())
|
||||
self.assertIn('is not of type', excinfo.exception.message)
|
||||
|
||||
class TestConf(config.Configuration):
|
||||
item = config.DictConfig(
|
||||
title='Poligon',
|
||||
description='Describe the Poligon',
|
||||
properties={
|
||||
'color': config.StringConfig(enum=['red', 'green', 'blue'])
|
||||
},
|
||||
pattern_properties={
|
||||
'si.*': config.IntegerConfig()
|
||||
},
|
||||
)
|
||||
|
||||
try:
|
||||
jsonschema.validate({'item': {'sides': 1, 'color': 'red'}}, TestConf.serialize())
|
||||
except jsonschema.exceptions.ValidationError as exc:
|
||||
self.fail('ValidationError raised: {0}'.format(exc))
|
||||
|
||||
with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
|
||||
jsonschema.validate({'item': {'sides': '4', 'color': 'blue'}}, TestConf.serialize())
|
||||
self.assertIn('is not of type', excinfo.exception.message)
|
||||
|
||||
with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
|
||||
jsonschema.validate({'item': 2}, TestConf.serialize())
|
||||
self.assertIn('is not of type', excinfo.exception.message)
|
||||
|
||||
class TestConf(config.Configuration):
|
||||
item = config.DictConfig(
|
||||
title='Poligon',
|
||||
description='Describe the Poligon',
|
||||
properties={
|
||||
'color': config.StringConfig(enum=['red', 'green', 'blue'])
|
||||
},
|
||||
pattern_properties={
|
||||
'si.*': config.IntegerConfig()
|
||||
},
|
||||
additional_properties=False
|
||||
)
|
||||
|
||||
with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
|
||||
jsonschema.validate({'item': {'color': 'green', 'sides': 4, 'surfaces': 4}}, TestConf.serialize())
|
||||
self.assertIn('Additional properties are not allowed', excinfo.exception.message)
|
||||
|
||||
class TestConf(config.Configuration):
|
||||
item = config.DictConfig(
|
||||
title='Poligon',
|
||||
description='Describe the Poligon',
|
||||
properties={
|
||||
'color': config.StringConfig(enum=['red', 'green', 'blue'])
|
||||
},
|
||||
additional_properties=config.OneOfConfig(items=[
|
||||
config.BooleanConfig(),
|
||||
config.IntegerConfig()
|
||||
|
||||
])
|
||||
)
|
||||
|
||||
try:
|
||||
jsonschema.validate({'item': {'sides': 1,
|
||||
'color': 'red',
|
||||
'rugged_surface': False}}, TestConf.serialize())
|
||||
except jsonschema.exceptions.ValidationError as exc:
|
||||
self.fail('ValidationError raised: {0}'.format(exc))
|
||||
|
||||
with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
|
||||
jsonschema.validate({'item': {'sides': '4', 'color': 'blue'}}, TestConf.serialize())
|
||||
self.assertIn('is not valid under any of the given schemas', excinfo.exception.message)
|
||||
|
||||
class TestConf(config.Configuration):
|
||||
item = config.DictConfig(
|
||||
title='Poligon',
|
||||
description='Describe the Poligon',
|
||||
properties={
|
||||
'color': config.StringConfig(enum=['red', 'green', 'blue'])
|
||||
},
|
||||
additional_properties=config.OneOfConfig(items=[
|
||||
config.BooleanConfig(),
|
||||
config.IntegerConfig()
|
||||
|
||||
]),
|
||||
min_properties=2,
|
||||
max_properties=3
|
||||
)
|
||||
|
||||
try:
|
||||
jsonschema.validate({'item': {'color': 'red', 'sides': 1}}, TestConf.serialize())
|
||||
except jsonschema.exceptions.ValidationError as exc:
|
||||
self.fail('ValidationError raised: {0}'.format(exc))
|
||||
|
||||
try:
|
||||
jsonschema.validate({'item': {'sides': 1, 'color': 'red', 'rugged_surface': False}}, TestConf.serialize())
|
||||
except jsonschema.exceptions.ValidationError as exc:
|
||||
self.fail('ValidationError raised: {0}'.format(exc))
|
||||
|
||||
with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
|
||||
jsonschema.validate({'item': {'color': 'blue'}}, TestConf.serialize())
|
||||
self.assertIn('does not have enough properties', excinfo.exception.message)
|
||||
|
||||
with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
|
||||
jsonschema.validate({'item': {'sides': 4,
|
||||
'color': 'blue',
|
||||
'rugged_surface': False,
|
||||
'opaque': True}}, TestConf.serialize())
|
||||
self.assertIn('has too many properties', excinfo.exception.message)
|
||||
|
||||
def test_oneof_config(self):
|
||||
item = config.OneOfConfig(
|
||||
items=(config.StringConfig(title='Yes', enum=['yes']),
|
||||
|
|
Loading…
Add table
Reference in a new issue