Merge pull request #411 from adamcik/feature/config-schema-names

Add names to config schemas
This commit is contained in:
Stein Magnus Jodal 2013-04-13 14:04:50 -07:00
commit 7745842fe5
6 changed files with 59 additions and 57 deletions

View File

@ -53,7 +53,7 @@ def main():
raw_config = config_lib.load(config_files, config_overrides, extensions)
extensions = ext.filter_enabled_extensions(raw_config, extensions)
config = config_lib.validate(
raw_config, config_lib.config_schemas, extensions)
raw_config, config_lib.core_schemas, extensions)
log.setup_log_levels(config)
check_old_locations()
@ -139,23 +139,24 @@ def show_config_callback(option, opt, value, parser):
raw_config = config_lib.load(files, overrides, extensions)
enabled_extensions = ext.filter_enabled_extensions(raw_config, extensions)
config = config_lib.validate(
raw_config, config_lib.config_schemas, enabled_extensions)
raw_config, config_lib.core_schemas, enabled_extensions)
# TODO: create mopidy.config.format?
output = []
for section_name, schema in config_lib.config_schemas.items():
options = config.get(section_name, {})
for schema in config_lib.core_schemas:
options = config.get(schema.name, {})
if not options:
continue
output.append(schema.format(section_name, options))
output.append(schema.format(options))
for extension in extensions:
schema = extension.get_config_schema()
if extension in enabled_extensions:
schema = extension.get_config_schema()
options = config.get(extension.ext_name, {})
output.append(schema.format(extension.ext_name, options))
options = config.get(schema.name, {})
output.append(schema.format(options))
else:
lines = ['[%s]' % extension.ext_name, 'enabled = false',
lines = ['[%s]' % schema.name, 'enabled = false',
'# Config hidden as extension is disabled']
output.append('\n'.join(lines))

View File

@ -13,26 +13,27 @@ from mopidy.utils import path
logger = logging.getLogger('mopdiy.config')
config_schemas = {} # TODO: use ordered dict or list?
config_schemas['logging'] = ConfigSchema()
config_schemas['logging']['console_format'] = String()
config_schemas['logging']['debug_format'] = String()
config_schemas['logging']['debug_file'] = Path()
_logging_schema = ConfigSchema('logging')
_logging_schema['console_format'] = String()
_logging_schema['debug_format'] = String()
_logging_schema['debug_file'] = Path()
config_schemas['logging.levels'] = LogLevelConfigSchema()
_loglevels_schema = LogLevelConfigSchema('logging.levels')
config_schemas['audio'] = ConfigSchema()
config_schemas['audio']['mixer'] = String()
config_schemas['audio']['mixer_track'] = String(optional=True)
config_schemas['audio']['output'] = String()
_audio_schema = ConfigSchema('audio')
_audio_schema['mixer'] = String()
_audio_schema['mixer_track'] = String(optional=True)
_audio_schema['output'] = String()
config_schemas['proxy'] = ConfigSchema()
config_schemas['proxy']['hostname'] = Hostname(optional=True)
config_schemas['proxy']['username'] = String(optional=True)
config_schemas['proxy']['password'] = String(optional=True, secret=True)
_proxy_schema = ConfigSchema('proxy')
_proxy_schema['hostname'] = Hostname(optional=True)
_proxy_schema['username'] = String(optional=True)
_proxy_schema['password'] = String(optional=True, secret=True)
# NOTE: if multiple outputs ever comes something like LogLevelConfigSchema
#config_schemas['audio.outputs'] = config.AudioOutputConfigSchema()
#_outputs_schema = config.AudioOutputConfigSchema()
core_schemas = [_logging_schema, _loglevels_schema, _audio_schema, _proxy_schema]
def read(config_file):
@ -85,12 +86,8 @@ def _load(files, defaults, overrides):
def validate(raw_config, schemas, extensions=None):
# Collect config schemas to validate against
sections_and_schemas = schemas.items()
for extension in extensions or []:
sections_and_schemas.append(
(extension.ext_name, extension.get_config_schema()))
config, errors = _validate(raw_config, sections_and_schemas)
extension_schemas = [e.get_config_schema() for e in extensions or []]
config, errors = _validate(raw_config, schemas + extension_schemas)
if errors:
# TODO: raise error instead.
@ -107,10 +104,10 @@ def _validate(raw_config, schemas):
# Get validated config
config = {}
errors = []
for name, schema in schemas:
for schema in schemas:
try:
items = raw_config[name].items()
config[name] = schema.convert(items)
items = raw_config[schema.name].items()
config[schema.name] = schema.convert(items)
except KeyError:
errors.append('%s: section not found.' % name)
except exceptions.ConfigError as error:

View File

@ -45,7 +45,8 @@ class ConfigSchema(object):
:meth:`format` method that can used for printing out the converted values.
"""
# TODO: Use collections.OrderedDict once 2.6 support is gone (#344)
def __init__(self):
def __init__(self, name):
self.name = name
self._schema = {}
self._order = []
@ -57,10 +58,10 @@ class ConfigSchema(object):
def __getitem__(self, key):
return self._schema[key]
def format(self, name, values):
def format(self, values):
# TODO: should the output be encoded utf-8 since we use that in
# serialize for strings?
lines = ['[%s]' % name]
lines = ['[%s]' % self.name]
for key in self._order:
value = values.get(key)
if value is not None:
@ -97,8 +98,8 @@ class ExtensionConfigSchema(ConfigSchema):
Ensures that ``enabled`` config value is present.
"""
def __init__(self):
super(ExtensionConfigSchema, self).__init__()
def __init__(self, name):
super(ExtensionConfigSchema, self).__init__(name)
self['enabled'] = types.Boolean()
@ -109,11 +110,12 @@ class LogLevelConfigSchema(object):
as understood by the :class:`LogLevel` config value. Does not sub-class
:class:`ConfigSchema`, but implements the same interface.
"""
def __init__(self):
def __init__(self, name):
self.name = name
self._config_value = types.LogLevel()
def format(self, name, values):
lines = ['[%s]' % name]
def format(self, values):
lines = ['[%s]' % self.name]
for key, value in sorted(values.items()):
if value is not None:
lines.append('%s = %s' % (

View File

@ -24,7 +24,7 @@ class Extension(object):
def get_config_schema(self):
"""TODO"""
return config_lib.ExtensionConfigSchema()
return config_lib.ExtensionConfigSchema(self.ext_name)
def validate_environment(self):
"""TODO"""

View File

@ -52,6 +52,10 @@ class LoadConfigTest(unittest.TestCase):
class ValidateTest(unittest.TestCase):
def setUp(self):
self.schema = mock.Mock()
self.schema.name = 'foo'
def test_empty_config_no_schemas(self):
conf, errors = config._validate({}, [])
self.assertEqual({}, conf)
@ -64,23 +68,21 @@ class ValidateTest(unittest.TestCase):
self.assertEqual([], errors)
def test_empty_config_single_schema(self):
conf, errors = config._validate({}, [('foo', mock.Mock())])
conf, errors = config._validate({}, [self.schema])
self.assertEqual({}, conf)
self.assertEqual(['foo: section not found.'], errors)
def test_config_single_schema(self):
raw_config = {'foo': {'bar': 'baz'}}
schema = mock.Mock()
schema.convert.return_value = {'baz': 'bar'}
conf, errors = config._validate(raw_config, [('foo', schema)])
self.schema.convert.return_value = {'baz': 'bar'}
conf, errors = config._validate(raw_config, [self.schema])
self.assertEqual({'foo': {'baz': 'bar'}}, conf)
self.assertEqual([], errors)
def test_config_single_schema_config_error(self):
raw_config = {'foo': {'bar': 'baz'}}
schema = mock.Mock()
schema.convert.side_effect = exceptions.ConfigError({'bar': 'bad'})
conf, errors = config._validate(raw_config, [('foo', schema)])
self.schema.convert.side_effect = exceptions.ConfigError({'bar': 'bad'})
conf, errors = config._validate(raw_config, [self.schema])
self.assertEqual(['foo/bar: bad'], errors)
self.assertEqual({}, conf)

View File

@ -11,7 +11,7 @@ from tests import unittest
class ConfigSchemaTest(unittest.TestCase):
def setUp(self):
self.schema = schemas.ConfigSchema()
self.schema = schemas.ConfigSchema('test')
self.schema['foo'] = mock.Mock()
self.schema['bar'] = mock.Mock()
self.schema['baz'] = mock.Mock()
@ -22,8 +22,8 @@ class ConfigSchemaTest(unittest.TestCase):
self.schema['bar'].format.return_value = 'asd'
self.schema['baz'].format.return_value = 'zxc'
expected = ['[qwerty]', 'foo = qwe', 'bar = asd', 'baz = zxc']
result = self.schema.format('qwerty', self.values)
expected = ['[test]', 'foo = qwe', 'bar = asd', 'baz = zxc']
result = self.schema.format(self.values)
self.assertEqual('\n'.join(expected), result)
def test_format_unkwown_value(self):
@ -32,7 +32,7 @@ class ConfigSchemaTest(unittest.TestCase):
self.schema['baz'].format.return_value = 'zxc'
self.values['unknown'] = 'rty'
result = self.schema.format('qwerty', self.values)
result = self.schema.format(self.values)
self.assertNotIn('unknown = rty', result)
def test_convert(self):
@ -88,7 +88,7 @@ class ConfigSchemaTest(unittest.TestCase):
class ExtensionConfigSchemaTest(unittest.TestCase):
def test_schema_includes_enabled(self):
schema = schemas.ExtensionConfigSchema()
schema = schemas.ExtensionConfigSchema('test')
self.assertIsInstance(schema['enabled'], types.Boolean)
@ -101,10 +101,10 @@ class LogLevelConfigSchemaTest(unittest.TestCase):
self.assertEqual(logging.INFO, result['baz'])
def test_format(self):
schema = schemas.LogLevelConfigSchema()
schema = schemas.LogLevelConfigSchema('test')
values = {'foo.bar': logging.DEBUG, 'baz': logging.INFO}
expected = ['[levels]', 'baz = info', 'foo.bar = debug']
result = schema.format('levels', values)
expected = ['[test]', 'baz = info', 'foo.bar = debug']
result = schema.format(values)
self.assertEqual('\n'.join(expected), result)