Repository: beetbox/beets

Issue:
fetchart  python3 compatibility when using itunes
### Problem

This not exactly a problem on beets it-self, but when using fetchart on a python 3 installation it fails on itune import as the module used is compatible with python 3

Running this command:

```sh
 beet fetchart genre:Classical
```

Led to this problem:

```
** error loading plugin fetchart:
Traceback (most recent call last):
  File "/home/pierre/.env_beets/lib/python3.5/site-packages/beets/plugins.py", line 254, in load_plugins
    namespace = __import__(modname, None, None)
  File "/home/pierre/.env_beets/lib/python3.5/site-packages/beetsplug/fetchart.py", line 39, in <module>
    import itunes
  File "/home/pierre/.env_beets/lib/python3.5/site-packages/itunes/__init__.py", line 94
    except urllib2.HTTPError, e:
```

Of course, the best solution (and probably only solution, except patchin python-itunes) is not to use itune as a source at all. I could be enough to mention this in the documentation.



Discussion:
Hmm; did that traceback get cut off? Looks like we're missing the all-important last line.

And yeah, on first glance it looks like that module is not py3-compatible. We should make it an optional import (if it's not already) and add a note to the docs.
I didn't even know we imported another module. I took the list in `setup.py` for plugin deps as gospel. I guess that was wrong. :(   I guess it's time to do a search for all imports.

Resolving patch:
diff --git a/beetsplug/fetchart.py b/beetsplug/fetchart.py
--- a/beetsplug/fetchart.py
+++ b/beetsplug/fetchart.py
@@ -35,12 +35,6 @@
 from beets.util import syspath, bytestring_path, py3_path
 import six
 
-try:
-    import itunes
-    HAVE_ITUNES = True
-except ImportError:
-    HAVE_ITUNES = False
-
 CONTENT_TYPES = {
     'image/jpeg': [b'jpg', b'jpeg'],
     'image/png': [b'png']
@@ -458,37 +452,65 @@ def get(self, album, plugin, paths):
 
 class ITunesStore(RemoteArtSource):
     NAME = u"iTunes Store"
+    API_URL = u'https://itunes.apple.com/search'
 
     def get(self, album, plugin, paths):
         """Return art URL from iTunes Store given an album title.
         """
         if not (album.albumartist and album.album):
             return
-        search_string = (album.albumartist + ' ' + album.album).encode('utf-8')
+
+        payload = {
+            'term': album.albumartist + u' ' + album.album,
+            'entity': u'album',
+            'media': u'music',
+            'limit': 200
+        }
         try:
-            # Isolate bugs in the iTunes library while searching.
-            try:
-                results = itunes.search_album(search_string)
-            except Exception as exc:
-                self._log.debug(u'iTunes search failed: {0}', exc)
-                return
+            r = self.request(self.API_URL, params=payload)
+            r.raise_for_status()
+        except requests.RequestException as e:
+            self._log.debug(u'iTunes search failed: {0}', e)
+            return
 
-            # Get the first match.
-            if results:
-                itunes_album = results[0]
-            else:
-                self._log.debug(u'iTunes search for {:r} got no results',
-                                search_string)
-                return
+        try:
+            candidates = r.json()['results']
+        except ValueError as e:
+            self._log.debug(u'Could not decode json response: {0}', e)
+            return
+        except KeyError as e:
+            self._log.debug(u'{} not found in json. Fields are {} ',
+                            e,
+                            list(r.json().keys()))
+            return
 
-            if itunes_album.get_artwork()['100']:
-                small_url = itunes_album.get_artwork()['100']
-                big_url = small_url.replace('100x100', '1200x1200')
-                yield self._candidate(url=big_url, match=Candidate.MATCH_EXACT)
-            else:
-                self._log.debug(u'album has no artwork in iTunes Store')
-        except IndexError:
-            self._log.debug(u'album not found in iTunes Store')
+        if not candidates:
+            self._log.debug(u'iTunes search for {!r} got no results',
+                            payload['term'])
+            return
+
+        for c in candidates:
+            try:
+                if (c['artistName'] == album.albumartist
+                        and c['collectionName'] == album.album):
+                    art_url = c['artworkUrl100']
+                    art_url = art_url.replace('100x100', '1200x1200')
+                    yield self._candidate(url=art_url,
+                                          match=Candidate.MATCH_EXACT)
+            except KeyError as e:
+                self._log.debug(u'Malformed itunes candidate: {} not found in {}',  # NOQA E501
+                                e,
+                                list(c.keys()))
+
+        try:
+            fallback_art_url = candidates[0]['artworkUrl100']
+            fallback_art_url = fallback_art_url.replace('100x100', '1200x1200')
+            yield self._candidate(url=fallback_art_url,
+                                  match=Candidate.MATCH_FALLBACK)
+        except KeyError as e:
+            self._log.debug(u'Malformed itunes candidate: {} not found in {}',
+                            e,
+                            list(c.keys()))
 
 
 class Wikipedia(RemoteArtSource):
@@ -756,8 +778,6 @@ def __init__(self):
             self.register_listener('import_task_files', self.assign_art)
 
         available_sources = list(SOURCES_ALL)
-        if not HAVE_ITUNES and u'itunes' in available_sources:
-            available_sources.remove(u'itunes')
         if not self.config['google_key'].get() and \
                 u'google' in available_sources:
             available_sources.remove(u'google')


Test patch: