Merge pull request #264 from pylast/save-artist-images

Save artist images and refactor album images
This commit is contained in:
Hugo 2018-04-18 12:09:08 +03:00 committed by GitHub
commit fad76315bb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 144 additions and 74 deletions

View file

@ -133,5 +133,5 @@ network = pylast.LastFMNetwork(...)
To enable from pytest: To enable from pytest:
```sh ```sh
pytest -k test_album_search_images --log-cli-level debug pytest --log-cli-level debug -k test_album_search_images
``` ```

View file

@ -903,7 +903,7 @@ class SessionKeyGenerator(object):
a. network = get_*_network(API_KEY, API_SECRET) a. network = get_*_network(API_KEY, API_SECRET)
b. sg = SessionKeyGenerator(network) b. sg = SessionKeyGenerator(network)
c. url = sg.get_web_auth_url() c. url = sg.get_web_auth_url()
d. Ask the user to open the url and authorize you, and wait for it. d. Ask the user to open the URL and authorize you, and wait for it.
e. session_key = sg.get_web_auth_session_key(url) e. session_key = sg.get_web_auth_session_key(url)
2) Username and Password Authentication: 2) Username and Password Authentication:
a. network = get_*_network(API_KEY, API_SECRET) a. network = get_*_network(API_KEY, API_SECRET)
@ -961,7 +961,7 @@ class SessionKeyGenerator(object):
def get_web_auth_session_key(self, url, token=""): def get_web_auth_session_key(self, url, token=""):
""" """
Retrieves the session key of a web authorization process by its url. Retrieves the session key of a web authorization process by its URL.
""" """
if url in self.web_auth_tokens.keys(): if url in self.web_auth_tokens.keys():
@ -1375,7 +1375,7 @@ class _Opus(_BaseObject, _Taggable):
__hash__ = _BaseObject.__hash__ __hash__ = _BaseObject.__hash__
def __init__(self, artist, title, network, ws_prefix, username=None, def __init__(self, artist, title, network, ws_prefix, username=None,
images=None): info=None):
""" """
Create an opus instance. Create an opus instance.
# Parameters: # Parameters:
@ -1384,6 +1384,9 @@ class _Opus(_BaseObject, _Taggable):
* ws_prefix: 'album' or 'track' * ws_prefix: 'album' or 'track'
""" """
if info is None:
info = {}
_BaseObject.__init__(self, network, ws_prefix) _BaseObject.__init__(self, network, ws_prefix)
_Taggable.__init__(self, ws_prefix) _Taggable.__init__(self, ws_prefix)
@ -1394,7 +1397,7 @@ class _Opus(_BaseObject, _Taggable):
self.title = title self.title = title
self.username = username self.username = username
self.images = images self.info = info
def __repr__(self): def __repr__(self):
return "pylast.%s(%s, %s, %s)" % ( return "pylast.%s(%s, %s, %s)" % (
@ -1428,6 +1431,21 @@ class _Opus(_BaseObject, _Taggable):
return self.artist return self.artist
def get_cover_image(self, size=SIZE_EXTRA_LARGE):
"""
Returns a URI to the cover image
size can be one of:
SIZE_EXTRA_LARGE
SIZE_LARGE
SIZE_MEDIUM
SIZE_SMALL
"""
if "image" not in self.info:
self.info["image"] = _extract_all(
self._request(self.ws_prefix + ".getInfo", cacheable=True),
"image")
return self.info["image"][size]
def get_title(self, properly_capitalized=False): def get_title(self, properly_capitalized=False):
"""Returns the artist or track title.""" """Returns the artist or track title."""
if properly_capitalized: if properly_capitalized:
@ -1492,24 +1510,9 @@ class Album(_Opus):
__hash__ = _Opus.__hash__ __hash__ = _Opus.__hash__
def __init__(self, artist, title, network, username=None, images=None): def __init__(self, artist, title, network, username=None, info=None):
super(Album, self).__init__(artist, title, network, "album", username, super(Album, self).__init__(artist, title, network, "album", username,
images) info)
def get_cover_image(self, size=SIZE_EXTRA_LARGE):
"""
Returns a uri to the cover image
size can be one of:
SIZE_EXTRA_LARGE
SIZE_LARGE
SIZE_MEDIUM
SIZE_SMALL
"""
if not self.images:
self.images = _extract_all(
self._request(self.ws_prefix + ".getInfo", cacheable=True),
'image')
return self.images[size]
def get_tracks(self): def get_tracks(self):
"""Returns the list of Tracks on this album.""" """Returns the list of Tracks on this album."""
@ -1552,17 +1555,21 @@ class Artist(_BaseObject, _Taggable):
__hash__ = _BaseObject.__hash__ __hash__ = _BaseObject.__hash__
def __init__(self, name, network, username=None): def __init__(self, name, network, username=None, info=None):
"""Create an artist object. """Create an artist object.
# Parameters: # Parameters:
* name str: The artist's name. * name str: The artist's name.
""" """
if info is None:
info = {}
_BaseObject.__init__(self, network, 'artist') _BaseObject.__init__(self, network, 'artist')
_Taggable.__init__(self, 'artist') _Taggable.__init__(self, 'artist')
self.name = name self.name = name
self.username = username self.username = username
self.info = info
def __repr__(self): def __repr__(self):
return "pylast.Artist(%s, %s)" % ( return "pylast.Artist(%s, %s)" % (
@ -1606,7 +1613,7 @@ class Artist(_BaseObject, _Taggable):
def get_cover_image(self, size=SIZE_EXTRA_LARGE): def get_cover_image(self, size=SIZE_EXTRA_LARGE):
""" """
Returns a uri to the cover image Returns a URI to the cover image
size can be one of: size can be one of:
SIZE_MEGA SIZE_MEGA
SIZE_EXTRA_LARGE SIZE_EXTRA_LARGE
@ -1615,8 +1622,11 @@ class Artist(_BaseObject, _Taggable):
SIZE_SMALL SIZE_SMALL
""" """
return _extract_all( if "image" not in self.info:
self._request(self.ws_prefix + ".getInfo", True), "image")[size] self.info["image"] = _extract_all(
self._request(self.ws_prefix + ".getInfo", cacheable=True),
"image")
return self.info["image"][size]
def get_playcount(self): def get_playcount(self):
"""Returns the number of plays on the network.""" """Returns the number of plays on the network."""
@ -1724,7 +1734,7 @@ class Artist(_BaseObject, _Taggable):
"getTopTracks", "track", Track, params, cacheable) "getTopTracks", "track", Track, params, cacheable)
def get_url(self, domain_name=DOMAIN_ENGLISH): def get_url(self, domain_name=DOMAIN_ENGLISH):
"""Returns the url of the artist page on the network. """Returns the URL of the artist page on the network.
# Parameters: # Parameters:
* domain_name: The network's language domain. Possible values: * domain_name: The network's language domain. Possible values:
o DOMAIN_ENGLISH o DOMAIN_ENGLISH
@ -1800,7 +1810,7 @@ class Country(_BaseObject):
"getTopTracks", "track", Track, params, cacheable) "getTopTracks", "track", Track, params, cacheable)
def get_url(self, domain_name=DOMAIN_ENGLISH): def get_url(self, domain_name=DOMAIN_ENGLISH):
"""Returns the url of the country page on the network. """Returns the URL of the country page on the network.
* domain_name: The network's language domain. Possible values: * domain_name: The network's language domain. Possible values:
o DOMAIN_ENGLISH o DOMAIN_ENGLISH
o DOMAIN_GERMAN o DOMAIN_GERMAN
@ -1945,7 +1955,7 @@ class Tag(_BaseObject, _Chartable):
return _extract_top_artists(doc, self.network) return _extract_top_artists(doc, self.network)
def get_url(self, domain_name=DOMAIN_ENGLISH): def get_url(self, domain_name=DOMAIN_ENGLISH):
"""Returns the url of the tag page on the network. """Returns the URL of the tag page on the network.
* domain_name: The network's language domain. Possible values: * domain_name: The network's language domain. Possible values:
o DOMAIN_ENGLISH o DOMAIN_ENGLISH
o DOMAIN_GERMAN o DOMAIN_GERMAN
@ -1971,8 +1981,9 @@ class Track(_Opus):
__hash__ = _Opus.__hash__ __hash__ = _Opus.__hash__
def __init__(self, artist, title, network, username=None): def __init__(self, artist, title, network, username=None, info=None):
super(Track, self).__init__(artist, title, network, "track", username) super(Track, self).__init__(artist, title, network, "track", username,
info)
def get_correction(self): def get_correction(self):
"""Returns the corrected track name.""" """Returns the corrected track name."""
@ -2454,7 +2465,7 @@ class User(_BaseObject, _Chartable):
return _extract_all(doc, "image")[size] return _extract_all(doc, "image")[size]
def get_url(self, domain_name=DOMAIN_ENGLISH): def get_url(self, domain_name=DOMAIN_ENGLISH):
"""Returns the url of the user page on the network. """Returns the URL of the user page on the network.
* domain_name: The network's language domain. Possible values: * domain_name: The network's language domain. Possible values:
o DOMAIN_ENGLISH o DOMAIN_ENGLISH
o DOMAIN_GERMAN o DOMAIN_GERMAN
@ -2550,11 +2561,14 @@ class AlbumSearch(_Search):
seq = [] seq = []
for node in master_node.getElementsByTagName("album"): for node in master_node.getElementsByTagName("album"):
seq.append(Album( seq.append(
Album(
_extract(node, "artist"), _extract(node, "artist"),
_extract(node, "name"), _extract(node, "name"),
self.network, self.network,
images=_extract_all(node, 'image'))) info={"image": _extract_all(node, "image")},
),
)
return seq return seq
@ -2572,7 +2586,11 @@ class ArtistSearch(_Search):
seq = [] seq = []
for node in master_node.getElementsByTagName("artist"): for node in master_node.getElementsByTagName("artist"):
artist = Artist(_extract(node, "name"), self.network) artist = Artist(
_extract(node, "name"),
self.network,
info={"image": _extract_all(node, "image")},
)
artist.listener_count = _number(_extract(node, "listeners")) artist.listener_count = _number(_extract(node, "listeners"))
seq.append(artist) seq.append(artist)
@ -2603,7 +2621,9 @@ class TrackSearch(_Search):
track = Track( track = Track(
_extract(node, "artist"), _extract(node, "artist"),
_extract(node, "name"), _extract(node, "name"),
self.network) self.network,
info={"image": _extract_all(node, "image")},
)
track.listener_count = _number(_extract(node, "listeners")) track.listener_count = _number(_extract(node, "listeners"))
seq.append(track) seq.append(track)
@ -2767,7 +2787,7 @@ def _extract_tracks(doc, network):
def _url_safe(text): def _url_safe(text):
"""Does all kinds of tricks on a text to make it safe to use in a url.""" """Does all kinds of tricks on a text to make it safe to use in a URL."""
return url_quote_plus(url_quote_plus(_string(text))).lower() return url_quote_plus(url_quote_plus(_string(text))).lower()

View file

@ -6,10 +6,10 @@ import unittest
import pylast import pylast
from .test_pylast import PyLastTestCase from .test_pylast import TestPyLastWithLastFm
class TestPyLastAlbum(PyLastTestCase): class TestPyLastAlbum(TestPyLastWithLastFm):
def test_album_tags_are_topitems(self): def test_album_tags_are_topitems(self):
# Arrange # Arrange
@ -107,8 +107,8 @@ class TestPyLastAlbum(PyLastTestCase):
image = album.get_cover_image() image = album.get_cover_image()
# Assert # Assert
self.assertTrue(image.startswith("https://")) self.assert_startswith(image, "https://")
self.assertTrue(image.endswith(".png")) self.assert_endswith(image, ".png")
if __name__ == '__main__': if __name__ == '__main__':

View file

@ -6,10 +6,10 @@ import unittest
import pylast import pylast
from .test_pylast import PyLastTestCase from .test_pylast import TestPyLastWithLastFm
class TestPyLastArtist(PyLastTestCase): class TestPyLastArtist(TestPyLastWithLastFm):
def test_repr(self): def test_repr(self):
# Arrange # Arrange

View file

@ -6,10 +6,10 @@ import unittest
import pylast import pylast
from .test_pylast import PyLastTestCase from .test_pylast import TestPyLastWithLastFm
class TestPyLastCountry(PyLastTestCase): class TestPyLastCountry(TestPyLastWithLastFm):
def test_country_is_hashable(self): def test_country_is_hashable(self):
# Arrange # Arrange

