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'
30 from datetime import datetime, timedelta
31 from time import sleep
33 from requests import get, Request, Timeout, ConnectionError
36 from sima.lib.meta import Artist
37 from sima.utils.utils import getws, Throttle, Cache, purge_cache
38 if len(ECH.get('apikey')) == 23: # simple hack allowing imp.reload
42 WAIT_BETWEEN_REQUESTS = timedelta(0, 1)
46 class EchoError(Exception):
49 class EchoNotFound(EchoError):
52 class EchoTimeout(EchoError):
55 class EchoHTTPError(EchoError):
61 root_url = 'http://{host}/api/{version}'.format(**ECH)
63 timestamp = datetime.utcnow()
66 def __init__(self, cache=True):
68 self._ressource = None
69 self.current_element = None
71 purge_cache(self.__class__)
73 def _fetch(self, payload):
74 """Use cached elements or proceed http request"""
75 url = Request('GET', self._ressource, params=payload,).prepare().url
76 if url in SimaEch.cache:
77 self.current_element = SimaEch.cache.get(url).elem
80 self._fetch_ech(payload)
82 raise EchoTimeout('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._ressource, 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 EchoHTTPError(req.status_code)
95 self.current_element = req.json()
96 self._controls_answer()
98 SimaEch.cache.update({req.url:
99 Cache(self.current_element)})
101 def _controls_answer(self):
104 status = self.current_element.get('response').get('status')
105 code = status.get('code')
109 raise EchoNotFound('Artist not found: "{0}"'.format(self.artist))
110 raise EchoError(status.get('message'))
112 def _forge_payload(self, artist):
115 payload = {'api_key': ECH.get('apikey')}
116 if not isinstance(artist, Artist):
117 raise TypeError('"{0!r}" not an Artist object'.format(artist))
121 id='musicbrainz:artist:{0}'.format(artist.mbid))
123 payload.update(name=artist.name)
124 payload.update(bucket='id:musicbrainz')
125 payload.update(results=100)
128 def get_similar(self, artist=None):
131 payload = self._forge_payload(artist)
133 self._ressource = '{0}/artist/similar'.format(SimaEch.root_url)
135 for art in self.current_element.get('response').get('artists'):
138 if 'foreign_ids' in art:
139 for frgnid in art.get('foreign_ids'):
140 if frgnid.get('catalog') == 'musicbrainz':
141 mbid = frgnid.get('foreign_id'
142 ).lstrip('musicbrainz:artist:')
143 yield Artist(mbid=mbid, name=art.get('name'))
147 # vim: ai ts=4 sw=4 sts=4 expandtab