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
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):
59 def __init__(self, wait):
61 self.last_called = datetime.now()
63 def __call__(self, func):
64 def wrapper(*args, **kwargs):
65 while self.last_called + self.wait > datetime.now():
67 result = func(*args, **kwargs)
68 self.last_called = datetime.now()
74 def __init__(self, elem, last=None):
76 self.requestdate = last
78 self.requestdate = datetime.utcnow()
81 return self.requestdate
87 def purge_cache(age=4):
88 now = datetime.utcnow()
89 if now.hour == SimaEch.timestamp.hour:
91 SimaEch.timestamp = datetime.utcnow()
93 delta = timedelta(hours=age)
94 for url in list(cache.keys()):
95 timestamp = cache.get(url).created()
96 if now - timestamp > delta:
103 root_url = 'http://{host}/api/{version}'.format(**ECH)
105 timestamp = datetime.utcnow()
108 def __init__(self, cache=True):
110 self._ressource = None
111 self.current_element = None
115 def _fetch(self, payload):
116 """Use cached elements or proceed http request"""
117 url = Request('GET', self._ressource, params=payload,).prepare().url
118 if url in SimaEch.cache:
119 self.current_element = SimaEch.cache.get(url).elem
122 self._fetch_ech(payload)
124 raise EchoTimeout('Failed to reach server within {0}s'.format(
126 except ConnectionError as err:
129 @Throttle(WAIT_BETWEEN_REQUESTS)
130 def _fetch_ech(self, payload):
131 """fetch from web service"""
132 req = get(self._ressource, params=payload,
133 timeout=SOCKET_TIMEOUT)
134 self.__class__.ratelimit = req.headers.get('x-ratelimit-remaining', None)
135 if req.status_code is not 200:
136 raise EchoHTTPError(req.status_code)
137 self.current_element = req.json()
138 self._controls_answer()
140 SimaEch.cache.update({req.url:
141 Cache(self.current_element)})
143 def _controls_answer(self):
146 status = self.current_element.get('response').get('status')
147 code = status.get('code')
151 raise EchoNotFound('Artist not found: "{0}"'.format(self.artist))
152 raise EchoError(status.get('message'))
154 def _forge_payload(self, artist):
157 payload = {'api_key': ECH.get('apikey')}
158 if not isinstance(artist, Artist):
159 raise TypeError('"{0!r}" not an Artist object'.format(artist))
163 id='musicbrainz:artist:{0}'.format(artist.mbid))
165 payload.update(name=artist.name)
166 payload.update(bucket='id:musicbrainz')
167 payload.update(results=100)
170 def get_similar(self, artist=None):
173 payload = self._forge_payload(artist)
175 self._ressource = '{0}/artist/similar'.format(SimaEch.root_url)
177 for art in self.current_element.get('response').get('artists'):
180 if 'foreign_ids' in art:
181 for frgnid in art.get('foreign_ids'):
182 if frgnid.get('catalog') == 'musicbrainz':
183 mbid = frgnid.get('foreign_id'
184 ).lstrip('musicbrainz:artist:')
185 yield Artist(mbid=mbid, name=art.get('name'))
189 # vim: ai ts=4 sw=4 sts=4 expandtab