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:
```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)
b. sg = SessionKeyGenerator(network)
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)
2) Username and Password Authentication:
a. network = get_*_network(API_KEY, API_SECRET)
@ -961,7 +961,7 @@ class SessionKeyGenerator(object):
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():
@ -1375,7 +1375,7 @@ class _Opus(_BaseObject, _Taggable):
__hash__ = _BaseObject.__hash__
def __init__(self, artist, title, network, ws_prefix, username=None,
images=None):
info=None):
"""
Create an opus instance.
# Parameters:
@ -1384,6 +1384,9 @@ class _Opus(_BaseObject, _Taggable):
* ws_prefix: 'album' or 'track'
"""
if info is None:
info = {}
_BaseObject.__init__(self, network, ws_prefix)
_Taggable.__init__(self, ws_prefix)
@ -1394,7 +1397,7 @@ class _Opus(_BaseObject, _Taggable):
self.title = title
self.username = username
self.images = images
self.info = info
def __repr__(self):
return "pylast.%s(%s, %s, %s)" % (
@ -1428,6 +1431,21 @@ class _Opus(_BaseObject, _Taggable):
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):
"""Returns the artist or track title."""
if properly_capitalized:
@ -1492,24 +1510,9 @@ class Album(_Opus):
__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,
images)
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]
info)
def get_tracks(self):
"""Returns the list of Tracks on this album."""
@ -1552,17 +1555,21 @@ class Artist(_BaseObject, _Taggable):
__hash__ = _BaseObject.__hash__
def __init__(self, name, network, username=None):
def __init__(self, name, network, username=None, info=None):
"""Create an artist object.
# Parameters:
* name str: The artist's name.
"""
if info is None:
info = {}
_BaseObject.__init__(self, network, 'artist')
_Taggable.__init__(self, 'artist')
self.name = name
self.username = username
self.info = info
def __repr__(self):
return "pylast.Artist(%s, %s)" % (
@ -1606,7 +1613,7 @@ class Artist(_BaseObject, _Taggable):
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_MEGA
SIZE_EXTRA_LARGE
@ -1615,8 +1622,11 @@ class Artist(_BaseObject, _Taggable):
SIZE_SMALL
"""
return _extract_all(
self._request(self.ws_prefix + ".getInfo", True), "image")[size]
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_playcount(self):
"""Returns the number of plays on the network."""
@ -1724,7 +1734,7 @@ class Artist(_BaseObject, _Taggable):
"getTopTracks", "track", Track, params, cacheable)
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:
* domain_name: The network's language domain. Possible values:
o DOMAIN_ENGLISH
@ -1800,7 +1810,7 @@ class Country(_BaseObject):
"getTopTracks", "track", Track, params, cacheable)
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:
o DOMAIN_ENGLISH
o DOMAIN_GERMAN
@ -1945,7 +1955,7 @@ class Tag(_BaseObject, _Chartable):
return _extract_top_artists(doc, self.network)
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:
o DOMAIN_ENGLISH
o DOMAIN_GERMAN
@ -1971,8 +1981,9 @@ class Track(_Opus):
__hash__ = _Opus.__hash__
def __init__(self, artist, title, network, username=None):
super(Track, self).__init__(artist, title, network, "track", username)
def __init__(self, artist, title, network, username=None, info=None):
super(Track, self).__init__(artist, title, network, "track", username,
info)
def get_correction(self):
"""Returns the corrected track name."""
@ -2454,7 +2465,7 @@ class User(_BaseObject, _Chartable):
return _extract_all(doc, "image")[size]
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:
o DOMAIN_ENGLISH
o DOMAIN_GERMAN
@ -2550,11 +2561,14 @@ class AlbumSearch(_Search):
seq = []
for node in master_node.getElementsByTagName("album"):
seq.append(Album(
seq.append(
Album(
_extract(node, "artist"),
_extract(node, "name"),
self.network,
images=_extract_all(node, 'image')))
info={"image": _extract_all(node, "image")},
),
)
return seq
@ -2572,7 +2586,11 @@ class ArtistSearch(_Search):
seq = []
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"))
seq.append(artist)
@ -2603,7 +2621,9 @@ class TrackSearch(_Search):
track = Track(
_extract(node, "artist"),
_extract(node, "name"),
self.network)
self.network,
info={"image": _extract_all(node, "image")},
)
track.listener_count = _number(_extract(node, "listeners"))
seq.append(track)
@ -2767,7 +2787,7 @@ def _extract_tracks(doc, network):
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()

View file

@ -6,10 +6,10 @@ import unittest
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):
# Arrange
@ -107,8 +107,8 @@ class TestPyLastAlbum(PyLastTestCase):
image = album.get_cover_image()
# Assert
self.assertTrue(image.startswith("https://"))
self.assertTrue(image.endswith(".png"))
self.assert_startswith(image, "https://")
self.assert_endswith(image, ".png")
if __name__ == '__main__':

View file

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

View file

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

View file

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

View file

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

View file

@ -7,10 +7,10 @@ import unittest
import pylast
from .test_pylast import PyLastTestCase
from .test_pylast import TestPyLastWithLastFm
class TestPyLastNetwork(PyLastTestCase):
class TestPyLastNetwork(TestPyLastWithLastFm):
def test_scrobble(self):
# Arrange
@ -322,17 +322,17 @@ class TestPyLastNetwork(PyLastTestCase):
# Act
results = search.get_next_page()
images = results[0].images
images = results[0].info["image"]
# Assert
self.assertEqual(len(images), 4)
self.assertTrue(images[pylast.SIZE_SMALL].startswith("https://"))
self.assertTrue(images[pylast.SIZE_SMALL].endswith(".png"))
self.assert_startswith(images[pylast.SIZE_SMALL], "https://")
self.assert_endswith(images[pylast.SIZE_SMALL], ".png")
self.assertIn("/34s/", images[pylast.SIZE_SMALL])
self.assertTrue(images[pylast.SIZE_EXTRA_LARGE].startswith("https://"))
self.assertTrue(images[pylast.SIZE_EXTRA_LARGE].endswith(".png"))
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_artist_search(self):
@ -347,6 +347,26 @@ class TestPyLastNetwork(PyLastTestCase):
self.assertIsInstance(results, list)
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):
# Arrange
artist = "Nirvana"
@ -360,6 +380,27 @@ class TestPyLastNetwork(PyLastTestCase):
self.assertIsInstance(results, list)
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):
# Arrange
artist = "Nirvana"

View file

@ -30,9 +30,18 @@ def load_secrets():
return doc
@flaky(max_runs=5, min_passes=1)
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
def unix_timestamp(self):

View file

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

View file

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

View file

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