From bd3d8f693201f7018a2e62c2eaeb469f57e9aa93 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Fri, 5 Apr 2013 23:29:45 +0200 Subject: [PATCH 1/6] config: Steal did you mean code from settings. --- mopidy/utils/config.py | 36 ++++++++++++++++++++++++++++++++++++ tests/utils/config_test.py | 19 +++++++++++++++++++ tests/utils/settings_test.py | 24 ------------------------ 3 files changed, 55 insertions(+), 24 deletions(-) diff --git a/mopidy/utils/config.py b/mopidy/utils/config.py index 1a3127b5..aa1b06fd 100644 --- a/mopidy/utils/config.py +++ b/mopidy/utils/config.py @@ -33,6 +33,39 @@ def validate_maximum(value, maximum): raise ValueError('%r must be smaller than %r.' % (value, maximum)) +# TODO: move this and levenshtein to a more appropriate class. +def did_you_mean(name, choices): + """Suggest most likely setting based on levenshtein.""" + if not choices: + return None + + name = name.lower() + candidates = [(levenshtein(name, c), c) for c in choices] + candidates.sort() + + if candidates[0][0] <= 3: + return candidates[0][1] + return None + + +def levenshtein(a, b): + """Calculates the Levenshtein distance between a and b.""" + n, m = len(a), len(b) + if n > m: + return levenshtein(b, a) + + current = xrange(n + 1) + for i in xrange(1, m + 1): + previous, current = current, [i] + [0] * n + for j in xrange(1, n + 1): + add, delete = previous[j] + 1, current[j - 1] + 1 + change = previous[j - 1] + if a[j - 1] != b[i - 1]: + change += 1 + current[j] = min(add, delete, change) + return current[n] + + class ConfigValue(object): """Represents a config key's value and how to handle it. @@ -248,6 +281,9 @@ class ConfigSchema(object): values[key] = self._schema[key].deserialize(value) except KeyError: # not in our schema errors[key] = 'unknown config key.' + suggestion = did_you_mean(key, self._schema.keys()) + if suggestion: + errors[key] += ' Did you mean %s?' % suggestion except ValueError as e: # deserialization failed errors[key] = str(e) diff --git a/tests/utils/config_test.py b/tests/utils/config_test.py index 77c846df..ad86b961 100644 --- a/tests/utils/config_test.py +++ b/tests/utils/config_test.py @@ -395,3 +395,22 @@ class LogLevelConfigSchemaTest(unittest.TestCase): result = schema.format('levels', {'foo.bar': logging.DEBUG, 'baz': logging.INFO}) self.assertEqual('\n'.join(expected), result) + +class DidYouMeanTest(unittest.TestCase): + def testSuggestoins(self): + choices = ('enabled', 'username', 'password', 'bitrate', 'timeout') + + suggestion = config.did_you_mean('bitrate', choices) + self.assertEqual(suggestion, 'bitrate') + + suggestion = config.did_you_mean('bitrote', choices) + self.assertEqual(suggestion, 'bitrate') + + suggestion = config.did_you_mean('Bitrot', choices) + self.assertEqual(suggestion, 'bitrate') + + suggestion = config.did_you_mean('BTROT', choices) + self.assertEqual(suggestion, 'bitrate') + + suggestion = config.did_you_mean('btro', choices) + self.assertEqual(suggestion, None) diff --git a/tests/utils/settings_test.py b/tests/utils/settings_test.py index 3b1e67b0..3aa595e3 100644 --- a/tests/utils/settings_test.py +++ b/tests/utils/settings_test.py @@ -149,27 +149,3 @@ class SettingsProxyTest(unittest.TestCase): def test_value_ending_in_path_can_be_none(self): self.settings.TEST_PATH = None self.assertEqual(self.settings.TEST_PATH, None) - - -class DidYouMeanTest(unittest.TestCase): - def testSuggestoins(self): - defaults = { - 'MPD_SERVER_HOSTNAME': '::', - 'MPD_SERVER_PORT': 6600, - 'SPOTIFY_BITRATE': 160, - } - - suggestion = setting_utils.did_you_mean('spotify_bitrate', defaults) - self.assertEqual(suggestion, 'SPOTIFY_BITRATE') - - suggestion = setting_utils.did_you_mean('SPOTIFY_BITROTE', defaults) - self.assertEqual(suggestion, 'SPOTIFY_BITRATE') - - suggestion = setting_utils.did_you_mean('SPITIFY_BITROT', defaults) - self.assertEqual(suggestion, 'SPOTIFY_BITRATE') - - suggestion = setting_utils.did_you_mean('SPTIFY_BITROT', defaults) - self.assertEqual(suggestion, 'SPOTIFY_BITRATE') - - suggestion = setting_utils.did_you_mean('SPTIFY_BITRO', defaults) - self.assertEqual(suggestion, None) From 5a79b65d47abad90e80d6eb37a3e581d98f727e9 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Fri, 5 Apr 2013 23:31:31 +0200 Subject: [PATCH 2/6] settings: Remove did you mean. --- mopidy/utils/settings.py | 36 ------------------------------------ tests/utils/settings_test.py | 3 +-- 2 files changed, 1 insertion(+), 38 deletions(-) diff --git a/mopidy/utils/settings.py b/mopidy/utils/settings.py index 5916ee24..f903a70d 100644 --- a/mopidy/utils/settings.py +++ b/mopidy/utils/settings.py @@ -169,41 +169,5 @@ def validate_settings(defaults, settings): elif setting not in defaults and not setting.startswith('CUSTOM_'): errors[setting] = 'Unknown setting.' - suggestion = did_you_mean(setting, defaults) - - if suggestion: - errors[setting] += ' Did you mean %s?' % suggestion return errors - - -def did_you_mean(setting, defaults): - """Suggest most likely setting based on levenshtein.""" - if not defaults: - return None - - setting = setting.upper() - candidates = [(levenshtein(setting, d), d) for d in defaults] - candidates.sort() - - if candidates[0][0] <= 3: - return candidates[0][1] - return None - - -def levenshtein(a, b): - """Calculates the Levenshtein distance between a and b.""" - n, m = len(a), len(b) - if n > m: - return levenshtein(b, a) - - current = xrange(n + 1) - for i in xrange(1, m + 1): - previous, current = current, [i] + [0] * n - for j in xrange(1, n + 1): - add, delete = previous[j] + 1, current[j - 1] + 1 - change = previous[j - 1] - if a[j - 1] != b[i - 1]: - change += 1 - current[j] = min(add, delete, change) - return current[n] diff --git a/tests/utils/settings_test.py b/tests/utils/settings_test.py index 3aa595e3..ce763486 100644 --- a/tests/utils/settings_test.py +++ b/tests/utils/settings_test.py @@ -24,8 +24,7 @@ class ValidateSettingsTest(unittest.TestCase): result = setting_utils.validate_settings( self.defaults, {'MPD_SERVER_HOSTNMAE': '127.0.0.1'}) self.assertEqual( - result['MPD_SERVER_HOSTNMAE'], - 'Unknown setting. Did you mean MPD_SERVER_HOSTNAME?') + result['MPD_SERVER_HOSTNMAE'], 'Unknown setting.') def test_custom_settings_does_not_return_errors(self): result = setting_utils.validate_settings( From 7ac629a675a39a9440c503ffd79d53f9e5ab5951 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Fri, 5 Apr 2013 23:39:22 +0200 Subject: [PATCH 3/6] config: Create global proxy config section. --- mopidy/backends/spotify/__init__.py | 8 -------- mopidy/backends/spotify/session_manager.py | 6 +++--- mopidy/config.py | 9 +++++++++ 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/mopidy/backends/spotify/__init__.py b/mopidy/backends/spotify/__init__.py index b03849eb..8ea0ca11 100644 --- a/mopidy/backends/spotify/__init__.py +++ b/mopidy/backends/spotify/__init__.py @@ -26,11 +26,6 @@ timeout = 10 # Path to the Spotify data cache. Cannot be shared with other Spotify apps cache_path = $XDG_CACHE_DIR/mopidy/spotify - -# Connect to Spotify through a proxy -proxy_hostname = -proxy_username = -proxy_password = """ __doc__ = """A backend for playing music from Spotify @@ -81,9 +76,6 @@ class Extension(ext.Extension): schema['bitrate'] = config.Integer(choices=(96, 160, 320)) schema['timeout'] = config.Integer(minimum=0) schema['cache_path'] = config.String() - schema['proxy_hostname'] = config.Hostname(optional=True) - schema['proxy_username'] = config.String(optional=True) - schema['proxy_password'] = config.String(optional=True, secret=True) return schema def validate_environment(self): diff --git a/mopidy/backends/spotify/session_manager.py b/mopidy/backends/spotify/session_manager.py index c27d8215..22ad4632 100644 --- a/mopidy/backends/spotify/session_manager.py +++ b/mopidy/backends/spotify/session_manager.py @@ -35,9 +35,9 @@ class SpotifySessionManager(process.BaseThread, PyspotifySessionManager): PyspotifySessionManager.__init__( self, config['spotify']['username'], config['spotify']['password'], - proxy=config['spotify']['proxy_hostname'], - proxy_username=config['spotify']['proxy_username'], - proxy_password=config['spotify']['proxy_password']) + proxy=config['proxy']['hostname'], + proxy_username=config['proxy']['username'], + proxy_password=config['proxy']['password']) process.BaseThread.__init__(self) self.name = 'SpotifyThread' diff --git a/mopidy/config.py b/mopidy/config.py index 85feffcc..8c55dd9d 100644 --- a/mopidy/config.py +++ b/mopidy/config.py @@ -16,6 +16,11 @@ pykka = info mixer = autoaudiomixer mixer_track = output = autoaudiosink + +[proxy] +hostname = +username = +password = """ config_schemas = {} # TODO: use ordered dict? @@ -31,6 +36,10 @@ config_schemas['audio']['mixer'] = config.String() config_schemas['audio']['mixer_track'] = config.String(optional=True) config_schemas['audio']['output'] = config.String() +config_schemas['proxy']['hostname'] = config.Hostname(optional=True) +config_schemas['proxy']['username'] = config.String(optional=True) +config_schemas['proxy']['password'] = config.String(optional=True, secret=True) + # NOTE: if multiple outputs ever comes something like LogLevelConfigSchema #config_schemas['audio.outputs'] = config.AudioOutputConfigSchema() From 7d44f9967dc86c37ba8936cf0c0d2e9f92de5cef Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Fri, 5 Apr 2013 23:57:35 +0200 Subject: [PATCH 4/6] scrobbler: Renamed lastfm to scrobbler (fixes #375) --- docs/api/frontends.rst | 2 +- docs/modules/frontends/lastfm.rst | 6 ------ docs/modules/frontends/scrobbler.rst | 6 ++++++ docs/settings.rst | 4 ++-- mopidy/frontends/{lastfm => scrobbler}/__init__.py | 12 ++++++------ mopidy/frontends/{lastfm => scrobbler}/actor.py | 6 +++--- requirements/{lastfm.txt => scrobbler.txt} | 0 setup.py | 4 ++-- 8 files changed, 20 insertions(+), 20 deletions(-) delete mode 100644 docs/modules/frontends/lastfm.rst create mode 100644 docs/modules/frontends/scrobbler.rst rename mopidy/frontends/{lastfm => scrobbler}/__init__.py (85%) rename mopidy/frontends/{lastfm => scrobbler}/actor.py (95%) rename requirements/{lastfm.txt => scrobbler.txt} (100%) diff --git a/docs/api/frontends.rst b/docs/api/frontends.rst index a6e6f500..ac1f8bd1 100644 --- a/docs/api/frontends.rst +++ b/docs/api/frontends.rst @@ -45,6 +45,6 @@ Frontend implementations ======================== * :mod:`mopidy.frontends.http` -* :mod:`mopidy.frontends.lastfm` +* :mod:`mopidy.frontends.scrobbler` * :mod:`mopidy.frontends.mpd` * :mod:`mopidy.frontends.mpris` diff --git a/docs/modules/frontends/lastfm.rst b/docs/modules/frontends/lastfm.rst deleted file mode 100644 index 0dba922f..00000000 --- a/docs/modules/frontends/lastfm.rst +++ /dev/null @@ -1,6 +0,0 @@ -*************************************************** -:mod:`mopidy.frontends.lastfm` -- Last.fm Scrobbler -*************************************************** - -.. automodule:: mopidy.frontends.lastfm - :synopsis: Last.fm scrobbler frontend diff --git a/docs/modules/frontends/scrobbler.rst b/docs/modules/frontends/scrobbler.rst new file mode 100644 index 00000000..eee65724 --- /dev/null +++ b/docs/modules/frontends/scrobbler.rst @@ -0,0 +1,6 @@ +********************************************** +:mod:`mopidy.frontends.scrobble` -- Scrobbler +********************************************** + +.. automodule:: mopidy.frontends.scrobbler + :synopsis: Music scrobbler frontend diff --git a/docs/settings.rst b/docs/settings.rst index cb47a71f..4c9acd96 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -106,8 +106,8 @@ Scrobbling tracks to Last.fm If you want to submit the tracks you are playing to your `Last.fm `_ profile, make sure you've installed the dependencies -found at :mod:`mopidy.frontends.lastfm` and add the following to your settings -file:: +found at :mod:`mopidy.frontends.scrobbler` and add the following to your +settings file:: LASTFM_USERNAME = u'myusername' LASTFM_PASSWORD = u'mysecret' diff --git a/mopidy/frontends/lastfm/__init__.py b/mopidy/frontends/scrobbler/__init__.py similarity index 85% rename from mopidy/frontends/lastfm/__init__.py rename to mopidy/frontends/scrobbler/__init__.py index f4bff0e5..c33a5fa3 100644 --- a/mopidy/frontends/lastfm/__init__.py +++ b/mopidy/frontends/scrobbler/__init__.py @@ -6,7 +6,7 @@ from mopidy.utils import config, formatting default_config = """ -[lastfm] +[scrobbler] # If the Last.fm extension should be enabled or not enabled = true @@ -28,7 +28,7 @@ Frontend which scrobbles the music you play to your `Last.fm **Dependencies** -.. literalinclude:: ../../../requirements/lastfm.txt +.. literalinclude:: ../../../requirements/scrobbler.txt **Default config** @@ -44,8 +44,8 @@ The frontend is enabled by default if all dependencies are available. class Extension(ext.Extension): - dist_name = 'Mopidy-Lastfm' - ext_name = 'lastfm' + dist_name = 'Mopidy-Scrobbler' + ext_name = 'scrobbler' version = mopidy.__version__ def get_default_config(self): @@ -64,5 +64,5 @@ class Extension(ext.Extension): raise exceptions.ExtensionError('pylast library not found', e) def get_frontend_classes(self): - from .actor import LastfmFrontend - return [LastfmFrontend] + from .actor import ScrobblerFrontend + return [ScrobblerFrontend] diff --git a/mopidy/frontends/lastfm/actor.py b/mopidy/frontends/scrobbler/actor.py similarity index 95% rename from mopidy/frontends/lastfm/actor.py rename to mopidy/frontends/scrobbler/actor.py index 1e157d4f..eea088de 100644 --- a/mopidy/frontends/lastfm/actor.py +++ b/mopidy/frontends/scrobbler/actor.py @@ -13,15 +13,15 @@ try: except ImportError as import_error: raise exceptions.OptionalDependencyError(import_error) -logger = logging.getLogger('mopidy.frontends.lastfm') +logger = logging.getLogger('mopidy.frontends.scrobbler') API_KEY = '2236babefa8ebb3d93ea467560d00d04' API_SECRET = '94d9a09c0cd5be955c4afaeaffcaefcd' -class LastfmFrontend(pykka.ThreadingActor, CoreListener): +class ScrobblerFrontend(pykka.ThreadingActor, CoreListener): def __init__(self, config, core): - super(LastfmFrontend, self).__init__() + super(ScrobblerFrontend, self).__init__() self.lastfm = None self.last_start_time = None diff --git a/requirements/lastfm.txt b/requirements/scrobbler.txt similarity index 100% rename from requirements/lastfm.txt rename to requirements/scrobbler.txt diff --git a/setup.py b/setup.py index 3c0b7c4c..2f4d98d6 100644 --- a/setup.py +++ b/setup.py @@ -29,7 +29,7 @@ setup( ], extras_require={ b'spotify': ['pyspotify >= 1.9, < 1.11'], - b'lastfm': ['pylast >= 0.5.7'], + b'scrobbler': ['pylast >= 0.5.7'], b'http': ['cherrypy >= 3.2.2', 'ws4py >= 0.2.3'], b'external_mixers': ['pyserial'], }, @@ -46,7 +46,7 @@ setup( ], b'mopidy.ext': [ 'http = mopidy.frontends.http:Extension [http]', - 'lastfm = mopidy.frontends.lastfm:Extension [lastfm]', + 'scrobbler = mopidy.frontends.scrobbler:Extension [scrobbler]', 'local = mopidy.backends.local:Extension', 'mpd = mopidy.frontends.mpd:Extension', 'mpris = mopidy.frontends.mpris:Extension', From 60ce7507476af0a0cc626bafb9cb51ec47f5af56 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 6 Apr 2013 01:44:05 +0200 Subject: [PATCH 5/6] config: Add missing config section schema --- mopidy/config.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mopidy/config.py b/mopidy/config.py index 8c55dd9d..3d242a95 100644 --- a/mopidy/config.py +++ b/mopidy/config.py @@ -36,6 +36,7 @@ config_schemas['audio']['mixer'] = config.String() config_schemas['audio']['mixer_track'] = config.String(optional=True) config_schemas['audio']['output'] = config.String() +config_schemas['proxy'] = config.ConfigSchema() config_schemas['proxy']['hostname'] = config.Hostname(optional=True) config_schemas['proxy']['username'] = config.String(optional=True) config_schemas['proxy']['password'] = config.String(optional=True, secret=True) From 837db09aff3345db74719e84ddd1b2df3b067c72 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 6 Apr 2013 01:45:09 +0200 Subject: [PATCH 6/6] docs: Sort list --- docs/api/frontends.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/frontends.rst b/docs/api/frontends.rst index ac1f8bd1..58436c03 100644 --- a/docs/api/frontends.rst +++ b/docs/api/frontends.rst @@ -45,6 +45,6 @@ Frontend implementations ======================== * :mod:`mopidy.frontends.http` -* :mod:`mopidy.frontends.scrobbler` * :mod:`mopidy.frontends.mpd` * :mod:`mopidy.frontends.mpris` +* :mod:`mopidy.frontends.scrobbler`