View file

@ -6,10 +6,10 @@ import unittest
import pylast import pylast
from .test_pylast import PyLastTestCase from .test_pylast import TestPyLastWithLastFm
class TestPyLastLibrary(PyLastTestCase): class TestPyLastLibrary(TestPyLastWithLastFm):
def test_repr(self): def test_repr(self):
# Arrange # Arrange
@ -19,7 +19,7 @@ class TestPyLastLibrary(PyLastTestCase):
representation = repr(library) representation = repr(library)
# Assert # Assert
self.assertTrue(representation.startswith("pylast.Library(")) self.assert_startswith(representation, "pylast.Library(")
def test_str(self): def test_str(self):
# Arrange # Arrange
@ -29,7 +29,7 @@ class TestPyLastLibrary(PyLastTestCase):
string = str(library) string = str(library)
# Assert # Assert
self.assertTrue(string.endswith("'s Library")) self.assert_endswith(string, "'s Library")
def test_library_is_hashable(self): def test_library_is_hashable(self):
# Arrange # Arrange

View file

@ -8,11 +8,11 @@ from flaky import flaky
import pylast import pylast
from .test_pylast import load_secrets from .test_pylast import PyLastTestCase, load_secrets
@flaky(max_runs=5, min_passes=1) @flaky(max_runs=3, min_passes=1)
class TestPyLastWithLibreFm(unittest.TestCase): class TestPyLastWithLibreFm(PyLastTestCase):
"""Own class for Libre.fm because we don't need the Last.fm setUp""" """Own class for Libre.fm because we don't need the Last.fm setUp"""
def test_libre_fm(self): def test_libre_fm(self):
@ -42,7 +42,7 @@ class TestPyLastWithLibreFm(unittest.TestCase):
representation = repr(network) representation = repr(network)
# Assert # Assert
self.assertTrue(representation.startswith("pylast.LibreFMNetwork(")) self.assert_startswith(representation, "pylast.LibreFMNetwork(")
if __name__ == '__main__': if __name__ == '__main__':

View file

@ -7,10 +7,10 @@ import unittest
import pylast import pylast
from .test_pylast import PyLastTestCase from .test_pylast import TestPyLastWithLastFm
class TestPyLastNetwork(PyLastTestCase): class TestPyLastNetwork(TestPyLastWithLastFm):
def test_scrobble(self): def test_scrobble(self):
# Arrange # Arrange
@ -322,17 +322,17 @@ class TestPyLastNetwork(PyLastTestCase):
# Act # Act
results = search.get_next_page() results = search.get_next_page()
images = results[0].images images = results[0].info["image"]
# Assert # Assert
self.assertEqual(len(images), 4) self.assertEqual(len(images), 4)
self.assertTrue(images[pylast.SIZE_SMALL].startswith("https://")) self.assert_startswith(images[pylast.SIZE_SMALL], "https://")
self.assertTrue(images[pylast.SIZE_SMALL].endswith(".png")) self.assert_endswith(images[pylast.SIZE_SMALL], ".png")
self.assertIn("/34s/", images[pylast.SIZE_SMALL]) self.assertIn("/34s/", images[pylast.SIZE_SMALL])
self.assertTrue(images[pylast.SIZE_EXTRA_LARGE].startswith("https://")) self.assert_startswith(images[pylast.SIZE_EXTRA_LARGE], "https://")
self.assertTrue(images[pylast.SIZE_EXTRA_LARGE].endswith(".png")) self.assert_endswith(images[pylast.SIZE_EXTRA_LARGE], ".png")
self.assertIn("/300x300/", images[pylast.SIZE_EXTRA_LARGE]) self.assertIn("/300x300/", images[pylast.SIZE_EXTRA_LARGE])
def test_artist_search(self): def test_artist_search(self):
@ -347,6 +347,26 @@ class TestPyLastNetwork(PyLastTestCase):
self.assertIsInstance(results, list) self.assertIsInstance(results, list)
self.assertIsInstance(results[0], pylast.Artist) self.assertIsInstance(results[0], pylast.Artist)
def test_artist_search_images(self):
# Arrange
artist = "Nirvana"
search = self.network.search_for_artist(artist)
# Act
results = search.get_next_page()
images = results[0].info["image"]
# Assert
self.assertEqual(len(images), 5)
self.assert_startswith(images[pylast.SIZE_SMALL], "https://")
self.assert_endswith(images[pylast.SIZE_SMALL], ".png")
self.assertIn("/34s/", images[pylast.SIZE_SMALL])
self.assert_startswith(images[pylast.SIZE_EXTRA_LARGE], "https://")
self.assert_endswith(images[pylast.SIZE_EXTRA_LARGE], ".png")
self.assertIn("/300x300/", images[pylast.SIZE_EXTRA_LARGE])
def test_track_search(self): def test_track_search(self):
# Arrange # Arrange
artist = "Nirvana" artist = "Nirvana"
@ -360,6 +380,27 @@ class TestPyLastNetwork(PyLastTestCase):
self.assertIsInstance(results, list) self.assertIsInstance(results, list)
self.assertIsInstance(results[0], pylast.Track) self.assertIsInstance(results[0], pylast.Track)
def test_track_search_images(self):
# Arrange
artist = "Nirvana"
track = "Smells Like Teen Spirit"
search = self.network.search_for_track(artist, track)
# Act
results = search.get_next_page()
images = results[0].info["image"]
# Assert
self.assertEqual(len(images), 4)
self.assert_startswith(images[pylast.SIZE_SMALL], "https://")
self.assert_endswith(images[pylast.SIZE_SMALL], ".png")
self.assertIn("/34s/", images[pylast.SIZE_SMALL])
self.assert_startswith(images[pylast.SIZE_EXTRA_LARGE], "https://")
self.assert_endswith(images[pylast.SIZE_EXTRA_LARGE], ".png")
self.assertIn("/300x300/", images[pylast.SIZE_EXTRA_LARGE])
def test_search_get_total_result_count(self): def test_search_get_total_result_count(self):
# Arrange # Arrange
artist = "Nirvana" artist = "Nirvana"

