Merge pull request #264 from pylast/save-artist-images
Save artist images and refactor album images
This commit is contained in:
commit
fad76315bb
|
@ -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
|
||||
```
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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__':
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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__':
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue