settings: Remove settings utils
This commit is contained in:
parent
777993583f
commit
63b7260c01
@ -1,173 +0,0 @@
|
||||
# Absolute import needed to import ~/.config/mopidy/settings.py and not
|
||||
# ourselves
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import copy
|
||||
import getpass
|
||||
import logging
|
||||
import os
|
||||
import pprint
|
||||
import sys
|
||||
|
||||
from mopidy import exceptions
|
||||
from mopidy.utils import formatting, path
|
||||
|
||||
logger = logging.getLogger('mopidy.utils.settings')
|
||||
|
||||
|
||||
class SettingsProxy(object):
|
||||
def __init__(self, default_settings_module):
|
||||
self.default = self._get_settings_dict_from_module(
|
||||
default_settings_module)
|
||||
self.local = self._get_local_settings()
|
||||
self.runtime = {}
|
||||
|
||||
def _get_local_settings(self):
|
||||
if not os.path.isfile(path.SETTINGS_FILE):
|
||||
return {}
|
||||
sys.path.insert(0, path.SETTINGS_PATH)
|
||||
# pylint: disable = F0401
|
||||
import settings as local_settings_module
|
||||
# pylint: enable = F0401
|
||||
return self._get_settings_dict_from_module(local_settings_module)
|
||||
|
||||
def _get_settings_dict_from_module(self, module):
|
||||
settings = filter(
|
||||
lambda (key, value): self._is_setting(key),
|
||||
module.__dict__.iteritems())
|
||||
return dict(settings)
|
||||
|
||||
def _is_setting(self, name):
|
||||
return name.isupper()
|
||||
|
||||
@property
|
||||
def current(self):
|
||||
current = copy.copy(self.default)
|
||||
current.update(self.local)
|
||||
current.update(self.runtime)
|
||||
return current
|
||||
|
||||
def __getattr__(self, attr):
|
||||
if not self._is_setting(attr):
|
||||
return
|
||||
|
||||
current = self.current # bind locally to avoid copying+updates
|
||||
if attr not in current:
|
||||
raise exceptions.SettingsError('Setting "%s" is not set.' % attr)
|
||||
|
||||
value = current[attr]
|
||||
if isinstance(value, basestring) and len(value) == 0:
|
||||
raise exceptions.SettingsError('Setting "%s" is empty.' % attr)
|
||||
if not value:
|
||||
return value
|
||||
if attr.endswith('_PATH') or attr.endswith('_FILE'):
|
||||
value = path.expand_path(value)
|
||||
return value
|
||||
|
||||
def __setattr__(self, attr, value):
|
||||
if self._is_setting(attr):
|
||||
self.runtime[attr] = value
|
||||
else:
|
||||
super(SettingsProxy, self).__setattr__(attr, value)
|
||||
|
||||
def validate(self):
|
||||
if self.get_errors():
|
||||
logger.error(
|
||||
'Settings validation errors: %s',
|
||||
formatting.indent(self.get_errors_as_string()))
|
||||
raise exceptions.SettingsError('Settings validation failed.')
|
||||
|
||||
def _read_from_stdin(self, prompt):
|
||||
if '_PASSWORD' in prompt:
|
||||
return (
|
||||
getpass.getpass(prompt)
|
||||
.decode(sys.stdin.encoding, 'ignore'))
|
||||
else:
|
||||
sys.stdout.write(prompt)
|
||||
return (
|
||||
sys.stdin.readline().strip()
|
||||
.decode(sys.stdin.encoding, 'ignore'))
|
||||
|
||||
def get_errors(self):
|
||||
return validate_settings(self.default, self.local)
|
||||
|
||||
def get_errors_as_string(self):
|
||||
lines = []
|
||||
for (setting, error) in self.get_errors().iteritems():
|
||||
lines.append('%s: %s' % (setting, error))
|
||||
return '\n'.join(lines)
|
||||
|
||||
|
||||
def validate_settings(defaults, settings):
|
||||
"""
|
||||
Checks the settings for both errors like misspellings and against a set of
|
||||
rules for renamed settings, etc.
|
||||
|
||||
Returns mapping from setting names to associated errors.
|
||||
|
||||
:param defaults: Mopidy's default settings
|
||||
:type defaults: dict
|
||||
:param settings: the user's local settings
|
||||
:type settings: dict
|
||||
:rtype: dict
|
||||
"""
|
||||
errors = {}
|
||||
|
||||
changed = {
|
||||
'DUMP_LOG_FILENAME': 'DEBUG_LOG_FILENAME',
|
||||
'DUMP_LOG_FORMAT': 'DEBUG_LOG_FORMAT',
|
||||
'GSTREAMER_AUDIO_SINK': 'OUTPUT',
|
||||
'LOCAL_MUSIC_FOLDER': 'LOCAL_MUSIC_PATH',
|
||||
'LOCAL_OUTPUT_OVERRIDE': 'OUTPUT',
|
||||
'LOCAL_PLAYLIST_FOLDER': 'LOCAL_PLAYLIST_PATH',
|
||||
'LOCAL_TAG_CACHE': 'LOCAL_TAG_CACHE_FILE',
|
||||
'MIXER_ALSA_CONTROL': None,
|
||||
'MIXER_EXT_PORT': None,
|
||||
'MIXER_EXT_SPEAKERS_A': None,
|
||||
'MIXER_EXT_SPEAKERS_B': None,
|
||||
'MIXER_MAX_VOLUME': None,
|
||||
'SERVER': None,
|
||||
'SERVER_HOSTNAME': 'MPD_SERVER_HOSTNAME',
|
||||
'SERVER_PORT': 'MPD_SERVER_PORT',
|
||||
'SPOTIFY_HIGH_BITRATE': 'SPOTIFY_BITRATE',
|
||||
'SPOTIFY_LIB_APPKEY': None,
|
||||
'SPOTIFY_LIB_CACHE': 'SPOTIFY_CACHE_PATH',
|
||||
}
|
||||
|
||||
must_be_iterable = [
|
||||
'STREAM_PROTOCOLS',
|
||||
]
|
||||
|
||||
for setting, value in settings.iteritems():
|
||||
if setting in changed:
|
||||
if changed[setting] is None:
|
||||
errors[setting] = 'Deprecated setting. It may be removed.'
|
||||
else:
|
||||
errors[setting] = 'Deprecated setting. Use %s.' % (
|
||||
changed[setting],)
|
||||
|
||||
elif setting == 'OUTPUTS':
|
||||
errors[setting] = (
|
||||
'Deprecated setting, please change to OUTPUT. OUTPUT expects '
|
||||
'a GStreamer bin description string for your desired output.')
|
||||
|
||||
elif setting == 'SPOTIFY_BITRATE':
|
||||
if value not in (96, 160, 320):
|
||||
errors[setting] = (
|
||||
'Unavailable Spotify bitrate. Available bitrates are 96, '
|
||||
'160, and 320.')
|
||||
|
||||
elif setting.startswith('SHOUTCAST_OUTPUT_'):
|
||||
errors[setting] = (
|
||||
'Deprecated setting, please set the value via the GStreamer '
|
||||
'bin in OUTPUT.')
|
||||
|
||||
elif setting in must_be_iterable and not hasattr(value, '__iter__'):
|
||||
errors[setting] = (
|
||||
'Must be a tuple. '
|
||||
"Remember the comma after single values: (u'value',)")
|
||||
|
||||
elif setting not in defaults and not setting.startswith('CUSTOM_'):
|
||||
errors[setting] = 'Unknown setting.'
|
||||
|
||||
return errors
|
||||
@ -1,150 +0,0 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
|
||||
from mopidy import exceptions, settings
|
||||
from mopidy.utils import settings as setting_utils
|
||||
|
||||
from tests import unittest
|
||||
|
||||
|
||||
class ValidateSettingsTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.defaults = {
|
||||
'MPD_SERVER_HOSTNAME': '::',
|
||||
'MPD_SERVER_PORT': 6600,
|
||||
'SPOTIFY_BITRATE': 160,
|
||||
}
|
||||
|
||||
def test_no_errors_yields_empty_dict(self):
|
||||
result = setting_utils.validate_settings(self.defaults, {})
|
||||
self.assertEqual(result, {})
|
||||
|
||||
def test_unknown_setting_returns_error(self):
|
||||
result = setting_utils.validate_settings(
|
||||
self.defaults, {'MPD_SERVER_HOSTNMAE': '127.0.0.1'})
|
||||
self.assertEqual(
|
||||
result['MPD_SERVER_HOSTNMAE'], 'Unknown setting.')
|
||||
|
||||
def test_custom_settings_does_not_return_errors(self):
|
||||
result = setting_utils.validate_settings(
|
||||
self.defaults, {'CUSTOM_MYAPP_SETTING': 'foobar'})
|
||||
self.assertNotIn('CUSTOM_MYAPP_SETTING', result)
|
||||
|
||||
def test_not_renamed_setting_returns_error(self):
|
||||
result = setting_utils.validate_settings(
|
||||
self.defaults, {'SERVER_HOSTNAME': '127.0.0.1'})
|
||||
self.assertEqual(
|
||||
result['SERVER_HOSTNAME'],
|
||||
'Deprecated setting. Use MPD_SERVER_HOSTNAME.')
|
||||
|
||||
def test_unneeded_settings_returns_error(self):
|
||||
result = setting_utils.validate_settings(
|
||||
self.defaults, {'SPOTIFY_LIB_APPKEY': '/tmp/foo'})
|
||||
self.assertEqual(
|
||||
result['SPOTIFY_LIB_APPKEY'],
|
||||
'Deprecated setting. It may be removed.')
|
||||
|
||||
def test_unavailable_bitrate_setting_returns_error(self):
|
||||
result = setting_utils.validate_settings(
|
||||
self.defaults, {'SPOTIFY_BITRATE': 50})
|
||||
self.assertEqual(
|
||||
result['SPOTIFY_BITRATE'],
|
||||
'Unavailable Spotify bitrate. '
|
||||
'Available bitrates are 96, 160, and 320.')
|
||||
|
||||
def test_two_errors_are_both_reported(self):
|
||||
result = setting_utils.validate_settings(
|
||||
self.defaults, {'FOO': '', 'BAR': ''})
|
||||
self.assertEqual(len(result), 2)
|
||||
|
||||
|
||||
class SettingsProxyTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.settings = setting_utils.SettingsProxy(settings)
|
||||
self.settings.local.clear()
|
||||
|
||||
def test_set_and_get_attr(self):
|
||||
self.settings.TEST = 'test'
|
||||
self.assertEqual(self.settings.TEST, 'test')
|
||||
|
||||
def test_getattr_raises_error_on_missing_setting(self):
|
||||
try:
|
||||
self.settings.TEST
|
||||
self.fail('Should raise exception')
|
||||
except exceptions.SettingsError as e:
|
||||
self.assertEqual('Setting "TEST" is not set.', e.message)
|
||||
|
||||
def test_getattr_raises_error_on_empty_setting(self):
|
||||
self.settings.TEST = ''
|
||||
try:
|
||||
self.settings.TEST
|
||||
self.fail('Should raise exception')
|
||||
except exceptions.SettingsError as e:
|
||||
self.assertEqual('Setting "TEST" is empty.', e.message)
|
||||
|
||||
def test_getattr_does_not_raise_error_if_setting_is_false(self):
|
||||
self.settings.TEST = False
|
||||
self.assertEqual(False, self.settings.TEST)
|
||||
|
||||
def test_getattr_does_not_raise_error_if_setting_is_none(self):
|
||||
self.settings.TEST = None
|
||||
self.assertEqual(None, self.settings.TEST)
|
||||
|
||||
def test_getattr_does_not_raise_error_if_setting_is_zero(self):
|
||||
self.settings.TEST = 0
|
||||
self.assertEqual(0, self.settings.TEST)
|
||||
|
||||
def test_setattr_updates_runtime_settings(self):
|
||||
self.settings.TEST = 'test'
|
||||
self.assertIn('TEST', self.settings.runtime)
|
||||
|
||||
def test_setattr_updates_runtime_with_value(self):
|
||||
self.settings.TEST = 'test'
|
||||
self.assertEqual(self.settings.runtime['TEST'], 'test')
|
||||
|
||||
def test_runtime_value_included_in_current(self):
|
||||
self.settings.TEST = 'test'
|
||||
self.assertEqual(self.settings.current['TEST'], 'test')
|
||||
|
||||
def test_value_ending_in_path_is_expanded(self):
|
||||
self.settings.TEST_PATH = '~/test'
|
||||
actual = self.settings.TEST_PATH
|
||||
expected = os.path.expanduser('~/test')
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
def test_value_ending_in_path_is_absolute(self):
|
||||
self.settings.TEST_PATH = './test'
|
||||
actual = self.settings.TEST_PATH
|
||||
expected = os.path.abspath('./test')
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
def test_value_ending_in_file_is_expanded(self):
|
||||
self.settings.TEST_FILE = '~/test'
|
||||
actual = self.settings.TEST_FILE
|
||||
expected = os.path.expanduser('~/test')
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
def test_value_ending_in_file_is_absolute(self):
|
||||
self.settings.TEST_FILE = './test'
|
||||
actual = self.settings.TEST_FILE
|
||||
expected = os.path.abspath('./test')
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
def test_value_not_ending_in_path_or_file_is_not_expanded(self):
|
||||
self.settings.TEST = '~/test'
|
||||
actual = self.settings.TEST
|
||||
self.assertEqual(actual, '~/test')
|
||||
|
||||
def test_value_not_ending_in_path_or_file_is_not_absolute(self):
|
||||
self.settings.TEST = './test'
|
||||
actual = self.settings.TEST
|
||||
self.assertEqual(actual, './test')
|
||||
|
||||
def test_value_ending_in_file_can_be_none(self):
|
||||
self.settings.TEST_FILE = None
|
||||
self.assertEqual(self.settings.TEST_FILE, None)
|
||||
|
||||
def test_value_ending_in_path_can_be_none(self):
|
||||
self.settings.TEST_PATH = None
|
||||
self.assertEqual(self.settings.TEST_PATH, None)
|
||||
Loading…
Reference in New Issue
Block a user