Source code for sima.lib.simaecho
# -*- coding: utf-8 -*-
# Copyright (c) 2014 Jack Kaliko <kaliko@azylum.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#
"""
Consume EchoNest web service
"""
__version__ = '0.0.5'
__author__ = 'Jack Kaliko'
from sima import ECH
from sima.lib.meta import Artist
from sima.lib.track import Track
from sima.lib.http import HttpClient
from sima.utils.utils import WSError, WSNotFound
from sima.utils.utils import getws
if len(ECH.get('apikey')) == 23: # simple hack allowing imp.reload
getws(ECH)
def get_mbid(obj, foreign='foreign_ids'):
if foreign in obj:
for frgnid in obj.get(foreign):
if frgnid.get('catalog') == 'musicbrainz':
return frgnid.get('foreign_id').split(':')[2]
return None
[docs]class SimaEch:
"""EchoNest http client
"""
root_url = 'http://{host}/api/{version}'.format(**ECH)
name = 'EchoNest'
cache = False
"""HTTP cache to use, in memory or persitent.
:param BaseCache cache: Set a cache, defaults to `False`.
"""
stats = {'etag':0,
'ccontrol':0,
'minrl':120,
'total':0}
def __init__(self):
self.http = HttpClient(cache=self.cache, stats=self.stats)
def _controls_answer(self, ans):
"""Controls answer.
"""
status = ans.get('response').get('status')
code = status.get('code')
if code is 0:
return True
if code is 5:
raise WSNotFound('Artist not found')
raise WSError(status.get('message'))
def _forge_payload(self, artist, top=False):
"""Build payload
"""
payload = {'api_key': ECH.get('apikey')}
if not isinstance(artist, Artist):
raise TypeError('"{0!r}" not an Artist object'.format(artist))
if artist.mbid:
payload.update(id='musicbrainz:artist:{0}'.format(artist.mbid))
else:
payload.update(name=artist.name)
payload.update(bucket='id:musicbrainz')
payload.update(results=100)
if top:
if artist.mbid:
aid = payload.pop('id')
payload.update(artist_id=aid)
else:
name = payload.pop('name')
payload.update(artist=name)
payload.update(results=100)
payload.update(sort='song_hotttnesss-desc')
# > hashing the URL into a cache key
# return a sorted list of 2-tuple to have consistent cache
return sorted(payload.items(), key=lambda param: param[0])
[docs] def get_similar(self, artist):
"""Fetch similar artists
:param sima.lib.meta.Artist artist: `Artist` to fetch similar artists from
:returns: generator of :class:`sima.lib.meta.Artist`
"""
payload = self._forge_payload(artist)
# Construct URL
ressource = '{0}/artist/similar'.format(SimaEch.root_url)
ans = self.http(ressource, payload)
self._controls_answer(ans.json()) # pylint: disable=no-member
for art in ans.json().get('response').get('artists'): # pylint: disable=no-member
mbid = get_mbid(art)
yield Artist(mbid=mbid, name=art.get('name'))
[docs] def get_toptrack(self, artist):
"""Fetch artist top tracks
:param sima.lib.meta.Artist artist: `Artist` to fetch top tracks from
:returns: generator of :class:`sima.lib.track.Track`
"""
payload = self._forge_payload(artist, top=True)
# Construct URL
ressource = '{0}/song/search'.format(SimaEch.root_url)
ans = self.http(ressource, payload)
self._controls_answer(ans.json()) # pylint: disable=no-member
titles = list()
art = {'artist': artist.name,
'musicbrainz_artistid': artist.mbid,}
for song in ans.json().get('response').get('songs'): # pylint: disable=no-member
title = song.get('title')
if not art.get('musicbrainz_artistid'):
art['musicbrainz_artistid'] = get_mbid(song, 'artist_foreign_ids')
if title not in titles:
titles.append(title)
yield Track(title=title, **art)
# VIM MODLINE
# vim: ai ts=4 sw=4 sts=4 expandtab