diff --git a/README.md b/README.md index d95d09d..323dec4 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ pyLast ====== -[![Build Status](https://travis-ci.org/pylast/pylast.svg?branch=develop)](https://travis-ci.org/pylast/pylast) [![PyPI version](https://pypip.in/version/pylast/badge.svg)](https://pypi.python.org/pypi/pylast/) [![PyPI downloads](https://pypip.in/download/pylast/badge.svg)](https://pypi.python.org/pypi/pylast/) [![Coverage Status](https://coveralls.io/repos/pylast/pylast/badge.png?branch=develop)](https://coveralls.io/r/pylast/pylast?branch=develop) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/pylast/pylast/badges/quality-score.png?b=develop)](https://scrutinizer-ci.com/g/pylast/pylast/?branch=develop) [![Code Health](https://landscape.io/github/pylast/pylast/develop/landscape.svg)](https://landscape.io/github/hugovk/pylast/develop) +[![Build Status](https://travis-ci.org/pylast/pylast.svg?branch=develop)](https://travis-ci.org/pylast/pylast) [![PyPI version](https://img.shields.io/pypi/v/pylast.svg)](https://pypi.python.org/pypi/pylast/) [![PyPI downloads](https://img.shields.io/pypi/dm/pylast.svg)](https://pypi.python.org/pypi/pylast/) [![Coverage Status](https://coveralls.io/repos/pylast/pylast/badge.png?branch=develop)](https://coveralls.io/r/pylast/pylast?branch=develop) [![Code Health](https://landscape.io/github/pylast/pylast/develop/landscape.svg)](https://landscape.io/github/hugovk/pylast/develop) A Python interface to [Last.fm](http://www.last.fm/) and other api-compatible websites such as [Libre.fm](http://libre.fm/). diff --git a/pylast/__init__.py b/pylast/__init__.py index 50d40c7..33e19cd 100644 --- a/pylast/__init__.py +++ b/pylast/__init__.py @@ -32,7 +32,7 @@ import warnings import re import six -__version__ = '1.2.1' +__version__ = '1.2.2' __author__ = 'Amr Hassan, hugovk' __copyright__ = "Copyright (C) 2008-2010 Amr Hassan, 2013-2015 hugovk" __license__ = "apache2" @@ -1928,6 +1928,12 @@ class Artist(_BaseObject, _Taggable): return self.name + def get_correction(self): + """Returns the corrected artist name.""" + + return _extract( + self._request(self.ws_prefix + ".getCorrection"), "name") + def get_cover_image(self, size=COVER_MEGA): """ Returns a uri to the cover image @@ -2947,6 +2953,12 @@ class Track(_Opus): def __init__(self, artist, title, network, username=None): super(Track, self).__init__(artist, title, network, "track", username) + def get_correction(self): + """Returns the corrected track name.""" + + return _extract( + self._request(self.ws_prefix + ".getCorrection"), "name") + def get_duration(self): """Returns the track duration.""" @@ -3505,6 +3517,41 @@ class User(_BaseObject, _Chartable): return doc.getElementsByTagName( "registered")[0].getAttribute("unixtime") + def get_tagged_albums(self, tag, limit=None, cacheable=True): + """Returns the albums tagged by a user.""" + + params = self._get_params() + params['tag'] = tag + params['taggingtype'] = 'album' + if limit: + params['limit'] = limit + doc = self._request(self.ws_prefix + '.getpersonaltags', cacheable, + params) + return _extract_albums(doc, self.network) + + def get_tagged_artists(self, tag, limit=None): + """Returns the artists tagged by a user.""" + + params = self._get_params() + params['tag'] = tag + params['taggingtype'] = 'artist' + if limit: + params["limit"] = limit + doc = self._request(self.ws_prefix + '.getpersonaltags', True, params) + return _extract_artists(doc, self.network) + + def get_tagged_tracks(self, tag, limit=None, cacheable=True): + """Returns the tracks tagged by a user.""" + + params = self._get_params() + params['tag'] = tag + params['taggingtype'] = 'track' + if limit: + params['limit'] = limit + doc = self._request(self.ws_prefix + '.getpersonaltags', cacheable, + params) + return _extract_tracks(doc, self.network) + def get_top_albums( self, period=PERIOD_OVERALL, limit=None, cacheable=True): """Returns the top albums played by a user. @@ -3693,7 +3740,7 @@ class AuthenticatedUser(User): def get_recommended_artists(self, limit=50, cacheable=False): """ - Returns a sequence of Event objects + Returns a sequence of Artist objects if limit==None it will return all """ @@ -4087,6 +4134,31 @@ def _extract_top_albums(doc, network): return seq +def _extract_artists(doc, network): + seq = [] + for node in doc.getElementsByTagName("artist"): + seq.append(Artist(_extract(node, "name"), network)) + return seq + + +def _extract_albums(doc, network): + seq = [] + for node in doc.getElementsByTagName("album"): + name = _extract(node, "name") + artist = _extract(node, "name", 1) + seq.append(Album(artist, name, network)) + return seq + + +def _extract_tracks(doc, network): + seq = [] + for node in doc.getElementsByTagName("track"): + name = _extract(node, "name") + artist = _extract(node, "name", 1) + seq.append(Track(artist, name, network)) + return seq + + def _extract_events_from_doc(doc, network): events = [] for node in doc.getElementsByTagName("event"): diff --git a/tests/test_pylast.py b/tests/test_pylast.py index 1980e7a..abe9917 100755 --- a/tests/test_pylast.py +++ b/tests/test_pylast.py @@ -1041,6 +1041,12 @@ class TestPyLast(unittest.TestCase): self.assertIsInstance(things[0], pylast.TopItem) self.assertIsInstance(things[0].item, expected_type) + def helper_only_one_thing_in_list(self, things, expected_type): + # Assert + self.assertEqual(len(things), 1) + self.assertIsInstance(things, list) + self.assertIsInstance(things[0], expected_type) + def helper_two_different_things_in_top_list(self, things, expected_type): # Assert self.assertEqual(len(things), 2) @@ -1400,6 +1406,44 @@ class TestPyLast(unittest.TestCase): # Assert self.helper_only_one_thing_in_top_list(albums, pylast.Album) + def test_user_tagged_artists(self): + # Arrange + lastfm_user = self.network.get_user(self.username) + tags = ["artisttagola"] + artist = self.network.get_artist("Test Artist") + artist.add_tags(tags) + + # Act + artists = lastfm_user.get_tagged_artists('artisttagola', limit=1) + + # Assert + self.helper_only_one_thing_in_list(artists, pylast.Artist) + + def test_user_tagged_albums(self): + # Arrange + lastfm_user = self.network.get_user(self.username) + tags = ["albumtagola"] + album = self.network.get_album("Test Artist", "Test Album") + album.add_tags(tags) + + # Act + albums = lastfm_user.get_tagged_albums('albumtagola', limit=1) + + # Assert + self.helper_only_one_thing_in_list(albums, pylast.Album) + + def test_user_tagged_tracks(self): + # Arrange + lastfm_user = self.network.get_user(self.username) + tags = ["tracktagola"] + track = self.network.get_track("Test Artist", "Test Title") + track.add_tags(tags) + # Act + tracks = lastfm_user.get_tagged_tracks('tracktagola', limit=1) + + # Assert + self.helper_only_one_thing_in_list(tracks, pylast.Track) + def test_caching(self): # Arrange user = self.network.get_user("RJ") @@ -1908,6 +1952,25 @@ class TestPyLast(unittest.TestCase): self.assertEqual(str(tracks[0].track.artist), "Johnny Cash") self.assertEqual(str(tracks[0].track.title), "Ring of Fire") + def test_artist_get_correction(self): + # Arrange + artist = pylast.Artist("guns and roses", self.network) + + # Act + corrected_artist_name = artist.get_correction() + + # Assert + self.assertEqual(corrected_artist_name, "Guns N' Roses") + + def test_track_get_correction(self): + # Arrange + track = pylast.Track("Guns N' Roses", "mrbrownstone", self.network) + + # Act + corrected_track_name = track.get_correction() + + # Assert + self.assertEqual(corrected_track_name, "Mr. Brownstone") if __name__ == '__main__': unittest.main(failfast=True) diff --git a/tox.ini b/tox.ini index ec0e215..b5dd199 100644 --- a/tox.ini +++ b/tox.ini @@ -4,6 +4,11 @@ recreate = False [testenv] downloadcache = {homedir}/.pipcache +setenv = + PYLAST_USERNAME={env:PYLAST_USERNAME:} + PYLAST_PASSWORD_HASH={env:PYLAST_PASSWORD_HASH:} + PYLAST_API_KEY={env:PYLAST_API_KEY:} + PYLAST_API_SECRET={env:PYLAST_API_SECRET:} deps = pyyaml pytest