diff --git a/mopidy/utils/settings.py b/mopidy/utils/settings.py deleted file mode 100644 index f903a70d..00000000 --- a/mopidy/utils/settings.py +++ /dev/null @@ -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 diff --git a/tests/utils/settings_test.py b/tests/utils/settings_test.py deleted file mode 100644 index ce763486..00000000 --- a/tests/utils/settings_test.py +++ /dev/null @@ -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)