]> kaliko git repositories - mpd-sima.git/blobdiff - sima/lib/simaecho.py
Fixed simadb_cli auth (Closes #8)
[mpd-sima.git] / sima / lib / simaecho.py
index caf8b5bfbc12b9e21cbd8a3e193f952621581f82..393d723f62e216d7a6027d02c33a363b5f9a2d40 100644 (file)
 Consume EchoNest web service
 """
 
 Consume EchoNest web service
 """
 
-__version__ = '0.0.2'
+__version__ = '0.0.5'
 __author__ = 'Jack Kaliko'
 
 
 __author__ = 'Jack Kaliko'
 
 
-from datetime import datetime, timedelta
-
-from requests import Session, Request, Timeout, ConnectionError
 
 from sima import ECH
 from sima.lib.meta import Artist
 from sima.lib.track import Track
 
 from sima import ECH
 from sima.lib.meta import Artist
 from sima.lib.track import Track
-from sima.lib.http import CacheController
-from sima.utils.utils import WSError, WSNotFound, WSTimeout, WSHTTPError
-from sima.utils.utils import getws, Throttle
+from sima.lib.http import HttpClient
+from sima.utils.utils import WSError, WSNotFound
+from sima.utils.utils import getws
 if len(ECH.get('apikey')) == 23:  # simple hack allowing imp.reload
     getws(ECH)
 
 if len(ECH.get('apikey')) == 23:  # simple hack allowing imp.reload
     getws(ECH)
 
-# Some definitions
-WAIT_BETWEEN_REQUESTS = timedelta(0, 1)
-SOCKET_TIMEOUT = 4
+
+def get_mbid(obj, foreign='foreign_ids'):
+    if foreign in obj:
+        for frgnid in obj.get(foreign):
+            if frgnid.get('catalog') == 'musicbrainz':
+                return frgnid.get('foreign_id').split(':')[2]
+    return None
 
 
 class SimaEch:
     """EchoNest http client
     """
     root_url = 'http://{host}/api/{version}'.format(**ECH)
 
 
 class SimaEch:
     """EchoNest http client
     """
     root_url = 'http://{host}/api/{version}'.format(**ECH)
-    timestamp = datetime.utcnow()
-    ratelimit = None
     name = 'EchoNest'
     cache = False
     name = 'EchoNest'
     cache = False
+    """HTTP cache to use, in memory or persitent.
+
+    :param BaseCache cache: Set a cache, defaults to `False`.
+    """
+    stats = {'etag':0,
+             'ccontrol':0,
+             'minrl':120,
+             'total':0}
 
     def __init__(self):
 
     def __init__(self):
-        self._ressource = None
-        self.current_element = None
-        self.controller = CacheController(self.cache)
-
-    def _fetch(self, payload):
-        """Use cached elements or proceed http request"""
-        req = Request('GET', self._ressource, params=payload,
-                      ).prepare()
-        if self.cache:
-            cached_response = self.controller.cached_request(req.url, req.headers)
-            if cached_response:
-                return cached_response.json()
-
-        try:
-            return self._fetch_ws(req)
-        except Timeout:
-            raise WSTimeout('Failed to reach server within {0}s'.format(
-                               SOCKET_TIMEOUT))
-        except ConnectionError as err:
-            raise WSError(err)
-
-    @Throttle(WAIT_BETWEEN_REQUESTS)
-    def _fetch_ws(self, prepreq):
-        """fetch from web service"""
-        sess = Session()
-        resp = sess.send(prepreq, timeout=SOCKET_TIMEOUT)
-        self.__class__.ratelimit = resp.headers.get('x-ratelimit-remaining', None)
-        if resp.status_code is not 200:
-            raise WSHTTPError('{0.status_code}: {0.reason}'.format(resp))
-        ans = resp.json()
-        self._controls_answer(ans)
-        if self.cache:
-            self.controller.cache_response(resp.request, resp)
-        return ans
+        self.http = HttpClient(cache=self.cache, stats=self.stats)
 
     def _controls_answer(self, ans):
         """Controls answer.
 
     def _controls_answer(self, ans):
         """Controls answer.
@@ -106,8 +80,7 @@ class SimaEch:
         if not isinstance(artist, Artist):
             raise TypeError('"{0!r}" not an Artist object'.format(artist))
         if artist.mbid:
         if not isinstance(artist, Artist):
             raise TypeError('"{0!r}" not an Artist object'.format(artist))
         if artist.mbid:
-            payload.update(
-                    id='musicbrainz:artist:{0}'.format(artist.mbid))
+            payload.update(id='musicbrainz:artist:{0}'.format(artist.mbid))
         else:
             payload.update(name=artist.name)
         payload.update(bucket='id:musicbrainz')
         else:
             payload.update(name=artist.name)
         payload.update(bucket='id:musicbrainz')
@@ -121,42 +94,46 @@ class SimaEch:
                 payload.update(artist=name)
             payload.update(results=100)
             payload.update(sort='song_hotttnesss-desc')
                 payload.update(artist=name)
             payload.update(results=100)
             payload.update(sort='song_hotttnesss-desc')
-        return payload
+        # > hashing the URL into a cache key
+        # return a sorted list of 2-tuple to have consistent cache
+        return sorted(payload.items(), key=lambda param: param[0])
 
 
-    def get_similar(self, artist=None):
+    def get_similar(self, artist):
         """Fetch similar artists
         """Fetch similar artists
+
+        :param sima.lib.meta.Artist artist: `Artist` to fetch similar artists from
+        :returns: generator of :class:`sima.lib.meta.Artist`
         """
         payload = self._forge_payload(artist)
         # Construct URL
         """
         payload = self._forge_payload(artist)
         # Construct URL
-        self._ressource = '{0}/artist/similar'.format(SimaEch.root_url)
-        ans = self._fetch(payload)
-        for art in ans.get('response').get('artists'):
-            artist = {}
-            mbid = None
-            if 'foreign_ids' in art:
-                for frgnid in art.get('foreign_ids'):
-                    if frgnid.get('catalog') == 'musicbrainz':
-                        mbid = frgnid.get('foreign_id'
-                                          ).lstrip('musicbrainz:artist:')
+        ressource = '{0}/artist/similar'.format(SimaEch.root_url)
+        ans = self.http(ressource, payload)
+        self._controls_answer(ans.json())  # pylint: disable=no-member
+        for art in ans.json().get('response').get('artists'):  # pylint: disable=no-member
+            mbid = get_mbid(art)
             yield Artist(mbid=mbid, name=art.get('name'))
 
             yield Artist(mbid=mbid, name=art.get('name'))
 
-    def get_toptrack(self, artist=None):
+    def get_toptrack(self, artist):
         """Fetch artist top tracks
         """Fetch artist top tracks
+
+        :param sima.lib.meta.Artist artist: `Artist` to fetch top tracks from
+        :returns: generator of :class:`sima.lib.track.Track`
         """
         payload = self._forge_payload(artist, top=True)
         # Construct URL
         """
         payload = self._forge_payload(artist, top=True)
         # Construct URL
-        self._ressource = '{0}/song/search'.format(SimaEch.root_url)
-        ans = self._fetch(payload)
+        ressource = '{0}/song/search'.format(SimaEch.root_url)
+        ans = self.http(ressource, payload)
+        self._controls_answer(ans.json())  # pylint: disable=no-member
         titles = list()
         titles = list()
-        artist = {
-                'artist': artist.name,
-                'musicbrainz_artistid': artist.mbid,
-                }
-        for song in ans.get('response').get('songs'):
+        art = {'artist': artist.name,
+               'musicbrainz_artistid': artist.mbid,}
+        for song in ans.json().get('response').get('songs'):  # pylint: disable=no-member
             title = song.get('title')
             title = song.get('title')
+            if not art.get('musicbrainz_artistid'):
+                art['musicbrainz_artistid'] = get_mbid(song, 'artist_foreign_ids')
             if title not in titles:
                 titles.append(title)
             if title not in titles:
                 titles.append(title)
-                yield Track(title=title, **artist)
+                yield Track(title=title, **art)
 
 
 # VIM MODLINE
 
 
 # VIM MODLINE