View file

@ -30,9 +30,18 @@ def load_secrets():
return doc return doc
@flaky(max_runs=5, min_passes=1)
class PyLastTestCase(unittest.TestCase): class PyLastTestCase(unittest.TestCase):
def assert_startswith(self, str, prefix, start=None, end=None):
self.assertTrue(str.startswith(prefix, start, end))
def assert_endswith(self, str, suffix, start=None, end=None):
self.assertTrue(str.endswith(suffix, start, end))
@flaky(max_runs=3, min_passes=1)
class TestPyLastWithLastFm(PyLastTestCase):
secrets = None secrets = None
def unix_timestamp(self): def unix_timestamp(self):

View file

@ -6,10 +6,10 @@ import unittest
import pylast import pylast
from .test_pylast import PyLastTestCase from .test_pylast import TestPyLastWithLastFm
class TestPyLastTag(PyLastTestCase): class TestPyLastTag(TestPyLastWithLastFm):
def test_tag_is_hashable(self): def test_tag_is_hashable(self):
# Arrange # Arrange

View file

@ -6,10 +6,10 @@ import unittest
import pylast import pylast
from .test_pylast import PyLastTestCase from .test_pylast import TestPyLastWithLastFm
class TestPyLastTrack(PyLastTestCase): class TestPyLastTrack(TestPyLastWithLastFm):
def test_love(self): def test_love(self):
# Arrange # Arrange

View file

@ -7,10 +7,10 @@ import unittest
import pylast import pylast
from .test_pylast import PyLastTestCase from .test_pylast import TestPyLastWithLastFm
class TestPyLastUser(PyLastTestCase): class TestPyLastUser(TestPyLastWithLastFm):
def test_repr(self): def test_repr(self):
# Arrange # Arrange
@ -20,7 +20,7 @@ class TestPyLastUser(PyLastTestCase):
representation = repr(user) representation = repr(user)
# Assert # Assert
self.assertTrue(representation.startswith("pylast.User('RJ',")) self.assert_startswith(representation, "pylast.User('RJ',")
def test_str(self): def test_str(self):
# Arrange # Arrange
@ -342,7 +342,7 @@ class TestPyLastUser(PyLastTestCase):
url = user.get_image() url = user.get_image()
# Assert # Assert
self.assertTrue(url.startswith("https://")) self.assert_startswith(url, "https://")
def test_user_get_library(self): def test_user_get_library(self):
# Arrange # Arrange
@ -392,8 +392,8 @@ class TestPyLastUser(PyLastTestCase):
image = user.get_image() image = user.get_image()
# Assert # Assert
self.assertTrue(image.startswith("https://")) self.assert_startswith(image, "https://")
self.assertTrue(image.endswith(".png")) self.assert_endswith(image, ".png")
def test_get_url(self): def test_get_url(self):
# Arrange # Arrange