Repository: beetbox/beets

Issue:
Disable plugin options requiring a missing dependency
When a library related to a plugin option (langdetect for translate, bs4 for google lyrics backend, etc) is missing, it should be detected at the plugin init and the offending option should be disabled with a message printed once pointing to the docs.
Currently we have an error/warning for each song.



Discussion:
It would be cool if plugins themselves could tell beets core about the issue and beets would disable the plugin automatically. I wonder if this is possible?

maybe with `__import__ / importlib` help. You would try to import and logs a message if can't import something.

This is exactly what beets already does (see https://github.com/beetbox/beets/blob/master/beets/plugins.py#L267 which also catches any `ImportError`. Raising any error during plugin init will effectively disable the plugin). The problem with lyrics is different, though. The lack of either of these modules should not shutdown the whole plugin, but only deactivate the corresponding sources. The recent patch does that, but it will also print a warning every time lyrics are fetched, which messes up the UI during imports. That's what Kraymer was about, the plugin would need some refactoring to print that warning only once.

adding something like this at the top would work / is too hacky?

``` py
try:
  import langdetect
except ImportError:
  disable_source('bing')
  warn_user()
```

The warning should not print when the source is not enabled through the config, though, as that combination may aswell be intentional. Have a look at `LyricsPlugin.__init__`, the google source does that when no API key is given.


Resolving patch:
diff --git a/beetsplug/lyrics.py b/beetsplug/lyrics.py
--- a/beetsplug/lyrics.py
+++ b/beetsplug/lyrics.py
@@ -28,6 +28,18 @@
 import warnings
 from HTMLParser import HTMLParseError
 
+try:
+    from bs4 import SoupStrainer, BeautifulSoup
+    HAS_BEAUTIFUL_SOUP = True
+except ImportError:
+    HAS_BEAUTIFUL_SOUP = False
+
+try:
+    import langdetect
+    HAS_LANGDETECT = True
+except ImportError:
+    HAS_LANGDETECT = False
+
 from beets import plugins
 from beets import ui
 
@@ -415,11 +427,7 @@ def scrape_lyrics_from_html(html):
     """Scrape lyrics from a URL. If no lyrics can be found, return None
     instead.
     """
-    try:
-        from bs4 import SoupStrainer, BeautifulSoup
-    except ImportError:
-        # TODO: refactor the plugin to get access to a logger here and log
-        # a warning
+    if not HAS_BEAUTIFUL_SOUP:
         return None
 
     if not html:
@@ -593,18 +601,33 @@ def __init__(self):
         self.config['genius_api_key'].redact = True
 
         available_sources = list(self.SOURCES)
-        if not self.config['google_API_key'].get() and \
-                'google' in self.SOURCES:
-            available_sources.remove('google')
-        self.config['sources'] = plugins.sanitize_choices(
+        sources = plugins.sanitize_choices(
             self.config['sources'].as_str_seq(), available_sources)
 
-        self.backends = [self.SOURCE_BACKENDS[key](self.config, self._log)
-                         for key in self.config['sources'].as_str_seq()]
+        if 'google' in sources:
+            if not self.config['google_API_key'].get():
+                self._log.warn(u'To use the google lyrics source, you must '
+                               u'provide an API key in the configuration. '
+                               u'See the documentation for further details.')
+                sources.remove('google')
+            if not HAS_BEAUTIFUL_SOUP:
+                self._log.warn(u'To use the google lyrics source, you must '
+                               u'install the beautifulsoup4 module. See the '
+                               u'documentation for further details.')
+                sources.remove('google')
+
         self.config['bing_lang_from'] = [
             x.lower() for x in self.config['bing_lang_from'].as_str_seq()]
         self.bing_auth_token = None
 
+        if not HAS_LANGDETECT and self.config['bing_client_secret'].get():
+            self._log.warn(u'To use bing translations, you need to '
+                           u'install the langdetect module. See the '
+                           u'documentation for further details.')
+
+        self.backends = [self.SOURCE_BACKENDS[source](self.config, self._log)
+                         for source in sources]
+
     def get_bing_access_token(self):
         params = {
             'client_id': 'beets',
@@ -675,20 +698,10 @@ def fetch_item_lyrics(self, lib, item, write, force):
 
         lyrics = u"\n\n---\n\n".join([l for l in lyrics if l])
 
-        has_langdetect = False
-        if self.config['bing_client_secret'].get():
-            try:
-                from langdetect import detect
-                has_langdetect = True
-            except ImportError:
-                self._log.warn(u'To use bing translations, you need to '
-                               u'install the langdetect module. See the '
-                               u'documentation for further details.')
-
         if lyrics:
             self._log.info(u'fetched lyrics: {0}', item)
-            if has_langdetect:
-                lang_from = detect(lyrics)
+            if HAS_LANGDETECT and self.config['bing_client_secret'].get():
+                lang_from = langdetect.detect(lyrics)
                 if self.config['bing_lang_to'].get() != lang_from and (
                     not self.config['bing_lang_from'] or (
                         lang_from in self.config[


Test patch: