]> kaliko git repositories - mpd-sima.git/blob - sima/lib/simafm.py
Intercept missing artist in plugins' callback
[mpd-sima.git] / sima / lib / simafm.py
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2014 Jack Kaliko <kaliko@azylum.org>
4 #
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.
9 #
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.
14 #
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/>.
17 #
18 #
19
20 """
21 Consume EchoNest web service
22 """
23
24 __version__ = '0.0.1'
25 __author__ = 'Jack Kaliko'
26
27
28 from datetime import datetime, timedelta
29
30 from requests import get, Request, Timeout, ConnectionError
31
32 from sima import LFM
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
36     getws(LFM)
37
38 # Some definitions
39 WAIT_BETWEEN_REQUESTS = timedelta(0, 1)
40 SOCKET_TIMEOUT = 4
41
42
43 class WSError(Exception):
44     pass
45
46 class WSNotFound(WSError):
47     pass
48
49 class WSTimeout(WSError):
50     pass
51
52 class WSHTTPError(WSError):
53     pass
54
55
56
57 class SimaFM():
58     """
59     """
60     root_url = 'http://{host}/{version}/'.format(**LFM)
61     cache = {}
62     timestamp = datetime.utcnow()
63     #ratelimit = None
64
65     def __init__(self, cache=True):
66         self.artist = None
67         self._url = self.__class__.root_url
68         self.current_element = None
69         self.caching = cache
70         purge_cache(self.__class__)
71
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
77             return
78         try:
79             self._fetch_ech(payload)
80         except Timeout:
81             raise WSTimeout('Failed to reach server within {0}s'.format(
82                                SOCKET_TIMEOUT))
83         except ConnectionError as err:
84             raise WSError(err)
85
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()
96         if self.caching:
97             SimaFM.cache.update({req.url:
98                                  Cache(self.current_element)})
99
100     def _controls_answer(self):
101         """Controls answer.
102         """
103         if 'error' in self.current_element:
104             code = self.current_element.get('error')
105             mess = self.current_element.get('message')
106             if code == 6:
107                 raise WSNotFound('{0}: "{1}"'.format(mess, self.artist))
108             raise WSError(mess)
109         return True
110
111     def _forge_payload(self, artist, method='similar', track=None):
112         """
113         """
114         payloads = dict({'similar': {'method':'artist.getsimilar',},
115                         'top': {'method':'artist.gettoptracks',},
116                         'track': {'method':'track.getsimilar',},
117                         'info': {'method':'artist.getinfo',},
118                         })
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))
123         self.artist = artist
124         if artist.mbid:
125             payload.update(mbid='{0}'.format(artist.mbid))
126         else:
127            payload.update(artist=artist.name)
128         payload.update(results=100)
129         if method == 'track':
130             payload.update(track=track)
131         return payload
132
133     def get_similar(self, artist=None):
134         """
135         """
136         payload = self._forge_payload(artist)
137         # Construct URL
138         self._fetch(payload)
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
143
144
145 # VIM MODLINE
146 # vim: ai ts=4 sw=4 sts=4 expandtab