1 # -*- coding: utf-8 -*-
3 # Copyright (c) 2014 Jack Kaliko <kaliko@azylum.org>
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program. If not, see <http://www.gnu.org/licenses/>.
21 Consume EchoNest web service
25 __author__ = 'Jack Kaliko'
28 from datetime import datetime, timedelta
30 from requests import get, Request, Timeout, ConnectionError
33 from sima.lib.meta import Artist
34 from sima.lib.track import Track
35 from sima.utils.utils import WSError, WSNotFound, WSTimeout, WSHTTPError
36 from sima.utils.utils import getws, Throttle, Cache, purge_cache
37 if len(ECH.get('apikey')) == 23: # simple hack allowing imp.reload
41 WAIT_BETWEEN_REQUESTS = timedelta(0, 1)
46 """EchoNest http client
48 root_url = 'http://{host}/api/{version}'.format(**ECH)
50 timestamp = datetime.utcnow()
54 def __init__(self, cache=True):
56 self._ressource = None
57 self.current_element = None
59 purge_cache(self.__class__)
61 def _fetch(self, payload):
62 """Use cached elements or proceed http request"""
63 url = Request('GET', self._ressource, params=payload,).prepare().url
64 if url in SimaEch.cache:
65 self.current_element = SimaEch.cache.get(url).elem
68 self._fetch_ws(payload)
70 raise WSTimeout('Failed to reach server within {0}s'.format(
72 except ConnectionError as err:
75 @Throttle(WAIT_BETWEEN_REQUESTS)
76 def _fetch_ws(self, payload):
77 """fetch from web service"""
78 req = get(self._ressource, params=payload,
79 timeout=SOCKET_TIMEOUT)
80 self.__class__.ratelimit = req.headers.get('x-ratelimit-remaining', None)
81 if req.status_code is not 200:
82 raise WSHTTPError('{0.status_code}: {0.reason}'.format(req))
83 self.current_element = req.json()
84 self._controls_answer()
86 SimaEch.cache.update({req.url:
87 Cache(self.current_element)})
89 def _controls_answer(self):
92 status = self.current_element.get('response').get('status')
93 code = status.get('code')
97 raise WSNotFound('Artist not found: "{0}"'.format(self.artist))
98 raise WSError(status.get('message'))
100 def _forge_payload(self, artist, top=False):
103 payload = {'api_key': ECH.get('apikey')}
104 if not isinstance(artist, Artist):
105 raise TypeError('"{0!r}" not an Artist object'.format(artist))
109 id='musicbrainz:artist:{0}'.format(artist.mbid))
111 payload.update(name=artist.name)
112 payload.update(bucket='id:musicbrainz')
113 payload.update(results=100)
116 aid = payload.pop('id')
117 payload.update(artist_id=aid)
119 name = payload.pop('name')
120 payload.update(artist=name)
121 payload.update(results=100)
122 payload.update(sort='song_hotttnesss-desc')
125 def get_similar(self, artist=None):
126 """Fetch similar artists
128 payload = self._forge_payload(artist)
130 self._ressource = '{0}/artist/similar'.format(SimaEch.root_url)
132 for art in self.current_element.get('response').get('artists'):
135 if 'foreign_ids' in art:
136 for frgnid in art.get('foreign_ids'):
137 if frgnid.get('catalog') == 'musicbrainz':
138 mbid = frgnid.get('foreign_id'
139 ).lstrip('musicbrainz:artist:')
140 yield Artist(mbid=mbid, name=art.get('name'))
142 def get_toptrack(self, artist=None):
143 """Fetch artist top tracks
145 payload = self._forge_payload(artist, top=True)
147 self._ressource = '{0}/song/search'.format(SimaEch.root_url)
151 'artist': artist.name,
152 'musicbrainz_artistid': artist.mbid,
154 for song in self.current_element.get('response').get('songs'):
155 title = song.get('title')
156 if title not in titles:
158 yield Track(title=title, **artist)
162 # vim: ai ts=4 sw=4 sts=4 expandtab