ArrayConfig must have items passed. Added DictConfig with tests.

This commit is contained in:
Pedro Algarvio 2015-07-07 15:10:16 +01:00
parent 884212532a
commit f36123c4df
2 changed files with 434 additions and 38 deletions

View file

@ -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'

View file

@ -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']),