mopidy/mopidy/config/__init__.py
2013-04-15 23:37:40 +02:00

108 lines
3.4 KiB
Python

from __future__ import unicode_literals
import ConfigParser as configparser
import io
import logging
import os.path
import sys
from mopidy.config.schemas import *
from mopidy.config.types import *
from mopidy.utils import path
logger = logging.getLogger('mopidy.config')
_logging_schema = ConfigSchema('logging')
_logging_schema['console_format'] = String()
_logging_schema['debug_format'] = String()
_logging_schema['debug_file'] = Path()
_loglevels_schema = LogLevelConfigSchema('loglevels')
_audio_schema = ConfigSchema('audio')
_audio_schema['mixer'] = String()
_audio_schema['mixer_track'] = String(optional=True)
_audio_schema['output'] = String()
_proxy_schema = ConfigSchema('proxy')
_proxy_schema['hostname'] = Hostname(optional=True)
_proxy_schema['username'] = String(optional=True)
_proxy_schema['password'] = Secret(optional=True)
# NOTE: if multiple outputs ever comes something like LogLevelConfigSchema
#_outputs_schema = config.AudioOutputConfigSchema()
_schemas = [_logging_schema, _loglevels_schema, _audio_schema, _proxy_schema]
def read(config_file):
"""Helper to load config defaults in same way across core and extensions"""
with io.open(config_file, 'rb') as filehandle:
return filehandle.read()
def load(files, extensions, overrides):
# Helper to get configs, as the rest of our config system should not need
# to know about extensions.
config_dir = os.path.dirname(__file__)
defaults = [read(os.path.join(config_dir, 'default.conf'))]
defaults.extend(e.get_default_config() for e in extensions)
raw_config = _load(files, defaults, overrides)
schemas = _schemas[:]
schemas.extend(e.get_config_schema() for e in extensions)
return _validate(raw_config, schemas)
def _load(files, defaults, overrides):
parser = configparser.RawConfigParser()
files = [path.expand_path(f) for f in files]
sources = ['builtin-defaults'] + files + ['command-line']
logger.info('Loading config from: %s', ', '.join(sources))
# TODO: simply return path to config file for defaults so we can load it
# all in the same way?
for default in defaults:
parser.readfp(io.BytesIO(default))
# Load config from a series of config files
for filename in files:
try:
with io.open(filename, 'rb') as filehandle:
parser.readfp(filehandle)
except IOError:
# TODO: if this is the initial load of logging config we might not
# have a logger at this point, we might want to handle this better.
logger.debug('Config file %s not found; skipping', filename)
raw_config = {}
for section in parser.sections():
raw_config[section] = dict(parser.items(section))
for section, key, value in overrides or []:
raw_config.setdefault(section, {})[key] = value
return raw_config
def _validate(raw_config, schemas):
# Get validated config
config = {}
errors = {}
for schema in schemas:
values = raw_config.get(schema.name, {})
result, error = schema.deserialize(values)
if error:
errors[schema.name] = error
if result:
config[schema.name] = result
return config, errors
def parse_override(override):
"""Parse ``section/key=value`` command line overrides"""
section, remainder = override.split('/', 1)
key, value = remainder.split('=', 1)
return (section.strip(), key.strip(), value.strip())