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.utils.utils import getws, Throttle, Cache, purge_cache
35 if len(LFM.get('apikey')) == 43: # simple hack allowing imp.reload
39 WAIT_BETWEEN_REQUESTS = timedelta(0, 1)
43 class WSError(Exception):
46 class WSNotFound(WSError):
49 class WSTimeout(WSError):
52 class WSHTTPError(WSError):
60 root_url = 'http://{host}/{version}/'.format(**LFM)
62 timestamp = datetime.utcnow()
65 def __init__(self, cache=True):
67 self._url = self.__class__.root_url
68 self.current_element = None
70 purge_cache(self.__class__)
72 def _fetch(self, payload):
73 """Use cached elements or proceed http request"""
74 url = Request('GET', self._url, params=payload,).prepare().url
75 if url in SimaFM.cache:
76 self.current_element = SimaFM.cache.get(url).elem
80 self._fetch_ech(payload)
82 raise WSTimeout('Failed to reach server within {0}s'.format(
84 except ConnectionError as err:
87 @Throttle(WAIT_BETWEEN_REQUESTS)
88 def _fetch_ech(self, payload):
89 """fetch from web service"""
90 req = get(self._url, params=payload,
91 timeout=SOCKET_TIMEOUT)
92 #self.__class__.ratelimit = req.headers.get('x-ratelimit-remaining', None)
93 if req.status_code is not 200:
94 raise WSHTTPError(req.status_code)
95 self.current_element = req.json()
96 self._controls_answer()
98 SimaFM.cache.update({req.url:
99 Cache(self.current_element)})
101 def _controls_answer(self):
104 if 'error' in self.current_element:
105 code = self.current_element.get('error')
106 mess = self.current_element.get('message')
108 raise WSNotFound('{0}: "{1}"'.format(mess, self.artist))
112 def _forge_payload(self, artist, method='similar', track=None):
115 payloads = dict({'similar': {'method':'artist.getsimilar',},
116 'top': {'method':'artist.gettoptracks',},
117 'track': {'method':'track.getsimilar',},
118 'info': {'method':'artist.getinfo',},
120 payload = payloads.get(method)
121 payload.update(api_key=LFM.get('apikey'), format='json')
122 if not isinstance(artist, Artist):
123 raise TypeError('"{0!r}" not an Artist object'.format(artist))
126 payload.update(mbid='{0}'.format(artist.mbid))
128 payload.update(artist=artist.name)
129 payload.update(results=100)
130 if method == 'track':
131 payload.update(track=track)
134 def get_similar(self, artist=None):
137 payload = self._forge_payload(artist)
140 for art in self.current_element.get('similarartists').get('artist'):
141 match = 100 * float(art.get('match'))
142 yield Artist(mbid=art.get('mbid', None),
143 name=art.get('name')), match
147 # vim: ai ts=4 sw=4 sts=4 expandtab