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
79 self._fetch_ech(payload)
81 raise WSTimeout('Failed to reach server within {0}s'.format(
83 except ConnectionError as err:
86 @Throttle(WAIT_BETWEEN_REQUESTS)
87 def _fetch_ech(self, payload):
88 """fetch from web service"""
89 req = get(self._url, params=payload,
90 timeout=SOCKET_TIMEOUT)
91 #self.__class__.ratelimit = req.headers.get('x-ratelimit-remaining', None)
92 if req.status_code is not 200:
93 raise WSHTTPError(req.status_code)
94 self.current_element = req.json()
95 self._controls_answer()
97 SimaFM.cache.update({req.url:
98 Cache(self.current_element)})
100 def _controls_answer(self):
103 if 'error' in self.current_element:
104 code = self.current_element.get('error')
105 mess = self.current_element.get('message')
107 raise WSNotFound('{0}: "{1}"'.format(mess, self.artist))
111 def _forge_payload(self, artist, method='similar', track=None):
114 payloads = dict({'similar': {'method':'artist.getsimilar',},
115 'top': {'method':'artist.gettoptracks',},
116 'track': {'method':'track.getsimilar',},
117 'info': {'method':'artist.getinfo',},
119 payload = payloads.get(method)
120 payload.update(api_key=LFM.get('apikey'), format='json')
121 if not isinstance(artist, Artist):
122 raise TypeError('"{0!r}" not an Artist object'.format(artist))
125 payload.update(mbid='{0}'.format(artist.mbid))
127 payload.update(artist=artist.name)
128 payload.update(results=100)
129 if method == 'track':
130 payload.update(track=track)
133 def get_similar(self, artist=None):
136 payload = self._forge_payload(artist)
139 for art in self.current_element.get('similarartists').get('artist'):
140 match = 100 * float(art.get('match'))
141 yield Artist(mbid=art.get('mbid', None),
142 name=art.get('name')), match
146 # vim: ai ts=4 sw=4 sts=4 expandtab