Merge pull request #411 from adamcik/feature/config-schema-names
Add names to config schemas
This commit is contained in:
commit
7745842fe5
@ -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))
|
||||
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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' % (
|
||||
|
||||
@ -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"""
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user