From 93bb581842982690f0fd1934ccc58b4337ca9361 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mice=20P=C3=A1pai?= Date: Fri, 5 May 2017 10:46:31 +0200 Subject: [PATCH 01/66] Fix typos and style --- pylast/__init__.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/pylast/__init__.py b/pylast/__init__.py index 79fdcfb..85fe70e 100644 --- a/pylast/__init__.py +++ b/pylast/__init__.py @@ -708,7 +708,7 @@ class _Network(object): return Track(_extract(doc, "name", 1), _extract(doc, "name"), self) def get_artist_by_mbid(self, mbid): - """Loooks up an artist by its MusicBrainz ID""" + """Looks up an artist by its MusicBrainz ID""" params = {"mbid": mbid} @@ -1373,9 +1373,9 @@ Shout = collections.namedtuple( "Shout", ["body", "author", "date"]) -def _string_output(funct): +def _string_output(func): def r(*args): - return _string(funct(*args)) + return _string(func(*args)) return r @@ -1478,20 +1478,20 @@ class _BaseObject(object): """ # Last.fm currently accepts a max of 10 recipient at a time - while(len(users) > 10): + while len(users) > 10: section = users[0:9] users = users[9:] self.share(section, message) - nusers = [] + user_names = [] for user in users: if isinstance(user, User): - nusers.append(user.get_name()) + user_names.append(user.get_name()) else: - nusers.append(user) + user_names.append(user) params = self._get_params() - recipients = ','.join(nusers) + recipients = ','.join(user_names) params['recipient'] = recipients if message: params['message'] = message @@ -3004,7 +3004,7 @@ class Tag(_BaseObject, _Chartable): return seq def get_top_albums(self, limit=None, cacheable=True): - """Retuns a list of the top albums.""" + """Returns a list of the top albums.""" params = self._get_params() if limit: params['limit'] = limit @@ -3098,7 +3098,7 @@ class Track(_Opus): return _extract(doc, "streamable") == "1" def is_fulltrack_available(self): - """Returns True if the fulltrack is available for streaming.""" + """Returns True if the full track is available for streaming.""" doc = self._request(self.ws_prefix + ".getInfo", True) return doc.getElementsByTagName( @@ -4326,14 +4326,14 @@ def _unescape_htmlentity(string): return string -def extract_items(topitems_or_libraryitems): +def extract_items(top_items_or_library_items): """ Extracts a sequence of items from a sequence of TopItem or LibraryItem objects. """ seq = [] - for i in topitems_or_libraryitems: + for i in top_items_or_library_items: seq.append(i.item) return seq From 47531969b6d6f3e737492842f6968c8168d908e3 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 17 Sep 2017 01:23:37 +0300 Subject: [PATCH 02/66] Drop support for Python 3.3 CPython 3.3 is no longer supported after September 29, 2017. https://en.wikipedia.org/wiki/CPython#Version_history https://www.python.org/dev/peps/pep-0398/ --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9bd5c36..fbd8e89 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,8 +23,6 @@ matrix: env: TOXENV=py35 - python: 3.4 env: TOXENV=py34 - - python: 3.3 - env: TOXENV=py33 - python: pypy3 env: TOXENV=pypy3 - python: pypy From 3a403c943f4453361f76af7ba3d6a9a28b0b3ea7 Mon Sep 17 00:00:00 2001 From: hugovk Date: Sun, 17 Sep 2017 10:49:46 +0300 Subject: [PATCH 03/66] Drop support for Python 3.3 and <=2.7.9 by removing HTTP --- README.md | 5 +- pylast/__init__.py | 122 +++++++-------------------------------------- setup.py | 9 +--- 3 files changed, 22 insertions(+), 114 deletions(-) diff --git a/README.md b/README.md index a988b94..a941f0a 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ pyLast ====== -[![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/) +[![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/) [![Coverage (Codecov)](https://codecov.io/gh/pylast/pylast/branch/develop/graph/badge.svg)](https://codecov.io/gh/pylast/pylast) [![Coverage (Coveralls)](https://coveralls.io/repos/github/pylast/pylast/badge.svg?branch=develop)](https://coveralls.io/github/pylast/pylast?branch=develop) @@ -20,6 +20,7 @@ Install via pip: pip install pylast +pyLast >= 2.0.0 supports Python 2.7.9+ and 3.4+. Features -------- diff --git a/pylast/__init__.py b/pylast/__init__.py index 79fdcfb..1b1337a 100644 --- a/pylast/__init__.py +++ b/pylast/__init__.py @@ -10,7 +10,7 @@ # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, @@ -22,15 +22,16 @@ import hashlib from xml.dom import minidom, Node -import xml.dom -import time -import shelve -import tempfile -import sys import collections -import warnings import re +import shelve import six +import ssl +import sys +import tempfile +import time +import warnings +import xml.dom __version__ = '1.9.0' __author__ = 'Amr Hassan, hugovk' @@ -43,35 +44,17 @@ def _deprecation_warning(message): warnings.warn(message, DeprecationWarning) -def _can_use_ssl_securely(): - # Python 3.3 doesn't support create_default_context() but can be made to - # work sanely. - # <2.7.9 and <3.2 never did any SSL verification so don't do SSL there. - # >3.4 and >2.7.9 has sane defaults so use SSL there. - v = sys.version_info - return v > (3, 3) or ((2, 7, 9) < v < (3, 0)) - - -if _can_use_ssl_securely(): - import ssl - if sys.version_info[0] == 3: - if _can_use_ssl_securely(): - from http.client import HTTPSConnection - else: - from http.client import HTTPConnection import html.entities as htmlentitydefs + from http.client import HTTPSConnection from urllib.parse import splithost as url_split_host from urllib.parse import quote_plus as url_quote_plus unichr = chr elif sys.version_info[0] == 2: - if _can_use_ssl_securely(): - from httplib import HTTPSConnection - else: - from httplib import HTTPConnection import htmlentitydefs + from httplib import HTTPSConnection from urllib import splithost as url_split_host from urllib import quote_plus as url_quote_plus @@ -150,58 +133,8 @@ RE_XML_ILLEGAL = (u'([\u0000-\u0008\u000b-\u000c\u000e-\u001f\ufffe-\uffff])' + XML_ILLEGAL = re.compile(RE_XML_ILLEGAL) -# Python <=3.3 doesn't support create_default_context() -# <2.7.9 and <3.2 never did any SSL verification -# FIXME This can be removed after 2017-09 when 3.3 is no longer supported and -# pypy3 uses 3.4 or later, see -# https://en.wikipedia.org/wiki/CPython#Version_history -if sys.version_info[0] == 3 and sys.version_info[1] == 3: - import certifi - SSL_CONTEXT = ssl.SSLContext(ssl.PROTOCOL_TLSv1) - SSL_CONTEXT.verify_mode = ssl.CERT_REQUIRED - SSL_CONTEXT.options |= ssl.OP_NO_COMPRESSION - # Intermediate from https://wiki.mozilla.org/Security/Server_Side_TLS - # Create the cipher string - cipher_string = """ - ECDHE-ECDSA-CHACHA20-POLY1305 - ECDHE-RSA-CHACHA20-POLY1305 - ECDHE-ECDSA-AES128-GCM-SHA256 - ECDHE-RSA-AES128-GCM-SHA256 - ECDHE-ECDSA-AES256-GCM-SHA384 - ECDHE-RSA-AES256-GCM-SHA384 - DHE-RSA-AES128-GCM-SHA256 - DHE-RSA-AES256-GCM-SHA384 - ECDHE-ECDSA-AES128-SHA256 - ECDHE-RSA-AES128-SHA256 - ECDHE-ECDSA-AES128-SHA - ECDHE-RSA-AES256-SHA384 - ECDHE-RSA-AES128-SHA - ECDHE-ECDSA-AES256-SHA384 - ECDHE-ECDSA-AES256-SHA - ECDHE-RSA-AES256-SHA - DHE-RSA-AES128-SHA256 - DHE-RSA-AES128-SHA - DHE-RSA-AES256-SHA256 - DHE-RSA-AES256-SHA - ECDHE-ECDSA-DES-CBC3-SHA - ECDHE-RSA-DES-CBC3-SHA - EDH-RSA-DES-CBC3-SHA - AES128-GCM-SHA256 - AES256-GCM-SHA384 - AES128-SHA256 - AES256-SHA256 - AES128-SHA - AES256-SHA - DES-CBC3-SHA - !DSS - """ - cipher_string = ' '.join(cipher_string.split()) - SSL_CONTEXT.set_ciphers(cipher_string) - SSL_CONTEXT.load_verify_locations(certifi.where()) - # Python >3.4 and >2.7.9 has sane defaults -elif sys.version_info > (3, 4) or ((2, 7, 9) < sys.version_info < (3, 0)): - SSL_CONTEXT = ssl.create_default_context() +SSL_CONTEXT = ssl.create_default_context() class _Network(object): @@ -1180,15 +1113,10 @@ class _Request(object): (HOST_NAME, HOST_SUBDIR) = self.network.ws_server if self.network.is_proxy_enabled(): - if _can_use_ssl_securely(): - conn = HTTPSConnection( - context=SSL_CONTEXT, - host=self.network._get_proxy()[0], - port=self.network._get_proxy()[1]) - else: - conn = HTTPConnection( - host=self.network._get_proxy()[0], - port=self.network._get_proxy()[1]) + conn = HTTPSConnection( + context=SSL_CONTEXT, + host=self.network._get_proxy()[0], + port=self.network._get_proxy()[1]) try: conn.request( @@ -1198,15 +1126,7 @@ class _Request(object): raise NetworkError(self.network, e) else: - if _can_use_ssl_securely(): - conn = HTTPSConnection( - context=SSL_CONTEXT, - host=HOST_NAME - ) - else: - conn = HTTPConnection( - host=HOST_NAME - ) + conn = HTTPSConnection(context=SSL_CONTEXT, host=HOST_NAME) try: conn.request( @@ -4387,15 +4307,7 @@ class _ScrobblerRequest(object): def execute(self): """Returns a string response of this request.""" - if _can_use_ssl_securely(): - connection = HTTPSConnection( - context=SSL_CONTEXT, - host=self.hostname - ) - else: - connection = HTTPConnection( - host=self.hostname - ) + connection = HTTPSConnection(context=SSL_CONTEXT, host=self.hostname) data = [] for name in self.params.keys(): diff --git a/setup.py b/setup.py index 2bf413c..6d21e2a 100755 --- a/setup.py +++ b/setup.py @@ -7,12 +7,6 @@ setup( version="1.9.0", author="Amr Hassan ", install_requires=['six'], - # FIXME This can be removed after 2017-09 when 3.3 is no longer supported - # and pypy3 uses 3.4 or later, see - # https://en.wikipedia.org/wiki/CPython#Version_history - extras_require={ - ':python_version=="3.3"': ["certifi"], - }, tests_require=['mock', 'pytest', 'coverage', 'pep8', 'pyyaml', 'pyflakes'], description=("A Python interface to Last.fm and Libre.fm"), author_email="amr.hassan@gmail.com", @@ -26,10 +20,11 @@ setup( "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", ], keywords=["Last.fm", "music", "scrobble", "scrobbling"], packages=find_packages(exclude=('tests*',)), From 41e0dfe0d5ae0dd1a43420a3b464d7bce2583c13 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 18 Sep 2017 11:46:37 +0300 Subject: [PATCH 04/66] Update supported versions 2.0.0 will support 2.7.10+, not 2.7.9+. And fill in best guess for the older ones. [CI skip] --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a941f0a..6fa90c5 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,13 @@ Install via pip: pip install pylast -pyLast >= 2.0.0 supports Python 2.7.9+ and 3.4+. +Note: + +* pyLast >= 2.0.0 supports Python 2.7.10+ and 3.4, 3.5, 3.6. +* pyLast >= 1.7.0 < 2.0.0 supports Python 2.7, 3.3, 3.4, 3.5, 3.6. +* pyLast >= 1.0.0 < 1.7.0 supports Python 2.7, 3.3, 3.4. +* pyLast >= 0.5 < 1.0.0 supports Python 2, 3. +* pyLast < 0.5 supports Python 2. Features -------- From 4a1b50350fab85ca99f8c1780da82f677f6decfb Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 18 Sep 2017 11:58:02 +0300 Subject: [PATCH 05/66] Remove deprecated code --- pylast/__init__.py | 206 +-------------------------------------------- 1 file changed, 1 insertion(+), 205 deletions(-) diff --git a/pylast/__init__.py b/pylast/__init__.py index 1b1337a..f8d9a16 100644 --- a/pylast/__init__.py +++ b/pylast/__init__.py @@ -20,17 +20,16 @@ # # https://github.com/pylast/pylast -import hashlib from xml.dom import minidom, Node import collections import re +import hashlib import shelve import six import ssl import sys import tempfile import time -import warnings import xml.dom __version__ = '1.9.0' @@ -40,10 +39,6 @@ __license__ = "apache2" __email__ = 'amr.hassan@gmail.com' -def _deprecation_warning(message): - warnings.warn(message, DeprecationWarning) - - if sys.version_info[0] == 3: import html.entities as htmlentitydefs from http.client import HTTPSConnection @@ -272,40 +267,6 @@ class _Network(object): return Tag(name, self) - def get_scrobbler(self, client_id, client_version): - """ - Returns a Scrobbler object used for submitting tracks to the server - - Quote from https://www.last.fm/api/submissions: - ======== - Client identifiers are used to provide a centrally managed database - of the client versions, allowing clients to be banned if they are - found to be behaving undesirably. The client ID is associated with - a version number on the server, however these are only incremented - if a client is banned and do not have to reflect the version of the - actual client application. - - During development, clients which have not been allocated an - identifier should use the identifier tst, with a version number of - 1.0. Do not distribute code or client implementations which use - this test identifier. Do not use the identifiers used by other - clients. - ========= - - To obtain a new client identifier please contact: - * Last.fm: submissions@last.fm - * # TODO: list others - - ...and provide us with the name of your client and its homepage - address. - """ - - _deprecation_warning( - "Use _Network.scrobble(...), _Network.scrobble_many(...)," - " and Network.update_now_playing(...) instead") - - return Scrobbler(self, client_id, client_version) - def _get_language_domain(self, domain_language): """ Returns the mapped domain name of the network to a DOMAIN_* value @@ -877,37 +838,6 @@ class LastFMNetwork(_Network): "'%s'" % self.password_hash))) -def get_lastfm_network( - api_key="", api_secret="", session_key="", username="", - password_hash="", token=""): - """ - Returns a preconfigured _Network object for Last.fm - - api_key: a provided API_KEY - api_secret: a provided API_SECRET - session_key: a generated session_key or None - username: a username of a valid user - password_hash: the output of pylast.md5(password) where password is the - user's password - token: an authentication token to retrieve a session - - if username and password_hash were provided and not session_key, - session_key will be generated automatically when needed. - - Either a valid session_key, a combination of username and password_hash, - or token must be present for scrobbling. - - Most read-only webservices only require an api_key and an api_secret, see - about obtaining them from: - https://www.last.fm/api/account - """ - - _deprecation_warning("Create a LastFMNetwork object instead") - - return LastFMNetwork( - api_key, api_secret, session_key, username, password_hash, token) - - class LibreFMNetwork(_Network): """ A preconfigured _Network object for Libre.fm @@ -974,30 +904,6 @@ class LibreFMNetwork(_Network): "'%s'" % self.password_hash))) -def get_librefm_network( - api_key="", api_secret="", session_key="", username="", - password_hash=""): - """ - Returns a preconfigured _Network object for Libre.fm - - api_key: a provided API_KEY - api_secret: a provided API_SECRET - session_key: a generated session_key or None - username: a username of a valid user - password_hash: the output of pylast.md5(password) where password is the - user's password - - if username and password_hash were provided and not session_key, - session_key will be generated automatically when needed. - """ - - _deprecation_warning( - "DeprecationWarning: Create a LibreFMNetwork object instead") - - return LibreFMNetwork( - api_key, api_secret, session_key, username, password_hash) - - class _ShelfCacheBackend(object): """Used as a backend for caching cacheable requests.""" def __init__(self, file_path=None): @@ -4413,114 +4319,4 @@ class Scrobbler(object): return self.session_id - def report_now_playing( - self, artist, title, album="", duration="", track_number="", - mbid=""): - - _deprecation_warning( - "DeprecationWarning: Use Network.update_now_playing(...) instead") - - params = { - "s": self._get_session_id(), "a": artist, "t": title, - "b": album, "l": duration, "n": track_number, "m": mbid} - - try: - _ScrobblerRequest( - self.nowplaying_url, params, self.network - ).execute() - except BadSessionError: - self._do_handshake() - self.report_now_playing( - artist, title, album, duration, track_number, mbid) - - def scrobble( - self, artist, title, time_started, source, mode, duration, - album="", track_number="", mbid=""): - """Scrobble a track. parameters: - artist: Artist name. - title: Track title. - time_started: UTC timestamp of when the track started playing. - source: The source of the track - SCROBBLE_SOURCE_USER: Chosen by the user - (the most common value, unless you have a reason for - choosing otherwise, use this). - SCROBBLE_SOURCE_NON_PERSONALIZED_BROADCAST: Non-personalised - broadcast (e.g. Shoutcast, BBC Radio 1). - SCROBBLE_SOURCE_PERSONALIZED_BROADCAST: Personalised - recommendation except Last.fm (e.g. Pandora, Launchcast). - SCROBBLE_SOURCE_LASTFM: ast.fm (any mode). In this case, the - 5-digit recommendation_key value must be set. - SCROBBLE_SOURCE_UNKNOWN: Source unknown. - mode: The submission mode - SCROBBLE_MODE_PLAYED: The track was played. - SCROBBLE_MODE_LOVED: The user manually loved the track - (implies a listen) - SCROBBLE_MODE_SKIPPED: The track was skipped - (Only if source was Last.fm) - SCROBBLE_MODE_BANNED: The track was banned - (Only if source was Last.fm) - duration: Track duration in seconds. - album: The album name. - track_number: The track number on the album. - mbid: MusicBrainz ID. - """ - - _deprecation_warning( - "DeprecationWarning: Use Network.scrobble(...) instead") - - params = { - "s": self._get_session_id(), - "a[0]": _string(artist), - "t[0]": _string(title), - "i[0]": str(time_started), - "o[0]": source, - "r[0]": mode, - "l[0]": str(duration), - "b[0]": _string(album), - "n[0]": track_number, - "m[0]": mbid - } - - _ScrobblerRequest(self.submissions_url, params, self.network).execute() - - def scrobble_many(self, tracks): - """ - Scrobble several tracks at once. - - tracks: A sequence of a sequence of parameters for each track. - The order of parameters is the same as if passed to the - scrobble() method. - """ - - _deprecation_warning( - "DeprecationWarning: Use Network.scrobble_many(...) instead") - - remainder = [] - - if len(tracks) > 50: - remainder = tracks[50:] - tracks = tracks[:50] - - params = {"s": self._get_session_id()} - - i = 0 - for t in tracks: - _pad_list(t, 9, "") - params["a[%s]" % str(i)] = _string(t[0]) - params["t[%s]" % str(i)] = _string(t[1]) - params["i[%s]" % str(i)] = str(t[2]) - params["o[%s]" % str(i)] = t[3] - params["r[%s]" % str(i)] = t[4] - params["l[%s]" % str(i)] = str(t[5]) - params["b[%s]" % str(i)] = _string(t[6]) - params["n[%s]" % str(i)] = t[7] - params["m[%s]" % str(i)] = t[8] - - i += 1 - - _ScrobblerRequest(self.submissions_url, params, self.network).execute() - - if remainder: - self.scrobble_many(remainder) - # End of file From 5a55005240c83962b7ff38cfb70cf53bbe0d3f50 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 18 Sep 2017 13:52:19 +0300 Subject: [PATCH 06/66] http -> https [CI skip] --- LICENSE.txt | 4 ++-- README.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/LICENSE.txt b/LICENSE.txt index 8dada3e..9b259bd 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ Apache License Version 2.0, January 2004 - http://www.apache.org/licenses/ + https://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION @@ -192,7 +192,7 @@ you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + https://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, diff --git a/README.md b/README.md index a941f0a..d477aa8 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ pyLast [![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/). +A Python interface to [Last.fm](https://www.last.fm/) and other API-compatible websites such as [Libre.fm](https://libre.fm/). Try using the pydoc utility for help on usage or see [test_pylast.py](tests/test_pylast.py) for examples. @@ -44,7 +44,7 @@ Here's some simple code example to get you started. In order to create any objec import pylast # You have to have your own unique two values for API_KEY and API_SECRET -# Obtain yours from http://www.last.fm/api/account/create for Last.fm +# Obtain yours from https://www.last.fm/api/account/create for Last.fm API_KEY = "b25b959554ed76058ac220b7b2e0a026" # this is a sample key API_SECRET = "425b55975eed76058ac220b7b4e8a054" From bdd1b8d5e5564fa15246d61770d4304e0c4c4c97 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 18 Sep 2017 14:13:00 +0300 Subject: [PATCH 07/66] Add failing test --- tests/test_pylast.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_pylast.py b/tests/test_pylast.py index 45cbd8f..2ece3c8 100755 --- a/tests/test_pylast.py +++ b/tests/test_pylast.py @@ -1887,11 +1887,13 @@ class TestPyLast(unittest.TestCase): # Act tracks = album.get_tracks() + url = tracks[0].get_url() # Assert self.assertIsInstance(tracks, list) self.assertIsInstance(tracks[0], pylast.Track) self.assertEqual(len(tracks), 4) + self.assertTrue(url.startswith("https://www.last.fm/music/test")) @handle_lastfm_exceptions def test_tags(self): From 96d921cdc2b8303f06bdba4e7b4ac033ed18b6e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mice=20P=C3=A1pai?= Date: Wed, 26 Apr 2017 01:10:16 +0200 Subject: [PATCH 08/66] Fix Album.get_tracks() error (wrong positional argument) --- pylast/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pylast/__init__.py b/pylast/__init__.py index daa1efe..32a5540 100644 --- a/pylast/__init__.py +++ b/pylast/__init__.py @@ -1784,7 +1784,7 @@ class Album(_Opus): return _extract_tracks( self._request( - self.ws_prefix + ".getInfo", cacheable=True), "tracks") + self.ws_prefix + ".getInfo", cacheable=True), self.network) def get_url(self, domain_name=DOMAIN_ENGLISH): """Returns the URL of the album or track page on the network. From 508dc47bdb54d804d67f6f2f2c84245ccb4eb4ed Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 18 Sep 2017 14:26:15 +0300 Subject: [PATCH 09/66] Cache pip files --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index fbd8e89..fe967c7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,7 @@ language: python +cache: pip + env: global: - secure: ivg6II471E9HV8xyqnawLIuP/sZ0J63Y+BC0BQcRVKtLn/K3zmD1ozM3TFL9S549Nxd0FqDKHXJvXsgaTGIDpK8sxE2AMKV5IojyM0iAVuN7YjPK9vwSlRw1u0EysPMFqxOZVQnoDyHrSGIUrP/VMdnhBu6dbUX0FyEkvZshXhY= From 8e99f2b04e3d5e647858cf25534fe935f175a812 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 18 Sep 2017 14:47:21 +0300 Subject: [PATCH 10/66] Use different artists for test_set_tags and test_remove_tags to avoid parallel test collisions --- tests/test_pylast.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_pylast.py b/tests/test_pylast.py index 45cbd8f..cc582a3 100755 --- a/tests/test_pylast.py +++ b/tests/test_pylast.py @@ -1803,7 +1803,7 @@ class TestPyLast(unittest.TestCase): def test_set_tags(self): # Arrange tags = ["sometag1", "sometag2"] - artist = self.network.get_artist("Test Artist") + artist = self.network.get_artist("Test Artist 2") artist.add_tags(tags) tags_before = artist.get_tags() new_tags = ["settag1", "settag2"] From 705eafbd3e3e22d670472483de5e24742f6c8711 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 18 Sep 2017 14:55:52 +0300 Subject: [PATCH 11/66] Print all warnings (-W all), and show prints (-s# disable all capturing) --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 42f8433..ff9f1ed 100644 --- a/tox.ini +++ b/tox.ini @@ -15,7 +15,7 @@ deps = ipdb pytest-cov flaky -commands = py.test -v --cov pylast --cov-report term-missing {posargs} +commands = py.test -v -s -W all --cov pylast --cov-report term-missing {posargs} [testenv:venv] deps = ipdb From 618833e297c2aa3c577d2b7050028f7b6a045483 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mice=20P=C3=A1pai?= Date: Fri, 5 May 2017 10:39:04 +0200 Subject: [PATCH 12/66] Fix unclosed SSLSocket warning, Update credits --- pylast/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pylast/__init__.py b/pylast/__init__.py index daa1efe..fdd0cd9 100644 --- a/pylast/__init__.py +++ b/pylast/__init__.py @@ -33,8 +33,9 @@ import time import xml.dom __version__ = '1.9.0' -__author__ = 'Amr Hassan, hugovk' -__copyright__ = "Copyright (C) 2008-2010 Amr Hassan, 2013-2017 hugovk" +__author__ = 'Amr Hassan, hugovk, Mice Pápai' +__copyright__ = ('Copyright (C) 2008-2010 Amr Hassan, 2013-2017 hugovk, ' + '2017 Mice Pápai') __license__ = "apache2" __email__ = 'amr.hassan@gmail.com' @@ -1048,6 +1049,7 @@ class _Request(object): response_text = XML_ILLEGAL.sub("?", response_text) self._check_response_for_errors(response_text) + conn.close() return response_text def execute(self, cacheable=False): From a565b7f15925dd1dfd5d518d8325dedbeea05d58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mice=20P=C3=A1pai?= Date: Wed, 26 Apr 2017 01:13:43 +0200 Subject: [PATCH 13/66] Fix: _collect_nodes() break if there are no child nodes --- pylast/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pylast/__init__.py b/pylast/__init__.py index d6ee580..2259649 100644 --- a/pylast/__init__.py +++ b/pylast/__init__.py @@ -3984,6 +3984,9 @@ def _collect_nodes(limit, sender, method_name, cacheable, params=None): doc = sender._request(method_name, cacheable, params) doc = cleanup_nodes(doc) + # break if there are no child nodes + if not doc.documentElement.childNodes: + break main = doc.documentElement.childNodes[0] if main.hasAttribute("totalPages"): From 4d92dcfc83a10b0fb112abfe342ebba7d13aab93 Mon Sep 17 00:00:00 2001 From: Hugo Date: Tue, 19 Sep 2017 10:26:10 +0300 Subject: [PATCH 14/66] Remove deprecated class. Its main functions were removed in #211. --- pylast/__init__.py | 56 ---------------------------------------------- 1 file changed, 56 deletions(-) diff --git a/pylast/__init__.py b/pylast/__init__.py index d6ee580..fd5c94e 100644 --- a/pylast/__init__.py +++ b/pylast/__init__.py @@ -4265,60 +4265,4 @@ class _ScrobblerRequest(object): raise ScrobblingError(reason) -class Scrobbler(object): - """A class for scrobbling tracks to Last.fm""" - - session_id = None - nowplaying_url = None - submissions_url = None - - def __init__(self, network, client_id, client_version): - self.client_id = client_id - self.client_version = client_version - self.username = network.username - self.password = network.password_hash - self.network = network - - def _do_handshake(self): - """Handshakes with the server""" - - timestamp = str(int(time.time())) - - if self.password and self.username: - token = md5(self.password + timestamp) - elif self.network.api_key and self.network.api_secret and \ - self.network.session_key: - if not self.username: - self.username = self.network.get_authenticated_user()\ - .get_name() - token = md5(self.network.api_secret + timestamp) - - params = { - "hs": "true", "p": "1.2.1", "c": self.client_id, - "v": self.client_version, "u": self.username, "t": timestamp, - "a": token} - - if self.network.session_key and self.network.api_key: - params["sk"] = self.network.session_key - params["api_key"] = self.network.api_key - - server = self.network.submission_server - response = _ScrobblerRequest( - server, params, self.network, "GET").execute().split("\n") - - self.session_id = response[1] - self.nowplaying_url = response[2] - self.submissions_url = response[3] - - def _get_session_id(self, new=False): - """ - Returns a handshake. If new is true, then it will be requested from - the server even if one was cached. - """ - - if not self.session_id or new: - self._do_handshake() - - return self.session_id - # End of file From 338abc2883df75056d7ad3ca9a87196ecefb47f5 Mon Sep 17 00:00:00 2001 From: Hugo Date: Tue, 19 Sep 2017 10:40:19 +0300 Subject: [PATCH 15/66] Lint Python 3 --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index fe967c7..fa02da5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,6 +19,8 @@ matrix: env: TOXENV=lint - python: 2.7 env: TOXENV=py27 + - python: 3.6 + env: TOXENV=lint - python: 3.6 env: TOXENV=py36 - python: 3.5 From 68d9a4b78321c00f1bf7d0d6dc27a1ffb0d2dbe1 Mon Sep 17 00:00:00 2001 From: Hugo Date: Tue, 19 Sep 2017 10:55:25 +0300 Subject: [PATCH 16/66] Clonedigger only supports Python 2 --- .travis.yml | 4 ++-- tox.ini | 14 +++++++++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index fa02da5..63f0b5a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,11 +16,11 @@ env: matrix: include: - python: 2.7 - env: TOXENV=lint + env: TOXENV=py2lint - python: 2.7 env: TOXENV=py27 - python: 3.6 - env: TOXENV=lint + env: TOXENV=py3lint - python: 3.6 env: TOXENV=py36 - python: 3.5 diff --git a/tox.ini b/tox.ini index ff9f1ed..76d93c5 100644 --- a/tox.ini +++ b/tox.ini @@ -21,7 +21,7 @@ commands = py.test -v -s -W all --cov pylast --cov-report term-missing {posargs} deps = ipdb commands = {posargs} -[testenv:lint] +[testenv:py2lint] deps = coverage pep8 @@ -34,3 +34,15 @@ commands = pep8 pylast pep8 tests ./clonedigger.sh + +[testenv:py3lint] +deps = + coverage + pep8 + pyyaml + pyflakes +commands = + pyflakes pylast + pyflakes tests + pep8 pylast + pep8 tests From 0ce597b5b15188d0def2eede8d2d58be85ee7e47 Mon Sep 17 00:00:00 2001 From: Hugo Date: Tue, 19 Sep 2017 13:23:58 +0300 Subject: [PATCH 17/66] Remove unnecessary dependencies for lint --- tox.ini | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tox.ini b/tox.ini index 76d93c5..33c73be 100644 --- a/tox.ini +++ b/tox.ini @@ -23,9 +23,7 @@ commands = {posargs} [testenv:py2lint] deps = - coverage pep8 - pyyaml pyflakes clonedigger commands = @@ -37,9 +35,7 @@ commands = [testenv:py3lint] deps = - coverage pep8 - pyyaml pyflakes commands = pyflakes pylast From 32495cb15eba00c0a474a73f3d9157c98fd96209 Mon Sep 17 00:00:00 2001 From: Hugo Date: Tue, 19 Sep 2017 13:35:32 +0300 Subject: [PATCH 18/66] Use latest Tox (revert #158) --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 63f0b5a..e10837f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,7 +39,7 @@ matrix: sudo: false install: -- travis_retry pip install tox==2.1.1 +- travis_retry pip install tox - travis_retry pip install coverage script: tox From 13e965d3fd65cf1eeb129217b9c9995e724d5158 Mon Sep 17 00:00:00 2001 From: Hugo Date: Tue, 19 Sep 2017 16:47:10 +0300 Subject: [PATCH 19/66] Remove code rendered redundant after removing deprecated code --- pylast/__init__.py | 100 --------------------------------------------- 1 file changed, 100 deletions(-) diff --git a/pylast/__init__.py b/pylast/__init__.py index fd5c94e..edb14d6 100644 --- a/pylast/__init__.py +++ b/pylast/__init__.py @@ -43,7 +43,6 @@ __email__ = 'amr.hassan@gmail.com' if sys.version_info[0] == 3: import html.entities as htmlentitydefs from http.client import HTTPSConnection - from urllib.parse import splithost as url_split_host from urllib.parse import quote_plus as url_quote_plus unichr = chr @@ -51,7 +50,6 @@ if sys.version_info[0] == 3: elif sys.version_info[0] == 2: import htmlentitydefs from httplib import HTTPSConnection - from urllib import splithost as url_split_host from urllib import quote_plus as url_quote_plus STATUS_INVALID_SERVICE = 2 @@ -4167,102 +4165,4 @@ def extract_items(top_items_or_library_items): return seq -class ScrobblingError(Exception): - def __init__(self, message): - Exception.__init__(self) - self.message = message - - @_string_output - def __str__(self): - return self.message - - -class BannedClientError(ScrobblingError): - def __init__(self): - ScrobblingError.__init__( - self, "This version of the client has been banned") - - -class BadAuthenticationError(ScrobblingError): - def __init__(self): - ScrobblingError.__init__(self, "Bad authentication token") - - -class BadTimeError(ScrobblingError): - def __init__(self): - ScrobblingError.__init__( - self, "Time provided is not close enough to current time") - - -class BadSessionError(ScrobblingError): - def __init__(self): - ScrobblingError.__init__( - self, "Bad session id, consider re-handshaking") - - -class _ScrobblerRequest(object): - - def __init__(self, url, params, network, request_type="POST"): - - for key in params: - params[key] = str(params[key]) - - self.params = params - self.type = request_type - (self.hostname, self.subdir) = url_split_host(url[len("http:"):]) - self.network = network - - def execute(self): - """Returns a string response of this request.""" - - connection = HTTPSConnection(context=SSL_CONTEXT, host=self.hostname) - - data = [] - for name in self.params.keys(): - value = url_quote_plus(self.params[name]) - data.append('='.join((name, value))) - data = "&".join(data) - - headers = { - "Content-type": "application/x-www-form-urlencoded", - "Accept-Charset": "utf-8", - "User-Agent": "pylast" + "/" + __version__, - "HOST": self.hostname - } - - if self.type == "GET": - connection.request( - "GET", self.subdir + "?" + data, headers=headers) - else: - connection.request("POST", self.subdir, data, headers) - response = _unicode(connection.getresponse().read()) - - self._check_response_for_errors(response) - - return response - - def _check_response_for_errors(self, response): - """ - When passed a string response it checks for errors, raising any - exceptions as necessary. - """ - - lines = response.split("\n") - status_line = lines[0] - - if status_line == "OK": - return - elif status_line == "BANNED": - raise BannedClientError() - elif status_line == "BADAUTH": - raise BadAuthenticationError() - elif status_line == "BADTIME": - raise BadTimeError() - elif status_line == "BADSESSION": - raise BadSessionError() - elif status_line.startswith("FAILED "): - reason = status_line[status_line.find("FAILED ") + len("FAILED "):] - raise ScrobblingError(reason) - - # End of file From 5967590feb82fa19327b75d04a6790e0bd5bb446 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 25 Sep 2017 10:40:04 +0300 Subject: [PATCH 20/66] Remove parameter made redundant by removal of deprecated Scrobbler class --- pylast/__init__.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/pylast/__init__.py b/pylast/__init__.py index edb14d6..f74f3bd 100644 --- a/pylast/__init__.py +++ b/pylast/__init__.py @@ -139,8 +139,7 @@ class _Network(object): def __init__( self, name, homepage, ws_server, api_key, api_secret, session_key, - submission_server, username, password_hash, domain_names, urls, - token=None): + username, password_hash, domain_names, urls, token=None): """ name: the name of the network homepage: the homepage URL @@ -148,8 +147,6 @@ class _Network(object): api_key: a provided API_KEY api_secret: a provided API_SECRET session_key: a generated session_key or None - submission_server: the URL of the server to which tracks are - submitted (scrobbled) username: a username of a valid user password_hash: the output of pylast.md5(password) where password is the user's password @@ -175,7 +172,6 @@ class _Network(object): self.api_key = api_key self.api_secret = api_secret self.session_key = session_key - self.submission_server = submission_server self.username = username self.password_hash = password_hash self.domain_names = domain_names @@ -797,7 +793,6 @@ class LastFMNetwork(_Network): api_key=api_key, api_secret=api_secret, session_key=session_key, - submission_server="http://post.audioscrobbler.com:80/", username=username, password_hash=password_hash, token=token, @@ -864,7 +859,6 @@ class LibreFMNetwork(_Network): api_key=api_key, api_secret=api_secret, session_key=session_key, - submission_server="http://turtle.libre.fm:80/", username=username, password_hash=password_hash, domain_names={ From ebd0bb90b416bf5a20bac7cde00422613db78334 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 25 Sep 2017 10:43:01 +0300 Subject: [PATCH 21/66] Remove unfinished function, it's out of scope of pylast --- pylast/__init__.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pylast/__init__.py b/pylast/__init__.py index f74f3bd..f517e95 100644 --- a/pylast/__init__.py +++ b/pylast/__init__.py @@ -2237,12 +2237,6 @@ class Country(_BaseObject): def _get_params(self): # TODO can move to _BaseObject return {'country': self.get_name()} - def _get_name_from_code(self, alpha2code): - # TODO: Have this function lookup the alpha-2 code and return the - # country name. - - return alpha2code - def get_name(self): """Returns the country name. """ From fb1263a8dd272ce1d91f1409adf25ad3bbf69d0c Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 25 Sep 2017 10:49:52 +0300 Subject: [PATCH 22/66] Remove unused attributes --- pylast/__init__.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/pylast/__init__.py b/pylast/__init__.py index f517e95..36aa77e 100644 --- a/pylast/__init__.py +++ b/pylast/__init__.py @@ -2462,10 +2462,6 @@ class Library(_BaseObject): else: self.user = User(user, self.network) - self._albums_index = 0 - self._artists_index = 0 - self._tracks_index = 0 - def __repr__(self): return "pylast.Library(%s, %s)" % (repr(self.user), repr(self.network)) @@ -3120,10 +3116,6 @@ class User(_BaseObject, _Chartable): self.name = user_name - self._past_events_index = 0 - self._recommended_events_index = 0 - self._recommended_artists_index = 0 - def __repr__(self): return "pylast.User(%s, %s)" % (repr(self.name), repr(self.network)) From 3be6a0504e3f00694584e724d015120661921b85 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 25 Sep 2017 12:24:44 +0300 Subject: [PATCH 23/66] Remove ununsed function _pad_list --- pylast/__init__.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/pylast/__init__.py b/pylast/__init__.py index 36aa77e..d76716e 100644 --- a/pylast/__init__.py +++ b/pylast/__init__.py @@ -1200,17 +1200,6 @@ def _string_output(func): return r -def _pad_list(given_list, desired_length, padding=None): - """ - Pads a list to be of the desired_length. - """ - - while len(given_list) < desired_length: - given_list.append(padding) - - return given_list - - class _BaseObject(object): """An abstract webservices object.""" From 8c6c6aaab897389ccb7f644dee5d86aac39ff5c2 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 25 Sep 2017 13:02:12 +0300 Subject: [PATCH 24/66] Remove broken and untested extract_items --- pylast/__init__.py | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/pylast/__init__.py b/pylast/__init__.py index d76716e..692bec5 100644 --- a/pylast/__init__.py +++ b/pylast/__init__.py @@ -3195,9 +3195,6 @@ class User(_BaseObject, _Chartable): This method uses caching. Enable caching only if you're pulling a large amount of data. - - Use extract_items() with the return of this function to - get only a sequence of Track objects with no playback dates. """ params = self._get_params() @@ -3312,9 +3309,6 @@ class User(_BaseObject, _Chartable): This method uses caching. Enable caching only if you're pulling a large amount of data. - - Use extract_items() with the return of this function to - get only a sequence of Track objects with no playback dates. """ params = self._get_params() @@ -4121,17 +4115,4 @@ def _unescape_htmlentity(string): return string -def extract_items(top_items_or_library_items): - """ - Extracts a sequence of items from a sequence of TopItem or - LibraryItem objects. - """ - - seq = [] - for i in top_items_or_library_items: - seq.append(i.item) - - return seq - - # End of file From fe6673ba29f643e96183cd52644be494edddc22c Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 25 Sep 2017 13:14:17 +0300 Subject: [PATCH 25/66] Add more tests --- tests/test_pylast.py | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/test_pylast.py b/tests/test_pylast.py index d6fe823..ab8ccb4 100755 --- a/tests/test_pylast.py +++ b/tests/test_pylast.py @@ -1564,12 +1564,49 @@ class TestPyLast(unittest.TestCase): 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) + @handle_lastfm_exceptions + def test_user_subscriber(self): + # Arrange + subscriber = self.network.get_user("RJ") + non_subscriber = self.network.get_user("Test User") + + # Act + subscriber_is_subscriber = subscriber.is_subscriber() + non_subscriber_is_subscriber = non_subscriber.is_subscriber() + + # Assert + self.assertTrue(subscriber_is_subscriber) + self.assertFalse(non_subscriber_is_subscriber) + + @handle_lastfm_exceptions + def test_user_get_image(self): + # Arrange + user = self.network.get_user("RJ") + + # Act + url = user.get_image() + + # Assert + self.assertTrue(url.startswith("https://")) + + @handle_lastfm_exceptions + def test_user_get_library(self): + # Arrange + user = self.network.get_user(self.username) + + # Act + library = user.get_library() + + # Assert + self.assertIsInstance(library, pylast.Library) + @handle_lastfm_exceptions def test_caching(self): # Arrange From e19061a437a472e29bc7c82aba3093c7167f0cff Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 25 Sep 2017 14:29:44 +0300 Subject: [PATCH 26/66] Re-enable test for re-added Last.fm API --- tests/test_pylast.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/test_pylast.py b/tests/test_pylast.py index d6fe823..f636061 100755 --- a/tests/test_pylast.py +++ b/tests/test_pylast.py @@ -911,8 +911,7 @@ class TestPyLast(unittest.TestCase): lastfm_user = self.network.get_authenticated_user() # Act/Assert - # Skip the first one because Last.fm API is broken - # self.helper_validate_cacheable(lastfm_user, "get_friends") + self.helper_validate_cacheable(lastfm_user, "get_friends") self.helper_validate_cacheable(lastfm_user, "get_loved_tracks") self.helper_validate_cacheable(lastfm_user, "get_neighbours") self.helper_validate_cacheable(lastfm_user, "get_past_events") From 0d4f674ac7071230b1f14cf5f45dc21cad720993 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 25 Sep 2017 17:28:02 +0300 Subject: [PATCH 27/66] Remove dead Last.fm methods: get_top_fans --- pylast/__init__.py | 24 ------------------------ tests/test_pylast.py | 27 +-------------------------- 2 files changed, 1 insertion(+), 50 deletions(-) diff --git a/pylast/__init__.py b/pylast/__init__.py index fd17e99..245e1a3 100644 --- a/pylast/__init__.py +++ b/pylast/__init__.py @@ -1252,30 +1252,6 @@ class _BaseObject(object): return seq - def get_top_fans(self, limit=None, cacheable=True): - """Returns a list of the Users who played this the most. - # Parameters: - * limit int: Max elements. - # For Artist/Track - """ - - doc = self._request(self.ws_prefix + '.getTopFans', cacheable) - - seq = [] - - elements = doc.getElementsByTagName('user') - - for element in elements: - if limit and len(seq) >= limit: - break - - name = _extract(element, 'name') - weight = _number(_extract(element, 'weight')) - - seq.append(TopItem(User(name, self.network), weight)) - - return seq - def share(self, users, message=None): """ Shares this (sends out recommendations). diff --git a/tests/test_pylast.py b/tests/test_pylast.py index 0433fb9..a2c27a6 100755 --- a/tests/test_pylast.py +++ b/tests/test_pylast.py @@ -487,9 +487,7 @@ class TestPyLast(unittest.TestCase): @handle_lastfm_exceptions def test_user_is_hashable(self): # Arrange - artist = self.network.get_artist("Test Artist") - user = artist.get_top_fans(limit=1)[0].item - self.assertIsInstance(user, pylast.User) + user = self.network.get_user(self.username) # Act/Assert self.helper_is_thing_hashable(user) @@ -1232,18 +1230,6 @@ class TestPyLast(unittest.TestCase): # Assert self.helper_two_different_things_in_top_list(things, pylast.Album) - @handle_lastfm_exceptions - def test_artist_top_fans(self): - # Arrange - # Pick an artist with plenty of plays - artist = self.network.get_top_artists(limit=1)[0].item - - # Act - things = artist.get_top_fans(limit=2) - - # Assert - self.helper_two_different_things_in_top_list(things, pylast.User) - @handle_lastfm_exceptions def test_country_top_tracks(self): # Arrange @@ -1339,17 +1325,6 @@ class TestPyLast(unittest.TestCase): # Act/Assert self.helper_get_assert_charts(lastfm_user, dates[0]) - @handle_lastfm_exceptions - def test_track_top_fans(self): - # Arrange - track = self.network.get_track("The Cinematic Orchestra", "Postlude") - - # Act - fans = track.get_top_fans() - - # Assert - self.helper_at_least_one_thing_in_top_list(fans, pylast.User) - # Commented out to avoid spamming # def test_share_spam(self): # # Arrange From c303fd0139e000f10f4cdec0d3795cd066b39fd6 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 25 Sep 2017 17:37:24 +0300 Subject: [PATCH 28/66] Remove dead Last.fm attributes: releasedate in album.getInfo --- pylast/__init__.py | 6 ------ tests/test_pylast.py | 12 ------------ 2 files changed, 18 deletions(-) diff --git a/pylast/__init__.py b/pylast/__init__.py index 245e1a3..da167c6 100644 --- a/pylast/__init__.py +++ b/pylast/__init__.py @@ -1718,12 +1718,6 @@ class Album(_Opus): def __init__(self, artist, title, network, username=None): super(Album, self).__init__(artist, title, network, "album", username) - def get_release_date(self): - """Returns the release date of the album.""" - - return _extract(self._request( - self.ws_prefix + ".getInfo", cacheable=True), "releasedate") - def get_cover_image(self, size=COVER_EXTRA_LARGE): """ Returns a uri to the cover image diff --git a/tests/test_pylast.py b/tests/test_pylast.py index a2c27a6..efeb960 100755 --- a/tests/test_pylast.py +++ b/tests/test_pylast.py @@ -1879,18 +1879,6 @@ class TestPyLast(unittest.TestCase): # Assert self.assertGreater(count, 21) - @handle_lastfm_exceptions - def test_album_rel_date(self): - # Arrange - album = pylast.Album("Test Artist", "Test Release", self.network) - - # Act - date = album.get_release_date() - - # Assert - self.skip_if_lastfm_api_broken(date) - self.assertIn("2011", date) - @handle_lastfm_exceptions def test_album_tracks(self): # Arrange From 3e097b98fc42912075c2e44f16c368e46eb1205c Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 25 Sep 2017 17:49:12 +0300 Subject: [PATCH 29/66] Remove @handle_lastfm_exceptions: dead Last.fm things will be removed --- tests/test_pylast.py | 168 ------------------------------------------- 1 file changed, 168 deletions(-) diff --git a/tests/test_pylast.py b/tests/test_pylast.py index efeb960..6a7799e 100755 --- a/tests/test_pylast.py +++ b/tests/test_pylast.py @@ -30,22 +30,6 @@ def load_secrets(): return doc -def handle_lastfm_exceptions(f): - """Skip exceptions caused by Last.fm's broken API""" - def wrapper(*args, **kw): - try: - return f(*args, **kw) - except pylast.WSError as e: - if (str(e) == "Invalid Method - " - "No method with that name in this package"): - msg = "Ignore broken Last.fm API: " + str(e) - print(msg) - pytest.skip(msg) - else: - raise(e) - return wrapper - - @flaky(max_runs=5, min_passes=1) class TestPyLast(unittest.TestCase): @@ -73,7 +57,6 @@ class TestPyLast(unittest.TestCase): if value is None or len(value) == 0: pytest.skip("Last.fm API is broken.") - @handle_lastfm_exceptions def test_scrobble(self): # Arrange artist = "Test Artist" @@ -91,7 +74,6 @@ class TestPyLast(unittest.TestCase): self.assertEqual(str(last_scrobble.track.title), str(title)) self.assertEqual(str(last_scrobble.timestamp), str(timestamp)) - @handle_lastfm_exceptions def test_unscrobble(self): # Arrange artist = "Test Artist 2" @@ -110,7 +92,6 @@ class TestPyLast(unittest.TestCase): last_scrobble = lastfm_user.get_recent_tracks(limit=2)[0] self.assertNotEqual(str(last_scrobble.timestamp), str(timestamp)) - @handle_lastfm_exceptions def test_add_album(self): # Arrange library = pylast.Library(user=self.username, network=self.network) @@ -127,7 +108,6 @@ class TestPyLast(unittest.TestCase): break self.assertTrue(value) - @handle_lastfm_exceptions def test_remove_album(self): # Arrange library = pylast.Library(user=self.username, network=self.network) @@ -149,7 +129,6 @@ class TestPyLast(unittest.TestCase): break self.assertFalse(value) - @handle_lastfm_exceptions def test_add_artist(self): # Arrange artist = "Test Artist 2" @@ -166,7 +145,6 @@ class TestPyLast(unittest.TestCase): break self.assertTrue(value) - @handle_lastfm_exceptions def test_remove_artist(self): # Arrange # Get plenty of artists @@ -187,7 +165,6 @@ class TestPyLast(unittest.TestCase): break self.assertFalse(value) - @handle_lastfm_exceptions def test_get_venue(self): # Arrange venue_name = "Last.fm Office" @@ -200,7 +177,6 @@ class TestPyLast(unittest.TestCase): # Assert self.assertEqual(str(venue.id), "8778225") - @handle_lastfm_exceptions def test_get_user_registration(self): # Arrange username = "RJ" @@ -217,7 +193,6 @@ class TestPyLast(unittest.TestCase): # Just check date because of timezones self.assertIn(u"2002-11-20 ", registered) - @handle_lastfm_exceptions def test_get_user_unixtime_registration(self): # Arrange username = "RJ" @@ -230,7 +205,6 @@ class TestPyLast(unittest.TestCase): # Just check date because of timezones self.assertEqual(unixtime_registered, u"1037793040") - @handle_lastfm_exceptions def test_get_genderless_user(self): # Arrange # Currently test_user has no gender set: @@ -242,7 +216,6 @@ class TestPyLast(unittest.TestCase): # Assert self.assertIsNone(gender) - @handle_lastfm_exceptions def test_get_countryless_user(self): # Arrange # Currently test_user has no country set: @@ -254,7 +227,6 @@ class TestPyLast(unittest.TestCase): # Assert self.assertIsNone(country) - @handle_lastfm_exceptions def test_love(self): # Arrange artist = "Test Artist" @@ -270,7 +242,6 @@ class TestPyLast(unittest.TestCase): self.assertEqual(str(loved[0].track.artist), "Test Artist") self.assertEqual(str(loved[0].track.title), "test title") - @handle_lastfm_exceptions def test_unlove(self): # Arrange artist = pylast.Artist("Test Artist", self.network) @@ -288,7 +259,6 @@ class TestPyLast(unittest.TestCase): self.assertNotEqual(str(loved.track.artist), "Test Artist") self.assertNotEqual(str(loved.track.title), "test title") - @handle_lastfm_exceptions def test_get_100_albums(self): # Arrange library = pylast.Library(user=self.username, network=self.network) @@ -299,7 +269,6 @@ class TestPyLast(unittest.TestCase): # Assert self.assertGreaterEqual(len(albums), 0) - @handle_lastfm_exceptions def test_get_limitless_albums(self): # Arrange library = pylast.Library(user=self.username, network=self.network) @@ -310,7 +279,6 @@ class TestPyLast(unittest.TestCase): # Assert self.assertGreaterEqual(len(albums), 0) - @handle_lastfm_exceptions def test_user_equals_none(self): # Arrange lastfm_user = self.network.get_user(self.username) @@ -321,7 +289,6 @@ class TestPyLast(unittest.TestCase): # Assert self.assertFalse(value) - @handle_lastfm_exceptions def test_user_not_equal_to_none(self): # Arrange lastfm_user = self.network.get_user(self.username) @@ -332,7 +299,6 @@ class TestPyLast(unittest.TestCase): # Assert self.assertTrue(value) - @handle_lastfm_exceptions def test_now_playing_user_with_no_scrobbles(self): # Arrange # Currently test-account has no scrobbles: @@ -344,7 +310,6 @@ class TestPyLast(unittest.TestCase): # Assert self.assertIsNone(current_track) - @handle_lastfm_exceptions def test_love_limits(self): # Arrange # Currently test-account has at least 23 loved tracks: @@ -356,7 +321,6 @@ class TestPyLast(unittest.TestCase): self.assertGreaterEqual(len(user.get_loved_tracks(limit=None)), 23) self.assertGreaterEqual(len(user.get_loved_tracks(limit=0)), 23) - @handle_lastfm_exceptions def test_update_now_playing(self): # Arrange artist = "Test Artist" @@ -375,7 +339,6 @@ class TestPyLast(unittest.TestCase): self.assertEqual(str(current_track.title), "test title") self.assertEqual(str(current_track.artist), "Test Artist") - @handle_lastfm_exceptions def test_album_tags_are_topitems(self): # Arrange albums = self.network.get_user('RJ').get_top_albums() @@ -398,7 +361,6 @@ class TestPyLast(unittest.TestCase): self.assertIsNotNone(thing) self.assertEqual(len(things), 1) - @handle_lastfm_exceptions def test_album_is_hashable(self): # Arrange album = self.network.get_album("Test Artist", "Test Album") @@ -406,7 +368,6 @@ class TestPyLast(unittest.TestCase): # Act/Assert self.helper_is_thing_hashable(album) - @handle_lastfm_exceptions def test_artist_is_hashable(self): # Arrange test_artist = self.network.get_artist("Test Artist") @@ -416,7 +377,6 @@ class TestPyLast(unittest.TestCase): # Act/Assert self.helper_is_thing_hashable(artist) - @handle_lastfm_exceptions def test_country_is_hashable(self): # Arrange country = self.network.get_country("Italy") @@ -424,7 +384,6 @@ class TestPyLast(unittest.TestCase): # Act/Assert self.helper_is_thing_hashable(country) - @handle_lastfm_exceptions def test_metro_is_hashable(self): # Arrange metro = self.network.get_metro("Helsinki", "Finland") @@ -432,7 +391,6 @@ class TestPyLast(unittest.TestCase): # Act/Assert self.helper_is_thing_hashable(metro) - @handle_lastfm_exceptions def test_event_is_hashable(self): # Arrange user = self.network.get_user("RJ") @@ -441,7 +399,6 @@ class TestPyLast(unittest.TestCase): # Act/Assert self.helper_is_thing_hashable(event) - @handle_lastfm_exceptions def test_group_is_hashable(self): # Arrange group = self.network.get_group("Audioscrobbler Beta") @@ -449,7 +406,6 @@ class TestPyLast(unittest.TestCase): # Act/Assert self.helper_is_thing_hashable(group) - @handle_lastfm_exceptions def test_library_is_hashable(self): # Arrange library = pylast.Library(user=self.username, network=self.network) @@ -457,7 +413,6 @@ class TestPyLast(unittest.TestCase): # Act/Assert self.helper_is_thing_hashable(library) - @handle_lastfm_exceptions def test_playlist_is_hashable(self): # Arrange playlist = pylast.Playlist( @@ -466,7 +421,6 @@ class TestPyLast(unittest.TestCase): # Act/Assert self.helper_is_thing_hashable(playlist) - @handle_lastfm_exceptions def test_tag_is_hashable(self): # Arrange tag = self.network.get_top_tags(limit=1)[0] @@ -474,7 +428,6 @@ class TestPyLast(unittest.TestCase): # Act/Assert self.helper_is_thing_hashable(tag) - @handle_lastfm_exceptions def test_track_is_hashable(self): # Arrange artist = self.network.get_artist("Test Artist") @@ -484,7 +437,6 @@ class TestPyLast(unittest.TestCase): # Act/Assert self.helper_is_thing_hashable(track) - @handle_lastfm_exceptions def test_user_is_hashable(self): # Arrange user = self.network.get_user(self.username) @@ -492,7 +444,6 @@ class TestPyLast(unittest.TestCase): # Act/Assert self.helper_is_thing_hashable(user) - @handle_lastfm_exceptions def test_venue_is_hashable(self): # Arrange venue_id = "8778225" # Last.fm office @@ -501,7 +452,6 @@ class TestPyLast(unittest.TestCase): # Act/Assert self.helper_is_thing_hashable(venue) - @handle_lastfm_exceptions def test_xspf_is_hashable(self): # Arrange xspf = pylast.XSPF( @@ -510,7 +460,6 @@ class TestPyLast(unittest.TestCase): # Act/Assert self.helper_is_thing_hashable(xspf) - @handle_lastfm_exceptions def test_invalid_xml(self): # Arrange # Currently causes PCDATA invalid Char value 25 @@ -525,7 +474,6 @@ class TestPyLast(unittest.TestCase): self.skip_if_lastfm_api_broken(total) self.assertGreaterEqual(int(total), 0) - @handle_lastfm_exceptions def test_user_play_count_in_track_info(self): # Arrange artist = "Test Artist" @@ -540,7 +488,6 @@ class TestPyLast(unittest.TestCase): # Assert self.assertGreaterEqual(count, 0) - @handle_lastfm_exceptions def test_user_loved_in_track_info(self): # Arrange artist = "Test Artist" @@ -557,7 +504,6 @@ class TestPyLast(unittest.TestCase): self.assertIsInstance(loved, bool) self.assertNotIsInstance(loved, str) - @handle_lastfm_exceptions def test_album_in_recent_tracks(self): # Arrange lastfm_user = self.network.get_user(self.username) @@ -569,7 +515,6 @@ class TestPyLast(unittest.TestCase): # Assert self.assertTrue(hasattr(track, 'album')) - @handle_lastfm_exceptions def test_album_in_artist_tracks(self): # Arrange lastfm_user = self.network.get_user(self.username) @@ -580,7 +525,6 @@ class TestPyLast(unittest.TestCase): # Assert self.assertTrue(hasattr(track, 'album')) - @handle_lastfm_exceptions def test_enable_rate_limiting(self): # Arrange self.assertFalse(self.network.is_rate_limited()) @@ -598,7 +542,6 @@ class TestPyLast(unittest.TestCase): self.assertTrue(self.network.is_rate_limited()) self.assertGreaterEqual(now - then, 0.2) - @handle_lastfm_exceptions def test_disable_rate_limiting(self): # Arrange self.network.enable_rate_limit() @@ -649,7 +592,6 @@ class TestPyLast(unittest.TestCase): for event in events[:2]: # checking first two should be enough self.assertIsInstance(event.get_headliner(), pylast.Artist) - @handle_lastfm_exceptions def test_artist_upcoming_events_returns_valid_ids(self): # Arrange artist = pylast.Artist("Test Artist", self.network) @@ -657,7 +599,6 @@ class TestPyLast(unittest.TestCase): # Act/Assert self.helper_upcoming_events_have_valid_ids(artist) - @handle_lastfm_exceptions def test_user_past_events_returns_valid_ids(self): # Arrange lastfm_user = self.network.get_user(self.username) @@ -665,7 +606,6 @@ class TestPyLast(unittest.TestCase): # Act/Assert self.helper_past_events_have_valid_ids(lastfm_user) - @handle_lastfm_exceptions def test_user_recommended_events_returns_valid_ids(self): # Arrange lastfm_user = self.network.get_user(self.username) @@ -676,7 +616,6 @@ class TestPyLast(unittest.TestCase): # Assert self.helper_assert_events_have_valid_ids(events) - @handle_lastfm_exceptions def test_user_upcoming_events_returns_valid_ids(self): # Arrange lastfm_user = self.network.get_user(self.username) @@ -684,7 +623,6 @@ class TestPyLast(unittest.TestCase): # Act/Assert self.helper_upcoming_events_have_valid_ids(lastfm_user) - @handle_lastfm_exceptions def test_venue_past_events_returns_valid_ids(self): # Arrange venue_id = "8778225" # Last.fm office @@ -693,7 +631,6 @@ class TestPyLast(unittest.TestCase): # Act/Assert self.helper_past_events_have_valid_ids(venue) - @handle_lastfm_exceptions def test_venue_upcoming_events_returns_valid_ids(self): # Arrange venue_id = "8778225" # Last.fm office @@ -702,7 +639,6 @@ class TestPyLast(unittest.TestCase): # Act/Assert self.helper_upcoming_events_have_valid_ids(venue) - @handle_lastfm_exceptions def test_pickle(self): # Arrange import pickle @@ -719,7 +655,6 @@ class TestPyLast(unittest.TestCase): # Assert self.assertEqual(lastfm_user, loaded_user) - @handle_lastfm_exceptions def test_bio_published_date(self): # Arrange artist = pylast.Artist("Test Artist", self.network) @@ -731,7 +666,6 @@ class TestPyLast(unittest.TestCase): self.assertIsNotNone(bio) self.assertGreaterEqual(len(bio), 1) - @handle_lastfm_exceptions def test_bio_content(self): # Arrange artist = pylast.Artist("Test Artist", self.network) @@ -743,7 +677,6 @@ class TestPyLast(unittest.TestCase): self.assertIsNotNone(bio) self.assertGreaterEqual(len(bio), 1) - @handle_lastfm_exceptions def test_bio_summary(self): # Arrange artist = pylast.Artist("Test Artist", self.network) @@ -755,7 +688,6 @@ class TestPyLast(unittest.TestCase): self.assertIsNotNone(bio) self.assertGreaterEqual(len(bio), 1) - @handle_lastfm_exceptions def test_album_wiki_content(self): # Arrange album = pylast.Album("Test Artist", "Test Album", self.network) @@ -767,7 +699,6 @@ class TestPyLast(unittest.TestCase): self.assertIsNotNone(wiki) self.assertGreaterEqual(len(wiki), 1) - @handle_lastfm_exceptions def test_album_wiki_published_date(self): # Arrange album = pylast.Album("Test Artist", "Test Album", self.network) @@ -779,7 +710,6 @@ class TestPyLast(unittest.TestCase): self.assertIsNotNone(wiki) self.assertGreaterEqual(len(wiki), 1) - @handle_lastfm_exceptions def test_album_wiki_summary(self): # Arrange album = pylast.Album("Test Artist", "Test Album", self.network) @@ -791,7 +721,6 @@ class TestPyLast(unittest.TestCase): self.assertIsNotNone(wiki) self.assertGreaterEqual(len(wiki), 1) - @handle_lastfm_exceptions def test_track_wiki_content(self): # Arrange track = pylast.Track("Test Artist", "test title", self.network) @@ -803,7 +732,6 @@ class TestPyLast(unittest.TestCase): self.assertIsNotNone(wiki) self.assertGreaterEqual(len(wiki), 1) - @handle_lastfm_exceptions def test_track_wiki_summary(self): # Arrange track = pylast.Track("Test Artist", "test title", self.network) @@ -815,7 +743,6 @@ class TestPyLast(unittest.TestCase): self.assertIsNotNone(wiki) self.assertGreaterEqual(len(wiki), 1) - @handle_lastfm_exceptions def test_lastfm_network_name(self): # Act name = str(self.network) @@ -847,7 +774,6 @@ class TestPyLast(unittest.TestCase): # Assert self.helper_validate_results(result1, result2, result3) - @handle_lastfm_exceptions def test_cacheable_artist_get_shouts(self): # Arrange artist = self.network.get_artist("Test Artist") @@ -855,7 +781,6 @@ class TestPyLast(unittest.TestCase): # Act/Assert self.helper_validate_cacheable(artist, "get_shouts") - @handle_lastfm_exceptions def test_cacheable_event_get_shouts(self): # Arrange user = self.network.get_user("RJ") @@ -864,7 +789,6 @@ class TestPyLast(unittest.TestCase): # Act/Assert self.helper_validate_cacheable(event, "get_shouts") - @handle_lastfm_exceptions def test_cacheable_track_get_shouts(self): # Arrange track = self.network.get_top_tracks()[0].item @@ -872,7 +796,6 @@ class TestPyLast(unittest.TestCase): # Act/Assert self.helper_validate_cacheable(track, "get_shouts") - @handle_lastfm_exceptions def test_cacheable_group_get_members(self): # Arrange group = self.network.get_group("Audioscrobbler Beta") @@ -880,7 +803,6 @@ class TestPyLast(unittest.TestCase): # Act/Assert self.helper_validate_cacheable(group, "get_members") - @handle_lastfm_exceptions def test_cacheable_library(self): # Arrange library = pylast.Library(self.username, self.network) @@ -890,7 +812,6 @@ class TestPyLast(unittest.TestCase): self.helper_validate_cacheable(library, "get_artists") self.helper_validate_cacheable(library, "get_tracks") - @handle_lastfm_exceptions def test_cacheable_user_artist_tracks(self): # Arrange lastfm_user = self.network.get_authenticated_user() @@ -903,7 +824,6 @@ class TestPyLast(unittest.TestCase): # Assert self.helper_validate_results(result1, result2, result3) - @handle_lastfm_exceptions def test_cacheable_user(self): # Arrange lastfm_user = self.network.get_authenticated_user() @@ -918,7 +838,6 @@ class TestPyLast(unittest.TestCase): self.helper_validate_cacheable(lastfm_user, "get_recommended_events") self.helper_validate_cacheable(lastfm_user, "get_shouts") - @handle_lastfm_exceptions def test_geo_get_events_in_location(self): # Arrange # Act @@ -932,7 +851,6 @@ class TestPyLast(unittest.TestCase): self.assertIn(event.get_venue().location['city'], ["London", "Camden"]) - @handle_lastfm_exceptions def test_geo_get_events_in_latlong(self): # Arrange # Act @@ -945,7 +863,6 @@ class TestPyLast(unittest.TestCase): self.assertIsInstance(event, pylast.Event) self.assertEqual(event.get_venue().location['city'], "Manchester") - @handle_lastfm_exceptions def test_geo_get_events_festival(self): # Arrange # Act @@ -965,7 +882,6 @@ class TestPyLast(unittest.TestCase): (start, end) = dates[0] self.assertLess(start, end) - @handle_lastfm_exceptions def test_get_metro_weekly_chart_dates(self): # Arrange # Act @@ -991,39 +907,32 @@ class TestPyLast(unittest.TestCase): self.assertIsInstance(chart[0], pylast.TopItem) self.assertIsInstance(chart[0].item, expected_type) - @handle_lastfm_exceptions def test_get_metro_artist_chart(self): # Arrange/Act/Assert self.helper_geo_chart("get_artist_chart") - @handle_lastfm_exceptions def test_get_metro_hype_artist_chart(self): # Arrange/Act/Assert self.helper_geo_chart("get_hype_artist_chart") - @handle_lastfm_exceptions def test_get_metro_unique_artist_chart(self): # Arrange/Act/Assert self.helper_geo_chart("get_unique_artist_chart") - @handle_lastfm_exceptions def test_get_metro_track_chart(self): # Arrange/Act/Assert self.helper_geo_chart("get_track_chart", expected_type=pylast.Track) - @handle_lastfm_exceptions def test_get_metro_hype_track_chart(self): # Arrange/Act/Assert self.helper_geo_chart( "get_hype_track_chart", expected_type=pylast.Track) - @handle_lastfm_exceptions def test_get_metro_unique_track_chart(self): # Arrange/Act/Assert self.helper_geo_chart( "get_unique_track_chart", expected_type=pylast.Track) - @handle_lastfm_exceptions def test_geo_get_metros(self): # Arrange # Act @@ -1034,7 +943,6 @@ class TestPyLast(unittest.TestCase): self.assertIsInstance(metros[0], pylast.Metro) self.assertEqual(metros[0].get_country(), "Poland") - @handle_lastfm_exceptions def test_geo_get_top_artists(self): # Arrange # Act @@ -1046,7 +954,6 @@ class TestPyLast(unittest.TestCase): self.assertIsInstance(artists[0], pylast.TopItem) self.assertIsInstance(artists[0].item, pylast.Artist) - @handle_lastfm_exceptions def test_geo_get_top_tracks(self): # Arrange # Act @@ -1058,7 +965,6 @@ class TestPyLast(unittest.TestCase): self.assertIsInstance(tracks[0], pylast.TopItem) self.assertIsInstance(tracks[0].item, pylast.Track) - @handle_lastfm_exceptions def test_metro_class(self): # Arrange # Act @@ -1073,7 +979,6 @@ class TestPyLast(unittest.TestCase): metro, pylast.Metro("Wellington", "New Zealand", self.network)) - @handle_lastfm_exceptions def test_get_album_play_links(self): # Arrange album1 = self.network.get_album("Portishead", "Dummy") @@ -1089,7 +994,6 @@ class TestPyLast(unittest.TestCase): self.assertIn("spotify:album:", links[0]) self.assertIn("spotify:album:", links[1]) - @handle_lastfm_exceptions def test_get_artist_play_links(self): # Arrange artists = ["Portishead", "Radiohead"] @@ -1102,7 +1006,6 @@ class TestPyLast(unittest.TestCase): self.assertIn("spotify:artist:", links[0]) self.assertIn("spotify:artist:", links[1]) - @handle_lastfm_exceptions def test_get_track_play_links(self): # Arrange track1 = self.network.get_track(artist="Portishead", title="Mysterons") @@ -1158,7 +1061,6 @@ class TestPyLast(unittest.TestCase): self.assertIsInstance(thing1, expected_type) self.assertIsInstance(thing2, expected_type) - @handle_lastfm_exceptions def test_user_get_top_tags_with_limit(self): # Arrange user = self.network.get_user("RJ") @@ -1170,7 +1072,6 @@ class TestPyLast(unittest.TestCase): self.skip_if_lastfm_api_broken(tags) self.helper_only_one_thing_in_top_list(tags, pylast.Tag) - @handle_lastfm_exceptions def test_network_get_top_artists_with_limit(self): # Arrange # Act @@ -1179,7 +1080,6 @@ class TestPyLast(unittest.TestCase): # Assert self.helper_only_one_thing_in_top_list(artists, pylast.Artist) - @handle_lastfm_exceptions def test_network_get_top_tags_with_limit(self): # Arrange # Act @@ -1188,7 +1088,6 @@ class TestPyLast(unittest.TestCase): # Assert self.helper_only_one_thing_in_top_list(tags, pylast.Tag) - @handle_lastfm_exceptions def test_network_get_top_tags_with_no_limit(self): # Arrange # Act @@ -1197,7 +1096,6 @@ class TestPyLast(unittest.TestCase): # Assert self.helper_at_least_one_thing_in_top_list(tags, pylast.Tag) - @handle_lastfm_exceptions def test_network_get_top_tracks_with_limit(self): # Arrange # Act @@ -1206,7 +1104,6 @@ class TestPyLast(unittest.TestCase): # Assert self.helper_only_one_thing_in_top_list(tracks, pylast.Track) - @handle_lastfm_exceptions def test_artist_top_tracks(self): # Arrange # Pick an artist with plenty of plays @@ -1218,7 +1115,6 @@ class TestPyLast(unittest.TestCase): # Assert self.helper_two_different_things_in_top_list(things, pylast.Track) - @handle_lastfm_exceptions def test_artist_top_albums(self): # Arrange # Pick an artist with plenty of plays @@ -1230,7 +1126,6 @@ class TestPyLast(unittest.TestCase): # Assert self.helper_two_different_things_in_top_list(things, pylast.Album) - @handle_lastfm_exceptions def test_country_top_tracks(self): # Arrange country = self.network.get_country("Croatia") @@ -1241,7 +1136,6 @@ class TestPyLast(unittest.TestCase): # Assert self.helper_two_different_things_in_top_list(things, pylast.Track) - @handle_lastfm_exceptions def test_country_network_top_tracks(self): # Arrange # Act @@ -1250,7 +1144,6 @@ class TestPyLast(unittest.TestCase): # Assert self.helper_two_different_things_in_top_list(things, pylast.Track) - @handle_lastfm_exceptions def test_tag_top_tracks(self): # Arrange tag = self.network.get_tag("blues") @@ -1261,7 +1154,6 @@ class TestPyLast(unittest.TestCase): # Assert self.helper_two_different_things_in_top_list(things, pylast.Track) - @handle_lastfm_exceptions def test_user_top_tracks(self): # Arrange lastfm_user = self.network.get_user(self.username) @@ -1295,7 +1187,6 @@ class TestPyLast(unittest.TestCase): self.helper_assert_chart(album_chart, pylast.Album) self.helper_assert_chart(track_chart, pylast.Track) - @handle_lastfm_exceptions def test_group_charts(self): # Arrange group = self.network.get_group("mnml") @@ -1305,7 +1196,6 @@ class TestPyLast(unittest.TestCase): # Act/Assert self.helper_get_assert_charts(group, dates[-2]) - @handle_lastfm_exceptions def test_tag_charts(self): # Arrange tag = self.network.get_tag("rock") @@ -1315,7 +1205,6 @@ class TestPyLast(unittest.TestCase): # Act/Assert self.helper_get_assert_charts(tag, dates[-2]) - @handle_lastfm_exceptions def test_user_charts(self): # Arrange lastfm_user = self.network.get_user("RJ") @@ -1344,7 +1233,6 @@ class TestPyLast(unittest.TestCase): # album/artist/event/track/user - @handle_lastfm_exceptions def test_album_shouts(self): # Arrange # Pick an artist with plenty of plays @@ -1357,7 +1245,6 @@ class TestPyLast(unittest.TestCase): # Assert self.helper_two_things_in_list(shouts, pylast.Shout) - @handle_lastfm_exceptions def test_artist_shouts(self): # Arrange # Pick an artist with plenty of plays @@ -1369,7 +1256,6 @@ class TestPyLast(unittest.TestCase): # Assert self.helper_two_things_in_list(shouts, pylast.Shout) - @handle_lastfm_exceptions def test_event_shouts(self): # Arrange event_id = 3478520 # Glasto 2014 @@ -1381,7 +1267,6 @@ class TestPyLast(unittest.TestCase): # Assert self.helper_two_things_in_list(shouts, pylast.Shout) - @handle_lastfm_exceptions def test_track_shouts(self): # Arrange track = self.network.get_track("The Cinematic Orchestra", "Postlude") @@ -1392,7 +1277,6 @@ class TestPyLast(unittest.TestCase): # Assert self.helper_two_things_in_list(shouts, pylast.Shout) - @handle_lastfm_exceptions def test_user_shouts(self): # Arrange user = self.network.get_user("RJ") @@ -1403,7 +1287,6 @@ class TestPyLast(unittest.TestCase): # Assert self.helper_two_things_in_list(shouts, pylast.Shout) - @handle_lastfm_exceptions def test_album_data(self): # Arrange thing = self.network.get_album("Test Artist", "Test Album") @@ -1425,7 +1308,6 @@ class TestPyLast(unittest.TestCase): self.assertEqual( "https://www.last.fm/music/test%2bartist/test%2balbum", url) - @handle_lastfm_exceptions def test_track_data(self): # Arrange thing = self.network.get_track("Test Artist", "test title") @@ -1448,7 +1330,6 @@ class TestPyLast(unittest.TestCase): self.assertEqual( "https://www.last.fm/fr/music/test%2bartist/_/test%2btitle", url) - @handle_lastfm_exceptions def test_tag_top_artists(self): # Arrange tag = self.network.get_tag("blues") @@ -1459,7 +1340,6 @@ class TestPyLast(unittest.TestCase): # Assert self.helper_only_one_thing_in_top_list(artists, pylast.Artist) - @handle_lastfm_exceptions def test_country_top_artists(self): # Arrange country = self.network.get_country("Ukraine") @@ -1470,7 +1350,6 @@ class TestPyLast(unittest.TestCase): # Assert self.helper_only_one_thing_in_top_list(artists, pylast.Artist) - @handle_lastfm_exceptions def test_user_top_artists(self): # Arrange lastfm_user = self.network.get_user(self.username) @@ -1481,7 +1360,6 @@ class TestPyLast(unittest.TestCase): # Assert self.helper_only_one_thing_in_top_list(artists, pylast.Artist) - @handle_lastfm_exceptions def test_tag_top_albums(self): # Arrange tag = self.network.get_tag("blues") @@ -1492,7 +1370,6 @@ class TestPyLast(unittest.TestCase): # Assert self.helper_only_one_thing_in_top_list(albums, pylast.Album) - @handle_lastfm_exceptions def test_user_top_albums(self): # Arrange user = self.network.get_user("RJ") @@ -1503,7 +1380,6 @@ class TestPyLast(unittest.TestCase): # Assert self.helper_only_one_thing_in_top_list(albums, pylast.Album) - @handle_lastfm_exceptions def test_user_tagged_artists(self): # Arrange lastfm_user = self.network.get_user(self.username) @@ -1517,7 +1393,6 @@ class TestPyLast(unittest.TestCase): # Assert self.helper_only_one_thing_in_list(artists, pylast.Artist) - @handle_lastfm_exceptions def test_user_tagged_albums(self): # Arrange lastfm_user = self.network.get_user(self.username) @@ -1531,7 +1406,6 @@ class TestPyLast(unittest.TestCase): # Assert self.helper_only_one_thing_in_list(albums, pylast.Album) - @handle_lastfm_exceptions def test_user_tagged_tracks(self): # Arrange lastfm_user = self.network.get_user(self.username) @@ -1545,7 +1419,6 @@ class TestPyLast(unittest.TestCase): # Assert self.helper_only_one_thing_in_list(tracks, pylast.Track) - @handle_lastfm_exceptions def test_user_subscriber(self): # Arrange subscriber = self.network.get_user("RJ") @@ -1559,7 +1432,6 @@ class TestPyLast(unittest.TestCase): self.assertTrue(subscriber_is_subscriber) self.assertFalse(non_subscriber_is_subscriber) - @handle_lastfm_exceptions def test_user_get_image(self): # Arrange user = self.network.get_user("RJ") @@ -1570,7 +1442,6 @@ class TestPyLast(unittest.TestCase): # Assert self.assertTrue(url.startswith("https://")) - @handle_lastfm_exceptions def test_user_get_library(self): # Arrange user = self.network.get_user(self.username) @@ -1581,7 +1452,6 @@ class TestPyLast(unittest.TestCase): # Assert self.assertIsInstance(library, pylast.Library) - @handle_lastfm_exceptions def test_caching(self): # Arrange user = self.network.get_user("RJ") @@ -1597,7 +1467,6 @@ class TestPyLast(unittest.TestCase): self.network.disable_caching() self.assertFalse(self.network.is_caching_enabled()) - @handle_lastfm_exceptions def test_create_playlist(self): # Arrange title = "Test playlist" @@ -1613,7 +1482,6 @@ class TestPyLast(unittest.TestCase): self.assertEqual(playlist.get_description(), "Testing") self.assertEqual(playlist.get_user(), lastfm_user) - @handle_lastfm_exceptions def test_empty_playlist_unstreamable(self): # Arrange title = "Empty playlist" @@ -1627,7 +1495,6 @@ class TestPyLast(unittest.TestCase): self.assertEqual(playlist.get_duration(), 0) self.assertFalse(playlist.is_streamable()) - @handle_lastfm_exceptions def test_big_playlist_is_streamable(self): # Arrange # Find a big playlist on Last.fm, eg "top 100 classick rock songs" @@ -1648,7 +1515,6 @@ class TestPyLast(unittest.TestCase): self.assertGreater(playlist.get_duration(), 0) self.assertTrue(playlist.is_streamable()) - @handle_lastfm_exceptions def test_add_track_to_playlist(self): # Arrange title = "One track playlist" @@ -1664,7 +1530,6 @@ class TestPyLast(unittest.TestCase): self.assertEqual(len(playlist.get_tracks()), 1) self.assertTrue(playlist.has_track(track)) - @handle_lastfm_exceptions def test_album_mbid(self): # Arrange mbid = "a6a265bf-9f81-4055-8224-f7ac0aa6b937" @@ -1678,7 +1543,6 @@ class TestPyLast(unittest.TestCase): self.assertEqual(album.title.lower(), "test") self.assertEqual(album_mbid, mbid) - @handle_lastfm_exceptions def test_artist_mbid(self): # Arrange mbid = "7e84f845-ac16-41fe-9ff8-df12eb32af55" @@ -1690,7 +1554,6 @@ class TestPyLast(unittest.TestCase): self.assertIsInstance(artist, pylast.Artist) self.assertEqual(artist.name, "MusicBrainz Test Artist") - @handle_lastfm_exceptions def test_track_mbid(self): # Arrange mbid = "ebc037b1-cc9c-44f2-a21f-83c219f0e1e0" @@ -1704,7 +1567,6 @@ class TestPyLast(unittest.TestCase): self.assertEqual(track.title, "first") self.assertEqual(track_mbid, mbid) - @handle_lastfm_exceptions def test_artist_listener_count(self): # Arrange artist = self.network.get_artist("Test Artist") @@ -1716,7 +1578,6 @@ class TestPyLast(unittest.TestCase): self.assertIsInstance(count, int) self.assertGreater(count, 0) - @handle_lastfm_exceptions def test_event_attendees(self): # Arrange user = self.network.get_user("RJ") @@ -1729,7 +1590,6 @@ class TestPyLast(unittest.TestCase): self.assertIsInstance(users, list) self.assertIsInstance(users[0], pylast.User) - @handle_lastfm_exceptions def test_tag_artist(self): # Arrange artist = self.network.get_artist("Test Artist") @@ -1748,7 +1608,6 @@ class TestPyLast(unittest.TestCase): break self.assertTrue(found) - @handle_lastfm_exceptions def test_remove_tag_of_type_text(self): # Arrange tag = "testing" # text @@ -1767,7 +1626,6 @@ class TestPyLast(unittest.TestCase): break self.assertFalse(found) - @handle_lastfm_exceptions def test_remove_tag_of_type_tag(self): # Arrange tag = pylast.Tag("testing", self.network) # Tag @@ -1786,7 +1644,6 @@ class TestPyLast(unittest.TestCase): break self.assertFalse(found) - @handle_lastfm_exceptions def test_remove_tags(self): # Arrange tags = ["removetag1", "removetag2"] @@ -1810,7 +1667,6 @@ class TestPyLast(unittest.TestCase): self.assertFalse(found1) self.assertFalse(found2) - @handle_lastfm_exceptions def test_set_tags(self): # Arrange tags = ["sometag1", "sometag2"] @@ -1835,7 +1691,6 @@ class TestPyLast(unittest.TestCase): self.assertTrue(found1) self.assertTrue(found2) - @handle_lastfm_exceptions def test_tracks_notequal(self): # Arrange track1 = pylast.Track("Test Artist", "test title", self.network) @@ -1845,7 +1700,6 @@ class TestPyLast(unittest.TestCase): # Assert self.assertNotEqual(track1, track2) - @handle_lastfm_exceptions def test_track_id(self): # Arrange track = pylast.Track("Test Artist", "test title", self.network) @@ -1857,7 +1711,6 @@ class TestPyLast(unittest.TestCase): self.skip_if_lastfm_api_broken(id) self.assertEqual(id, "14053327") - @handle_lastfm_exceptions def test_track_title_prop_caps(self): # Arrange track = pylast.Track("test artist", "test title", self.network) @@ -1868,7 +1721,6 @@ class TestPyLast(unittest.TestCase): # Assert self.assertEqual(title, "test title") - @handle_lastfm_exceptions def test_track_listener_count(self): # Arrange track = pylast.Track("test artist", "test title", self.network) @@ -1879,7 +1731,6 @@ class TestPyLast(unittest.TestCase): # Assert self.assertGreater(count, 21) - @handle_lastfm_exceptions def test_album_tracks(self): # Arrange album = pylast.Album("Test Artist", "Test Release", self.network) @@ -1894,7 +1745,6 @@ class TestPyLast(unittest.TestCase): self.assertEqual(len(tracks), 4) self.assertTrue(url.startswith("https://www.last.fm/music/test")) - @handle_lastfm_exceptions def test_tags(self): # Arrange tag1 = self.network.get_tag("blues") @@ -1915,7 +1765,6 @@ class TestPyLast(unittest.TestCase): self.assertTrue(tag1 != tag2) self.assertEqual(url, "https://www.last.fm/tag/blues") - @handle_lastfm_exceptions def test_tags_similar(self): # Arrange tag = self.network.get_tag("blues") @@ -1932,7 +1781,6 @@ class TestPyLast(unittest.TestCase): break self.assertTrue(found) - @handle_lastfm_exceptions def test_artists(self): # Arrange artist1 = self.network.get_artist("Radiohead") @@ -1956,7 +1804,6 @@ class TestPyLast(unittest.TestCase): self.assertEqual(mbid, "a74b1b7f-71a5-4011-9441-d0b5e4122711") self.assertIsInstance(streamable, bool) - @handle_lastfm_exceptions def test_events(self): # Arrange event_id_1 = 3162700 # Glasto 2013 @@ -1991,7 +1838,6 @@ class TestPyLast(unittest.TestCase): self.assertGreater(review_count, 0) self.assertGreater(attendance_count, 100) - @handle_lastfm_exceptions def test_countries(self): # Arrange country1 = pylast.Country("Italy", self.network) @@ -2010,7 +1856,6 @@ class TestPyLast(unittest.TestCase): self.assertTrue(country1 != country2) self.assertEqual(url, "https://www.last.fm/place/italy") - @handle_lastfm_exceptions def test_track_eq_none_is_false(self): # Arrange track1 = None @@ -2019,7 +1864,6 @@ class TestPyLast(unittest.TestCase): # Act / Assert self.assertFalse(track1 == track2) - @handle_lastfm_exceptions def test_track_ne_none_is_true(self): # Arrange track1 = None @@ -2028,7 +1872,6 @@ class TestPyLast(unittest.TestCase): # Act / Assert self.assertTrue(track1 != track2) - @handle_lastfm_exceptions def test_artist_eq_none_is_false(self): # Arrange artist1 = None @@ -2037,7 +1880,6 @@ class TestPyLast(unittest.TestCase): # Act / Assert self.assertFalse(artist1 == artist2) - @handle_lastfm_exceptions def test_artist_ne_none_is_true(self): # Arrange artist1 = None @@ -2046,7 +1888,6 @@ class TestPyLast(unittest.TestCase): # Act / Assert self.assertTrue(artist1 != artist2) - @handle_lastfm_exceptions def test_album_eq_none_is_false(self): # Arrange album1 = None @@ -2055,7 +1896,6 @@ class TestPyLast(unittest.TestCase): # Act / Assert self.assertFalse(album1 == album2) - @handle_lastfm_exceptions def test_album_ne_none_is_true(self): # Arrange album1 = None @@ -2064,7 +1904,6 @@ class TestPyLast(unittest.TestCase): # Act / Assert self.assertTrue(album1 != album2) - @handle_lastfm_exceptions def test_event_eq_none_is_false(self): # Arrange event1 = None @@ -2074,7 +1913,6 @@ class TestPyLast(unittest.TestCase): # Act / Assert self.assertFalse(event1 == event2) - @handle_lastfm_exceptions def test_event_ne_none_is_true(self): # Arrange event1 = None @@ -2084,7 +1922,6 @@ class TestPyLast(unittest.TestCase): # Act / Assert self.assertTrue(event1 != event2) - @handle_lastfm_exceptions def test_band_members(self): # Arrange artist = pylast.Artist("The Beatles", self.network) @@ -2096,7 +1933,6 @@ class TestPyLast(unittest.TestCase): self.skip_if_lastfm_api_broken(band_members) self.assertGreaterEqual(len(band_members), 4) - @handle_lastfm_exceptions def test_no_band_members(self): # Arrange artist = pylast.Artist("John Lennon", self.network) @@ -2107,7 +1943,6 @@ class TestPyLast(unittest.TestCase): # Assert self.assertIsNone(band_members) - @handle_lastfm_exceptions def test_get_recent_tracks_from_to(self): # Arrange lastfm_user = self.network.get_user("RJ") @@ -2128,7 +1963,6 @@ class TestPyLast(unittest.TestCase): self.assertEqual(str(tracks[0].track.artist), "Johnny Cash") self.assertEqual(str(tracks[0].track.title), "Ring of Fire") - @handle_lastfm_exceptions def test_artist_get_correction(self): # Arrange artist = pylast.Artist("guns and roses", self.network) @@ -2139,7 +1973,6 @@ class TestPyLast(unittest.TestCase): # Assert self.assertEqual(corrected_artist_name, "Guns N' Roses") - @handle_lastfm_exceptions def test_track_get_correction(self): # Arrange track = pylast.Track("Guns N' Roses", "mrbrownstone", self.network) @@ -2150,7 +1983,6 @@ class TestPyLast(unittest.TestCase): # Assert self.assertEqual(corrected_track_name, "Mr. Brownstone") - @handle_lastfm_exceptions def test_track_with_no_mbid(self): # Arrange track = pylast.Track("Static-X", "Set It Off", self.network) From f419c39ef04f5d53bdd945586ceb0cef160161ec Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 25 Sep 2017 18:03:00 +0300 Subject: [PATCH 30/66] Remove dead Last.fm library methods --- pylast/__init__.py | 122 ------------------------------------------- tests/test_pylast.py | 111 --------------------------------------- 2 files changed, 233 deletions(-) diff --git a/pylast/__init__.py b/pylast/__init__.py index da167c6..46a61b5 100644 --- a/pylast/__init__.py +++ b/pylast/__init__.py @@ -2433,86 +2433,8 @@ class Library(_BaseObject): def get_user(self): """Returns the user who owns this library.""" - return self.user - def add_album(self, album): - """Add an album to this library.""" - - params = self._get_params() - params["artist"] = album.get_artist().get_name() - params["album"] = album.get_name() - - self._request("library.addAlbum", False, params) - - def remove_album(self, album): - """Remove an album from this library.""" - - params = self._get_params() - params["artist"] = album.get_artist().get_name() - params["album"] = album.get_name() - - self._request(self.ws_prefix + ".removeAlbum", False, params) - - def add_artist(self, artist): - """Add an artist to this library.""" - - params = self._get_params() - if type(artist) == str: - params["artist"] = artist - else: - params["artist"] = artist.get_name() - - self._request(self.ws_prefix + ".addArtist", False, params) - - def remove_artist(self, artist): - """Remove an artist from this library.""" - - params = self._get_params() - if type(artist) == str: - params["artist"] = artist - else: - params["artist"] = artist.get_name() - - self._request(self.ws_prefix + ".removeArtist", False, params) - - def add_track(self, track): - """Add a track to this library.""" - - params = self._get_params() - params["track"] = track.get_title() - - self._request(self.ws_prefix + ".addTrack", False, params) - - def get_albums(self, artist=None, limit=50, cacheable=True): - """ - Returns a sequence of Album objects - If no artist is specified, it will return all, sorted by decreasing - play count. - If limit==None it will return all (may take a while) - """ - - params = self._get_params() - if artist: - params["artist"] = artist - - seq = [] - for node in _collect_nodes( - limit, - self, - self.ws_prefix + ".getAlbums", - cacheable, - params): - name = _extract(node, "name") - artist = _extract(node, "name", 1) - playcount = _number(_extract(node, "playcount")) - tagcount = _number(_extract(node, "tagcount")) - - seq.append(LibraryItem( - Album(artist, name, self.network), playcount, tagcount)) - - return seq - def get_artists(self, limit=50, cacheable=True): """ Returns a sequence of Album objects @@ -2535,50 +2457,6 @@ class Library(_BaseObject): return seq - def get_tracks(self, artist=None, album=None, limit=50, cacheable=True): - """ - Returns a sequence of Album objects - If limit==None it will return all (may take a while) - """ - - params = self._get_params() - if artist: - params["artist"] = artist - if album: - params["album"] = album - - seq = [] - for node in _collect_nodes( - limit, - self, - self.ws_prefix + ".getTracks", - cacheable, - params): - name = _extract(node, "name") - artist = _extract(node, "name", 1) - playcount = _number(_extract(node, "playcount")) - tagcount = _number(_extract(node, "tagcount")) - - seq.append(LibraryItem( - Track(artist, name, self.network), playcount, tagcount)) - - return seq - - def remove_scrobble(self, artist, title, timestamp): - """Remove a scrobble from a user's Last.fm library. Parameters: - artist (Required) : The artist that composed the track - title (Required) : The name of the track - timestamp (Required) : The unix timestamp of the scrobble - that you wish to remove - """ - - params = self._get_params() - params["artist"] = artist - params["track"] = title - params["timestamp"] = timestamp - - self._request(self.ws_prefix + ".removeScrobble", False, params) - class Playlist(_BaseObject): """A Last.fm user playlist.""" diff --git a/tests/test_pylast.py b/tests/test_pylast.py index 6a7799e..e5e415e 100755 --- a/tests/test_pylast.py +++ b/tests/test_pylast.py @@ -74,97 +74,6 @@ class TestPyLast(unittest.TestCase): self.assertEqual(str(last_scrobble.track.title), str(title)) self.assertEqual(str(last_scrobble.timestamp), str(timestamp)) - def test_unscrobble(self): - # Arrange - artist = "Test Artist 2" - title = "Test Title 2" - timestamp = self.unix_timestamp() - library = pylast.Library(user=self.username, network=self.network) - self.network.scrobble(artist=artist, title=title, timestamp=timestamp) - lastfm_user = self.network.get_user(self.username) - - # Act - library.remove_scrobble( - artist=artist, title=title, timestamp=timestamp) - - # Assert - # limit=2 to ignore now-playing: - last_scrobble = lastfm_user.get_recent_tracks(limit=2)[0] - self.assertNotEqual(str(last_scrobble.timestamp), str(timestamp)) - - def test_add_album(self): - # Arrange - library = pylast.Library(user=self.username, network=self.network) - album = self.network.get_album("Test Artist", "Test Album") - - # Act - library.add_album(album) - - # Assert - my_albums = library.get_albums() - for my_album in my_albums: - value = (album == my_album[0]) - if value: - break - self.assertTrue(value) - - def test_remove_album(self): - # Arrange - library = pylast.Library(user=self.username, network=self.network) - # Pick an artist with plenty of albums - artist = self.network.get_top_artists(limit=1)[0].item - albums = artist.get_top_albums() - # Pick a random one to avoid problems running concurrent tests - album = choice(albums)[0] - library.add_album(album) - - # Act - library.remove_album(album) - - # Assert - my_albums = library.get_albums() - for my_album in my_albums: - value = (album == my_album[0]) - if value: - break - self.assertFalse(value) - - def test_add_artist(self): - # Arrange - artist = "Test Artist 2" - library = pylast.Library(user=self.username, network=self.network) - - # Act - library.add_artist(artist) - - # Assert - artists = library.get_artists() - for artist in artists: - value = (str(artist[0]) == "Test Artist 2") - if value: - break - self.assertTrue(value) - - def test_remove_artist(self): - # Arrange - # Get plenty of artists - artists = self.network.get_top_artists() - # Pick a random one to avoid problems running concurrent tests - my_artist = choice(artists).item - library = pylast.Library(user=self.username, network=self.network) - library.add_artist(my_artist) - - # Act - library.remove_artist(my_artist) - - # Assert - artists = library.get_artists() - for artist in artists: - value = (artist[0] == my_artist) - if value: - break - self.assertFalse(value) - def test_get_venue(self): # Arrange venue_name = "Last.fm Office" @@ -259,26 +168,6 @@ class TestPyLast(unittest.TestCase): self.assertNotEqual(str(loved.track.artist), "Test Artist") self.assertNotEqual(str(loved.track.title), "test title") - def test_get_100_albums(self): - # Arrange - library = pylast.Library(user=self.username, network=self.network) - - # Act - albums = library.get_albums(limit=100) - - # Assert - self.assertGreaterEqual(len(albums), 0) - - def test_get_limitless_albums(self): - # Arrange - library = pylast.Library(user=self.username, network=self.network) - - # Act - albums = library.get_albums(limit=None) - - # Assert - self.assertGreaterEqual(len(albums), 0) - def test_user_equals_none(self): # Arrange lastfm_user = self.network.get_user(self.username) From 56e193d14903225964bedb88da959b20a2614019 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 25 Sep 2017 18:11:48 +0300 Subject: [PATCH 31/66] Remove dead Last.fm playlist methods --- pylast/__init__.py | 219 ------------------------------------------- tests/test_pylast.py | 80 ---------------- 2 files changed, 299 deletions(-) diff --git a/pylast/__init__.py b/pylast/__init__.py index 46a61b5..f1b5880 100644 --- a/pylast/__init__.py +++ b/pylast/__init__.py @@ -296,24 +296,6 @@ class _Network(object): self.last_call_time = now - def create_new_playlist(self, title, description): - """ - Creates a playlist for the authenticated user and returns it - title: The title of the new playlist. - description: The description of the new playlist. - """ - - params = {} - params['title'] = title - params['description'] = description - - doc = _Request(self, 'playlist.create', params).execute(False) - - e_id = doc.getElementsByTagName("id")[0].firstChild.data - user = doc.getElementsByTagName('playlists')[0].getAttribute('user') - - return Playlist(user, e_id, self) - def get_top_artists(self, limit=None, cacheable=True): """Returns the most played artists as a sequence of TopItem objects.""" @@ -815,7 +797,6 @@ class LastFMNetwork(_Network): "artist": "music/%(artist)s", "event": "event/%(id)s", "country": "place/%(country_name)s", - "playlist": "user/%(user)s/library/playlists/%(appendix)s", "tag": "tag/%(name)s", "track": "music/%(artist)s/_/%(title)s", "group": "group/%(name)s", @@ -880,7 +861,6 @@ class LibreFMNetwork(_Network): "artist": "artist/%(artist)s", "event": "event/%(id)s", "country": "place/%(country_name)s", - "playlist": "user/%(user)s/library/playlists/%(appendix)s", "tag": "tag/%(name)s", "track": "music/%(artist)s/_/%(title)s", "group": "group/%(name)s", @@ -2458,148 +2438,6 @@ class Library(_BaseObject): return seq -class Playlist(_BaseObject): - """A Last.fm user playlist.""" - - id = None - user = None - - __hash__ = _BaseObject.__hash__ - - def __init__(self, user, playlist_id, network): - _BaseObject.__init__(self, network, "playlist") - - if isinstance(user, User): - self.user = user - else: - self.user = User(user, self.network) - - self.id = playlist_id - - @_string_output - def __str__(self): - return repr(self.user) + "'s playlist # " + repr(self.id) - - def _get_info_node(self): - """ - Returns the node from user.getPlaylists where this playlist's info is. - """ - - doc = self._request("user.getPlaylists", True) - - for node in doc.getElementsByTagName("playlist"): - if _extract(node, "id") == str(self.get_id()): - return node - - def _get_params(self): - return {'user': self.user.get_name(), 'playlistID': self.get_id()} - - def get_id(self): - """Returns the playlist ID.""" - - return self.id - - def get_user(self): - """Returns the owner user of this playlist.""" - - return self.user - - def get_tracks(self): - """Returns a list of the tracks on this user playlist.""" - - uri = _unicode('lastfm://playlist/%s') % self.get_id() - - return XSPF(uri, self.network).get_tracks() - - def add_track(self, track): - """Adds a Track to this Playlist.""" - - params = self._get_params() - params['artist'] = track.get_artist().get_name() - params['track'] = track.get_title() - - self._request('playlist.addTrack', False, params) - - def get_title(self): - """Returns the title of this playlist.""" - - return _extract(self._get_info_node(), "title") - - def get_creation_date(self): - """Returns the creation date of this playlist.""" - - return _extract(self._get_info_node(), "date") - - def get_size(self): - """Returns the number of tracks in this playlist.""" - - return _number(_extract(self._get_info_node(), "size")) - - def get_description(self): - """Returns the description of this playlist.""" - - return _extract(self._get_info_node(), "description") - - def get_duration(self): - """Returns the duration of this playlist in milliseconds.""" - - return _number(_extract(self._get_info_node(), "duration")) - - def is_streamable(self): - """ - Returns True if the playlist is streamable. - For a playlist to be streamable, it needs at least 45 tracks by 15 - different artists.""" - - if _extract(self._get_info_node(), "streamable") == '1': - return True - else: - return False - - def has_track(self, track): - """Checks to see if track is already in the playlist. - * track: Any Track object. - """ - - return track in self.get_tracks() - - def get_cover_image(self, size=COVER_EXTRA_LARGE): - """ - Returns a uri to the cover image - size can be one of: - COVER_MEGA - COVER_EXTRA_LARGE - COVER_LARGE - COVER_MEDIUM - COVER_SMALL - """ - - return _extract(self._get_info_node(), "image")[size] - - def get_url(self, domain_name=DOMAIN_ENGLISH): - """Returns the url of the playlist on the network. - * domain_name: The network's language domain. Possible values: - o DOMAIN_ENGLISH - o DOMAIN_GERMAN - o DOMAIN_SPANISH - o DOMAIN_FRENCH - o DOMAIN_ITALIAN - o DOMAIN_POLISH - o DOMAIN_PORTUGUESE - o DOMAIN_SWEDISH - o DOMAIN_TURKISH - o DOMAIN_RUSSIAN - o DOMAIN_JAPANESE - o DOMAIN_CHINESE - """ - - english_url = _extract(self._get_info_node(), "url") - appendix = english_url[english_url.rfind("/") + 1:] - - return self.network._get_url(domain_name, "playlist") % { - 'appendix': appendix, "user": self.get_user().get_name()} - - class Tag(_BaseObject, _Chartable): """A Last.fm object tag.""" @@ -2895,51 +2733,6 @@ class Group(_BaseObject, _Chartable): return users -class XSPF(_BaseObject): - "A Last.fm XSPF playlist.""" - - uri = None - - __hash__ = _BaseObject.__hash__ - - def __init__(self, uri, network): - _BaseObject.__init__(self, network, None) - - self.uri = uri - - def _get_params(self): - return {'playlistURL': self.get_uri()} - - @_string_output - def __str__(self): - return self.get_uri() - - def __eq__(self, other): - return self.get_uri() == other.get_uri() - - def __ne__(self, other): - return self.get_uri() != other.get_uri() - - def get_uri(self): - """Returns the Last.fm playlist URI. """ - - return self.uri - - def get_tracks(self): - """Returns the tracks on this playlist.""" - - doc = self._request('playlist.fetch', True) - - seq = [] - for node in doc.getElementsByTagName('track'): - title = _extract(node, 'title') - artist = _extract(node, 'creator') - - seq.append(Track(artist, title, self.network)) - - return seq - - class User(_BaseObject, _Chartable): """A Last.fm user.""" @@ -3101,18 +2894,6 @@ class User(_BaseObject, _Chartable): return seq - def get_playlists(self): - """Returns a list of Playlists that this user owns.""" - - doc = self._request(self.ws_prefix + ".getPlaylists", True) - - playlists = [] - for playlist_id in _extract_all(doc, "id"): - playlists.append( - Playlist(self.get_name(), playlist_id, self.network)) - - return playlists - def get_now_playing(self): """ Returns the currently playing track, or None if nothing is playing. diff --git a/tests/test_pylast.py b/tests/test_pylast.py index e5e415e..1739bd9 100755 --- a/tests/test_pylast.py +++ b/tests/test_pylast.py @@ -5,7 +5,6 @@ Integration (not unit) tests for pylast.py from flaky import flaky import os import pytest -from random import choice import time import unittest @@ -302,14 +301,6 @@ class TestPyLast(unittest.TestCase): # Act/Assert self.helper_is_thing_hashable(library) - def test_playlist_is_hashable(self): - # Arrange - playlist = pylast.Playlist( - user="RJ", playlist_id="1k1qp_doglist", network=self.network) - - # Act/Assert - self.helper_is_thing_hashable(playlist) - def test_tag_is_hashable(self): # Arrange tag = self.network.get_top_tags(limit=1)[0] @@ -341,14 +332,6 @@ class TestPyLast(unittest.TestCase): # Act/Assert self.helper_is_thing_hashable(venue) - def test_xspf_is_hashable(self): - # Arrange - xspf = pylast.XSPF( - uri="lastfm://playlist/1k1qp_doglist", network=self.network) - - # Act/Assert - self.helper_is_thing_hashable(xspf) - def test_invalid_xml(self): # Arrange # Currently causes PCDATA invalid Char value 25 @@ -1356,69 +1339,6 @@ class TestPyLast(unittest.TestCase): self.network.disable_caching() self.assertFalse(self.network.is_caching_enabled()) - def test_create_playlist(self): - # Arrange - title = "Test playlist" - description = "Testing" - lastfm_user = self.network.get_user(self.username) - - # Act - playlist = self.network.create_new_playlist(title, description) - - # Assert - self.assertIsInstance(playlist, pylast.Playlist) - self.assertEqual(playlist.get_title(), "Test playlist") - self.assertEqual(playlist.get_description(), "Testing") - self.assertEqual(playlist.get_user(), lastfm_user) - - def test_empty_playlist_unstreamable(self): - # Arrange - title = "Empty playlist" - description = "Unstreamable" - - # Act - playlist = self.network.create_new_playlist(title, description) - - # Assert - self.assertEqual(playlist.get_size(), 0) - self.assertEqual(playlist.get_duration(), 0) - self.assertFalse(playlist.is_streamable()) - - def test_big_playlist_is_streamable(self): - # Arrange - # Find a big playlist on Last.fm, eg "top 100 classick rock songs" - user = "kaxior" - id = 10417943 - playlist = pylast.Playlist(user, id, self.network) - self.assertEqual( - playlist.get_url(), - "https://www.last.fm/user/kaxior/library/" - "playlists/67ajb_top_100_classick_rock_songs") - - # Act - # Nothing - - # Assert - self.assertIsInstance(playlist, pylast.Playlist) - self.assertGreaterEqual(playlist.get_size(), 45) - self.assertGreater(playlist.get_duration(), 0) - self.assertTrue(playlist.is_streamable()) - - def test_add_track_to_playlist(self): - # Arrange - title = "One track playlist" - description = "Testing" - playlist = self.network.create_new_playlist(title, description) - track = pylast.Track("Test Artist", "test title", self.network) - - # Act - playlist.add_track(track) - - # Assert - self.assertEqual(playlist.get_size(), 1) - self.assertEqual(len(playlist.get_tracks()), 1) - self.assertTrue(playlist.has_track(track)) - def test_album_mbid(self): # Arrange mbid = "a6a265bf-9f81-4055-8224-f7ac0aa6b937" From 6d738d3f431a6113f100e23e97c50007793ab466 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 25 Sep 2017 18:24:13 +0300 Subject: [PATCH 32/66] Remove dead Last.fm artist/shout methods --- pylast/__init__.py | 92 ---------------------------- tests/test_pylast.py | 141 ------------------------------------------- 2 files changed, 233 deletions(-) diff --git a/pylast/__init__.py b/pylast/__init__.py index f1b5880..f0436d1 100644 --- a/pylast/__init__.py +++ b/pylast/__init__.py @@ -708,39 +708,6 @@ class _Network(object): if remaining_tracks: self.scrobble_many(remaining_tracks) - def get_play_links(self, link_type, things, cacheable=True): - method = link_type + ".getPlaylinks" - params = {} - - for i, thing in enumerate(things): - if link_type == "artist": - params['artist[' + str(i) + ']'] = thing - elif link_type == "album": - params['artist[' + str(i) + ']'] = thing.artist - params['album[' + str(i) + ']'] = thing.title - elif link_type == "track": - params['artist[' + str(i) + ']'] = thing.artist - params['track[' + str(i) + ']'] = thing.title - - doc = _Request(self, method, params).execute(cacheable) - - seq = [] - - for node in doc.getElementsByTagName("externalids"): - spotify = _extract(node, "spotify") - seq.append(spotify) - - return seq - - def get_artist_play_links(self, artists, cacheable=True): - return self.get_play_links("artist", artists, cacheable) - - def get_album_play_links(self, albums, cacheable=True): - return self.get_play_links("album", albums, cacheable) - - def get_track_play_links(self, tracks, cacheable=True): - return self.get_play_links("track", tracks, cacheable) - class LastFMNetwork(_Network): @@ -1169,8 +1136,6 @@ ImageSizes = collections.namedtuple( Image = collections.namedtuple( "Image", [ "title", "url", "dateadded", "format", "owner", "sizes", "votes"]) -Shout = collections.namedtuple( - "Shout", ["body", "author", "date"]) def _string_output(func): @@ -1301,26 +1266,6 @@ class _BaseObject(object): return _extract(node, section) - def get_shouts(self, limit=50, cacheable=False): - """ - Returns a sequence of Shout objects - """ - - shouts = [] - for node in _collect_nodes( - limit, - self, - self.ws_prefix + ".getShouts", - cacheable): - shouts.append( - Shout( - _extract(node, "body"), - User(_extract(node, "author"), self.network), - _extract(node, "date") - ) - ) - return shouts - class _Chartable(object): """Common functions for classes with charts.""" @@ -1887,13 +1832,6 @@ class Artist(_BaseObject, _Taggable): """Returns the content of the artist's biography.""" return self.get_bio("content", language) - def get_upcoming_events(self): - """Returns a list of the upcoming Events for this artist.""" - - doc = self._request(self.ws_prefix + '.getEvents', True) - - return _extract_events_from_doc(doc, self.network) - def get_similar(self, limit=None): """Returns the similar artists on the network.""" @@ -1954,16 +1892,6 @@ class Artist(_BaseObject, _Taggable): return self.network._get_url( domain_name, "artist") % {'artist': artist} - def shout(self, message): - """ - Post a shout - """ - - params = self._get_params() - params["message"] = message - - self._request("artist.Shout", False, params) - def get_band_members(self): """Returns a list of band members or None if unknown.""" @@ -2137,16 +2065,6 @@ class Event(_BaseObject): return self.network._get_url( domain_name, "event") % {'id': self.get_id()} - def shout(self, message): - """ - Post a shout - """ - - params = self._get_params() - params["message"] = message - - self._request("event.Shout", False, params) - class Country(_BaseObject): """A country at Last.fm.""" @@ -3232,16 +3150,6 @@ class User(_BaseObject, _Chartable): return Library(self, self.network) - def shout(self, message): - """ - Post a shout - """ - - params = self._get_params() - params["message"] = message - - self._request(self.ws_prefix + ".Shout", False, params) - class AuthenticatedUser(User): def __init__(self, network): diff --git a/tests/test_pylast.py b/tests/test_pylast.py index 1739bd9..717c1be 100755 --- a/tests/test_pylast.py +++ b/tests/test_pylast.py @@ -464,13 +464,6 @@ class TestPyLast(unittest.TestCase): for event in events[:2]: # checking first two should be enough self.assertIsInstance(event.get_headliner(), pylast.Artist) - def test_artist_upcoming_events_returns_valid_ids(self): - # Arrange - artist = pylast.Artist("Test Artist", self.network) - - # Act/Assert - self.helper_upcoming_events_have_valid_ids(artist) - def test_user_past_events_returns_valid_ids(self): # Arrange lastfm_user = self.network.get_user(self.username) @@ -646,28 +639,6 @@ class TestPyLast(unittest.TestCase): # Assert self.helper_validate_results(result1, result2, result3) - def test_cacheable_artist_get_shouts(self): - # Arrange - artist = self.network.get_artist("Test Artist") - - # Act/Assert - self.helper_validate_cacheable(artist, "get_shouts") - - def test_cacheable_event_get_shouts(self): - # Arrange - user = self.network.get_user("RJ") - event = user.get_past_events(limit=1)[0] - - # Act/Assert - self.helper_validate_cacheable(event, "get_shouts") - - def test_cacheable_track_get_shouts(self): - # Arrange - track = self.network.get_top_tracks()[0].item - - # Act/Assert - self.helper_validate_cacheable(track, "get_shouts") - def test_cacheable_group_get_members(self): # Arrange group = self.network.get_group("Audioscrobbler Beta") @@ -708,7 +679,6 @@ class TestPyLast(unittest.TestCase): self.helper_validate_cacheable(lastfm_user, "get_recent_tracks") self.helper_validate_cacheable(lastfm_user, "get_recommended_artists") self.helper_validate_cacheable(lastfm_user, "get_recommended_events") - self.helper_validate_cacheable(lastfm_user, "get_shouts") def test_geo_get_events_in_location(self): # Arrange @@ -851,48 +821,6 @@ class TestPyLast(unittest.TestCase): metro, pylast.Metro("Wellington", "New Zealand", self.network)) - def test_get_album_play_links(self): - # Arrange - album1 = self.network.get_album("Portishead", "Dummy") - album2 = self.network.get_album("Radiohead", "OK Computer") - albums = [album1, album2] - - # Act - links = self.network.get_album_play_links(albums) - - # Assert - self.assertIsInstance(links, list) - self.assertEqual(len(links), 2) - self.assertIn("spotify:album:", links[0]) - self.assertIn("spotify:album:", links[1]) - - def test_get_artist_play_links(self): - # Arrange - artists = ["Portishead", "Radiohead"] - # Act - links = self.network.get_artist_play_links(artists) - - # Assert - self.assertIsInstance(links, list) - self.assertEqual(len(links), 2) - self.assertIn("spotify:artist:", links[0]) - self.assertIn("spotify:artist:", links[1]) - - def test_get_track_play_links(self): - # Arrange - track1 = self.network.get_track(artist="Portishead", title="Mysterons") - track2 = self.network.get_track(artist="Radiohead", title="Creep") - tracks = [track1, track2] - - # Act - links = self.network.get_track_play_links(tracks) - - # Assert - self.assertIsInstance(links, list) - self.assertEqual(len(links), 2) - self.assertIn("spotify:track:", links[0]) - self.assertIn("spotify:track:", links[1]) - def helper_at_least_one_thing_in_top_list(self, things, expected_type): # Assert self.assertGreater(len(things), 1) @@ -1105,60 +1033,6 @@ class TestPyLast(unittest.TestCase): # album/artist/event/track/user - def test_album_shouts(self): - # Arrange - # Pick an artist with plenty of plays - artist = self.network.get_top_artists(limit=1)[0].item - album = artist.get_top_albums(limit=1)[0].item - - # Act - shouts = album.get_shouts(limit=2) - - # Assert - self.helper_two_things_in_list(shouts, pylast.Shout) - - def test_artist_shouts(self): - # Arrange - # Pick an artist with plenty of plays - artist = self.network.get_top_artists(limit=1)[0].item - - # Act - shouts = artist.get_shouts(limit=2) - - # Assert - self.helper_two_things_in_list(shouts, pylast.Shout) - - def test_event_shouts(self): - # Arrange - event_id = 3478520 # Glasto 2014 - event = pylast.Event(event_id, self.network) - - # Act - shouts = event.get_shouts(limit=2) - - # Assert - self.helper_two_things_in_list(shouts, pylast.Shout) - - def test_track_shouts(self): - # Arrange - track = self.network.get_track("The Cinematic Orchestra", "Postlude") - - # Act - shouts = track.get_shouts(limit=2) - - # Assert - self.helper_two_things_in_list(shouts, pylast.Shout) - - def test_user_shouts(self): - # Arrange - user = self.network.get_user("RJ") - - # Act - shouts = user.get_shouts(limit=2) - - # Assert - self.helper_two_things_in_list(shouts, pylast.Shout) - def test_album_data(self): # Arrange thing = self.network.get_album("Test Artist", "Test Album") @@ -1324,21 +1198,6 @@ class TestPyLast(unittest.TestCase): # Assert self.assertIsInstance(library, pylast.Library) - def test_caching(self): - # Arrange - user = self.network.get_user("RJ") - - # Act - self.network.enable_caching() - shouts1 = user.get_shouts(limit=1, cacheable=True) - shouts2 = user.get_shouts(limit=1, cacheable=True) - - # Assert - self.assertTrue(self.network.is_caching_enabled()) - self.assertEqual(shouts1, shouts2) - self.network.disable_caching() - self.assertFalse(self.network.is_caching_enabled()) - def test_album_mbid(self): # Arrange mbid = "a6a265bf-9f81-4055-8224-f7ac0aa6b937" From 454c519fd9b1494c51606f826057b72dd8b8de70 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 25 Sep 2017 18:29:12 +0300 Subject: [PATCH 33/66] Remove dead Last.fm group methods --- pylast/__init__.py | 86 ++------------------------------------------ tests/test_pylast.py | 23 ------------ 2 files changed, 3 insertions(+), 106 deletions(-) diff --git a/pylast/__init__.py b/pylast/__init__.py index f0436d1..fe9b0bf 100644 --- a/pylast/__init__.py +++ b/pylast/__init__.py @@ -241,13 +241,6 @@ class _Network(object): return Metro(metro_name, country_name, self) - def get_group(self, name): - """ - Returns a Group object - """ - - return Group(name, self) - def get_user(self, username): """ Returns a user object @@ -766,7 +759,6 @@ class LastFMNetwork(_Network): "country": "place/%(country_name)s", "tag": "tag/%(name)s", "track": "music/%(artist)s/_/%(title)s", - "group": "group/%(name)s", "user": "user/%(name)s", } ) @@ -830,7 +822,6 @@ class LibreFMNetwork(_Network): "country": "place/%(country_name)s", "tag": "tag/%(name)s", "track": "music/%(artist)s/_/%(title)s", - "group": "group/%(name)s", "user": "user/%(name)s", } ) @@ -1288,7 +1279,7 @@ class _Chartable(object): """ Returns the weekly album charts for the week starting from the from_date value to the to_date value. - Only for Group or User. + Only for User. """ return self.get_weekly_charts("album", from_date, to_date) @@ -1296,7 +1287,7 @@ class _Chartable(object): """ Returns the weekly artist charts for the week starting from the from_date value to the to_date value. - Only for Group, Tag or User. + Only for Tag or User. """ return self.get_weekly_charts("artist", from_date, to_date) @@ -1304,7 +1295,7 @@ class _Chartable(object): """ Returns the weekly track charts for the week starting from the from_date value to the to_date value. - Only for Group or User. + Only for User. """ return self.get_weekly_charts("track", from_date, to_date) @@ -2580,77 +2571,6 @@ class Track(_Opus): 'artist': artist, 'title': title} -class Group(_BaseObject, _Chartable): - """A Last.fm group.""" - - name = None - - __hash__ = _BaseObject.__hash__ - - def __init__(self, name, network): - _BaseObject.__init__(self, network, 'group') - _Chartable.__init__(self, 'group') - - self.name = name - - def __repr__(self): - return "pylast.Group(%s, %s)" % (repr(self.name), repr(self.network)) - - @_string_output - def __str__(self): - return self.get_name() - - def __eq__(self, other): - return self.get_name().lower() == other.get_name().lower() - - def __ne__(self, other): - return self.get_name() != other.get_name() - - def _get_params(self): - return {self.ws_prefix: self.get_name()} - - def get_name(self): - """Returns the group name. """ - return self.name - - def get_url(self, domain_name=DOMAIN_ENGLISH): - """Returns the url of the group page on the network. - * domain_name: The network's language domain. Possible values: - o DOMAIN_ENGLISH - o DOMAIN_GERMAN - o DOMAIN_SPANISH - o DOMAIN_FRENCH - o DOMAIN_ITALIAN - o DOMAIN_POLISH - o DOMAIN_PORTUGUESE - o DOMAIN_SWEDISH - o DOMAIN_TURKISH - o DOMAIN_RUSSIAN - o DOMAIN_JAPANESE - o DOMAIN_CHINESE - """ - - name = _url_safe(self.get_name()) - - return self.network._get_url(domain_name, "group") % {'name': name} - - def get_members(self, limit=50, cacheable=False): - """ - Returns a sequence of User objects - if limit==None it will return all - """ - - nodes = _collect_nodes( - limit, self, self.ws_prefix + ".getMembers", cacheable) - - users = [] - - for node in nodes: - users.append(User(_extract(node, "name"), self.network)) - - return users - - class User(_BaseObject, _Chartable): """A Last.fm user.""" diff --git a/tests/test_pylast.py b/tests/test_pylast.py index 717c1be..0146174 100755 --- a/tests/test_pylast.py +++ b/tests/test_pylast.py @@ -287,13 +287,6 @@ class TestPyLast(unittest.TestCase): # Act/Assert self.helper_is_thing_hashable(event) - def test_group_is_hashable(self): - # Arrange - group = self.network.get_group("Audioscrobbler Beta") - - # Act/Assert - self.helper_is_thing_hashable(group) - def test_library_is_hashable(self): # Arrange library = pylast.Library(user=self.username, network=self.network) @@ -639,13 +632,6 @@ class TestPyLast(unittest.TestCase): # Assert self.helper_validate_results(result1, result2, result3) - def test_cacheable_group_get_members(self): - # Arrange - group = self.network.get_group("Audioscrobbler Beta") - - # Act/Assert - self.helper_validate_cacheable(group, "get_members") - def test_cacheable_library(self): # Arrange library = pylast.Library(self.username, self.network) @@ -987,15 +973,6 @@ class TestPyLast(unittest.TestCase): self.helper_assert_chart(album_chart, pylast.Album) self.helper_assert_chart(track_chart, pylast.Track) - def test_group_charts(self): - # Arrange - group = self.network.get_group("mnml") - dates = group.get_weekly_chart_dates() - self.helper_dates_valid(dates) - - # Act/Assert - self.helper_get_assert_charts(group, dates[-2]) - def test_tag_charts(self): # Arrange tag = self.network.get_tag("rock") From 61216f73c0b95dc4ac7760db8ab75a01f62b7108 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 25 Sep 2017 18:36:02 +0300 Subject: [PATCH 34/66] Remove dead Last.fm artist/user methods --- tests/test_pylast.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/test_pylast.py b/tests/test_pylast.py index 0146174..7cfbb6c 100755 --- a/tests/test_pylast.py +++ b/tests/test_pylast.py @@ -637,9 +637,7 @@ class TestPyLast(unittest.TestCase): library = pylast.Library(self.username, self.network) # Act/Assert - self.helper_validate_cacheable(library, "get_albums") self.helper_validate_cacheable(library, "get_artists") - self.helper_validate_cacheable(library, "get_tracks") def test_cacheable_user_artist_tracks(self): # Arrange @@ -660,11 +658,7 @@ class TestPyLast(unittest.TestCase): # Act/Assert self.helper_validate_cacheable(lastfm_user, "get_friends") self.helper_validate_cacheable(lastfm_user, "get_loved_tracks") - self.helper_validate_cacheable(lastfm_user, "get_neighbours") - self.helper_validate_cacheable(lastfm_user, "get_past_events") self.helper_validate_cacheable(lastfm_user, "get_recent_tracks") - self.helper_validate_cacheable(lastfm_user, "get_recommended_artists") - self.helper_validate_cacheable(lastfm_user, "get_recommended_events") def test_geo_get_events_in_location(self): # Arrange From 230439f52f1d539144252d1d81848880405c17e8 Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 26 Sep 2017 08:38:32 +0300 Subject: [PATCH 35/66] Remove dead Last.fm event/venu methods --- pylast/__init__.py | 367 +------------------------------------------ tests/test_pylast.py | 194 +---------------------- 2 files changed, 3 insertions(+), 558 deletions(-) diff --git a/pylast/__init__.py b/pylast/__init__.py index fe9b0bf..f31be7b 100644 --- a/pylast/__init__.py +++ b/pylast/__init__.py @@ -67,10 +67,6 @@ STATUS_INVALID_SIGNATURE = 13 STATUS_TOKEN_UNAUTHORIZED = 14 STATUS_TOKEN_EXPIRED = 15 -EVENT_ATTENDING = '0' -EVENT_MAYBE_ATTENDING = '1' -EVENT_NOT_ATTENDING = '2' - PERIOD_OVERALL = 'overall' PERIOD_7DAYS = '7day' PERIOD_1MONTH = '1month' @@ -336,50 +332,6 @@ class _Network(object): return seq - def get_geo_events( - self, longitude=None, latitude=None, location=None, distance=None, - tag=None, festivalsonly=None, limit=None, cacheable=True): - """ - Returns all events in a specific location by country or city name. - Parameters: - longitude (Optional) : Specifies a longitude value to retrieve events - for (service returns nearby events by default) - latitude (Optional) : Specifies a latitude value to retrieve events for - (service returns nearby events by default) - location (Optional) : Specifies a location to retrieve events for - (service returns nearby events by default) - distance (Optional) : Find events within a specified radius - (in kilometres) - tag (Optional) : Specifies a tag to filter by. - festivalsonly[0|1] (Optional) : Whether only festivals should be - returned, or all events. - limit (Optional) : The number of results to fetch per page. - Defaults to 10. - """ - - params = {} - - if longitude: - params["long"] = longitude - if latitude: - params["lat"] = latitude - if location: - params["location"] = location - if limit: - params["limit"] = limit - if distance: - params["distance"] = distance - if tag: - params["tag"] = tag - if festivalsonly: - params["festivalsonly"] = 1 - elif not festivalsonly: - params["festivalsonly"] = 0 - - doc = _Request(self, "geo.getEvents", params).execute(cacheable) - - return _extract_events_from_doc(doc, self) - def get_metro_weekly_chart_dates(self, cacheable=True): """ Returns a list of From and To tuples for the available metro charts. @@ -554,14 +506,6 @@ class _Network(object): return TrackSearch(artist_name, track_name, self) - def search_for_venue(self, venue_name, country_name): - """Searches of a venue by its name and its country. Set country_name to - an empty string if not available. - Returns a VenueSearch object. - Use get_next_page() to retrieve sequences of results.""" - - return VenueSearch(venue_name, country_name, self) - def get_track_by_mbid(self, mbid): """Looks up a track by its MusicBrainz ID""" @@ -755,7 +699,6 @@ class LastFMNetwork(_Network): urls={ "album": "music/%(artist)s/%(album)s", "artist": "music/%(artist)s", - "event": "event/%(id)s", "country": "place/%(country_name)s", "tag": "tag/%(name)s", "track": "music/%(artist)s/_/%(title)s", @@ -818,7 +761,6 @@ class LibreFMNetwork(_Network): urls={ "album": "artist/%(artist)s/album/%(album)s", "artist": "artist/%(artist)s", - "event": "event/%(id)s", "country": "place/%(country_name)s", "tag": "tag/%(name)s", "track": "music/%(artist)s/_/%(title)s", @@ -1195,7 +1137,7 @@ class _BaseObject(object): * users [User|str,]: A list that can contain usernames, emails, User objects, or all of them. * message str: A message to include in the recommendation message. - Only for Artist/Event/Track. + Only for Artist/Track. """ # Last.fm currently accepts a max of 10 recipient at a time @@ -1895,168 +1837,6 @@ class Artist(_BaseObject, _Taggable): return names -class Event(_BaseObject): - """An event.""" - - id = None - - __hash__ = _BaseObject.__hash__ - - def __init__(self, event_id, network): - _BaseObject.__init__(self, network, 'event') - - self.id = event_id - - def __repr__(self): - return "pylast.Event(%s, %s)" % (repr(self.id), repr(self.network)) - - @_string_output - def __str__(self): - return "Event #" + str(self.get_id()) - - def __eq__(self, other): - if type(self) is type(other): - return self.get_id() == other.get_id() - else: - return False - - def __ne__(self, other): - return not self.__eq__(other) - - def _get_params(self): - return {'event': self.get_id()} - - def attend(self, attending_status): - """Sets the attending status. - * attending_status: The attending status. Possible values: - o EVENT_ATTENDING - o EVENT_MAYBE_ATTENDING - o EVENT_NOT_ATTENDING - """ - - params = self._get_params() - params['status'] = attending_status - - self._request('event.attend', False, params) - - def get_attendees(self): - """ - Get a list of attendees for an event - """ - - doc = self._request("event.getAttendees", False) - - users = [] - for name in _extract_all(doc, "name"): - users.append(User(name, self.network)) - - return users - - def get_id(self): - """Returns the id of the event on the network. """ - - return self.id - - def get_title(self): - """Returns the title of the event. """ - - doc = self._request("event.getInfo", True) - - return _extract(doc, "title") - - def get_headliner(self): - """Returns the headliner of the event. """ - - doc = self._request("event.getInfo", True) - - return Artist(_extract(doc, "headliner"), self.network) - - def get_artists(self): - """Returns a list of the participating Artists. """ - - doc = self._request("event.getInfo", True) - names = _extract_all(doc, "artist") - - artists = [] - for name in names: - artists.append(Artist(name, self.network)) - - return artists - - def get_venue(self): - """Returns the venue where the event is held.""" - - doc = self._request("event.getInfo", True) - - v = doc.getElementsByTagName("venue")[0] - venue_id = _number(_extract(v, "id")) - - return Venue(venue_id, self.network, venue_element=v) - - def get_start_date(self): - """Returns the date when the event starts.""" - - doc = self._request("event.getInfo", True) - - return _extract(doc, "startDate") - - def get_description(self): - """Returns the description of the event. """ - - doc = self._request("event.getInfo", True) - - return _extract(doc, "description") - - def get_cover_image(self, size=COVER_MEGA): - """ - Returns a uri to the cover image - size can be one of: - COVER_MEGA - COVER_EXTRA_LARGE - COVER_LARGE - COVER_MEDIUM - COVER_SMALL - """ - - doc = self._request("event.getInfo", True) - - return _extract_all(doc, "image")[size] - - def get_attendance_count(self): - """Returns the number of attending people. """ - - doc = self._request("event.getInfo", True) - - return _number(_extract(doc, "attendance")) - - def get_review_count(self): - """Returns the number of available reviews for this event. """ - - doc = self._request("event.getInfo", True) - - return _number(_extract(doc, "reviews")) - - def get_url(self, domain_name=DOMAIN_ENGLISH): - """Returns the url of the event page on the network. - * domain_name: The network's language domain. Possible values: - o DOMAIN_ENGLISH - o DOMAIN_GERMAN - o DOMAIN_SPANISH - o DOMAIN_FRENCH - o DOMAIN_ITALIAN - o DOMAIN_POLISH - o DOMAIN_PORTUGUESE - o DOMAIN_SWEDISH - o DOMAIN_TURKISH - o DOMAIN_RUSSIAN - o DOMAIN_JAPANESE - o DOMAIN_CHINESE - """ - - return self.network._get_url( - domain_name, "event") % {'id': self.get_id()} - - class Country(_BaseObject): """A country at Last.fm.""" @@ -2110,7 +1890,7 @@ class Country(_BaseObject): "getTopTracks", "track", Track, params, cacheable) def get_url(self, domain_name=DOMAIN_ENGLISH): - """Returns the url of the event 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 @@ -2615,13 +2395,6 @@ class User(_BaseObject, _Chartable): return self.name - def get_upcoming_events(self): - """Returns all the upcoming events for this user.""" - - doc = self._request(self.ws_prefix + '.getEvents', True) - - return _extract_events_from_doc(doc, self.network) - def get_artist_tracks(self, artist, cacheable=False): """ Get a list of tracks by a given artist scrobbled by this user, @@ -2716,22 +2489,6 @@ class User(_BaseObject, _Chartable): return seq - def get_past_events(self, limit=50, cacheable=False): - """ - Returns a sequence of Event objects - if limit==None it will return all - """ - - seq = [] - for node in _collect_nodes( - limit, - self, - self.ws_prefix + ".getPastEvents", - cacheable): - seq.append(Event(_extract(node, "id"), self.network)) - - return seq - def get_now_playing(self): """ Returns the currently playing track, or None if nothing is playing. @@ -3086,19 +2843,6 @@ class AuthenticatedUser(User): self.name = _extract(doc, "name") return self.name - def get_recommended_events(self, limit=50, cacheable=False): - """ - Returns a sequence of Event objects - if limit==None it will return all - """ - - seq = [] - for node in _collect_nodes( - limit, self, "user.getRecommendedEvents", cacheable): - seq.append(Event(_extract(node, "id"), self.network)) - - return seq - def get_recommended_artists(self, limit=50, cacheable=False): """ Returns a sequence of Artist objects @@ -3247,106 +2991,6 @@ class TrackSearch(_Search): return seq -class VenueSearch(_Search): - """ - Search for a venue by its name. If you don't want to narrow the results - down by specifying a country, set it to empty string. - """ - - def __init__(self, venue_name, country_name, network): - - _Search.__init__( - self, - "venue", - {"venue": venue_name, "country": country_name}, - network) - - def get_next_page(self): - """Returns the next page of results as a sequence of Track objects.""" - - master_node = self._retrieve_next_page() - - seq = [] - for node in master_node.getElementsByTagName("venue"): - seq.append(Venue(_extract(node, "id"), self.network)) - - return seq - - -class Venue(_BaseObject): - """A venue where events are held.""" - - # TODO: waiting for a venue.getInfo web service to use. - # TODO: As an intermediate use case, can pass the venue DOM element when - # using Event.get_venue() to populate the venue info, if the venue.getInfo - # API call becomes available this workaround should be removed - - id = None - info = None - name = None - location = None - url = None - - __hash__ = _BaseObject.__hash__ - - def __init__(self, netword_id, network, venue_element=None): - _BaseObject.__init__(self, network, "venue") - - self.id = _number(netword_id) - if venue_element is not None: - self.info = _extract_element_tree(venue_element) - self.name = self.info.get('name') - self.url = self.info.get('url') - self.location = self.info.get('location') - - def __repr__(self): - return "pylast.Venue(%s, %s)" % (repr(self.id), repr(self.network)) - - @_string_output - def __str__(self): - return "Venue #" + str(self.id) - - def __eq__(self, other): - return self.get_id() == other.get_id() - - def _get_params(self): - return {self.ws_prefix: self.get_id()} - - def get_id(self): - """Returns the id of the venue.""" - - return self.id - - def get_name(self): - """Returns the name of the venue.""" - - return self.name - - def get_url(self): - """Returns the URL of the venue page.""" - - return self.url - - def get_location(self): - """Returns the location of the venue (dictionary).""" - - return self.location - - def get_upcoming_events(self): - """Returns the upcoming events in this venue.""" - - doc = self._request(self.ws_prefix + ".getEvents", True) - - return _extract_events_from_doc(doc, self.network) - - def get_past_events(self): - """Returns the past events held in this venue.""" - - doc = self._request(self.ws_prefix + ".getEvents", True) - - return _extract_events_from_doc(doc, self.network) - - def md5(text): """Returns the md5 hash of a string.""" @@ -3534,13 +3178,6 @@ def _extract_tracks(doc, network): return seq -def _extract_events_from_doc(doc, network): - events = [] - for node in doc.getElementsByTagName("event"): - events.append(Event(_extract(node, "id"), network)) - return events - - def _url_safe(text): """Does all kinds of tricks on a text to make it safe to use in a url.""" diff --git a/tests/test_pylast.py b/tests/test_pylast.py index 7cfbb6c..21671ff 100755 --- a/tests/test_pylast.py +++ b/tests/test_pylast.py @@ -73,18 +73,6 @@ class TestPyLast(unittest.TestCase): self.assertEqual(str(last_scrobble.track.title), str(title)) self.assertEqual(str(last_scrobble.timestamp), str(timestamp)) - def test_get_venue(self): - # Arrange - venue_name = "Last.fm Office" - country_name = "United Kingdom" - - # Act - venue_search = self.network.search_for_venue(venue_name, country_name) - venue = venue_search.get_next_page()[0] - - # Assert - self.assertEqual(str(venue.id), "8778225") - def test_get_user_registration(self): # Arrange username = "RJ" @@ -279,14 +267,6 @@ class TestPyLast(unittest.TestCase): # Act/Assert self.helper_is_thing_hashable(metro) - def test_event_is_hashable(self): - # Arrange - user = self.network.get_user("RJ") - event = user.get_past_events(limit=1)[0] - - # Act/Assert - self.helper_is_thing_hashable(event) - def test_library_is_hashable(self): # Arrange library = pylast.Library(user=self.username, network=self.network) @@ -317,14 +297,6 @@ class TestPyLast(unittest.TestCase): # Act/Assert self.helper_is_thing_hashable(user) - def test_venue_is_hashable(self): - # Arrange - venue_id = "8778225" # Last.fm office - venue = pylast.Venue(venue_id, self.network) - - # Act/Assert - self.helper_is_thing_hashable(venue) - def test_invalid_xml(self): # Arrange # Currently causes PCDATA invalid Char value 25 @@ -436,67 +408,6 @@ class TestPyLast(unittest.TestCase): # # Assert # self.assertGreaterEqual(len(tracks), 0) - def helper_past_events_have_valid_ids(self, thing): - # Act - events = thing.get_past_events() - - # Assert - self.helper_assert_events_have_valid_ids(events) - - def helper_upcoming_events_have_valid_ids(self, thing): - # Act - events = thing.get_upcoming_events() - - # Assert - self.helper_assert_events_have_valid_ids(events) - - def helper_assert_events_have_valid_ids(self, events): - # Assert - # If fails, add past/future event for user/Test Artist: - self.assertGreaterEqual(len(events), 1) - for event in events[:2]: # checking first two should be enough - self.assertIsInstance(event.get_headliner(), pylast.Artist) - - def test_user_past_events_returns_valid_ids(self): - # Arrange - lastfm_user = self.network.get_user(self.username) - - # Act/Assert - self.helper_past_events_have_valid_ids(lastfm_user) - - def test_user_recommended_events_returns_valid_ids(self): - # Arrange - lastfm_user = self.network.get_user(self.username) - - # Act - events = lastfm_user.get_upcoming_events() - - # Assert - self.helper_assert_events_have_valid_ids(events) - - def test_user_upcoming_events_returns_valid_ids(self): - # Arrange - lastfm_user = self.network.get_user(self.username) - - # Act/Assert - self.helper_upcoming_events_have_valid_ids(lastfm_user) - - def test_venue_past_events_returns_valid_ids(self): - # Arrange - venue_id = "8778225" # Last.fm office - venue = pylast.Venue(venue_id, self.network) - - # Act/Assert - self.helper_past_events_have_valid_ids(venue) - - def test_venue_upcoming_events_returns_valid_ids(self): - # Arrange - venue_id = "8778225" # Last.fm office - venue = pylast.Venue(venue_id, self.network) - - # Act/Assert - self.helper_upcoming_events_have_valid_ids(venue) - def test_pickle(self): # Arrange import pickle @@ -660,43 +571,6 @@ class TestPyLast(unittest.TestCase): self.helper_validate_cacheable(lastfm_user, "get_loved_tracks") self.helper_validate_cacheable(lastfm_user, "get_recent_tracks") - def test_geo_get_events_in_location(self): - # Arrange - # Act - events = self.network.get_geo_events( - location="London", tag="blues", limit=1) - - # Assert - self.assertEqual(len(events), 1) - event = events[0] - self.assertIsInstance(event, pylast.Event) - self.assertIn(event.get_venue().location['city'], - ["London", "Camden"]) - - def test_geo_get_events_in_latlong(self): - # Arrange - # Act - events = self.network.get_geo_events( - latitude=53.466667, longitude=-2.233333, distance=5, limit=1) - - # Assert - self.assertEqual(len(events), 1) - event = events[0] - self.assertIsInstance(event, pylast.Event) - self.assertEqual(event.get_venue().location['city'], "Manchester") - - def test_geo_get_events_festival(self): - # Arrange - # Act - events = self.network.get_geo_events( - location="Reading", festivalsonly=True, limit=1) - - # Assert - self.assertEqual(len(events), 1) - event = events[0] - self.assertIsInstance(event, pylast.Event) - self.assertEqual(event.get_venue().location['city'], "Reading") - def helper_dates_valid(self, dates): # Assert self.assertGreaterEqual(len(dates), 1) @@ -992,17 +866,15 @@ class TestPyLast(unittest.TestCase): # spam_message = "Dig the krazee sound!" # artist = self.network.get_top_artists(limit=1)[0].item # track = artist.get_top_tracks(limit=1)[0].item - # event = artist.get_upcoming_events()[0] # # Act # artist.share(users_to_spam, spam_message) # track.share(users_to_spam, spam_message) - # event.share(users_to_spam, spam_message) # Assert # Check inbox for spam! - # album/artist/event/track/user + # album/artist/track/user def test_album_data(self): # Arrange @@ -1217,18 +1089,6 @@ class TestPyLast(unittest.TestCase): self.assertIsInstance(count, int) self.assertGreater(count, 0) - def test_event_attendees(self): - # Arrange - user = self.network.get_user("RJ") - event = user.get_past_events(limit=1)[0] - - # Act - users = event.get_attendees() - - # Assert - self.assertIsInstance(users, list) - self.assertIsInstance(users[0], pylast.User) - def test_tag_artist(self): # Arrange artist = self.network.get_artist("Test Artist") @@ -1443,40 +1303,6 @@ class TestPyLast(unittest.TestCase): self.assertEqual(mbid, "a74b1b7f-71a5-4011-9441-d0b5e4122711") self.assertIsInstance(streamable, bool) - def test_events(self): - # Arrange - event_id_1 = 3162700 # Glasto 2013 - event_id_2 = 3478520 # Glasto 2014 - event1 = pylast.Event(event_id_1, self.network) - event2 = pylast.Event(event_id_2, self.network) - - # Act - text = str(event1) - rep = repr(event1) - title = event1.get_title() - artists = event1.get_artists() - start = event1.get_start_date() - description = event1.get_description() - review_count = event1.get_review_count() - attendance_count = event1.get_attendance_count() - - # Assert - self.assertIn("3162700", rep) - self.assertIn("pylast.Event", rep) - self.assertEqual(text, "Event #3162700") - self.assertTrue(event1 != event2) - self.assertIn("Glastonbury", title) - found = False - for artist in artists: - if artist.name == "The Rolling Stones": - found = True - break - self.assertTrue(found) - self.assertIn("Wed, 26 Jun 2013", start) - self.assertIn("astonishing bundle", description) - self.assertGreater(review_count, 0) - self.assertGreater(attendance_count, 100) - def test_countries(self): # Arrange country1 = pylast.Country("Italy", self.network) @@ -1543,24 +1369,6 @@ class TestPyLast(unittest.TestCase): # Act / Assert self.assertTrue(album1 != album2) - def test_event_eq_none_is_false(self): - # Arrange - event1 = None - event_id = 3478520 # Glasto 2014 - event2 = pylast.Event(event_id, self.network) - - # Act / Assert - self.assertFalse(event1 == event2) - - def test_event_ne_none_is_true(self): - # Arrange - event1 = None - event_id = 3478520 # Glasto 2014 - event2 = pylast.Event(event_id, self.network) - - # Act / Assert - self.assertTrue(event1 != event2) - def test_band_members(self): # Arrange artist = pylast.Artist("The Beatles", self.network) From 27ba0dd6b3dbe79a2b77c8e4a0302c1767782100 Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 26 Sep 2017 08:58:37 +0300 Subject: [PATCH 36/66] Remove dead Last.fm metro methods --- pylast/__init__.py | 210 ------------------------------------------- tests/test_pylast.py | 82 ----------------- 2 files changed, 292 deletions(-) diff --git a/pylast/__init__.py b/pylast/__init__.py index f31be7b..67e1533 100644 --- a/pylast/__init__.py +++ b/pylast/__init__.py @@ -230,13 +230,6 @@ class _Network(object): return Country(country_name, self) - def get_metro(self, metro_name, country_name): - """ - Returns a metro object - """ - - return Metro(metro_name, country_name, self) - def get_user(self, username): """ Returns a user object @@ -332,46 +325,6 @@ class _Network(object): return seq - def get_metro_weekly_chart_dates(self, cacheable=True): - """ - Returns a list of From and To tuples for the available metro charts. - """ - - doc = _Request(self, "geo.getMetroWeeklyChartlist").execute(cacheable) - - seq = [] - for node in doc.getElementsByTagName("chart"): - seq.append((node.getAttribute("from"), node.getAttribute("to"))) - - return seq - - def get_metros(self, country=None, cacheable=True): - """ - Get a list of valid countries and metros for use in the other - webservices. - Parameters: - country (Optional) : Optionally restrict the results to those Metros - from a particular country, as defined by the ISO 3166-1 country - names standard. - """ - params = {} - - if country: - params["country"] = country - - doc = _Request(self, "geo.getMetros", params).execute(cacheable) - - metros = doc.getElementsByTagName("metro") - seq = [] - - for metro in metros: - name = _extract(metro, "name") - country = _extract(metro, "country") - - seq.append(Metro(name, country, self)) - - return seq - def get_geo_top_artists(self, country, limit=None, cacheable=True): """Get the most popular artists on Last.fm by country. Parameters: @@ -1912,169 +1865,6 @@ class Country(_BaseObject): domain_name, "country") % {'country_name': country_name} -class Metro(_BaseObject): - """A metro at Last.fm.""" - - name = None - country = None - - __hash__ = _BaseObject.__hash__ - - def __init__(self, name, country, network): - _BaseObject.__init__(self, network, None) - - self.name = name - self.country = country - - def __repr__(self): - return "pylast.Metro(%s, %s, %s)" % ( - repr(self.name), repr(self.country), repr(self.network)) - - @_string_output - def __str__(self): - return self.get_name() + ", " + self.get_country() - - def __eq__(self, other): - return (self.get_name().lower() == other.get_name().lower() and - self.get_country().lower() == other.get_country().lower()) - - def __ne__(self, other): - return (self.get_name() != other.get_name() or - self.get_country().lower() != other.get_country().lower()) - - def _get_params(self): - return {'metro': self.get_name(), 'country': self.get_country()} - - def get_name(self): - """Returns the metro name.""" - - return self.name - - def get_country(self): - """Returns the metro country.""" - - return self.country - - def _get_chart( - self, method, tag="artist", limit=None, from_date=None, - to_date=None, cacheable=True): - """Internal helper for getting geo charts.""" - params = self._get_params() - if limit: - params["limit"] = limit - if from_date and to_date: - params["from"] = from_date - params["to"] = to_date - - doc = self._request(method, cacheable, params) - - seq = [] - for node in doc.getElementsByTagName(tag): - if tag == "artist": - item = Artist(_extract(node, "name"), self.network) - elif tag == "track": - title = _extract(node, "name") - artist = _extract_element_tree(node).get('artist')['name'] - item = Track(artist, title, self.network) - else: - return None - weight = _number(_extract(node, "listeners")) - seq.append(TopItem(item, weight)) - - return seq - - def get_artist_chart( - self, tag="artist", limit=None, from_date=None, to_date=None, - cacheable=True): - """Get a chart of artists for a metro. - Parameters: - from_date (Optional) : Beginning timestamp of the weekly range - requested - to_date (Optional) : Ending timestamp of the weekly range requested - limit (Optional) : The number of results to fetch per page. - Defaults to 50. - """ - return self._get_chart( - "geo.getMetroArtistChart", tag=tag, limit=limit, - from_date=from_date, to_date=to_date, cacheable=cacheable) - - def get_hype_artist_chart( - self, tag="artist", limit=None, from_date=None, to_date=None, - cacheable=True): - """Get a chart of hyped (up and coming) artists for a metro. - Parameters: - from_date (Optional) : Beginning timestamp of the weekly range - requested - to_date (Optional) : Ending timestamp of the weekly range requested - limit (Optional) : The number of results to fetch per page. - Defaults to 50. - """ - return self._get_chart( - "geo.getMetroHypeArtistChart", tag=tag, limit=limit, - from_date=from_date, to_date=to_date, cacheable=cacheable) - - def get_unique_artist_chart( - self, tag="artist", limit=None, from_date=None, to_date=None, - cacheable=True): - """Get a chart of the artists which make that metro unique. - Parameters: - from_date (Optional) : Beginning timestamp of the weekly range - requested - to_date (Optional) : Ending timestamp of the weekly range requested - limit (Optional) : The number of results to fetch per page. - Defaults to 50. - """ - return self._get_chart( - "geo.getMetroUniqueArtistChart", tag=tag, limit=limit, - from_date=from_date, to_date=to_date, cacheable=cacheable) - - def get_track_chart( - self, tag="track", limit=None, from_date=None, to_date=None, - cacheable=True): - """Get a chart of tracks for a metro. - Parameters: - from_date (Optional) : Beginning timestamp of the weekly range - requested - to_date (Optional) : Ending timestamp of the weekly range requested - limit (Optional) : The number of results to fetch per page. - Defaults to 50. - """ - return self._get_chart( - "geo.getMetroTrackChart", tag=tag, limit=limit, - from_date=from_date, to_date=to_date, cacheable=cacheable) - - def get_hype_track_chart( - self, tag="track", limit=None, from_date=None, to_date=None, - cacheable=True): - """Get a chart of tracks for a metro. - Parameters: - from_date (Optional) : Beginning timestamp of the weekly range - requested - to_date (Optional) : Ending timestamp of the weekly range requested - limit (Optional) : The number of results to fetch per page. - Defaults to 50. - """ - return self._get_chart( - "geo.getMetroHypeTrackChart", tag=tag, - limit=limit, from_date=from_date, to_date=to_date, - cacheable=cacheable) - - def get_unique_track_chart( - self, tag="track", limit=None, from_date=None, to_date=None, - cacheable=True): - """Get a chart of tracks for a metro. - Parameters: - from_date (Optional) : Beginning timestamp of the weekly range - requested - to_date (Optional) : Ending timestamp of the weekly range requested - limit (Optional) : The number of results to fetch per page. - Defaults to 50. - """ - return self._get_chart( - "geo.getMetroUniqueTrackChart", tag=tag, limit=limit, - from_date=from_date, to_date=to_date, cacheable=cacheable) - - class Library(_BaseObject): """A user's Last.fm library.""" diff --git a/tests/test_pylast.py b/tests/test_pylast.py index 21671ff..0281631 100755 --- a/tests/test_pylast.py +++ b/tests/test_pylast.py @@ -260,13 +260,6 @@ class TestPyLast(unittest.TestCase): # Act/Assert self.helper_is_thing_hashable(country) - def test_metro_is_hashable(self): - # Arrange - metro = self.network.get_metro("Helsinki", "Finland") - - # Act/Assert - self.helper_is_thing_hashable(metro) - def test_library_is_hashable(self): # Arrange library = pylast.Library(user=self.username, network=self.network) @@ -578,67 +571,6 @@ class TestPyLast(unittest.TestCase): (start, end) = dates[0] self.assertLess(start, end) - def test_get_metro_weekly_chart_dates(self): - # Arrange - # Act - dates = self.network.get_metro_weekly_chart_dates() - - # Assert - self.helper_dates_valid(dates) - - def helper_geo_chart(self, function_name, expected_type=pylast.Artist): - # Arrange - metro = self.network.get_metro("Madrid", "Spain") - dates = self.network.get_metro_weekly_chart_dates() - (from_date, to_date) = dates[0] - - # get metro.function_name() - func = getattr(metro, function_name, None) - - # Act - chart = func(from_date=from_date, to_date=to_date, limit=1) - - # Assert - self.assertEqual(len(chart), 1) - self.assertIsInstance(chart[0], pylast.TopItem) - self.assertIsInstance(chart[0].item, expected_type) - - def test_get_metro_artist_chart(self): - # Arrange/Act/Assert - self.helper_geo_chart("get_artist_chart") - - def test_get_metro_hype_artist_chart(self): - # Arrange/Act/Assert - self.helper_geo_chart("get_hype_artist_chart") - - def test_get_metro_unique_artist_chart(self): - # Arrange/Act/Assert - self.helper_geo_chart("get_unique_artist_chart") - - def test_get_metro_track_chart(self): - # Arrange/Act/Assert - self.helper_geo_chart("get_track_chart", expected_type=pylast.Track) - - def test_get_metro_hype_track_chart(self): - # Arrange/Act/Assert - self.helper_geo_chart( - "get_hype_track_chart", expected_type=pylast.Track) - - def test_get_metro_unique_track_chart(self): - # Arrange/Act/Assert - self.helper_geo_chart( - "get_unique_track_chart", expected_type=pylast.Track) - - def test_geo_get_metros(self): - # Arrange - # Act - metros = self.network.get_metros(country="Poland") - - # Assert - self.assertGreaterEqual(len(metros), 1) - self.assertIsInstance(metros[0], pylast.Metro) - self.assertEqual(metros[0].get_country(), "Poland") - def test_geo_get_top_artists(self): # Arrange # Act @@ -661,20 +593,6 @@ class TestPyLast(unittest.TestCase): self.assertIsInstance(tracks[0], pylast.TopItem) self.assertIsInstance(tracks[0].item, pylast.Track) - def test_metro_class(self): - # Arrange - # Act - metro = self.network.get_metro("Bergen", "Norway") - - # Assert - self.assertEqual(metro.get_name(), "Bergen") - self.assertEqual(metro.get_country(), "Norway") - self.assertEqual(str(metro), "Bergen, Norway") - self.assertEqual(metro, pylast.Metro("Bergen", "Norway", self.network)) - self.assertNotEqual( - metro, - pylast.Metro("Wellington", "New Zealand", self.network)) - def helper_at_least_one_thing_in_top_list(self, things, expected_type): # Assert self.assertGreater(len(things), 1) From ec68660014d4ef9935f53a9b8331850d79b3e7bb Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 26 Sep 2017 09:08:40 +0300 Subject: [PATCH 37/66] Remove dead Last.fm chart test --- tests/test_pylast.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/tests/test_pylast.py b/tests/test_pylast.py index 0281631..ff92784 100755 --- a/tests/test_pylast.py +++ b/tests/test_pylast.py @@ -759,15 +759,6 @@ class TestPyLast(unittest.TestCase): self.helper_assert_chart(album_chart, pylast.Album) self.helper_assert_chart(track_chart, pylast.Track) - def test_tag_charts(self): - # Arrange - tag = self.network.get_tag("rock") - dates = tag.get_weekly_chart_dates() - self.helper_dates_valid(dates) - - # Act/Assert - self.helper_get_assert_charts(tag, dates[-2]) - def test_user_charts(self): # Arrange lastfm_user = self.network.get_user("RJ") From 0eac6e9ae20127712cd0ba17080760c501b058ef Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 26 Sep 2017 09:14:24 +0300 Subject: [PATCH 38/66] Remove redundant functions --- pylast/__init__.py | 31 ------------------------------- tests/test_pylast.py | 9 --------- 2 files changed, 40 deletions(-) diff --git a/pylast/__init__.py b/pylast/__init__.py index 67e1533..333768b 100644 --- a/pylast/__init__.py +++ b/pylast/__init__.py @@ -2873,37 +2873,6 @@ def _extract(node, name, index=0): return None -def _extract_element_tree(node): - """Extract an element tree into a multi-level dictionary - - NB: If any elements have text nodes as well as nested - elements this will ignore the text nodes""" - - def _recurse_build_tree(rootNode, targetDict): - """Recursively build a multi-level dict""" - - def _has_child_elements(rootNode): - """Check if an element has any nested (child) elements""" - - for node in rootNode.childNodes: - if node.nodeType == node.ELEMENT_NODE: - return True - return False - - for node in rootNode.childNodes: - if node.nodeType == node.ELEMENT_NODE: - if _has_child_elements(node): - targetDict[node.tagName] = {} - _recurse_build_tree(node, targetDict[node.tagName]) - else: - val = None if node.firstChild is None else \ - _unescape_htmlentity(node.firstChild.data.strip()) - targetDict[node.tagName] = val - return targetDict - - return _recurse_build_tree(node, {}) - - def _extract_all(node, name, limit_count=None): """Extracts all the values from the xml string. returning a list.""" diff --git a/tests/test_pylast.py b/tests/test_pylast.py index ff92784..91c51d6 100755 --- a/tests/test_pylast.py +++ b/tests/test_pylast.py @@ -624,15 +624,6 @@ class TestPyLast(unittest.TestCase): self.assertIsInstance(thing2.item, expected_type) self.assertNotEqual(thing1, thing2) - def helper_two_things_in_list(self, things, expected_type): - # Assert - self.assertEqual(len(things), 2) - self.assertIsInstance(things, list) - thing1 = things[0] - thing2 = things[1] - self.assertIsInstance(thing1, expected_type) - self.assertIsInstance(thing2, expected_type) - def test_user_get_top_tags_with_limit(self): # Arrange user = self.network.get_user("RJ") From a8522fded3c6a6904b9b9c93e6578c3751229a38 Mon Sep 17 00:00:00 2001 From: Hugo Date: Tue, 26 Sep 2017 11:04:02 +0300 Subject: [PATCH 39/66] Bring back test_caching on another method --- tests/test_pylast.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/test_pylast.py b/tests/test_pylast.py index 91c51d6..6b069b8 100755 --- a/tests/test_pylast.py +++ b/tests/test_pylast.py @@ -941,6 +941,21 @@ class TestPyLast(unittest.TestCase): # Assert self.assertIsInstance(library, pylast.Library) + def test_caching(self): + # Arrange + user = self.network.get_user("RJ") + + # Act + self.network.enable_caching() + tags1 = user.get_top_tags(limit=1, cacheable=True) + tags2 = user.get_top_tags(limit=1, cacheable=True) + + # Assert + self.assertTrue(self.network.is_caching_enabled()) + self.assertEqual(tags1, tags2) + self.network.disable_caching() + self.assertFalse(self.network.is_caching_enabled()) + def test_album_mbid(self): # Arrange mbid = "a6a265bf-9f81-4055-8224-f7ac0aa6b937" From 25f419204a526db9ca8493ad4c68599d2ae21c07 Mon Sep 17 00:00:00 2001 From: Hugo Date: Tue, 26 Sep 2017 18:01:17 +0300 Subject: [PATCH 40/66] Start tests refactor --- tests/test_pylast.py | 135 ++++++++++++++++++++++++------------------- 1 file changed, 75 insertions(+), 60 deletions(-) diff --git a/tests/test_pylast.py b/tests/test_pylast.py index 6b069b8..3d8ad83 100755 --- a/tests/test_pylast.py +++ b/tests/test_pylast.py @@ -30,7 +30,7 @@ def load_secrets(): @flaky(max_runs=5, min_passes=1) -class TestPyLast(unittest.TestCase): +class PyLastTestCase(unittest.TestCase): secrets = None @@ -56,6 +56,23 @@ class TestPyLast(unittest.TestCase): if value is None or len(value) == 0: pytest.skip("Last.fm API is broken.") + +class TestPyLastAlbum(PyLastTestCase): + + def test_album_tags_are_topitems(self): + # Arrange + albums = self.network.get_user('RJ').get_top_albums() + + # Act + tags = albums[0].item.get_top_tags(limit=1) + + # Assert + self.assertGreater(len(tags), 0) + self.assertIsInstance(tags[0], pylast.TopItem) + + +class TestPyLastNetwork(PyLastTestCase): + def test_scrobble(self): # Arrange artist = "Test Artist" @@ -73,6 +90,62 @@ class TestPyLast(unittest.TestCase): self.assertEqual(str(last_scrobble.track.title), str(title)) self.assertEqual(str(last_scrobble.timestamp), str(timestamp)) + def test_update_now_playing(self): + # Arrange + artist = "Test Artist" + title = "test title" + album = "Test Album" + track_number = 1 + lastfm_user = self.network.get_user(self.username) + + # Act + self.network.update_now_playing( + artist=artist, title=title, album=album, track_number=track_number) + + # Assert + current_track = lastfm_user.get_now_playing() + self.assertIsNotNone(current_track) + self.assertEqual(str(current_track.title), "test title") + self.assertEqual(str(current_track.artist), "Test Artist") + + +class TestPyLastTrack(PyLastTestCase): + + def test_love(self): + # Arrange + artist = "Test Artist" + title = "test title" + track = self.network.get_track(artist, title) + lastfm_user = self.network.get_user(self.username) + + # Act + track.love() + + # Assert + loved = lastfm_user.get_loved_tracks(limit=1) + self.assertEqual(str(loved[0].track.artist), "Test Artist") + self.assertEqual(str(loved[0].track.title), "test title") + + def test_unlove(self): + # Arrange + artist = pylast.Artist("Test Artist", self.network) + title = "test title" + track = pylast.Track(artist, title, self.network) + lastfm_user = self.network.get_user(self.username) + track.love() + + # Act + track.unlove() + + # Assert + loved = lastfm_user.get_loved_tracks(limit=1) + if len(loved): # OK to be empty but if not: + self.assertNotEqual(str(loved.track.artist), "Test Artist") + self.assertNotEqual(str(loved.track.title), "test title") + + +class TestPyLastUser(PyLastTestCase): + def test_get_user_registration(self): # Arrange username = "RJ" @@ -123,38 +196,6 @@ class TestPyLast(unittest.TestCase): # Assert self.assertIsNone(country) - def test_love(self): - # Arrange - artist = "Test Artist" - title = "test title" - track = self.network.get_track(artist, title) - lastfm_user = self.network.get_user(self.username) - - # Act - track.love() - - # Assert - loved = lastfm_user.get_loved_tracks(limit=1) - self.assertEqual(str(loved[0].track.artist), "Test Artist") - self.assertEqual(str(loved[0].track.title), "test title") - - def test_unlove(self): - # Arrange - artist = pylast.Artist("Test Artist", self.network) - title = "test title" - track = pylast.Track(artist, title, self.network) - lastfm_user = self.network.get_user(self.username) - track.love() - - # Act - track.unlove() - - # Assert - loved = lastfm_user.get_loved_tracks(limit=1) - if len(loved): # OK to be empty but if not: - self.assertNotEqual(str(loved.track.artist), "Test Artist") - self.assertNotEqual(str(loved.track.title), "test title") - def test_user_equals_none(self): # Arrange lastfm_user = self.network.get_user(self.username) @@ -197,34 +238,8 @@ class TestPyLast(unittest.TestCase): self.assertGreaterEqual(len(user.get_loved_tracks(limit=None)), 23) self.assertGreaterEqual(len(user.get_loved_tracks(limit=0)), 23) - def test_update_now_playing(self): - # Arrange - artist = "Test Artist" - title = "test title" - album = "Test Album" - track_number = 1 - lastfm_user = self.network.get_user(self.username) - # Act - self.network.update_now_playing( - artist=artist, title=title, album=album, track_number=track_number) - - # Assert - current_track = lastfm_user.get_now_playing() - self.assertIsNotNone(current_track) - self.assertEqual(str(current_track.title), "test title") - self.assertEqual(str(current_track.artist), "Test Artist") - - def test_album_tags_are_topitems(self): - # Arrange - albums = self.network.get_user('RJ').get_top_albums() - - # Act - tags = albums[0].item.get_top_tags(limit=1) - - # Assert - self.assertGreater(len(tags), 0) - self.assertIsInstance(tags[0], pylast.TopItem) +class TestPyLast(PyLastTestCase): def helper_is_thing_hashable(self, thing): # Arrange From 31aeb6e69a8f2f416dd8dcad50ccecdaf9b846ad Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 17 Oct 2017 21:45:20 +0300 Subject: [PATCH 41/66] Continue tests refactor --- tests/test_pylast.py | 2011 +++++++++++++++++++++--------------------- 1 file changed, 1010 insertions(+), 1001 deletions(-) diff --git a/tests/test_pylast.py b/tests/test_pylast.py index 3d8ad83..2342e02 100755 --- a/tests/test_pylast.py +++ b/tests/test_pylast.py @@ -56,6 +56,72 @@ class PyLastTestCase(unittest.TestCase): if value is None or len(value) == 0: pytest.skip("Last.fm API is broken.") + def helper_is_thing_hashable(self, thing): + # Arrange + things = set() + + # Act + things.add(thing) + + # Assert + self.assertIsNotNone(thing) + self.assertEqual(len(things), 1) + + def helper_validate_results(self, a, b, c): + # Assert + self.assertIsNotNone(a) + self.assertIsNotNone(b) + self.assertIsNotNone(c) + self.assertGreaterEqual(len(a), 0) + self.assertGreaterEqual(len(b), 0) + self.assertGreaterEqual(len(c), 0) + self.assertEqual(a, b) + self.assertEqual(b, c) + + def helper_validate_cacheable(self, thing, function_name): + # Arrange + # get thing.function_name() + func = getattr(thing, function_name, None) + + # Act + result1 = func(limit=1, cacheable=False) + result2 = func(limit=1, cacheable=True) + result3 = func(limit=1) + + # Assert + self.helper_validate_results(result1, result2, result3) + + def helper_at_least_one_thing_in_top_list(self, things, expected_type): + # Assert + self.assertGreater(len(things), 1) + self.assertIsInstance(things, list) + self.assertIsInstance(things[0], pylast.TopItem) + self.assertIsInstance(things[0].item, expected_type) + + def helper_only_one_thing_in_top_list(self, things, expected_type): + # Assert + self.assertEqual(len(things), 1) + self.assertIsInstance(things, list) + 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) + thing1 = things[0] + thing2 = things[1] + self.assertIsInstance(thing1, pylast.TopItem) + self.assertIsInstance(thing2, pylast.TopItem) + self.assertIsInstance(thing1.item, expected_type) + self.assertIsInstance(thing2.item, expected_type) + self.assertNotEqual(thing1, thing2) + class TestPyLastAlbum(PyLastTestCase): @@ -70,188 +136,6 @@ class TestPyLastAlbum(PyLastTestCase): self.assertGreater(len(tags), 0) self.assertIsInstance(tags[0], pylast.TopItem) - -class TestPyLastNetwork(PyLastTestCase): - - def test_scrobble(self): - # Arrange - artist = "Test Artist" - title = "test title" - timestamp = self.unix_timestamp() - lastfm_user = self.network.get_user(self.username) - - # Act - self.network.scrobble(artist=artist, title=title, timestamp=timestamp) - - # Assert - # limit=2 to ignore now-playing: - last_scrobble = lastfm_user.get_recent_tracks(limit=2)[0] - self.assertEqual(str(last_scrobble.track.artist), str(artist)) - self.assertEqual(str(last_scrobble.track.title), str(title)) - self.assertEqual(str(last_scrobble.timestamp), str(timestamp)) - - def test_update_now_playing(self): - # Arrange - artist = "Test Artist" - title = "test title" - album = "Test Album" - track_number = 1 - lastfm_user = self.network.get_user(self.username) - - # Act - self.network.update_now_playing( - artist=artist, title=title, album=album, track_number=track_number) - - # Assert - current_track = lastfm_user.get_now_playing() - self.assertIsNotNone(current_track) - self.assertEqual(str(current_track.title), "test title") - self.assertEqual(str(current_track.artist), "Test Artist") - - -class TestPyLastTrack(PyLastTestCase): - - def test_love(self): - # Arrange - artist = "Test Artist" - title = "test title" - track = self.network.get_track(artist, title) - lastfm_user = self.network.get_user(self.username) - - # Act - track.love() - - # Assert - loved = lastfm_user.get_loved_tracks(limit=1) - self.assertEqual(str(loved[0].track.artist), "Test Artist") - self.assertEqual(str(loved[0].track.title), "test title") - - def test_unlove(self): - # Arrange - artist = pylast.Artist("Test Artist", self.network) - title = "test title" - track = pylast.Track(artist, title, self.network) - lastfm_user = self.network.get_user(self.username) - track.love() - - # Act - track.unlove() - - # Assert - loved = lastfm_user.get_loved_tracks(limit=1) - if len(loved): # OK to be empty but if not: - self.assertNotEqual(str(loved.track.artist), "Test Artist") - self.assertNotEqual(str(loved.track.title), "test title") - - -class TestPyLastUser(PyLastTestCase): - - def test_get_user_registration(self): - # Arrange - username = "RJ" - user = self.network.get_user(username) - - # Act - registered = user.get_registered() - - # Assert - # Last.fm API broken? Should be yyyy-mm-dd not Unix timestamp - if int(registered): - pytest.skip("Last.fm API is broken.") - - # Just check date because of timezones - self.assertIn(u"2002-11-20 ", registered) - - def test_get_user_unixtime_registration(self): - # Arrange - username = "RJ" - user = self.network.get_user(username) - - # Act - unixtime_registered = user.get_unixtime_registered() - - # Assert - # Just check date because of timezones - self.assertEqual(unixtime_registered, u"1037793040") - - def test_get_genderless_user(self): - # Arrange - # Currently test_user has no gender set: - lastfm_user = self.network.get_user("test_user") - - # Act - gender = lastfm_user.get_gender() - - # Assert - self.assertIsNone(gender) - - def test_get_countryless_user(self): - # Arrange - # Currently test_user has no country set: - lastfm_user = self.network.get_user("test_user") - - # Act - country = lastfm_user.get_country() - - # Assert - self.assertIsNone(country) - - def test_user_equals_none(self): - # Arrange - lastfm_user = self.network.get_user(self.username) - - # Act - value = (lastfm_user is None) - - # Assert - self.assertFalse(value) - - def test_user_not_equal_to_none(self): - # Arrange - lastfm_user = self.network.get_user(self.username) - - # Act - value = (lastfm_user is not None) - - # Assert - self.assertTrue(value) - - def test_now_playing_user_with_no_scrobbles(self): - # Arrange - # Currently test-account has no scrobbles: - user = self.network.get_user('test-account') - - # Act - current_track = user.get_now_playing() - - # Assert - self.assertIsNone(current_track) - - def test_love_limits(self): - # Arrange - # Currently test-account has at least 23 loved tracks: - user = self.network.get_user("test-user") - - # Act/Assert - self.assertEqual(len(user.get_loved_tracks(limit=20)), 20) - self.assertLessEqual(len(user.get_loved_tracks(limit=100)), 100) - self.assertGreaterEqual(len(user.get_loved_tracks(limit=None)), 23) - self.assertGreaterEqual(len(user.get_loved_tracks(limit=0)), 23) - - -class TestPyLast(PyLastTestCase): - - def helper_is_thing_hashable(self, thing): - # Arrange - things = set() - - # Act - things.add(thing) - - # Assert - self.assertIsNotNone(thing) - self.assertEqual(len(things), 1) - def test_album_is_hashable(self): # Arrange album = self.network.get_album("Test Artist", "Test Album") @@ -259,96 +143,6 @@ class TestPyLast(PyLastTestCase): # Act/Assert self.helper_is_thing_hashable(album) - def test_artist_is_hashable(self): - # Arrange - test_artist = self.network.get_artist("Test Artist") - artist = test_artist.get_similar(limit=2)[0].item - self.assertIsInstance(artist, pylast.Artist) - - # Act/Assert - self.helper_is_thing_hashable(artist) - - def test_country_is_hashable(self): - # Arrange - country = self.network.get_country("Italy") - - # Act/Assert - self.helper_is_thing_hashable(country) - - def test_library_is_hashable(self): - # Arrange - library = pylast.Library(user=self.username, network=self.network) - - # Act/Assert - self.helper_is_thing_hashable(library) - - def test_tag_is_hashable(self): - # Arrange - tag = self.network.get_top_tags(limit=1)[0] - - # Act/Assert - self.helper_is_thing_hashable(tag) - - def test_track_is_hashable(self): - # Arrange - artist = self.network.get_artist("Test Artist") - track = artist.get_top_tracks()[0].item - self.assertIsInstance(track, pylast.Track) - - # Act/Assert - self.helper_is_thing_hashable(track) - - def test_user_is_hashable(self): - # Arrange - user = self.network.get_user(self.username) - - # Act/Assert - self.helper_is_thing_hashable(user) - - def test_invalid_xml(self): - # Arrange - # Currently causes PCDATA invalid Char value 25 - artist = "Blind Willie Johnson" - title = "It's nobody's fault but mine" - - # Act - search = self.network.search_for_track(artist, title) - total = search.get_total_result_count() - - # Assert - self.skip_if_lastfm_api_broken(total) - self.assertGreaterEqual(int(total), 0) - - def test_user_play_count_in_track_info(self): - # Arrange - artist = "Test Artist" - title = "test title" - track = pylast.Track( - artist=artist, title=title, - network=self.network, username=self.username) - - # Act - count = track.get_userplaycount() - - # Assert - self.assertGreaterEqual(count, 0) - - def test_user_loved_in_track_info(self): - # Arrange - artist = "Test Artist" - title = "test title" - track = pylast.Track( - artist=artist, title=title, - network=self.network, username=self.username) - - # Act - loved = track.get_userloved() - - # Assert - self.assertIsNotNone(loved) - self.assertIsInstance(loved, bool) - self.assertNotIsInstance(loved, str) - def test_album_in_recent_tracks(self): # Arrange lastfm_user = self.network.get_user(self.username) @@ -370,101 +164,6 @@ class TestPyLast(PyLastTestCase): # Assert self.assertTrue(hasattr(track, 'album')) - def test_enable_rate_limiting(self): - # Arrange - self.assertFalse(self.network.is_rate_limited()) - - # Act - self.network.enable_rate_limit() - then = time.time() - # Make some network call, limit not applied first time - self.network.get_user(self.username) - # Make a second network call, limiting should be applied - self.network.get_top_artists() - now = time.time() - - # Assert - self.assertTrue(self.network.is_rate_limited()) - self.assertGreaterEqual(now - then, 0.2) - - def test_disable_rate_limiting(self): - # Arrange - self.network.enable_rate_limit() - self.assertTrue(self.network.is_rate_limited()) - - # Act - self.network.disable_rate_limit() - # Make some network call, limit not applied first time - self.network.get_user(self.username) - # Make a second network call, limiting should be applied - self.network.get_top_artists() - - # Assert - self.assertFalse(self.network.is_rate_limited()) - - # Commented out because (a) it'll take a long time and (b) it strangely - # fails due Last.fm's complaining of hitting the rate limit, even when - # limited to one call per second. The ToS allows 5 calls per second. - # def test_get_all_scrobbles(self): - # # Arrange - # lastfm_user = self.network.get_user("RJ") - # self.network.enable_rate_limit() # this is going to be slow... - - # # Act - # tracks = lastfm_user.get_recent_tracks(limit=None) - - # # Assert - # self.assertGreaterEqual(len(tracks), 0) - - def test_pickle(self): - # Arrange - import pickle - lastfm_user = self.network.get_user(self.username) - filename = str(self.unix_timestamp()) + ".pkl" - - # Act - with open(filename, "wb") as f: - pickle.dump(lastfm_user, f) - with open(filename, "rb") as f: - loaded_user = pickle.load(f) - os.remove(filename) - - # Assert - self.assertEqual(lastfm_user, loaded_user) - - def test_bio_published_date(self): - # Arrange - artist = pylast.Artist("Test Artist", self.network) - - # Act - bio = artist.get_bio_published_date() - - # Assert - self.assertIsNotNone(bio) - self.assertGreaterEqual(len(bio), 1) - - def test_bio_content(self): - # Arrange - artist = pylast.Artist("Test Artist", self.network) - - # Act - bio = artist.get_bio_content(language="en") - - # Assert - self.assertIsNotNone(bio) - self.assertGreaterEqual(len(bio), 1) - - def test_bio_summary(self): - # Arrange - artist = pylast.Artist("Test Artist", self.network) - - # Act - bio = artist.get_bio_summary(language="en") - - # Assert - self.assertIsNotNone(bio) - self.assertGreaterEqual(len(bio), 1) - def test_album_wiki_content(self): # Arrange album = pylast.Album("Test Artist", "Test Album", self.network) @@ -498,189 +197,66 @@ class TestPyLast(PyLastTestCase): self.assertIsNotNone(wiki) self.assertGreaterEqual(len(wiki), 1) - def test_track_wiki_content(self): + def test_album_eq_none_is_false(self): # Arrange - track = pylast.Track("Test Artist", "test title", self.network) + album1 = None + album2 = pylast.Album("Test Artist", "Test Album", self.network) - # Act - wiki = track.get_wiki_content() + # Act / Assert + self.assertFalse(album1 == album2) - # Assert - self.assertIsNotNone(wiki) - self.assertGreaterEqual(len(wiki), 1) - - def test_track_wiki_summary(self): + def test_album_ne_none_is_true(self): # Arrange - track = pylast.Track("Test Artist", "test title", self.network) + album1 = None + album2 = pylast.Album("Test Artist", "Test Album", self.network) - # Act - wiki = track.get_wiki_summary() + # Act / Assert + self.assertTrue(album1 != album2) - # Assert - self.assertIsNotNone(wiki) - self.assertGreaterEqual(len(wiki), 1) - def test_lastfm_network_name(self): - # Act - name = str(self.network) +class TestPyLastArtist(PyLastTestCase): - # Assert - self.assertEqual(name, "Last.fm Network") - - def helper_validate_results(self, a, b, c): - # Assert - self.assertIsNotNone(a) - self.assertIsNotNone(b) - self.assertIsNotNone(c) - self.assertGreaterEqual(len(a), 0) - self.assertGreaterEqual(len(b), 0) - self.assertGreaterEqual(len(c), 0) - self.assertEqual(a, b) - self.assertEqual(b, c) - - def helper_validate_cacheable(self, thing, function_name): + def test_artist_is_hashable(self): # Arrange - # get thing.function_name() - func = getattr(thing, function_name, None) - - # Act - result1 = func(limit=1, cacheable=False) - result2 = func(limit=1, cacheable=True) - result3 = func(limit=1) - - # Assert - self.helper_validate_results(result1, result2, result3) - - def test_cacheable_library(self): - # Arrange - library = pylast.Library(self.username, self.network) + test_artist = self.network.get_artist("Test Artist") + artist = test_artist.get_similar(limit=2)[0].item + self.assertIsInstance(artist, pylast.Artist) # Act/Assert - self.helper_validate_cacheable(library, "get_artists") + self.helper_is_thing_hashable(artist) - def test_cacheable_user_artist_tracks(self): + def test_bio_published_date(self): # Arrange - lastfm_user = self.network.get_authenticated_user() + artist = pylast.Artist("Test Artist", self.network) # Act - result1 = lastfm_user.get_artist_tracks("Test Artist", cacheable=False) - result2 = lastfm_user.get_artist_tracks("Test Artist", cacheable=True) - result3 = lastfm_user.get_artist_tracks("Test Artist") + bio = artist.get_bio_published_date() # Assert - self.helper_validate_results(result1, result2, result3) + self.assertIsNotNone(bio) + self.assertGreaterEqual(len(bio), 1) - def test_cacheable_user(self): + def test_bio_content(self): # Arrange - lastfm_user = self.network.get_authenticated_user() - - # Act/Assert - self.helper_validate_cacheable(lastfm_user, "get_friends") - self.helper_validate_cacheable(lastfm_user, "get_loved_tracks") - self.helper_validate_cacheable(lastfm_user, "get_recent_tracks") - - def helper_dates_valid(self, dates): - # Assert - self.assertGreaterEqual(len(dates), 1) - self.assertIsInstance(dates[0], tuple) - (start, end) = dates[0] - self.assertLess(start, end) - - def test_geo_get_top_artists(self): - # Arrange - # Act - artists = self.network.get_geo_top_artists( - country="United Kingdom", limit=1) - - # Assert - self.assertEqual(len(artists), 1) - self.assertIsInstance(artists[0], pylast.TopItem) - self.assertIsInstance(artists[0].item, pylast.Artist) - - def test_geo_get_top_tracks(self): - # Arrange - # Act - tracks = self.network.get_geo_top_tracks( - country="United Kingdom", location="Manchester", limit=1) - - # Assert - self.assertEqual(len(tracks), 1) - self.assertIsInstance(tracks[0], pylast.TopItem) - self.assertIsInstance(tracks[0].item, pylast.Track) - - def helper_at_least_one_thing_in_top_list(self, things, expected_type): - # Assert - self.assertGreater(len(things), 1) - self.assertIsInstance(things, list) - self.assertIsInstance(things[0], pylast.TopItem) - self.assertIsInstance(things[0].item, expected_type) - - def helper_only_one_thing_in_top_list(self, things, expected_type): - # Assert - self.assertEqual(len(things), 1) - self.assertIsInstance(things, list) - 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) - thing1 = things[0] - thing2 = things[1] - self.assertIsInstance(thing1, pylast.TopItem) - self.assertIsInstance(thing2, pylast.TopItem) - self.assertIsInstance(thing1.item, expected_type) - self.assertIsInstance(thing2.item, expected_type) - self.assertNotEqual(thing1, thing2) - - def test_user_get_top_tags_with_limit(self): - # Arrange - user = self.network.get_user("RJ") + artist = pylast.Artist("Test Artist", self.network) # Act - tags = user.get_top_tags(limit=1) + bio = artist.get_bio_content(language="en") # Assert - self.skip_if_lastfm_api_broken(tags) - self.helper_only_one_thing_in_top_list(tags, pylast.Tag) + self.assertIsNotNone(bio) + self.assertGreaterEqual(len(bio), 1) - def test_network_get_top_artists_with_limit(self): + def test_bio_summary(self): # Arrange + artist = pylast.Artist("Test Artist", self.network) + # Act - artists = self.network.get_top_artists(limit=1) + bio = artist.get_bio_summary(language="en") # Assert - self.helper_only_one_thing_in_top_list(artists, pylast.Artist) - - def test_network_get_top_tags_with_limit(self): - # Arrange - # Act - tags = self.network.get_top_tags(limit=1) - - # Assert - self.helper_only_one_thing_in_top_list(tags, pylast.Tag) - - def test_network_get_top_tags_with_no_limit(self): - # Arrange - # Act - tags = self.network.get_top_tags() - - # Assert - self.helper_at_least_one_thing_in_top_list(tags, pylast.Tag) - - def test_network_get_top_tracks_with_limit(self): - # Arrange - # Act - tracks = self.network.get_top_tracks(limit=1) - - # Assert - self.helper_only_one_thing_in_top_list(tracks, pylast.Track) + self.assertIsNotNone(bio) + self.assertGreaterEqual(len(bio), 1) def test_artist_top_tracks(self): # Arrange @@ -704,310 +280,6 @@ class TestPyLast(PyLastTestCase): # Assert self.helper_two_different_things_in_top_list(things, pylast.Album) - def test_country_top_tracks(self): - # Arrange - country = self.network.get_country("Croatia") - - # Act - things = country.get_top_tracks(limit=2) - - # Assert - self.helper_two_different_things_in_top_list(things, pylast.Track) - - def test_country_network_top_tracks(self): - # Arrange - # Act - things = self.network.get_geo_top_tracks("Croatia", limit=2) - - # Assert - self.helper_two_different_things_in_top_list(things, pylast.Track) - - def test_tag_top_tracks(self): - # Arrange - tag = self.network.get_tag("blues") - - # Act - things = tag.get_top_tracks(limit=2) - - # Assert - self.helper_two_different_things_in_top_list(things, pylast.Track) - - def test_user_top_tracks(self): - # Arrange - lastfm_user = self.network.get_user(self.username) - - # Act - things = lastfm_user.get_top_tracks(limit=2) - - # Assert - self.helper_two_different_things_in_top_list(things, pylast.Track) - - def helper_assert_chart(self, chart, expected_type): - # Assert - self.assertIsNotNone(chart) - self.assertGreater(len(chart), 0) - self.assertIsInstance(chart[0], pylast.TopItem) - self.assertIsInstance(chart[0].item, expected_type) - - def helper_get_assert_charts(self, thing, date): - # Arrange - (from_date, to_date) = date - - # Act - artist_chart = thing.get_weekly_artist_charts(from_date, to_date) - if type(thing) is not pylast.Tag: - album_chart = thing.get_weekly_album_charts(from_date, to_date) - track_chart = thing.get_weekly_track_charts(from_date, to_date) - - # Assert - self.helper_assert_chart(artist_chart, pylast.Artist) - if type(thing) is not pylast.Tag: - self.helper_assert_chart(album_chart, pylast.Album) - self.helper_assert_chart(track_chart, pylast.Track) - - def test_user_charts(self): - # Arrange - lastfm_user = self.network.get_user("RJ") - dates = lastfm_user.get_weekly_chart_dates() - self.helper_dates_valid(dates) - - # Act/Assert - self.helper_get_assert_charts(lastfm_user, dates[0]) - - # Commented out to avoid spamming - # def test_share_spam(self): - # # Arrange - # users_to_spam = [TODO_ENTER_SPAMEES_HERE] - # spam_message = "Dig the krazee sound!" - # artist = self.network.get_top_artists(limit=1)[0].item - # track = artist.get_top_tracks(limit=1)[0].item - - # # Act - # artist.share(users_to_spam, spam_message) - # track.share(users_to_spam, spam_message) - - # Assert - # Check inbox for spam! - - # album/artist/track/user - - def test_album_data(self): - # Arrange - thing = self.network.get_album("Test Artist", "Test Album") - - # Act - stringed = str(thing) - repr = thing.__repr__() - title = thing.get_title() - name = thing.get_name() - playcount = thing.get_playcount() - url = thing.get_url() - - # Assert - self.assertEqual(stringed, "Test Artist - Test Album") - self.assertIn("pylast.Album('Test Artist', 'Test Album',", repr) - self.assertEqual(title, name) - self.assertIsInstance(playcount, int) - self.assertGreater(playcount, 1) - self.assertEqual( - "https://www.last.fm/music/test%2bartist/test%2balbum", url) - - def test_track_data(self): - # Arrange - thing = self.network.get_track("Test Artist", "test title") - - # Act - stringed = str(thing) - repr = thing.__repr__() - title = thing.get_title() - name = thing.get_name() - playcount = thing.get_playcount() - url = thing.get_url(pylast.DOMAIN_FRENCH) - - # Assert - self.assertEqual(stringed, "Test Artist - test title") - self.assertIn("pylast.Track('Test Artist', 'test title',", repr) - self.assertEqual(title, "test title") - self.assertEqual(title, name) - self.assertIsInstance(playcount, int) - self.assertGreater(playcount, 1) - self.assertEqual( - "https://www.last.fm/fr/music/test%2bartist/_/test%2btitle", url) - - def test_tag_top_artists(self): - # Arrange - tag = self.network.get_tag("blues") - - # Act - artists = tag.get_top_artists(limit=1) - - # Assert - self.helper_only_one_thing_in_top_list(artists, pylast.Artist) - - def test_country_top_artists(self): - # Arrange - country = self.network.get_country("Ukraine") - - # Act - artists = country.get_top_artists(limit=1) - - # Assert - self.helper_only_one_thing_in_top_list(artists, pylast.Artist) - - def test_user_top_artists(self): - # Arrange - lastfm_user = self.network.get_user(self.username) - - # Act - artists = lastfm_user.get_top_artists(limit=1) - - # Assert - self.helper_only_one_thing_in_top_list(artists, pylast.Artist) - - def test_tag_top_albums(self): - # Arrange - tag = self.network.get_tag("blues") - - # Act - albums = tag.get_top_albums(limit=1) - - # Assert - self.helper_only_one_thing_in_top_list(albums, pylast.Album) - - def test_user_top_albums(self): - # Arrange - user = self.network.get_user("RJ") - - # Act - albums = user.get_top_albums(limit=1) - - # 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_user_subscriber(self): - # Arrange - subscriber = self.network.get_user("RJ") - non_subscriber = self.network.get_user("Test User") - - # Act - subscriber_is_subscriber = subscriber.is_subscriber() - non_subscriber_is_subscriber = non_subscriber.is_subscriber() - - # Assert - self.assertTrue(subscriber_is_subscriber) - self.assertFalse(non_subscriber_is_subscriber) - - def test_user_get_image(self): - # Arrange - user = self.network.get_user("RJ") - - # Act - url = user.get_image() - - # Assert - self.assertTrue(url.startswith("https://")) - - def test_user_get_library(self): - # Arrange - user = self.network.get_user(self.username) - - # Act - library = user.get_library() - - # Assert - self.assertIsInstance(library, pylast.Library) - - def test_caching(self): - # Arrange - user = self.network.get_user("RJ") - - # Act - self.network.enable_caching() - tags1 = user.get_top_tags(limit=1, cacheable=True) - tags2 = user.get_top_tags(limit=1, cacheable=True) - - # Assert - self.assertTrue(self.network.is_caching_enabled()) - self.assertEqual(tags1, tags2) - self.network.disable_caching() - self.assertFalse(self.network.is_caching_enabled()) - - def test_album_mbid(self): - # Arrange - mbid = "a6a265bf-9f81-4055-8224-f7ac0aa6b937" - - # Act - album = self.network.get_album_by_mbid(mbid) - album_mbid = album.get_mbid() - - # Assert - self.assertIsInstance(album, pylast.Album) - self.assertEqual(album.title.lower(), "test") - self.assertEqual(album_mbid, mbid) - - def test_artist_mbid(self): - # Arrange - mbid = "7e84f845-ac16-41fe-9ff8-df12eb32af55" - - # Act - artist = self.network.get_artist_by_mbid(mbid) - - # Assert - self.assertIsInstance(artist, pylast.Artist) - self.assertEqual(artist.name, "MusicBrainz Test Artist") - - def test_track_mbid(self): - # Arrange - mbid = "ebc037b1-cc9c-44f2-a21f-83c219f0e1e0" - - # Act - track = self.network.get_track_by_mbid(mbid) - track_mbid = track.get_mbid() - - # Assert - self.assertIsInstance(track, pylast.Track) - self.assertEqual(track.title, "first") - self.assertEqual(track_mbid, mbid) - def test_artist_listener_count(self): # Arrange artist = self.network.get_artist("Test Artist") @@ -1120,6 +392,918 @@ class TestPyLast(PyLastTestCase): self.assertTrue(found1) self.assertTrue(found2) + def test_artists(self): + # Arrange + artist1 = self.network.get_artist("Radiohead") + artist2 = self.network.get_artist("Portishead") + + # Act + url = artist1.get_url() + mbid = artist1.get_mbid() + image = artist1.get_cover_image() + playcount = artist1.get_playcount() + streamable = artist1.is_streamable() + name = artist1.get_name(properly_capitalized=False) + name_cap = artist1.get_name(properly_capitalized=True) + + # Assert + self.assertIn("http", image) + self.assertGreater(playcount, 1) + self.assertTrue(artist1 != artist2) + self.assertEqual(name.lower(), name_cap.lower()) + self.assertEqual(url, "https://www.last.fm/music/radiohead") + self.assertEqual(mbid, "a74b1b7f-71a5-4011-9441-d0b5e4122711") + self.assertIsInstance(streamable, bool) + + def test_artist_eq_none_is_false(self): + # Arrange + artist1 = None + artist2 = pylast.Artist("Test Artist", self.network) + + # Act / Assert + self.assertFalse(artist1 == artist2) + + def test_artist_ne_none_is_true(self): + # Arrange + artist1 = None + artist2 = pylast.Artist("Test Artist", self.network) + + # Act / Assert + self.assertTrue(artist1 != artist2) + + def test_band_members(self): + # Arrange + artist = pylast.Artist("The Beatles", self.network) + + # Act + band_members = artist.get_band_members() + + # Assert + self.skip_if_lastfm_api_broken(band_members) + self.assertGreaterEqual(len(band_members), 4) + + def test_no_band_members(self): + # Arrange + artist = pylast.Artist("John Lennon", self.network) + + # Act + band_members = artist.get_band_members() + + # Assert + self.assertIsNone(band_members) + + 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") + + +class TestPyLastCountry(PyLastTestCase): + + def test_country_is_hashable(self): + # Arrange + country = self.network.get_country("Italy") + + # Act/Assert + self.helper_is_thing_hashable(country) + + def test_countries(self): + # Arrange + country1 = pylast.Country("Italy", self.network) + country2 = pylast.Country("Finland", self.network) + + # Act + text = str(country1) + rep = repr(country1) + url = country1.get_url() + + # Assert + self.assertIn("Italy", rep) + self.assertIn("pylast.Country", rep) + self.assertEqual(text, "Italy") + self.assertTrue(country1 == country1) + self.assertTrue(country1 != country2) + self.assertEqual(url, "https://www.last.fm/place/italy") + + +class TestPyLastLibrary(PyLastTestCase): + + def test_library_is_hashable(self): + # Arrange + library = pylast.Library(user=self.username, network=self.network) + + # Act/Assert + self.helper_is_thing_hashable(library) + + def test_cacheable_library(self): + # Arrange + library = pylast.Library(self.username, self.network) + + # Act/Assert + self.helper_validate_cacheable(library, "get_artists") + + +class TestPyLastNetwork(PyLastTestCase): + + def test_scrobble(self): + # Arrange + artist = "Test Artist" + title = "test title" + timestamp = self.unix_timestamp() + lastfm_user = self.network.get_user(self.username) + + # Act + self.network.scrobble(artist=artist, title=title, timestamp=timestamp) + + # Assert + # limit=2 to ignore now-playing: + last_scrobble = lastfm_user.get_recent_tracks(limit=2)[0] + self.assertEqual(str(last_scrobble.track.artist), str(artist)) + self.assertEqual(str(last_scrobble.track.title), str(title)) + self.assertEqual(str(last_scrobble.timestamp), str(timestamp)) + + def test_update_now_playing(self): + # Arrange + artist = "Test Artist" + title = "test title" + album = "Test Album" + track_number = 1 + lastfm_user = self.network.get_user(self.username) + + # Act + self.network.update_now_playing( + artist=artist, title=title, album=album, track_number=track_number) + + # Assert + current_track = lastfm_user.get_now_playing() + self.assertIsNotNone(current_track) + self.assertEqual(str(current_track.title), "test title") + self.assertEqual(str(current_track.artist), "Test Artist") + + def test_invalid_xml(self): + # Arrange + # Currently causes PCDATA invalid Char value 25 + artist = "Blind Willie Johnson" + title = "It's nobody's fault but mine" + + # Act + search = self.network.search_for_track(artist, title) + total = search.get_total_result_count() + + # Assert + self.skip_if_lastfm_api_broken(total) + self.assertGreaterEqual(int(total), 0) + + def test_enable_rate_limiting(self): + # Arrange + self.assertFalse(self.network.is_rate_limited()) + + # Act + self.network.enable_rate_limit() + then = time.time() + # Make some network call, limit not applied first time + self.network.get_user(self.username) + # Make a second network call, limiting should be applied + self.network.get_top_artists() + now = time.time() + + # Assert + self.assertTrue(self.network.is_rate_limited()) + self.assertGreaterEqual(now - then, 0.2) + + def test_disable_rate_limiting(self): + # Arrange + self.network.enable_rate_limit() + self.assertTrue(self.network.is_rate_limited()) + + # Act + self.network.disable_rate_limit() + # Make some network call, limit not applied first time + self.network.get_user(self.username) + # Make a second network call, limiting should be applied + self.network.get_top_artists() + + # Assert + self.assertFalse(self.network.is_rate_limited()) + + def test_lastfm_network_name(self): + # Act + name = str(self.network) + + # Assert + self.assertEqual(name, "Last.fm Network") + + def test_geo_get_top_artists(self): + # Arrange + # Act + artists = self.network.get_geo_top_artists( + country="United Kingdom", limit=1) + + # Assert + self.assertEqual(len(artists), 1) + self.assertIsInstance(artists[0], pylast.TopItem) + self.assertIsInstance(artists[0].item, pylast.Artist) + + def test_geo_get_top_tracks(self): + # Arrange + # Act + tracks = self.network.get_geo_top_tracks( + country="United Kingdom", location="Manchester", limit=1) + + # Assert + self.assertEqual(len(tracks), 1) + self.assertIsInstance(tracks[0], pylast.TopItem) + self.assertIsInstance(tracks[0].item, pylast.Track) + + def test_network_get_top_artists_with_limit(self): + # Arrange + # Act + artists = self.network.get_top_artists(limit=1) + + # Assert + self.helper_only_one_thing_in_top_list(artists, pylast.Artist) + + def test_network_get_top_tags_with_limit(self): + # Arrange + # Act + tags = self.network.get_top_tags(limit=1) + + # Assert + self.helper_only_one_thing_in_top_list(tags, pylast.Tag) + + def test_network_get_top_tags_with_no_limit(self): + # Arrange + # Act + tags = self.network.get_top_tags() + + # Assert + self.helper_at_least_one_thing_in_top_list(tags, pylast.Tag) + + def test_network_get_top_tracks_with_limit(self): + # Arrange + # Act + tracks = self.network.get_top_tracks(limit=1) + + # Assert + self.helper_only_one_thing_in_top_list(tracks, pylast.Track) + + def test_country_top_tracks(self): + # Arrange + country = self.network.get_country("Croatia") + + # Act + things = country.get_top_tracks(limit=2) + + # Assert + self.helper_two_different_things_in_top_list(things, pylast.Track) + + def test_country_network_top_tracks(self): + # Arrange + # Act + things = self.network.get_geo_top_tracks("Croatia", limit=2) + + # Assert + self.helper_two_different_things_in_top_list(things, pylast.Track) + + def test_tag_top_tracks(self): + # Arrange + tag = self.network.get_tag("blues") + + # Act + things = tag.get_top_tracks(limit=2) + + # Assert + self.helper_two_different_things_in_top_list(things, pylast.Track) + + def test_album_data(self): + # Arrange + thing = self.network.get_album("Test Artist", "Test Album") + + # Act + stringed = str(thing) + repr = thing.__repr__() + title = thing.get_title() + name = thing.get_name() + playcount = thing.get_playcount() + url = thing.get_url() + + # Assert + self.assertEqual(stringed, "Test Artist - Test Album") + self.assertIn("pylast.Album('Test Artist', 'Test Album',", repr) + self.assertEqual(title, name) + self.assertIsInstance(playcount, int) + self.assertGreater(playcount, 1) + self.assertEqual( + "https://www.last.fm/music/test%2bartist/test%2balbum", url) + + def test_track_data(self): + # Arrange + thing = self.network.get_track("Test Artist", "test title") + + # Act + stringed = str(thing) + repr = thing.__repr__() + title = thing.get_title() + name = thing.get_name() + playcount = thing.get_playcount() + url = thing.get_url(pylast.DOMAIN_FRENCH) + + # Assert + self.assertEqual(stringed, "Test Artist - test title") + self.assertIn("pylast.Track('Test Artist', 'test title',", repr) + self.assertEqual(title, "test title") + self.assertEqual(title, name) + self.assertIsInstance(playcount, int) + self.assertGreater(playcount, 1) + self.assertEqual( + "https://www.last.fm/fr/music/test%2bartist/_/test%2btitle", url) + + def test_country_top_artists(self): + # Arrange + country = self.network.get_country("Ukraine") + + # Act + artists = country.get_top_artists(limit=1) + + # Assert + self.helper_only_one_thing_in_top_list(artists, pylast.Artist) + + def test_caching(self): + # Arrange + user = self.network.get_user("RJ") + + # Act + self.network.enable_caching() + tags1 = user.get_top_tags(limit=1, cacheable=True) + tags2 = user.get_top_tags(limit=1, cacheable=True) + + # Assert + self.assertTrue(self.network.is_caching_enabled()) + self.assertEqual(tags1, tags2) + self.network.disable_caching() + self.assertFalse(self.network.is_caching_enabled()) + + def test_album_mbid(self): + # Arrange + mbid = "a6a265bf-9f81-4055-8224-f7ac0aa6b937" + + # Act + album = self.network.get_album_by_mbid(mbid) + album_mbid = album.get_mbid() + + # Assert + self.assertIsInstance(album, pylast.Album) + self.assertEqual(album.title.lower(), "test") + self.assertEqual(album_mbid, mbid) + + def test_artist_mbid(self): + # Arrange + mbid = "7e84f845-ac16-41fe-9ff8-df12eb32af55" + + # Act + artist = self.network.get_artist_by_mbid(mbid) + + # Assert + self.assertIsInstance(artist, pylast.Artist) + self.assertEqual(artist.name, "MusicBrainz Test Artist") + + def test_track_mbid(self): + # Arrange + mbid = "ebc037b1-cc9c-44f2-a21f-83c219f0e1e0" + + # Act + track = self.network.get_track_by_mbid(mbid) + track_mbid = track.get_mbid() + + # Assert + self.assertIsInstance(track, pylast.Track) + self.assertEqual(track.title, "first") + self.assertEqual(track_mbid, mbid) + + def test_init_with_token(self): + # Arrange/Act + try: + pylast.LastFMNetwork( + api_key=self.__class__.secrets["api_key"], + api_secret=self.__class__.secrets["api_secret"], + token="invalid", + ) + except pylast.WSError as exc: + msg = str(exc) + + # Assert + self.assertEqual(msg, + "Unauthorized Token - This token has not been issued") + + +class TestPyLastTag(PyLastTestCase): + + def test_tag_is_hashable(self): + # Arrange + tag = self.network.get_top_tags(limit=1)[0] + + # Act/Assert + self.helper_is_thing_hashable(tag) + + def test_tag_top_artists(self): + # Arrange + tag = self.network.get_tag("blues") + + # Act + artists = tag.get_top_artists(limit=1) + + # Assert + self.helper_only_one_thing_in_top_list(artists, pylast.Artist) + + def test_tag_top_albums(self): + # Arrange + tag = self.network.get_tag("blues") + + # Act + albums = tag.get_top_albums(limit=1) + + # Assert + self.helper_only_one_thing_in_top_list(albums, pylast.Album) + + def test_tags(self): + # Arrange + tag1 = self.network.get_tag("blues") + tag2 = self.network.get_tag("rock") + + # Act + tag_repr = repr(tag1) + tag_str = str(tag1) + name = tag1.get_name(properly_capitalized=True) + url = tag1.get_url() + + # Assert + self.assertEqual("blues", tag_str) + self.assertIn("pylast.Tag", tag_repr) + self.assertIn("blues", tag_repr) + self.assertEqual("blues", name) + self.assertTrue(tag1 == tag1) + self.assertTrue(tag1 != tag2) + self.assertEqual(url, "https://www.last.fm/tag/blues") + + def test_tags_similar(self): + # Arrange + tag = self.network.get_tag("blues") + + # Act + similar = tag.get_similar() + + # Assert + self.skip_if_lastfm_api_broken(similar) + found = False + for tag in similar: + if tag.name == "delta blues": + found = True + break + self.assertTrue(found) + + +class TestPyLastTrack(PyLastTestCase): + + def test_love(self): + # Arrange + artist = "Test Artist" + title = "test title" + track = self.network.get_track(artist, title) + lastfm_user = self.network.get_user(self.username) + + # Act + track.love() + + # Assert + loved = lastfm_user.get_loved_tracks(limit=1) + self.assertEqual(str(loved[0].track.artist), "Test Artist") + self.assertEqual(str(loved[0].track.title), "test title") + + def test_unlove(self): + # Arrange + artist = pylast.Artist("Test Artist", self.network) + title = "test title" + track = pylast.Track(artist, title, self.network) + lastfm_user = self.network.get_user(self.username) + track.love() + + # Act + track.unlove() + + # Assert + loved = lastfm_user.get_loved_tracks(limit=1) + if len(loved): # OK to be empty but if not: + self.assertNotEqual(str(loved.track.artist), "Test Artist") + self.assertNotEqual(str(loved.track.title), "test title") + + def test_user_play_count_in_track_info(self): + # Arrange + artist = "Test Artist" + title = "test title" + track = pylast.Track( + artist=artist, title=title, + network=self.network, username=self.username) + + # Act + count = track.get_userplaycount() + + # Assert + self.assertGreaterEqual(count, 0) + + def test_user_loved_in_track_info(self): + # Arrange + artist = "Test Artist" + title = "test title" + track = pylast.Track( + artist=artist, title=title, + network=self.network, username=self.username) + + # Act + loved = track.get_userloved() + + # Assert + self.assertIsNotNone(loved) + self.assertIsInstance(loved, bool) + self.assertNotIsInstance(loved, str) + + def test_track_is_hashable(self): + # Arrange + artist = self.network.get_artist("Test Artist") + track = artist.get_top_tracks()[0].item + self.assertIsInstance(track, pylast.Track) + + # Act/Assert + self.helper_is_thing_hashable(track) + + def test_track_wiki_content(self): + # Arrange + track = pylast.Track("Test Artist", "test title", self.network) + + # Act + wiki = track.get_wiki_content() + + # Assert + self.assertIsNotNone(wiki) + self.assertGreaterEqual(len(wiki), 1) + + def test_track_wiki_summary(self): + # Arrange + track = pylast.Track("Test Artist", "test title", self.network) + + # Act + wiki = track.get_wiki_summary() + + # Assert + self.assertIsNotNone(wiki) + self.assertGreaterEqual(len(wiki), 1) + + +class TestPyLastUser(PyLastTestCase): + + def test_get_user_registration(self): + # Arrange + username = "RJ" + user = self.network.get_user(username) + + # Act + registered = user.get_registered() + + # Assert + # Last.fm API broken? Should be yyyy-mm-dd not Unix timestamp + if int(registered): + pytest.skip("Last.fm API is broken.") + + # Just check date because of timezones + self.assertIn(u"2002-11-20 ", registered) + + def test_get_user_unixtime_registration(self): + # Arrange + username = "RJ" + user = self.network.get_user(username) + + # Act + unixtime_registered = user.get_unixtime_registered() + + # Assert + # Just check date because of timezones + self.assertEqual(unixtime_registered, u"1037793040") + + def test_get_genderless_user(self): + # Arrange + # Currently test_user has no gender set: + lastfm_user = self.network.get_user("test_user") + + # Act + gender = lastfm_user.get_gender() + + # Assert + self.assertIsNone(gender) + + def test_get_countryless_user(self): + # Arrange + # Currently test_user has no country set: + lastfm_user = self.network.get_user("test_user") + + # Act + country = lastfm_user.get_country() + + # Assert + self.assertIsNone(country) + + def test_user_equals_none(self): + # Arrange + lastfm_user = self.network.get_user(self.username) + + # Act + value = (lastfm_user is None) + + # Assert + self.assertFalse(value) + + def test_user_not_equal_to_none(self): + # Arrange + lastfm_user = self.network.get_user(self.username) + + # Act + value = (lastfm_user is not None) + + # Assert + self.assertTrue(value) + + def test_now_playing_user_with_no_scrobbles(self): + # Arrange + # Currently test-account has no scrobbles: + user = self.network.get_user('test-account') + + # Act + current_track = user.get_now_playing() + + # Assert + self.assertIsNone(current_track) + + def test_love_limits(self): + # Arrange + # Currently test-account has at least 23 loved tracks: + user = self.network.get_user("test-user") + + # Act/Assert + self.assertEqual(len(user.get_loved_tracks(limit=20)), 20) + self.assertLessEqual(len(user.get_loved_tracks(limit=100)), 100) + self.assertGreaterEqual(len(user.get_loved_tracks(limit=None)), 23) + self.assertGreaterEqual(len(user.get_loved_tracks(limit=0)), 23) + + def test_user_is_hashable(self): + # Arrange + user = self.network.get_user(self.username) + + # Act/Assert + self.helper_is_thing_hashable(user) + + # Commented out because (a) it'll take a long time and (b) it strangely + # fails due Last.fm's complaining of hitting the rate limit, even when + # limited to one call per second. The ToS allows 5 calls per second. + # def test_get_all_scrobbles(self): + # # Arrange + # lastfm_user = self.network.get_user("RJ") + # self.network.enable_rate_limit() # this is going to be slow... + + # # Act + # tracks = lastfm_user.get_recent_tracks(limit=None) + + # # Assert + # self.assertGreaterEqual(len(tracks), 0) + + def test_pickle(self): + # Arrange + import pickle + lastfm_user = self.network.get_user(self.username) + filename = str(self.unix_timestamp()) + ".pkl" + + # Act + with open(filename, "wb") as f: + pickle.dump(lastfm_user, f) + with open(filename, "rb") as f: + loaded_user = pickle.load(f) + os.remove(filename) + + # Assert + self.assertEqual(lastfm_user, loaded_user) + + def test_cacheable_user_artist_tracks(self): + # Arrange + lastfm_user = self.network.get_authenticated_user() + + # Act + result1 = lastfm_user.get_artist_tracks("Test Artist", cacheable=False) + result2 = lastfm_user.get_artist_tracks("Test Artist", cacheable=True) + result3 = lastfm_user.get_artist_tracks("Test Artist") + + # Assert + self.helper_validate_results(result1, result2, result3) + + def test_cacheable_user(self): + # Arrange + lastfm_user = self.network.get_authenticated_user() + + # Act/Assert + self.helper_validate_cacheable(lastfm_user, "get_friends") + self.helper_validate_cacheable(lastfm_user, "get_loved_tracks") + self.helper_validate_cacheable(lastfm_user, "get_recent_tracks") + + def test_user_get_top_tags_with_limit(self): + # Arrange + user = self.network.get_user("RJ") + + # Act + tags = user.get_top_tags(limit=1) + + # Assert + self.skip_if_lastfm_api_broken(tags) + self.helper_only_one_thing_in_top_list(tags, pylast.Tag) + + def test_user_top_tracks(self): + # Arrange + lastfm_user = self.network.get_user(self.username) + + # Act + things = lastfm_user.get_top_tracks(limit=2) + + # Assert + self.helper_two_different_things_in_top_list(things, pylast.Track) + + def helper_assert_chart(self, chart, expected_type): + # Assert + self.assertIsNotNone(chart) + self.assertGreater(len(chart), 0) + self.assertIsInstance(chart[0], pylast.TopItem) + self.assertIsInstance(chart[0].item, expected_type) + + def helper_get_assert_charts(self, thing, date): + # Arrange + (from_date, to_date) = date + + # Act + artist_chart = thing.get_weekly_artist_charts(from_date, to_date) + if type(thing) is not pylast.Tag: + album_chart = thing.get_weekly_album_charts(from_date, to_date) + track_chart = thing.get_weekly_track_charts(from_date, to_date) + + # Assert + self.helper_assert_chart(artist_chart, pylast.Artist) + if type(thing) is not pylast.Tag: + self.helper_assert_chart(album_chart, pylast.Album) + self.helper_assert_chart(track_chart, pylast.Track) + + def helper_dates_valid(self, dates): + # Assert + self.assertGreaterEqual(len(dates), 1) + self.assertIsInstance(dates[0], tuple) + (start, end) = dates[0] + self.assertLess(start, end) + + def test_user_charts(self): + # Arrange + lastfm_user = self.network.get_user("RJ") + dates = lastfm_user.get_weekly_chart_dates() + self.helper_dates_valid(dates) + + # Act/Assert + self.helper_get_assert_charts(lastfm_user, dates[0]) + + # Commented out to avoid spamming + # def test_share_spam(self): + # # Arrange + # users_to_spam = [TODO_ENTER_SPAMEES_HERE] + # spam_message = "Dig the krazee sound!" + # artist = self.network.get_top_artists(limit=1)[0].item + # track = artist.get_top_tracks(limit=1)[0].item + + # # Act + # artist.share(users_to_spam, spam_message) + # track.share(users_to_spam, spam_message) + + # Assert + # Check inbox for spam! + + # album/artist/track/user + + def test_user_top_artists(self): + # Arrange + lastfm_user = self.network.get_user(self.username) + + # Act + artists = lastfm_user.get_top_artists(limit=1) + + # Assert + self.helper_only_one_thing_in_top_list(artists, pylast.Artist) + + def test_user_top_albums(self): + # Arrange + user = self.network.get_user("RJ") + + # Act + albums = user.get_top_albums(limit=1) + + # 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_user_subscriber(self): + # Arrange + subscriber = self.network.get_user("RJ") + non_subscriber = self.network.get_user("Test User") + + # Act + subscriber_is_subscriber = subscriber.is_subscriber() + non_subscriber_is_subscriber = non_subscriber.is_subscriber() + + # Assert + self.assertTrue(subscriber_is_subscriber) + self.assertFalse(non_subscriber_is_subscriber) + + def test_user_get_image(self): + # Arrange + user = self.network.get_user("RJ") + + # Act + url = user.get_image() + + # Assert + self.assertTrue(url.startswith("https://")) + + def test_user_get_library(self): + # Arrange + user = self.network.get_user(self.username) + + # Act + library = user.get_library() + + # Assert + self.assertIsInstance(library, pylast.Library) + + def test_get_recent_tracks_from_to(self): + # Arrange + lastfm_user = self.network.get_user("RJ") + + from datetime import datetime + start = datetime(2011, 7, 21, 15, 10) + end = datetime(2011, 7, 21, 15, 15) + import calendar + utc_start = calendar.timegm(start.utctimetuple()) + utc_end = calendar.timegm(end.utctimetuple()) + + # Act + tracks = lastfm_user.get_recent_tracks(time_from=utc_start, + time_to=utc_end) + + # Assert + self.assertEqual(len(tracks), 1) + self.assertEqual(str(tracks[0].track.artist), "Johnny Cash") + self.assertEqual(str(tracks[0].track.title), "Ring of Fire") + def test_tracks_notequal(self): # Arrange track1 = pylast.Track("Test Artist", "test title", self.network) @@ -1174,83 +1358,6 @@ class TestPyLast(PyLastTestCase): self.assertEqual(len(tracks), 4) self.assertTrue(url.startswith("https://www.last.fm/music/test")) - def test_tags(self): - # Arrange - tag1 = self.network.get_tag("blues") - tag2 = self.network.get_tag("rock") - - # Act - tag_repr = repr(tag1) - tag_str = str(tag1) - name = tag1.get_name(properly_capitalized=True) - url = tag1.get_url() - - # Assert - self.assertEqual("blues", tag_str) - self.assertIn("pylast.Tag", tag_repr) - self.assertIn("blues", tag_repr) - self.assertEqual("blues", name) - self.assertTrue(tag1 == tag1) - self.assertTrue(tag1 != tag2) - self.assertEqual(url, "https://www.last.fm/tag/blues") - - def test_tags_similar(self): - # Arrange - tag = self.network.get_tag("blues") - - # Act - similar = tag.get_similar() - - # Assert - self.skip_if_lastfm_api_broken(similar) - found = False - for tag in similar: - if tag.name == "delta blues": - found = True - break - self.assertTrue(found) - - def test_artists(self): - # Arrange - artist1 = self.network.get_artist("Radiohead") - artist2 = self.network.get_artist("Portishead") - - # Act - url = artist1.get_url() - mbid = artist1.get_mbid() - image = artist1.get_cover_image() - playcount = artist1.get_playcount() - streamable = artist1.is_streamable() - name = artist1.get_name(properly_capitalized=False) - name_cap = artist1.get_name(properly_capitalized=True) - - # Assert - self.assertIn("http", image) - self.assertGreater(playcount, 1) - self.assertTrue(artist1 != artist2) - self.assertEqual(name.lower(), name_cap.lower()) - self.assertEqual(url, "https://www.last.fm/music/radiohead") - self.assertEqual(mbid, "a74b1b7f-71a5-4011-9441-d0b5e4122711") - self.assertIsInstance(streamable, bool) - - def test_countries(self): - # Arrange - country1 = pylast.Country("Italy", self.network) - country2 = pylast.Country("Finland", self.network) - - # Act - text = str(country1) - rep = repr(country1) - url = country1.get_url() - - # Assert - self.assertIn("Italy", rep) - self.assertIn("pylast.Country", rep) - self.assertEqual(text, "Italy") - self.assertTrue(country1 == country1) - self.assertTrue(country1 != country2) - self.assertEqual(url, "https://www.last.fm/place/italy") - def test_track_eq_none_is_false(self): # Arrange track1 = None @@ -1267,89 +1374,6 @@ class TestPyLast(PyLastTestCase): # Act / Assert self.assertTrue(track1 != track2) - def test_artist_eq_none_is_false(self): - # Arrange - artist1 = None - artist2 = pylast.Artist("Test Artist", self.network) - - # Act / Assert - self.assertFalse(artist1 == artist2) - - def test_artist_ne_none_is_true(self): - # Arrange - artist1 = None - artist2 = pylast.Artist("Test Artist", self.network) - - # Act / Assert - self.assertTrue(artist1 != artist2) - - def test_album_eq_none_is_false(self): - # Arrange - album1 = None - album2 = pylast.Album("Test Artist", "Test Album", self.network) - - # Act / Assert - self.assertFalse(album1 == album2) - - def test_album_ne_none_is_true(self): - # Arrange - album1 = None - album2 = pylast.Album("Test Artist", "Test Album", self.network) - - # Act / Assert - self.assertTrue(album1 != album2) - - def test_band_members(self): - # Arrange - artist = pylast.Artist("The Beatles", self.network) - - # Act - band_members = artist.get_band_members() - - # Assert - self.skip_if_lastfm_api_broken(band_members) - self.assertGreaterEqual(len(band_members), 4) - - def test_no_band_members(self): - # Arrange - artist = pylast.Artist("John Lennon", self.network) - - # Act - band_members = artist.get_band_members() - - # Assert - self.assertIsNone(band_members) - - def test_get_recent_tracks_from_to(self): - # Arrange - lastfm_user = self.network.get_user("RJ") - - from datetime import datetime - start = datetime(2011, 7, 21, 15, 10) - end = datetime(2011, 7, 21, 15, 15) - import calendar - utc_start = calendar.timegm(start.utctimetuple()) - utc_end = calendar.timegm(end.utctimetuple()) - - # Act - tracks = lastfm_user.get_recent_tracks(time_from=utc_start, - time_to=utc_end) - - # Assert - self.assertEqual(len(tracks), 1) - 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) @@ -1370,21 +1394,6 @@ class TestPyLast(PyLastTestCase): # Assert self.assertEqual(mbid, None) - def test_init_with_token(self): - # Arrange/Act - try: - pylast.LastFMNetwork( - api_key=self.__class__.secrets["api_key"], - api_secret=self.__class__.secrets["api_secret"], - token="invalid", - ) - except pylast.WSError as exc: - msg = str(exc) - - # Assert - self.assertEqual(msg, - "Unauthorized Token - This token has not been issued") - @flaky(max_runs=5, min_passes=1) class TestPyLastWithLibreFm(unittest.TestCase): From 2aa4dbdf880f971c9474b2abf78d8a6c0405d382 Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 17 Oct 2017 21:57:17 +0300 Subject: [PATCH 42/66] Split Last.fm/Libre.fm tests --- tests/test_pylast.py | 25 +++---------------------- tests/test_pylast_librefm.py | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 22 deletions(-) create mode 100755 tests/test_pylast_librefm.py diff --git a/tests/test_pylast.py b/tests/test_pylast.py index 2342e02..7024067 100755 --- a/tests/test_pylast.py +++ b/tests/test_pylast.py @@ -2,12 +2,13 @@ """ Integration (not unit) tests for pylast.py """ -from flaky import flaky import os -import pytest import time import unittest +import pytest +from flaky import flaky + import pylast @@ -1395,25 +1396,5 @@ class TestPyLastUser(PyLastTestCase): self.assertEqual(mbid, None) -@flaky(max_runs=5, min_passes=1) -class TestPyLastWithLibreFm(unittest.TestCase): - """Own class for Libre.fm because we don't need the Last.fm setUp""" - - def test_libre_fm(self): - # Arrange - secrets = load_secrets() - username = secrets["username"] - password_hash = secrets["password_hash"] - - # Act - network = pylast.LibreFMNetwork( - password_hash=password_hash, username=username) - artist = network.get_artist("Radiohead") - name = artist.get_name() - - # Assert - self.assertEqual(name, "Radiohead") - - if __name__ == '__main__': unittest.main(failfast=True) diff --git a/tests/test_pylast_librefm.py b/tests/test_pylast_librefm.py new file mode 100755 index 0000000..61cdced --- /dev/null +++ b/tests/test_pylast_librefm.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python +""" +Integration (not unit) tests for pylast.py +""" +import unittest + +from flaky import flaky + +import pylast +from test_pylast import load_secrets + + +@flaky(max_runs=5, min_passes=1) +class TestPyLastWithLibreFm(unittest.TestCase): + """Own class for Libre.fm because we don't need the Last.fm setUp""" + + def test_libre_fm(self): + # Arrange + secrets = load_secrets() + username = secrets["username"] + password_hash = secrets["password_hash"] + + # Act + network = pylast.LibreFMNetwork( + password_hash=password_hash, username=username) + artist = network.get_artist("Radiohead") + name = artist.get_name() + + # Assert + self.assertEqual(name, "Radiohead") + + +if __name__ == '__main__': + unittest.main(failfast=True) From 2edfc108d1335966e2f63c94528eb557bba13c59 Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 17 Oct 2017 23:14:00 +0300 Subject: [PATCH 43/66] Fix test import --- tests/test_pylast_librefm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_pylast_librefm.py b/tests/test_pylast_librefm.py index 61cdced..3d2cb53 100755 --- a/tests/test_pylast_librefm.py +++ b/tests/test_pylast_librefm.py @@ -7,7 +7,7 @@ import unittest from flaky import flaky import pylast -from test_pylast import load_secrets +from .test_pylast import load_secrets @flaky(max_runs=5, min_passes=1) From 5ba51c1e2c50d4843edee1e133fdc93fac8e3456 Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 17 Oct 2017 23:18:35 +0300 Subject: [PATCH 44/66] Finish refactor --- tests/test_pylast.py | 1272 ---------------------------------- tests/test_pylast_album.py | 104 +++ tests/test_pylast_artist.py | 262 +++++++ tests/test_pylast_country.py | 41 ++ tests/test_pylast_library.py | 30 + tests/test_pylast_librefm.py | 1 + tests/test_pylast_network.py | 307 ++++++++ tests/test_pylast_tag.py | 79 +++ tests/test_pylast_track.py | 109 +++ tests/test_pylast_user.py | 448 ++++++++++++ 10 files changed, 1381 insertions(+), 1272 deletions(-) create mode 100755 tests/test_pylast_album.py create mode 100755 tests/test_pylast_artist.py create mode 100755 tests/test_pylast_country.py create mode 100755 tests/test_pylast_library.py create mode 100755 tests/test_pylast_network.py create mode 100755 tests/test_pylast_tag.py create mode 100755 tests/test_pylast_track.py create mode 100755 tests/test_pylast_user.py diff --git a/tests/test_pylast.py b/tests/test_pylast.py index 7024067..9279112 100755 --- a/tests/test_pylast.py +++ b/tests/test_pylast.py @@ -124,1277 +124,5 @@ class PyLastTestCase(unittest.TestCase): self.assertNotEqual(thing1, thing2) -class TestPyLastAlbum(PyLastTestCase): - - def test_album_tags_are_topitems(self): - # Arrange - albums = self.network.get_user('RJ').get_top_albums() - - # Act - tags = albums[0].item.get_top_tags(limit=1) - - # Assert - self.assertGreater(len(tags), 0) - self.assertIsInstance(tags[0], pylast.TopItem) - - def test_album_is_hashable(self): - # Arrange - album = self.network.get_album("Test Artist", "Test Album") - - # Act/Assert - self.helper_is_thing_hashable(album) - - def test_album_in_recent_tracks(self): - # Arrange - lastfm_user = self.network.get_user(self.username) - - # Act - # limit=2 to ignore now-playing: - track = lastfm_user.get_recent_tracks(limit=2)[0] - - # Assert - self.assertTrue(hasattr(track, 'album')) - - def test_album_in_artist_tracks(self): - # Arrange - lastfm_user = self.network.get_user(self.username) - - # Act - track = lastfm_user.get_artist_tracks(artist="Test Artist")[0] - - # Assert - self.assertTrue(hasattr(track, 'album')) - - def test_album_wiki_content(self): - # Arrange - album = pylast.Album("Test Artist", "Test Album", self.network) - - # Act - wiki = album.get_wiki_content() - - # Assert - self.assertIsNotNone(wiki) - self.assertGreaterEqual(len(wiki), 1) - - def test_album_wiki_published_date(self): - # Arrange - album = pylast.Album("Test Artist", "Test Album", self.network) - - # Act - wiki = album.get_wiki_published_date() - - # Assert - self.assertIsNotNone(wiki) - self.assertGreaterEqual(len(wiki), 1) - - def test_album_wiki_summary(self): - # Arrange - album = pylast.Album("Test Artist", "Test Album", self.network) - - # Act - wiki = album.get_wiki_summary() - - # Assert - self.assertIsNotNone(wiki) - self.assertGreaterEqual(len(wiki), 1) - - def test_album_eq_none_is_false(self): - # Arrange - album1 = None - album2 = pylast.Album("Test Artist", "Test Album", self.network) - - # Act / Assert - self.assertFalse(album1 == album2) - - def test_album_ne_none_is_true(self): - # Arrange - album1 = None - album2 = pylast.Album("Test Artist", "Test Album", self.network) - - # Act / Assert - self.assertTrue(album1 != album2) - - -class TestPyLastArtist(PyLastTestCase): - - def test_artist_is_hashable(self): - # Arrange - test_artist = self.network.get_artist("Test Artist") - artist = test_artist.get_similar(limit=2)[0].item - self.assertIsInstance(artist, pylast.Artist) - - # Act/Assert - self.helper_is_thing_hashable(artist) - - def test_bio_published_date(self): - # Arrange - artist = pylast.Artist("Test Artist", self.network) - - # Act - bio = artist.get_bio_published_date() - - # Assert - self.assertIsNotNone(bio) - self.assertGreaterEqual(len(bio), 1) - - def test_bio_content(self): - # Arrange - artist = pylast.Artist("Test Artist", self.network) - - # Act - bio = artist.get_bio_content(language="en") - - # Assert - self.assertIsNotNone(bio) - self.assertGreaterEqual(len(bio), 1) - - def test_bio_summary(self): - # Arrange - artist = pylast.Artist("Test Artist", self.network) - - # Act - bio = artist.get_bio_summary(language="en") - - # Assert - self.assertIsNotNone(bio) - self.assertGreaterEqual(len(bio), 1) - - def test_artist_top_tracks(self): - # Arrange - # Pick an artist with plenty of plays - artist = self.network.get_top_artists(limit=1)[0].item - - # Act - things = artist.get_top_tracks(limit=2) - - # Assert - self.helper_two_different_things_in_top_list(things, pylast.Track) - - def test_artist_top_albums(self): - # Arrange - # Pick an artist with plenty of plays - artist = self.network.get_top_artists(limit=1)[0].item - - # Act - things = artist.get_top_albums(limit=2) - - # Assert - self.helper_two_different_things_in_top_list(things, pylast.Album) - - def test_artist_listener_count(self): - # Arrange - artist = self.network.get_artist("Test Artist") - - # Act - count = artist.get_listener_count() - - # Assert - self.assertIsInstance(count, int) - self.assertGreater(count, 0) - - def test_tag_artist(self): - # Arrange - artist = self.network.get_artist("Test Artist") -# artist.clear_tags() - - # Act - artist.add_tag("testing") - - # Assert - tags = artist.get_tags() - self.assertGreater(len(tags), 0) - found = False - for tag in tags: - if tag.name == "testing": - found = True - break - self.assertTrue(found) - - def test_remove_tag_of_type_text(self): - # Arrange - tag = "testing" # text - artist = self.network.get_artist("Test Artist") - artist.add_tag(tag) - - # Act - artist.remove_tag(tag) - - # Assert - tags = artist.get_tags() - found = False - for tag in tags: - if tag.name == "testing": - found = True - break - self.assertFalse(found) - - def test_remove_tag_of_type_tag(self): - # Arrange - tag = pylast.Tag("testing", self.network) # Tag - artist = self.network.get_artist("Test Artist") - artist.add_tag(tag) - - # Act - artist.remove_tag(tag) - - # Assert - tags = artist.get_tags() - found = False - for tag in tags: - if tag.name == "testing": - found = True - break - self.assertFalse(found) - - def test_remove_tags(self): - # Arrange - tags = ["removetag1", "removetag2"] - artist = self.network.get_artist("Test Artist") - artist.add_tags(tags) - artist.add_tags("1more") - tags_before = artist.get_tags() - - # Act - artist.remove_tags(tags) - - # Assert - tags_after = artist.get_tags() - self.assertEqual(len(tags_after), len(tags_before) - 2) - found1, found2 = False, False - for tag in tags_after: - if tag.name == "removetag1": - found1 = True - elif tag.name == "removetag2": - found2 = True - self.assertFalse(found1) - self.assertFalse(found2) - - def test_set_tags(self): - # Arrange - tags = ["sometag1", "sometag2"] - artist = self.network.get_artist("Test Artist 2") - artist.add_tags(tags) - tags_before = artist.get_tags() - new_tags = ["settag1", "settag2"] - - # Act - artist.set_tags(new_tags) - - # Assert - tags_after = artist.get_tags() - self.assertNotEqual(tags_before, tags_after) - self.assertEqual(len(tags_after), 2) - found1, found2 = False, False - for tag in tags_after: - if tag.name == "settag1": - found1 = True - elif tag.name == "settag2": - found2 = True - self.assertTrue(found1) - self.assertTrue(found2) - - def test_artists(self): - # Arrange - artist1 = self.network.get_artist("Radiohead") - artist2 = self.network.get_artist("Portishead") - - # Act - url = artist1.get_url() - mbid = artist1.get_mbid() - image = artist1.get_cover_image() - playcount = artist1.get_playcount() - streamable = artist1.is_streamable() - name = artist1.get_name(properly_capitalized=False) - name_cap = artist1.get_name(properly_capitalized=True) - - # Assert - self.assertIn("http", image) - self.assertGreater(playcount, 1) - self.assertTrue(artist1 != artist2) - self.assertEqual(name.lower(), name_cap.lower()) - self.assertEqual(url, "https://www.last.fm/music/radiohead") - self.assertEqual(mbid, "a74b1b7f-71a5-4011-9441-d0b5e4122711") - self.assertIsInstance(streamable, bool) - - def test_artist_eq_none_is_false(self): - # Arrange - artist1 = None - artist2 = pylast.Artist("Test Artist", self.network) - - # Act / Assert - self.assertFalse(artist1 == artist2) - - def test_artist_ne_none_is_true(self): - # Arrange - artist1 = None - artist2 = pylast.Artist("Test Artist", self.network) - - # Act / Assert - self.assertTrue(artist1 != artist2) - - def test_band_members(self): - # Arrange - artist = pylast.Artist("The Beatles", self.network) - - # Act - band_members = artist.get_band_members() - - # Assert - self.skip_if_lastfm_api_broken(band_members) - self.assertGreaterEqual(len(band_members), 4) - - def test_no_band_members(self): - # Arrange - artist = pylast.Artist("John Lennon", self.network) - - # Act - band_members = artist.get_band_members() - - # Assert - self.assertIsNone(band_members) - - 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") - - -class TestPyLastCountry(PyLastTestCase): - - def test_country_is_hashable(self): - # Arrange - country = self.network.get_country("Italy") - - # Act/Assert - self.helper_is_thing_hashable(country) - - def test_countries(self): - # Arrange - country1 = pylast.Country("Italy", self.network) - country2 = pylast.Country("Finland", self.network) - - # Act - text = str(country1) - rep = repr(country1) - url = country1.get_url() - - # Assert - self.assertIn("Italy", rep) - self.assertIn("pylast.Country", rep) - self.assertEqual(text, "Italy") - self.assertTrue(country1 == country1) - self.assertTrue(country1 != country2) - self.assertEqual(url, "https://www.last.fm/place/italy") - - -class TestPyLastLibrary(PyLastTestCase): - - def test_library_is_hashable(self): - # Arrange - library = pylast.Library(user=self.username, network=self.network) - - # Act/Assert - self.helper_is_thing_hashable(library) - - def test_cacheable_library(self): - # Arrange - library = pylast.Library(self.username, self.network) - - # Act/Assert - self.helper_validate_cacheable(library, "get_artists") - - -class TestPyLastNetwork(PyLastTestCase): - - def test_scrobble(self): - # Arrange - artist = "Test Artist" - title = "test title" - timestamp = self.unix_timestamp() - lastfm_user = self.network.get_user(self.username) - - # Act - self.network.scrobble(artist=artist, title=title, timestamp=timestamp) - - # Assert - # limit=2 to ignore now-playing: - last_scrobble = lastfm_user.get_recent_tracks(limit=2)[0] - self.assertEqual(str(last_scrobble.track.artist), str(artist)) - self.assertEqual(str(last_scrobble.track.title), str(title)) - self.assertEqual(str(last_scrobble.timestamp), str(timestamp)) - - def test_update_now_playing(self): - # Arrange - artist = "Test Artist" - title = "test title" - album = "Test Album" - track_number = 1 - lastfm_user = self.network.get_user(self.username) - - # Act - self.network.update_now_playing( - artist=artist, title=title, album=album, track_number=track_number) - - # Assert - current_track = lastfm_user.get_now_playing() - self.assertIsNotNone(current_track) - self.assertEqual(str(current_track.title), "test title") - self.assertEqual(str(current_track.artist), "Test Artist") - - def test_invalid_xml(self): - # Arrange - # Currently causes PCDATA invalid Char value 25 - artist = "Blind Willie Johnson" - title = "It's nobody's fault but mine" - - # Act - search = self.network.search_for_track(artist, title) - total = search.get_total_result_count() - - # Assert - self.skip_if_lastfm_api_broken(total) - self.assertGreaterEqual(int(total), 0) - - def test_enable_rate_limiting(self): - # Arrange - self.assertFalse(self.network.is_rate_limited()) - - # Act - self.network.enable_rate_limit() - then = time.time() - # Make some network call, limit not applied first time - self.network.get_user(self.username) - # Make a second network call, limiting should be applied - self.network.get_top_artists() - now = time.time() - - # Assert - self.assertTrue(self.network.is_rate_limited()) - self.assertGreaterEqual(now - then, 0.2) - - def test_disable_rate_limiting(self): - # Arrange - self.network.enable_rate_limit() - self.assertTrue(self.network.is_rate_limited()) - - # Act - self.network.disable_rate_limit() - # Make some network call, limit not applied first time - self.network.get_user(self.username) - # Make a second network call, limiting should be applied - self.network.get_top_artists() - - # Assert - self.assertFalse(self.network.is_rate_limited()) - - def test_lastfm_network_name(self): - # Act - name = str(self.network) - - # Assert - self.assertEqual(name, "Last.fm Network") - - def test_geo_get_top_artists(self): - # Arrange - # Act - artists = self.network.get_geo_top_artists( - country="United Kingdom", limit=1) - - # Assert - self.assertEqual(len(artists), 1) - self.assertIsInstance(artists[0], pylast.TopItem) - self.assertIsInstance(artists[0].item, pylast.Artist) - - def test_geo_get_top_tracks(self): - # Arrange - # Act - tracks = self.network.get_geo_top_tracks( - country="United Kingdom", location="Manchester", limit=1) - - # Assert - self.assertEqual(len(tracks), 1) - self.assertIsInstance(tracks[0], pylast.TopItem) - self.assertIsInstance(tracks[0].item, pylast.Track) - - def test_network_get_top_artists_with_limit(self): - # Arrange - # Act - artists = self.network.get_top_artists(limit=1) - - # Assert - self.helper_only_one_thing_in_top_list(artists, pylast.Artist) - - def test_network_get_top_tags_with_limit(self): - # Arrange - # Act - tags = self.network.get_top_tags(limit=1) - - # Assert - self.helper_only_one_thing_in_top_list(tags, pylast.Tag) - - def test_network_get_top_tags_with_no_limit(self): - # Arrange - # Act - tags = self.network.get_top_tags() - - # Assert - self.helper_at_least_one_thing_in_top_list(tags, pylast.Tag) - - def test_network_get_top_tracks_with_limit(self): - # Arrange - # Act - tracks = self.network.get_top_tracks(limit=1) - - # Assert - self.helper_only_one_thing_in_top_list(tracks, pylast.Track) - - def test_country_top_tracks(self): - # Arrange - country = self.network.get_country("Croatia") - - # Act - things = country.get_top_tracks(limit=2) - - # Assert - self.helper_two_different_things_in_top_list(things, pylast.Track) - - def test_country_network_top_tracks(self): - # Arrange - # Act - things = self.network.get_geo_top_tracks("Croatia", limit=2) - - # Assert - self.helper_two_different_things_in_top_list(things, pylast.Track) - - def test_tag_top_tracks(self): - # Arrange - tag = self.network.get_tag("blues") - - # Act - things = tag.get_top_tracks(limit=2) - - # Assert - self.helper_two_different_things_in_top_list(things, pylast.Track) - - def test_album_data(self): - # Arrange - thing = self.network.get_album("Test Artist", "Test Album") - - # Act - stringed = str(thing) - repr = thing.__repr__() - title = thing.get_title() - name = thing.get_name() - playcount = thing.get_playcount() - url = thing.get_url() - - # Assert - self.assertEqual(stringed, "Test Artist - Test Album") - self.assertIn("pylast.Album('Test Artist', 'Test Album',", repr) - self.assertEqual(title, name) - self.assertIsInstance(playcount, int) - self.assertGreater(playcount, 1) - self.assertEqual( - "https://www.last.fm/music/test%2bartist/test%2balbum", url) - - def test_track_data(self): - # Arrange - thing = self.network.get_track("Test Artist", "test title") - - # Act - stringed = str(thing) - repr = thing.__repr__() - title = thing.get_title() - name = thing.get_name() - playcount = thing.get_playcount() - url = thing.get_url(pylast.DOMAIN_FRENCH) - - # Assert - self.assertEqual(stringed, "Test Artist - test title") - self.assertIn("pylast.Track('Test Artist', 'test title',", repr) - self.assertEqual(title, "test title") - self.assertEqual(title, name) - self.assertIsInstance(playcount, int) - self.assertGreater(playcount, 1) - self.assertEqual( - "https://www.last.fm/fr/music/test%2bartist/_/test%2btitle", url) - - def test_country_top_artists(self): - # Arrange - country = self.network.get_country("Ukraine") - - # Act - artists = country.get_top_artists(limit=1) - - # Assert - self.helper_only_one_thing_in_top_list(artists, pylast.Artist) - - def test_caching(self): - # Arrange - user = self.network.get_user("RJ") - - # Act - self.network.enable_caching() - tags1 = user.get_top_tags(limit=1, cacheable=True) - tags2 = user.get_top_tags(limit=1, cacheable=True) - - # Assert - self.assertTrue(self.network.is_caching_enabled()) - self.assertEqual(tags1, tags2) - self.network.disable_caching() - self.assertFalse(self.network.is_caching_enabled()) - - def test_album_mbid(self): - # Arrange - mbid = "a6a265bf-9f81-4055-8224-f7ac0aa6b937" - - # Act - album = self.network.get_album_by_mbid(mbid) - album_mbid = album.get_mbid() - - # Assert - self.assertIsInstance(album, pylast.Album) - self.assertEqual(album.title.lower(), "test") - self.assertEqual(album_mbid, mbid) - - def test_artist_mbid(self): - # Arrange - mbid = "7e84f845-ac16-41fe-9ff8-df12eb32af55" - - # Act - artist = self.network.get_artist_by_mbid(mbid) - - # Assert - self.assertIsInstance(artist, pylast.Artist) - self.assertEqual(artist.name, "MusicBrainz Test Artist") - - def test_track_mbid(self): - # Arrange - mbid = "ebc037b1-cc9c-44f2-a21f-83c219f0e1e0" - - # Act - track = self.network.get_track_by_mbid(mbid) - track_mbid = track.get_mbid() - - # Assert - self.assertIsInstance(track, pylast.Track) - self.assertEqual(track.title, "first") - self.assertEqual(track_mbid, mbid) - - def test_init_with_token(self): - # Arrange/Act - try: - pylast.LastFMNetwork( - api_key=self.__class__.secrets["api_key"], - api_secret=self.__class__.secrets["api_secret"], - token="invalid", - ) - except pylast.WSError as exc: - msg = str(exc) - - # Assert - self.assertEqual(msg, - "Unauthorized Token - This token has not been issued") - - -class TestPyLastTag(PyLastTestCase): - - def test_tag_is_hashable(self): - # Arrange - tag = self.network.get_top_tags(limit=1)[0] - - # Act/Assert - self.helper_is_thing_hashable(tag) - - def test_tag_top_artists(self): - # Arrange - tag = self.network.get_tag("blues") - - # Act - artists = tag.get_top_artists(limit=1) - - # Assert - self.helper_only_one_thing_in_top_list(artists, pylast.Artist) - - def test_tag_top_albums(self): - # Arrange - tag = self.network.get_tag("blues") - - # Act - albums = tag.get_top_albums(limit=1) - - # Assert - self.helper_only_one_thing_in_top_list(albums, pylast.Album) - - def test_tags(self): - # Arrange - tag1 = self.network.get_tag("blues") - tag2 = self.network.get_tag("rock") - - # Act - tag_repr = repr(tag1) - tag_str = str(tag1) - name = tag1.get_name(properly_capitalized=True) - url = tag1.get_url() - - # Assert - self.assertEqual("blues", tag_str) - self.assertIn("pylast.Tag", tag_repr) - self.assertIn("blues", tag_repr) - self.assertEqual("blues", name) - self.assertTrue(tag1 == tag1) - self.assertTrue(tag1 != tag2) - self.assertEqual(url, "https://www.last.fm/tag/blues") - - def test_tags_similar(self): - # Arrange - tag = self.network.get_tag("blues") - - # Act - similar = tag.get_similar() - - # Assert - self.skip_if_lastfm_api_broken(similar) - found = False - for tag in similar: - if tag.name == "delta blues": - found = True - break - self.assertTrue(found) - - -class TestPyLastTrack(PyLastTestCase): - - def test_love(self): - # Arrange - artist = "Test Artist" - title = "test title" - track = self.network.get_track(artist, title) - lastfm_user = self.network.get_user(self.username) - - # Act - track.love() - - # Assert - loved = lastfm_user.get_loved_tracks(limit=1) - self.assertEqual(str(loved[0].track.artist), "Test Artist") - self.assertEqual(str(loved[0].track.title), "test title") - - def test_unlove(self): - # Arrange - artist = pylast.Artist("Test Artist", self.network) - title = "test title" - track = pylast.Track(artist, title, self.network) - lastfm_user = self.network.get_user(self.username) - track.love() - - # Act - track.unlove() - - # Assert - loved = lastfm_user.get_loved_tracks(limit=1) - if len(loved): # OK to be empty but if not: - self.assertNotEqual(str(loved.track.artist), "Test Artist") - self.assertNotEqual(str(loved.track.title), "test title") - - def test_user_play_count_in_track_info(self): - # Arrange - artist = "Test Artist" - title = "test title" - track = pylast.Track( - artist=artist, title=title, - network=self.network, username=self.username) - - # Act - count = track.get_userplaycount() - - # Assert - self.assertGreaterEqual(count, 0) - - def test_user_loved_in_track_info(self): - # Arrange - artist = "Test Artist" - title = "test title" - track = pylast.Track( - artist=artist, title=title, - network=self.network, username=self.username) - - # Act - loved = track.get_userloved() - - # Assert - self.assertIsNotNone(loved) - self.assertIsInstance(loved, bool) - self.assertNotIsInstance(loved, str) - - def test_track_is_hashable(self): - # Arrange - artist = self.network.get_artist("Test Artist") - track = artist.get_top_tracks()[0].item - self.assertIsInstance(track, pylast.Track) - - # Act/Assert - self.helper_is_thing_hashable(track) - - def test_track_wiki_content(self): - # Arrange - track = pylast.Track("Test Artist", "test title", self.network) - - # Act - wiki = track.get_wiki_content() - - # Assert - self.assertIsNotNone(wiki) - self.assertGreaterEqual(len(wiki), 1) - - def test_track_wiki_summary(self): - # Arrange - track = pylast.Track("Test Artist", "test title", self.network) - - # Act - wiki = track.get_wiki_summary() - - # Assert - self.assertIsNotNone(wiki) - self.assertGreaterEqual(len(wiki), 1) - - -class TestPyLastUser(PyLastTestCase): - - def test_get_user_registration(self): - # Arrange - username = "RJ" - user = self.network.get_user(username) - - # Act - registered = user.get_registered() - - # Assert - # Last.fm API broken? Should be yyyy-mm-dd not Unix timestamp - if int(registered): - pytest.skip("Last.fm API is broken.") - - # Just check date because of timezones - self.assertIn(u"2002-11-20 ", registered) - - def test_get_user_unixtime_registration(self): - # Arrange - username = "RJ" - user = self.network.get_user(username) - - # Act - unixtime_registered = user.get_unixtime_registered() - - # Assert - # Just check date because of timezones - self.assertEqual(unixtime_registered, u"1037793040") - - def test_get_genderless_user(self): - # Arrange - # Currently test_user has no gender set: - lastfm_user = self.network.get_user("test_user") - - # Act - gender = lastfm_user.get_gender() - - # Assert - self.assertIsNone(gender) - - def test_get_countryless_user(self): - # Arrange - # Currently test_user has no country set: - lastfm_user = self.network.get_user("test_user") - - # Act - country = lastfm_user.get_country() - - # Assert - self.assertIsNone(country) - - def test_user_equals_none(self): - # Arrange - lastfm_user = self.network.get_user(self.username) - - # Act - value = (lastfm_user is None) - - # Assert - self.assertFalse(value) - - def test_user_not_equal_to_none(self): - # Arrange - lastfm_user = self.network.get_user(self.username) - - # Act - value = (lastfm_user is not None) - - # Assert - self.assertTrue(value) - - def test_now_playing_user_with_no_scrobbles(self): - # Arrange - # Currently test-account has no scrobbles: - user = self.network.get_user('test-account') - - # Act - current_track = user.get_now_playing() - - # Assert - self.assertIsNone(current_track) - - def test_love_limits(self): - # Arrange - # Currently test-account has at least 23 loved tracks: - user = self.network.get_user("test-user") - - # Act/Assert - self.assertEqual(len(user.get_loved_tracks(limit=20)), 20) - self.assertLessEqual(len(user.get_loved_tracks(limit=100)), 100) - self.assertGreaterEqual(len(user.get_loved_tracks(limit=None)), 23) - self.assertGreaterEqual(len(user.get_loved_tracks(limit=0)), 23) - - def test_user_is_hashable(self): - # Arrange - user = self.network.get_user(self.username) - - # Act/Assert - self.helper_is_thing_hashable(user) - - # Commented out because (a) it'll take a long time and (b) it strangely - # fails due Last.fm's complaining of hitting the rate limit, even when - # limited to one call per second. The ToS allows 5 calls per second. - # def test_get_all_scrobbles(self): - # # Arrange - # lastfm_user = self.network.get_user("RJ") - # self.network.enable_rate_limit() # this is going to be slow... - - # # Act - # tracks = lastfm_user.get_recent_tracks(limit=None) - - # # Assert - # self.assertGreaterEqual(len(tracks), 0) - - def test_pickle(self): - # Arrange - import pickle - lastfm_user = self.network.get_user(self.username) - filename = str(self.unix_timestamp()) + ".pkl" - - # Act - with open(filename, "wb") as f: - pickle.dump(lastfm_user, f) - with open(filename, "rb") as f: - loaded_user = pickle.load(f) - os.remove(filename) - - # Assert - self.assertEqual(lastfm_user, loaded_user) - - def test_cacheable_user_artist_tracks(self): - # Arrange - lastfm_user = self.network.get_authenticated_user() - - # Act - result1 = lastfm_user.get_artist_tracks("Test Artist", cacheable=False) - result2 = lastfm_user.get_artist_tracks("Test Artist", cacheable=True) - result3 = lastfm_user.get_artist_tracks("Test Artist") - - # Assert - self.helper_validate_results(result1, result2, result3) - - def test_cacheable_user(self): - # Arrange - lastfm_user = self.network.get_authenticated_user() - - # Act/Assert - self.helper_validate_cacheable(lastfm_user, "get_friends") - self.helper_validate_cacheable(lastfm_user, "get_loved_tracks") - self.helper_validate_cacheable(lastfm_user, "get_recent_tracks") - - def test_user_get_top_tags_with_limit(self): - # Arrange - user = self.network.get_user("RJ") - - # Act - tags = user.get_top_tags(limit=1) - - # Assert - self.skip_if_lastfm_api_broken(tags) - self.helper_only_one_thing_in_top_list(tags, pylast.Tag) - - def test_user_top_tracks(self): - # Arrange - lastfm_user = self.network.get_user(self.username) - - # Act - things = lastfm_user.get_top_tracks(limit=2) - - # Assert - self.helper_two_different_things_in_top_list(things, pylast.Track) - - def helper_assert_chart(self, chart, expected_type): - # Assert - self.assertIsNotNone(chart) - self.assertGreater(len(chart), 0) - self.assertIsInstance(chart[0], pylast.TopItem) - self.assertIsInstance(chart[0].item, expected_type) - - def helper_get_assert_charts(self, thing, date): - # Arrange - (from_date, to_date) = date - - # Act - artist_chart = thing.get_weekly_artist_charts(from_date, to_date) - if type(thing) is not pylast.Tag: - album_chart = thing.get_weekly_album_charts(from_date, to_date) - track_chart = thing.get_weekly_track_charts(from_date, to_date) - - # Assert - self.helper_assert_chart(artist_chart, pylast.Artist) - if type(thing) is not pylast.Tag: - self.helper_assert_chart(album_chart, pylast.Album) - self.helper_assert_chart(track_chart, pylast.Track) - - def helper_dates_valid(self, dates): - # Assert - self.assertGreaterEqual(len(dates), 1) - self.assertIsInstance(dates[0], tuple) - (start, end) = dates[0] - self.assertLess(start, end) - - def test_user_charts(self): - # Arrange - lastfm_user = self.network.get_user("RJ") - dates = lastfm_user.get_weekly_chart_dates() - self.helper_dates_valid(dates) - - # Act/Assert - self.helper_get_assert_charts(lastfm_user, dates[0]) - - # Commented out to avoid spamming - # def test_share_spam(self): - # # Arrange - # users_to_spam = [TODO_ENTER_SPAMEES_HERE] - # spam_message = "Dig the krazee sound!" - # artist = self.network.get_top_artists(limit=1)[0].item - # track = artist.get_top_tracks(limit=1)[0].item - - # # Act - # artist.share(users_to_spam, spam_message) - # track.share(users_to_spam, spam_message) - - # Assert - # Check inbox for spam! - - # album/artist/track/user - - def test_user_top_artists(self): - # Arrange - lastfm_user = self.network.get_user(self.username) - - # Act - artists = lastfm_user.get_top_artists(limit=1) - - # Assert - self.helper_only_one_thing_in_top_list(artists, pylast.Artist) - - def test_user_top_albums(self): - # Arrange - user = self.network.get_user("RJ") - - # Act - albums = user.get_top_albums(limit=1) - - # 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_user_subscriber(self): - # Arrange - subscriber = self.network.get_user("RJ") - non_subscriber = self.network.get_user("Test User") - - # Act - subscriber_is_subscriber = subscriber.is_subscriber() - non_subscriber_is_subscriber = non_subscriber.is_subscriber() - - # Assert - self.assertTrue(subscriber_is_subscriber) - self.assertFalse(non_subscriber_is_subscriber) - - def test_user_get_image(self): - # Arrange - user = self.network.get_user("RJ") - - # Act - url = user.get_image() - - # Assert - self.assertTrue(url.startswith("https://")) - - def test_user_get_library(self): - # Arrange - user = self.network.get_user(self.username) - - # Act - library = user.get_library() - - # Assert - self.assertIsInstance(library, pylast.Library) - - def test_get_recent_tracks_from_to(self): - # Arrange - lastfm_user = self.network.get_user("RJ") - - from datetime import datetime - start = datetime(2011, 7, 21, 15, 10) - end = datetime(2011, 7, 21, 15, 15) - import calendar - utc_start = calendar.timegm(start.utctimetuple()) - utc_end = calendar.timegm(end.utctimetuple()) - - # Act - tracks = lastfm_user.get_recent_tracks(time_from=utc_start, - time_to=utc_end) - - # Assert - self.assertEqual(len(tracks), 1) - self.assertEqual(str(tracks[0].track.artist), "Johnny Cash") - self.assertEqual(str(tracks[0].track.title), "Ring of Fire") - - def test_tracks_notequal(self): - # Arrange - track1 = pylast.Track("Test Artist", "test title", self.network) - track2 = pylast.Track("Test Artist", "Test Track", self.network) - - # Act - # Assert - self.assertNotEqual(track1, track2) - - def test_track_id(self): - # Arrange - track = pylast.Track("Test Artist", "test title", self.network) - - # Act - id = track.get_id() - - # Assert - self.skip_if_lastfm_api_broken(id) - self.assertEqual(id, "14053327") - - def test_track_title_prop_caps(self): - # Arrange - track = pylast.Track("test artist", "test title", self.network) - - # Act - title = track.get_title(properly_capitalized=True) - - # Assert - self.assertEqual(title, "test title") - - def test_track_listener_count(self): - # Arrange - track = pylast.Track("test artist", "test title", self.network) - - # Act - count = track.get_listener_count() - - # Assert - self.assertGreater(count, 21) - - def test_album_tracks(self): - # Arrange - album = pylast.Album("Test Artist", "Test Release", self.network) - - # Act - tracks = album.get_tracks() - url = tracks[0].get_url() - - # Assert - self.assertIsInstance(tracks, list) - self.assertIsInstance(tracks[0], pylast.Track) - self.assertEqual(len(tracks), 4) - self.assertTrue(url.startswith("https://www.last.fm/music/test")) - - def test_track_eq_none_is_false(self): - # Arrange - track1 = None - track2 = pylast.Track("Test Artist", "test title", self.network) - - # Act / Assert - self.assertFalse(track1 == track2) - - def test_track_ne_none_is_true(self): - # Arrange - track1 = None - track2 = pylast.Track("Test Artist", "test title", self.network) - - # Act / Assert - self.assertTrue(track1 != track2) - - 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") - - def test_track_with_no_mbid(self): - # Arrange - track = pylast.Track("Static-X", "Set It Off", self.network) - - # Act - mbid = track.get_mbid() - - # Assert - self.assertEqual(mbid, None) - - if __name__ == '__main__': unittest.main(failfast=True) diff --git a/tests/test_pylast_album.py b/tests/test_pylast_album.py new file mode 100755 index 0000000..a93c27d --- /dev/null +++ b/tests/test_pylast_album.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python +""" +Integration (not unit) tests for pylast.py +""" +import unittest + +import pylast + +from .test_pylast import PyLastTestCase + + +class TestPyLastAlbum(PyLastTestCase): + + def test_album_tags_are_topitems(self): + # Arrange + albums = self.network.get_user('RJ').get_top_albums() + + # Act + tags = albums[0].item.get_top_tags(limit=1) + + # Assert + self.assertGreater(len(tags), 0) + self.assertIsInstance(tags[0], pylast.TopItem) + + def test_album_is_hashable(self): + # Arrange + album = self.network.get_album("Test Artist", "Test Album") + + # Act/Assert + self.helper_is_thing_hashable(album) + + def test_album_in_recent_tracks(self): + # Arrange + lastfm_user = self.network.get_user(self.username) + + # Act + # limit=2 to ignore now-playing: + track = lastfm_user.get_recent_tracks(limit=2)[0] + + # Assert + self.assertTrue(hasattr(track, 'album')) + + def test_album_in_artist_tracks(self): + # Arrange + lastfm_user = self.network.get_user(self.username) + + # Act + track = lastfm_user.get_artist_tracks(artist="Test Artist")[0] + + # Assert + self.assertTrue(hasattr(track, 'album')) + + def test_album_wiki_content(self): + # Arrange + album = pylast.Album("Test Artist", "Test Album", self.network) + + # Act + wiki = album.get_wiki_content() + + # Assert + self.assertIsNotNone(wiki) + self.assertGreaterEqual(len(wiki), 1) + + def test_album_wiki_published_date(self): + # Arrange + album = pylast.Album("Test Artist", "Test Album", self.network) + + # Act + wiki = album.get_wiki_published_date() + + # Assert + self.assertIsNotNone(wiki) + self.assertGreaterEqual(len(wiki), 1) + + def test_album_wiki_summary(self): + # Arrange + album = pylast.Album("Test Artist", "Test Album", self.network) + + # Act + wiki = album.get_wiki_summary() + + # Assert + self.assertIsNotNone(wiki) + self.assertGreaterEqual(len(wiki), 1) + + def test_album_eq_none_is_false(self): + # Arrange + album1 = None + album2 = pylast.Album("Test Artist", "Test Album", self.network) + + # Act / Assert + self.assertFalse(album1 == album2) + + def test_album_ne_none_is_true(self): + # Arrange + album1 = None + album2 = pylast.Album("Test Artist", "Test Album", self.network) + + # Act / Assert + self.assertTrue(album1 != album2) + + +if __name__ == '__main__': + unittest.main(failfast=True) diff --git a/tests/test_pylast_artist.py b/tests/test_pylast_artist.py new file mode 100755 index 0000000..4fe15a2 --- /dev/null +++ b/tests/test_pylast_artist.py @@ -0,0 +1,262 @@ +#!/usr/bin/env python +""" +Integration (not unit) tests for pylast.py +""" +import unittest + +import pylast + +from .test_pylast import PyLastTestCase + + +class TestPyLastArtist(PyLastTestCase): + + def test_artist_is_hashable(self): + # Arrange + test_artist = self.network.get_artist("Test Artist") + artist = test_artist.get_similar(limit=2)[0].item + self.assertIsInstance(artist, pylast.Artist) + + # Act/Assert + self.helper_is_thing_hashable(artist) + + def test_bio_published_date(self): + # Arrange + artist = pylast.Artist("Test Artist", self.network) + + # Act + bio = artist.get_bio_published_date() + + # Assert + self.assertIsNotNone(bio) + self.assertGreaterEqual(len(bio), 1) + + def test_bio_content(self): + # Arrange + artist = pylast.Artist("Test Artist", self.network) + + # Act + bio = artist.get_bio_content(language="en") + + # Assert + self.assertIsNotNone(bio) + self.assertGreaterEqual(len(bio), 1) + + def test_bio_summary(self): + # Arrange + artist = pylast.Artist("Test Artist", self.network) + + # Act + bio = artist.get_bio_summary(language="en") + + # Assert + self.assertIsNotNone(bio) + self.assertGreaterEqual(len(bio), 1) + + def test_artist_top_tracks(self): + # Arrange + # Pick an artist with plenty of plays + artist = self.network.get_top_artists(limit=1)[0].item + + # Act + things = artist.get_top_tracks(limit=2) + + # Assert + self.helper_two_different_things_in_top_list(things, pylast.Track) + + def test_artist_top_albums(self): + # Arrange + # Pick an artist with plenty of plays + artist = self.network.get_top_artists(limit=1)[0].item + + # Act + things = artist.get_top_albums(limit=2) + + # Assert + self.helper_two_different_things_in_top_list(things, pylast.Album) + + def test_artist_listener_count(self): + # Arrange + artist = self.network.get_artist("Test Artist") + + # Act + count = artist.get_listener_count() + + # Assert + self.assertIsInstance(count, int) + self.assertGreater(count, 0) + + def test_tag_artist(self): + # Arrange + artist = self.network.get_artist("Test Artist") +# artist.clear_tags() + + # Act + artist.add_tag("testing") + + # Assert + tags = artist.get_tags() + self.assertGreater(len(tags), 0) + found = False + for tag in tags: + if tag.name == "testing": + found = True + break + self.assertTrue(found) + + def test_remove_tag_of_type_text(self): + # Arrange + tag = "testing" # text + artist = self.network.get_artist("Test Artist") + artist.add_tag(tag) + + # Act + artist.remove_tag(tag) + + # Assert + tags = artist.get_tags() + found = False + for tag in tags: + if tag.name == "testing": + found = True + break + self.assertFalse(found) + + def test_remove_tag_of_type_tag(self): + # Arrange + tag = pylast.Tag("testing", self.network) # Tag + artist = self.network.get_artist("Test Artist") + artist.add_tag(tag) + + # Act + artist.remove_tag(tag) + + # Assert + tags = artist.get_tags() + found = False + for tag in tags: + if tag.name == "testing": + found = True + break + self.assertFalse(found) + + def test_remove_tags(self): + # Arrange + tags = ["removetag1", "removetag2"] + artist = self.network.get_artist("Test Artist") + artist.add_tags(tags) + artist.add_tags("1more") + tags_before = artist.get_tags() + + # Act + artist.remove_tags(tags) + + # Assert + tags_after = artist.get_tags() + self.assertEqual(len(tags_after), len(tags_before) - 2) + found1, found2 = False, False + for tag in tags_after: + if tag.name == "removetag1": + found1 = True + elif tag.name == "removetag2": + found2 = True + self.assertFalse(found1) + self.assertFalse(found2) + + def test_set_tags(self): + # Arrange + tags = ["sometag1", "sometag2"] + artist = self.network.get_artist("Test Artist 2") + artist.add_tags(tags) + tags_before = artist.get_tags() + new_tags = ["settag1", "settag2"] + + # Act + artist.set_tags(new_tags) + + # Assert + tags_after = artist.get_tags() + self.assertNotEqual(tags_before, tags_after) + self.assertEqual(len(tags_after), 2) + found1, found2 = False, False + for tag in tags_after: + if tag.name == "settag1": + found1 = True + elif tag.name == "settag2": + found2 = True + self.assertTrue(found1) + self.assertTrue(found2) + + def test_artists(self): + # Arrange + artist1 = self.network.get_artist("Radiohead") + artist2 = self.network.get_artist("Portishead") + + # Act + url = artist1.get_url() + mbid = artist1.get_mbid() + image = artist1.get_cover_image() + playcount = artist1.get_playcount() + streamable = artist1.is_streamable() + name = artist1.get_name(properly_capitalized=False) + name_cap = artist1.get_name(properly_capitalized=True) + + # Assert + self.assertIn("http", image) + self.assertGreater(playcount, 1) + self.assertTrue(artist1 != artist2) + self.assertEqual(name.lower(), name_cap.lower()) + self.assertEqual(url, "https://www.last.fm/music/radiohead") + self.assertEqual(mbid, "a74b1b7f-71a5-4011-9441-d0b5e4122711") + self.assertIsInstance(streamable, bool) + + def test_artist_eq_none_is_false(self): + # Arrange + artist1 = None + artist2 = pylast.Artist("Test Artist", self.network) + + # Act / Assert + self.assertFalse(artist1 == artist2) + + def test_artist_ne_none_is_true(self): + # Arrange + artist1 = None + artist2 = pylast.Artist("Test Artist", self.network) + + # Act / Assert + self.assertTrue(artist1 != artist2) + + def test_band_members(self): + # Arrange + artist = pylast.Artist("The Beatles", self.network) + + # Act + band_members = artist.get_band_members() + + # Assert + self.skip_if_lastfm_api_broken(band_members) + self.assertGreaterEqual(len(band_members), 4) + + def test_no_band_members(self): + # Arrange + artist = pylast.Artist("John Lennon", self.network) + + # Act + band_members = artist.get_band_members() + + # Assert + self.assertIsNone(band_members) + + 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") + + +if __name__ == '__main__': + unittest.main(failfast=True) diff --git a/tests/test_pylast_country.py b/tests/test_pylast_country.py new file mode 100755 index 0000000..7d9554e --- /dev/null +++ b/tests/test_pylast_country.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python +""" +Integration (not unit) tests for pylast.py +""" +import unittest + +import pylast + +from .test_pylast import PyLastTestCase + + +class TestPyLastCountry(PyLastTestCase): + + def test_country_is_hashable(self): + # Arrange + country = self.network.get_country("Italy") + + # Act/Assert + self.helper_is_thing_hashable(country) + + def test_countries(self): + # Arrange + country1 = pylast.Country("Italy", self.network) + country2 = pylast.Country("Finland", self.network) + + # Act + text = str(country1) + rep = repr(country1) + url = country1.get_url() + + # Assert + self.assertIn("Italy", rep) + self.assertIn("pylast.Country", rep) + self.assertEqual(text, "Italy") + self.assertTrue(country1 == country1) + self.assertTrue(country1 != country2) + self.assertEqual(url, "https://www.last.fm/place/italy") + + +if __name__ == '__main__': + unittest.main(failfast=True) diff --git a/tests/test_pylast_library.py b/tests/test_pylast_library.py new file mode 100755 index 0000000..0df22da --- /dev/null +++ b/tests/test_pylast_library.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python +""" +Integration (not unit) tests for pylast.py +""" +import unittest + +import pylast + +from .test_pylast import PyLastTestCase + + +class TestPyLastLibrary(PyLastTestCase): + + def test_library_is_hashable(self): + # Arrange + library = pylast.Library(user=self.username, network=self.network) + + # Act/Assert + self.helper_is_thing_hashable(library) + + def test_cacheable_library(self): + # Arrange + library = pylast.Library(self.username, self.network) + + # Act/Assert + self.helper_validate_cacheable(library, "get_artists") + + +if __name__ == '__main__': + unittest.main(failfast=True) diff --git a/tests/test_pylast_librefm.py b/tests/test_pylast_librefm.py index 3d2cb53..c403e2b 100755 --- a/tests/test_pylast_librefm.py +++ b/tests/test_pylast_librefm.py @@ -7,6 +7,7 @@ import unittest from flaky import flaky import pylast + from .test_pylast import load_secrets diff --git a/tests/test_pylast_network.py b/tests/test_pylast_network.py new file mode 100755 index 0000000..553a7aa --- /dev/null +++ b/tests/test_pylast_network.py @@ -0,0 +1,307 @@ +#!/usr/bin/env python +""" +Integration (not unit) tests for pylast.py +""" +import time +import unittest + +import pylast + +from .test_pylast import PyLastTestCase + + +class TestPyLastNetwork(PyLastTestCase): + + def test_scrobble(self): + # Arrange + artist = "Test Artist" + title = "test title" + timestamp = self.unix_timestamp() + lastfm_user = self.network.get_user(self.username) + + # Act + self.network.scrobble(artist=artist, title=title, timestamp=timestamp) + + # Assert + # limit=2 to ignore now-playing: + last_scrobble = lastfm_user.get_recent_tracks(limit=2)[0] + self.assertEqual(str(last_scrobble.track.artist), str(artist)) + self.assertEqual(str(last_scrobble.track.title), str(title)) + self.assertEqual(str(last_scrobble.timestamp), str(timestamp)) + + def test_update_now_playing(self): + # Arrange + artist = "Test Artist" + title = "test title" + album = "Test Album" + track_number = 1 + lastfm_user = self.network.get_user(self.username) + + # Act + self.network.update_now_playing( + artist=artist, title=title, album=album, track_number=track_number) + + # Assert + current_track = lastfm_user.get_now_playing() + self.assertIsNotNone(current_track) + self.assertEqual(str(current_track.title), "test title") + self.assertEqual(str(current_track.artist), "Test Artist") + + def test_invalid_xml(self): + # Arrange + # Currently causes PCDATA invalid Char value 25 + artist = "Blind Willie Johnson" + title = "It's nobody's fault but mine" + + # Act + search = self.network.search_for_track(artist, title) + total = search.get_total_result_count() + + # Assert + self.skip_if_lastfm_api_broken(total) + self.assertGreaterEqual(int(total), 0) + + def test_enable_rate_limiting(self): + # Arrange + self.assertFalse(self.network.is_rate_limited()) + + # Act + self.network.enable_rate_limit() + then = time.time() + # Make some network call, limit not applied first time + self.network.get_user(self.username) + # Make a second network call, limiting should be applied + self.network.get_top_artists() + now = time.time() + + # Assert + self.assertTrue(self.network.is_rate_limited()) + self.assertGreaterEqual(now - then, 0.2) + + def test_disable_rate_limiting(self): + # Arrange + self.network.enable_rate_limit() + self.assertTrue(self.network.is_rate_limited()) + + # Act + self.network.disable_rate_limit() + # Make some network call, limit not applied first time + self.network.get_user(self.username) + # Make a second network call, limiting should be applied + self.network.get_top_artists() + + # Assert + self.assertFalse(self.network.is_rate_limited()) + + def test_lastfm_network_name(self): + # Act + name = str(self.network) + + # Assert + self.assertEqual(name, "Last.fm Network") + + def test_geo_get_top_artists(self): + # Arrange + # Act + artists = self.network.get_geo_top_artists( + country="United Kingdom", limit=1) + + # Assert + self.assertEqual(len(artists), 1) + self.assertIsInstance(artists[0], pylast.TopItem) + self.assertIsInstance(artists[0].item, pylast.Artist) + + def test_geo_get_top_tracks(self): + # Arrange + # Act + tracks = self.network.get_geo_top_tracks( + country="United Kingdom", location="Manchester", limit=1) + + # Assert + self.assertEqual(len(tracks), 1) + self.assertIsInstance(tracks[0], pylast.TopItem) + self.assertIsInstance(tracks[0].item, pylast.Track) + + def test_network_get_top_artists_with_limit(self): + # Arrange + # Act + artists = self.network.get_top_artists(limit=1) + + # Assert + self.helper_only_one_thing_in_top_list(artists, pylast.Artist) + + def test_network_get_top_tags_with_limit(self): + # Arrange + # Act + tags = self.network.get_top_tags(limit=1) + + # Assert + self.helper_only_one_thing_in_top_list(tags, pylast.Tag) + + def test_network_get_top_tags_with_no_limit(self): + # Arrange + # Act + tags = self.network.get_top_tags() + + # Assert + self.helper_at_least_one_thing_in_top_list(tags, pylast.Tag) + + def test_network_get_top_tracks_with_limit(self): + # Arrange + # Act + tracks = self.network.get_top_tracks(limit=1) + + # Assert + self.helper_only_one_thing_in_top_list(tracks, pylast.Track) + + def test_country_top_tracks(self): + # Arrange + country = self.network.get_country("Croatia") + + # Act + things = country.get_top_tracks(limit=2) + + # Assert + self.helper_two_different_things_in_top_list(things, pylast.Track) + + def test_country_network_top_tracks(self): + # Arrange + # Act + things = self.network.get_geo_top_tracks("Croatia", limit=2) + + # Assert + self.helper_two_different_things_in_top_list(things, pylast.Track) + + def test_tag_top_tracks(self): + # Arrange + tag = self.network.get_tag("blues") + + # Act + things = tag.get_top_tracks(limit=2) + + # Assert + self.helper_two_different_things_in_top_list(things, pylast.Track) + + def test_album_data(self): + # Arrange + thing = self.network.get_album("Test Artist", "Test Album") + + # Act + stringed = str(thing) + repr = thing.__repr__() + title = thing.get_title() + name = thing.get_name() + playcount = thing.get_playcount() + url = thing.get_url() + + # Assert + self.assertEqual(stringed, "Test Artist - Test Album") + self.assertIn("pylast.Album('Test Artist', 'Test Album',", repr) + self.assertEqual(title, name) + self.assertIsInstance(playcount, int) + self.assertGreater(playcount, 1) + self.assertEqual( + "https://www.last.fm/music/test%2bartist/test%2balbum", url) + + def test_track_data(self): + # Arrange + thing = self.network.get_track("Test Artist", "test title") + + # Act + stringed = str(thing) + repr = thing.__repr__() + title = thing.get_title() + name = thing.get_name() + playcount = thing.get_playcount() + url = thing.get_url(pylast.DOMAIN_FRENCH) + + # Assert + self.assertEqual(stringed, "Test Artist - test title") + self.assertIn("pylast.Track('Test Artist', 'test title',", repr) + self.assertEqual(title, "test title") + self.assertEqual(title, name) + self.assertIsInstance(playcount, int) + self.assertGreater(playcount, 1) + self.assertEqual( + "https://www.last.fm/fr/music/test%2bartist/_/test%2btitle", url) + + def test_country_top_artists(self): + # Arrange + country = self.network.get_country("Ukraine") + + # Act + artists = country.get_top_artists(limit=1) + + # Assert + self.helper_only_one_thing_in_top_list(artists, pylast.Artist) + + def test_caching(self): + # Arrange + user = self.network.get_user("RJ") + + # Act + self.network.enable_caching() + tags1 = user.get_top_tags(limit=1, cacheable=True) + tags2 = user.get_top_tags(limit=1, cacheable=True) + + # Assert + self.assertTrue(self.network.is_caching_enabled()) + self.assertEqual(tags1, tags2) + self.network.disable_caching() + self.assertFalse(self.network.is_caching_enabled()) + + def test_album_mbid(self): + # Arrange + mbid = "a6a265bf-9f81-4055-8224-f7ac0aa6b937" + + # Act + album = self.network.get_album_by_mbid(mbid) + album_mbid = album.get_mbid() + + # Assert + self.assertIsInstance(album, pylast.Album) + self.assertEqual(album.title.lower(), "test") + self.assertEqual(album_mbid, mbid) + + def test_artist_mbid(self): + # Arrange + mbid = "7e84f845-ac16-41fe-9ff8-df12eb32af55" + + # Act + artist = self.network.get_artist_by_mbid(mbid) + + # Assert + self.assertIsInstance(artist, pylast.Artist) + self.assertEqual(artist.name, "MusicBrainz Test Artist") + + def test_track_mbid(self): + # Arrange + mbid = "ebc037b1-cc9c-44f2-a21f-83c219f0e1e0" + + # Act + track = self.network.get_track_by_mbid(mbid) + track_mbid = track.get_mbid() + + # Assert + self.assertIsInstance(track, pylast.Track) + self.assertEqual(track.title, "first") + self.assertEqual(track_mbid, mbid) + + def test_init_with_token(self): + # Arrange/Act + try: + pylast.LastFMNetwork( + api_key=self.__class__.secrets["api_key"], + api_secret=self.__class__.secrets["api_secret"], + token="invalid", + ) + except pylast.WSError as exc: + msg = str(exc) + + # Assert + self.assertEqual(msg, + "Unauthorized Token - This token has not been issued") + + +if __name__ == '__main__': + unittest.main(failfast=True) diff --git a/tests/test_pylast_tag.py b/tests/test_pylast_tag.py new file mode 100755 index 0000000..d1a5f72 --- /dev/null +++ b/tests/test_pylast_tag.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python +""" +Integration (not unit) tests for pylast.py +""" +import unittest + +import pylast + +from .test_pylast import PyLastTestCase + + +class TestPyLastTag(PyLastTestCase): + + def test_tag_is_hashable(self): + # Arrange + tag = self.network.get_top_tags(limit=1)[0] + + # Act/Assert + self.helper_is_thing_hashable(tag) + + def test_tag_top_artists(self): + # Arrange + tag = self.network.get_tag("blues") + + # Act + artists = tag.get_top_artists(limit=1) + + # Assert + self.helper_only_one_thing_in_top_list(artists, pylast.Artist) + + def test_tag_top_albums(self): + # Arrange + tag = self.network.get_tag("blues") + + # Act + albums = tag.get_top_albums(limit=1) + + # Assert + self.helper_only_one_thing_in_top_list(albums, pylast.Album) + + def test_tags(self): + # Arrange + tag1 = self.network.get_tag("blues") + tag2 = self.network.get_tag("rock") + + # Act + tag_repr = repr(tag1) + tag_str = str(tag1) + name = tag1.get_name(properly_capitalized=True) + url = tag1.get_url() + + # Assert + self.assertEqual("blues", tag_str) + self.assertIn("pylast.Tag", tag_repr) + self.assertIn("blues", tag_repr) + self.assertEqual("blues", name) + self.assertTrue(tag1 == tag1) + self.assertTrue(tag1 != tag2) + self.assertEqual(url, "https://www.last.fm/tag/blues") + + def test_tags_similar(self): + # Arrange + tag = self.network.get_tag("blues") + + # Act + similar = tag.get_similar() + + # Assert + self.skip_if_lastfm_api_broken(similar) + found = False + for tag in similar: + if tag.name == "delta blues": + found = True + break + self.assertTrue(found) + + +if __name__ == '__main__': + unittest.main(failfast=True) diff --git a/tests/test_pylast_track.py b/tests/test_pylast_track.py new file mode 100755 index 0000000..9f446da --- /dev/null +++ b/tests/test_pylast_track.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python +""" +Integration (not unit) tests for pylast.py +""" +import unittest + +import pylast + +from .test_pylast import PyLastTestCase + + +class TestPyLastTrack(PyLastTestCase): + + def test_love(self): + # Arrange + artist = "Test Artist" + title = "test title" + track = self.network.get_track(artist, title) + lastfm_user = self.network.get_user(self.username) + + # Act + track.love() + + # Assert + loved = lastfm_user.get_loved_tracks(limit=1) + self.assertEqual(str(loved[0].track.artist), "Test Artist") + self.assertEqual(str(loved[0].track.title), "test title") + + def test_unlove(self): + # Arrange + artist = pylast.Artist("Test Artist", self.network) + title = "test title" + track = pylast.Track(artist, title, self.network) + lastfm_user = self.network.get_user(self.username) + track.love() + + # Act + track.unlove() + + # Assert + loved = lastfm_user.get_loved_tracks(limit=1) + if len(loved): # OK to be empty but if not: + self.assertNotEqual(str(loved.track.artist), "Test Artist") + self.assertNotEqual(str(loved.track.title), "test title") + + def test_user_play_count_in_track_info(self): + # Arrange + artist = "Test Artist" + title = "test title" + track = pylast.Track( + artist=artist, title=title, + network=self.network, username=self.username) + + # Act + count = track.get_userplaycount() + + # Assert + self.assertGreaterEqual(count, 0) + + def test_user_loved_in_track_info(self): + # Arrange + artist = "Test Artist" + title = "test title" + track = pylast.Track( + artist=artist, title=title, + network=self.network, username=self.username) + + # Act + loved = track.get_userloved() + + # Assert + self.assertIsNotNone(loved) + self.assertIsInstance(loved, bool) + self.assertNotIsInstance(loved, str) + + def test_track_is_hashable(self): + # Arrange + artist = self.network.get_artist("Test Artist") + track = artist.get_top_tracks()[0].item + self.assertIsInstance(track, pylast.Track) + + # Act/Assert + self.helper_is_thing_hashable(track) + + def test_track_wiki_content(self): + # Arrange + track = pylast.Track("Test Artist", "test title", self.network) + + # Act + wiki = track.get_wiki_content() + + # Assert + self.assertIsNotNone(wiki) + self.assertGreaterEqual(len(wiki), 1) + + def test_track_wiki_summary(self): + # Arrange + track = pylast.Track("Test Artist", "test title", self.network) + + # Act + wiki = track.get_wiki_summary() + + # Assert + self.assertIsNotNone(wiki) + self.assertGreaterEqual(len(wiki), 1) + + +if __name__ == '__main__': + unittest.main(failfast=True) diff --git a/tests/test_pylast_user.py b/tests/test_pylast_user.py new file mode 100755 index 0000000..3f24baa --- /dev/null +++ b/tests/test_pylast_user.py @@ -0,0 +1,448 @@ +#!/usr/bin/env python +""" +Integration (not unit) tests for pylast.py +""" +import os +import unittest + +import pytest + +import pylast + +from .test_pylast import PyLastTestCase + + +class TestPyLastUser(PyLastTestCase): + + def test_get_user_registration(self): + # Arrange + username = "RJ" + user = self.network.get_user(username) + + # Act + registered = user.get_registered() + + # Assert + # Last.fm API broken? Should be yyyy-mm-dd not Unix timestamp + if int(registered): + pytest.skip("Last.fm API is broken.") + + # Just check date because of timezones + self.assertIn(u"2002-11-20 ", registered) + + def test_get_user_unixtime_registration(self): + # Arrange + username = "RJ" + user = self.network.get_user(username) + + # Act + unixtime_registered = user.get_unixtime_registered() + + # Assert + # Just check date because of timezones + self.assertEqual(unixtime_registered, u"1037793040") + + def test_get_genderless_user(self): + # Arrange + # Currently test_user has no gender set: + lastfm_user = self.network.get_user("test_user") + + # Act + gender = lastfm_user.get_gender() + + # Assert + self.assertIsNone(gender) + + def test_get_countryless_user(self): + # Arrange + # Currently test_user has no country set: + lastfm_user = self.network.get_user("test_user") + + # Act + country = lastfm_user.get_country() + + # Assert + self.assertIsNone(country) + + def test_user_equals_none(self): + # Arrange + lastfm_user = self.network.get_user(self.username) + + # Act + value = (lastfm_user is None) + + # Assert + self.assertFalse(value) + + def test_user_not_equal_to_none(self): + # Arrange + lastfm_user = self.network.get_user(self.username) + + # Act + value = (lastfm_user is not None) + + # Assert + self.assertTrue(value) + + def test_now_playing_user_with_no_scrobbles(self): + # Arrange + # Currently test-account has no scrobbles: + user = self.network.get_user('test-account') + + # Act + current_track = user.get_now_playing() + + # Assert + self.assertIsNone(current_track) + + def test_love_limits(self): + # Arrange + # Currently test-account has at least 23 loved tracks: + user = self.network.get_user("test-user") + + # Act/Assert + self.assertEqual(len(user.get_loved_tracks(limit=20)), 20) + self.assertLessEqual(len(user.get_loved_tracks(limit=100)), 100) + self.assertGreaterEqual(len(user.get_loved_tracks(limit=None)), 23) + self.assertGreaterEqual(len(user.get_loved_tracks(limit=0)), 23) + + def test_user_is_hashable(self): + # Arrange + user = self.network.get_user(self.username) + + # Act/Assert + self.helper_is_thing_hashable(user) + + # Commented out because (a) it'll take a long time and (b) it strangely + # fails due Last.fm's complaining of hitting the rate limit, even when + # limited to one call per second. The ToS allows 5 calls per second. + # def test_get_all_scrobbles(self): + # # Arrange + # lastfm_user = self.network.get_user("RJ") + # self.network.enable_rate_limit() # this is going to be slow... + + # # Act + # tracks = lastfm_user.get_recent_tracks(limit=None) + + # # Assert + # self.assertGreaterEqual(len(tracks), 0) + + def test_pickle(self): + # Arrange + import pickle + lastfm_user = self.network.get_user(self.username) + filename = str(self.unix_timestamp()) + ".pkl" + + # Act + with open(filename, "wb") as f: + pickle.dump(lastfm_user, f) + with open(filename, "rb") as f: + loaded_user = pickle.load(f) + os.remove(filename) + + # Assert + self.assertEqual(lastfm_user, loaded_user) + + def test_cacheable_user_artist_tracks(self): + # Arrange + lastfm_user = self.network.get_authenticated_user() + + # Act + result1 = lastfm_user.get_artist_tracks("Test Artist", cacheable=False) + result2 = lastfm_user.get_artist_tracks("Test Artist", cacheable=True) + result3 = lastfm_user.get_artist_tracks("Test Artist") + + # Assert + self.helper_validate_results(result1, result2, result3) + + def test_cacheable_user(self): + # Arrange + lastfm_user = self.network.get_authenticated_user() + + # Act/Assert + self.helper_validate_cacheable(lastfm_user, "get_friends") + self.helper_validate_cacheable(lastfm_user, "get_loved_tracks") + self.helper_validate_cacheable(lastfm_user, "get_recent_tracks") + + def test_user_get_top_tags_with_limit(self): + # Arrange + user = self.network.get_user("RJ") + + # Act + tags = user.get_top_tags(limit=1) + + # Assert + self.skip_if_lastfm_api_broken(tags) + self.helper_only_one_thing_in_top_list(tags, pylast.Tag) + + def test_user_top_tracks(self): + # Arrange + lastfm_user = self.network.get_user(self.username) + + # Act + things = lastfm_user.get_top_tracks(limit=2) + + # Assert + self.helper_two_different_things_in_top_list(things, pylast.Track) + + def helper_assert_chart(self, chart, expected_type): + # Assert + self.assertIsNotNone(chart) + self.assertGreater(len(chart), 0) + self.assertIsInstance(chart[0], pylast.TopItem) + self.assertIsInstance(chart[0].item, expected_type) + + def helper_get_assert_charts(self, thing, date): + # Arrange + (from_date, to_date) = date + + # Act + artist_chart = thing.get_weekly_artist_charts(from_date, to_date) + if type(thing) is not pylast.Tag: + album_chart = thing.get_weekly_album_charts(from_date, to_date) + track_chart = thing.get_weekly_track_charts(from_date, to_date) + + # Assert + self.helper_assert_chart(artist_chart, pylast.Artist) + if type(thing) is not pylast.Tag: + self.helper_assert_chart(album_chart, pylast.Album) + self.helper_assert_chart(track_chart, pylast.Track) + + def helper_dates_valid(self, dates): + # Assert + self.assertGreaterEqual(len(dates), 1) + self.assertIsInstance(dates[0], tuple) + (start, end) = dates[0] + self.assertLess(start, end) + + def test_user_charts(self): + # Arrange + lastfm_user = self.network.get_user("RJ") + dates = lastfm_user.get_weekly_chart_dates() + self.helper_dates_valid(dates) + + # Act/Assert + self.helper_get_assert_charts(lastfm_user, dates[0]) + + # Commented out to avoid spamming + # def test_share_spam(self): + # # Arrange + # users_to_spam = [TODO_ENTER_SPAMEES_HERE] + # spam_message = "Dig the krazee sound!" + # artist = self.network.get_top_artists(limit=1)[0].item + # track = artist.get_top_tracks(limit=1)[0].item + + # # Act + # artist.share(users_to_spam, spam_message) + # track.share(users_to_spam, spam_message) + + # Assert + # Check inbox for spam! + + # album/artist/track/user + + def test_user_top_artists(self): + # Arrange + lastfm_user = self.network.get_user(self.username) + + # Act + artists = lastfm_user.get_top_artists(limit=1) + + # Assert + self.helper_only_one_thing_in_top_list(artists, pylast.Artist) + + def test_user_top_albums(self): + # Arrange + user = self.network.get_user("RJ") + + # Act + albums = user.get_top_albums(limit=1) + + # 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_user_subscriber(self): + # Arrange + subscriber = self.network.get_user("RJ") + non_subscriber = self.network.get_user("Test User") + + # Act + subscriber_is_subscriber = subscriber.is_subscriber() + non_subscriber_is_subscriber = non_subscriber.is_subscriber() + + # Assert + self.assertTrue(subscriber_is_subscriber) + self.assertFalse(non_subscriber_is_subscriber) + + def test_user_get_image(self): + # Arrange + user = self.network.get_user("RJ") + + # Act + url = user.get_image() + + # Assert + self.assertTrue(url.startswith("https://")) + + def test_user_get_library(self): + # Arrange + user = self.network.get_user(self.username) + + # Act + library = user.get_library() + + # Assert + self.assertIsInstance(library, pylast.Library) + + def test_get_recent_tracks_from_to(self): + # Arrange + lastfm_user = self.network.get_user("RJ") + + from datetime import datetime + start = datetime(2011, 7, 21, 15, 10) + end = datetime(2011, 7, 21, 15, 15) + import calendar + utc_start = calendar.timegm(start.utctimetuple()) + utc_end = calendar.timegm(end.utctimetuple()) + + # Act + tracks = lastfm_user.get_recent_tracks(time_from=utc_start, + time_to=utc_end) + + # Assert + self.assertEqual(len(tracks), 1) + self.assertEqual(str(tracks[0].track.artist), "Johnny Cash") + self.assertEqual(str(tracks[0].track.title), "Ring of Fire") + + def test_tracks_notequal(self): + # Arrange + track1 = pylast.Track("Test Artist", "test title", self.network) + track2 = pylast.Track("Test Artist", "Test Track", self.network) + + # Act + # Assert + self.assertNotEqual(track1, track2) + + def test_track_id(self): + # Arrange + track = pylast.Track("Test Artist", "test title", self.network) + + # Act + id = track.get_id() + + # Assert + self.skip_if_lastfm_api_broken(id) + self.assertEqual(id, "14053327") + + def test_track_title_prop_caps(self): + # Arrange + track = pylast.Track("test artist", "test title", self.network) + + # Act + title = track.get_title(properly_capitalized=True) + + # Assert + self.assertEqual(title, "test title") + + def test_track_listener_count(self): + # Arrange + track = pylast.Track("test artist", "test title", self.network) + + # Act + count = track.get_listener_count() + + # Assert + self.assertGreater(count, 21) + + def test_album_tracks(self): + # Arrange + album = pylast.Album("Test Artist", "Test Release", self.network) + + # Act + tracks = album.get_tracks() + url = tracks[0].get_url() + + # Assert + self.assertIsInstance(tracks, list) + self.assertIsInstance(tracks[0], pylast.Track) + self.assertEqual(len(tracks), 4) + self.assertTrue(url.startswith("https://www.last.fm/music/test")) + + def test_track_eq_none_is_false(self): + # Arrange + track1 = None + track2 = pylast.Track("Test Artist", "test title", self.network) + + # Act / Assert + self.assertFalse(track1 == track2) + + def test_track_ne_none_is_true(self): + # Arrange + track1 = None + track2 = pylast.Track("Test Artist", "test title", self.network) + + # Act / Assert + self.assertTrue(track1 != track2) + + 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") + + def test_track_with_no_mbid(self): + # Arrange + track = pylast.Track("Static-X", "Set It Off", self.network) + + # Act + mbid = track.get_mbid() + + # Assert + self.assertEqual(mbid, None) + + +if __name__ == '__main__': + unittest.main(failfast=True) From 58b93c847e0e3ec71b1eff0b63a7d45e94005bc3 Mon Sep 17 00:00:00 2001 From: hugovk Date: Wed, 18 Oct 2017 08:57:39 +0300 Subject: [PATCH 45/66] WIP: More tests --- tests/test_pylast_network.py | 63 ++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/tests/test_pylast_network.py b/tests/test_pylast_network.py index 553a7aa..04037f4 100755 --- a/tests/test_pylast_network.py +++ b/tests/test_pylast_network.py @@ -302,6 +302,69 @@ class TestPyLastNetwork(PyLastTestCase): self.assertEqual(msg, "Unauthorized Token - This token has not been issued") + def test_proxy(self): + # Arrange + host = "https://example.com" + port = 1234 + + # Act / Assert + self.network.enable_proxy(host, port) + self.assertTrue(self.network.is_proxy_enabled()) + self.assertEqual(self.network._get_proxy(), + ["https://example.com", 1234]) + + self.network.disable_proxy() + self.assertFalse(self.network.is_proxy_enabled()) + + def test_album_search(self): + # Arrange + album = "Nevermind" + + # Act + search = self.network.search_for_album(album) + results = search.get_next_page() + + # Assert + self.assertIsInstance(results, list) + self.assertIsInstance(results[0], pylast.Album) + + def test_artist_search(self): + # Arrange + artist = "Nirvana" + + # Act + search = self.network.search_for_artist(artist) + results = search.get_next_page() + + # Assert + self.assertIsInstance(results, list) + self.assertIsInstance(results[0], pylast.Artist) + + def test_tag_search(self): + # Arrange + tag = "rock" + + # Act + search = self.network.search_for_tag(tag) + results = search.get_next_page() + + # Assert + self.assertIsInstance(results, list) + self.assertIsInstance(results[0], pylast.Tag) + + def test_track_search(self): + # Arrange + artist = "Nirvana" + track = "Smells Like Teen Spirit" + + # Act + search = self.network.search_for_track(artist, track) + results = search.get_next_page() + + # Assert + self.assertIsInstance(results, list) + self.assertIsInstance(results[0], pylast.Track) + if __name__ == '__main__': unittest.main(failfast=True) From f70254b9473bc133e4138e1c4680df6c4ed3a72d Mon Sep 17 00:00:00 2001 From: hugovk Date: Wed, 18 Oct 2017 22:31:15 +0300 Subject: [PATCH 46/66] Remove dead Last.fm tag search --- pylast/__init__.py | 27 --------------------------- tests/test_pylast_network.py | 12 ------------ 2 files changed, 39 deletions(-) diff --git a/pylast/__init__.py b/pylast/__init__.py index 333768b..0470a17 100644 --- a/pylast/__init__.py +++ b/pylast/__init__.py @@ -445,12 +445,6 @@ class _Network(object): return ArtistSearch(artist_name, self) - def search_for_tag(self, tag_name): - """Searches of a tag by its name. Returns a TagSearch object. - Use get_next_page() to retrieve sequences of results.""" - - return TagSearch(tag_name, self) - def search_for_track(self, artist_name, track_name): """Searches of a track by its name and its artist. Set artist to an empty string if not available. @@ -2729,27 +2723,6 @@ class ArtistSearch(_Search): return seq -class TagSearch(_Search): - """Search for a tag by tag name.""" - - def __init__(self, tag_name, network): - - _Search.__init__(self, "tag", {"tag": tag_name}, network) - - def get_next_page(self): - """Returns the next page of results as a sequence of Tag objects.""" - - master_node = self._retrieve_next_page() - - seq = [] - for node in master_node.getElementsByTagName("tag"): - tag = Tag(_extract(node, "name"), self.network) - tag.tag_count = _number(_extract(node, "count")) - seq.append(tag) - - return seq - - class TrackSearch(_Search): """ Search for a track by track title. If you don't want to narrow the results diff --git a/tests/test_pylast_network.py b/tests/test_pylast_network.py index 04037f4..7ea6432 100755 --- a/tests/test_pylast_network.py +++ b/tests/test_pylast_network.py @@ -340,18 +340,6 @@ class TestPyLastNetwork(PyLastTestCase): self.assertIsInstance(results, list) self.assertIsInstance(results[0], pylast.Artist) - def test_tag_search(self): - # Arrange - tag = "rock" - - # Act - search = self.network.search_for_tag(tag) - results = search.get_next_page() - - # Assert - self.assertIsInstance(results, list) - self.assertIsInstance(results[0], pylast.Tag) - def test_track_search(self): # Arrange artist = "Nirvana" From f2b699a11f37e4dda26da4281e9ce9379ff1b1b4 Mon Sep 17 00:00:00 2001 From: hugovk Date: Wed, 18 Oct 2017 23:48:02 +0300 Subject: [PATCH 47/66] More tests --- tests/test_pylast_library.py | 31 ++++++++++++++++++++++++++ tests/test_pylast_librefm.py | 14 ++++++++++++ tests/test_pylast_user.py | 43 ++++++++++++++++++++++++++++++++++++ 3 files changed, 88 insertions(+) diff --git a/tests/test_pylast_library.py b/tests/test_pylast_library.py index 0df22da..1e437f1 100755 --- a/tests/test_pylast_library.py +++ b/tests/test_pylast_library.py @@ -11,6 +11,26 @@ from .test_pylast import PyLastTestCase class TestPyLastLibrary(PyLastTestCase): + def test_repr(self): + # Arrange + library = pylast.Library(user=self.username, network=self.network) + + # Act + representation = repr(library) + + # Assert + self.assertTrue(representation.startswith("pylast.Library(")) + + def test_str(self): + # Arrange + library = pylast.Library(user=self.username, network=self.network) + + # Act + string = str(library) + + # Assert + self.assertTrue(string.endswith("'s Library")) + def test_library_is_hashable(self): # Arrange library = pylast.Library(user=self.username, network=self.network) @@ -25,6 +45,17 @@ class TestPyLastLibrary(PyLastTestCase): # Act/Assert self.helper_validate_cacheable(library, "get_artists") + def test_get_user(self): + # Arrange + library = pylast.Library(user=self.username, network=self.network) + user_to_get = self.network.get_user(self.username) + + # Act + library_user = library.get_user() + + # Assert + self.assertEqual(library_user, user_to_get) + if __name__ == '__main__': unittest.main(failfast=True) diff --git a/tests/test_pylast_librefm.py b/tests/test_pylast_librefm.py index c403e2b..7c958cb 100755 --- a/tests/test_pylast_librefm.py +++ b/tests/test_pylast_librefm.py @@ -30,6 +30,20 @@ class TestPyLastWithLibreFm(unittest.TestCase): # Assert self.assertEqual(name, "Radiohead") + def test_repr(self): + # Arrange + secrets = load_secrets() + username = secrets["username"] + password_hash = secrets["password_hash"] + network = pylast.LibreFMNetwork( + password_hash=password_hash, username=username) + + # Act + representation = repr(network) + + # Assert + self.assertTrue(representation.startswith("pylast.LibreFMNetwork(")) + if __name__ == '__main__': unittest.main(failfast=True) diff --git a/tests/test_pylast_user.py b/tests/test_pylast_user.py index 3f24baa..36ef7bd 100755 --- a/tests/test_pylast_user.py +++ b/tests/test_pylast_user.py @@ -14,6 +14,49 @@ from .test_pylast import PyLastTestCase class TestPyLastUser(PyLastTestCase): + def test_repr(self): + # Arrange + username = "RJ" + user = self.network.get_user(username) + + # Act + representation = repr(user) + + # Assert + self.assertTrue( + representation.startswith(representation), "pylast.User('RJ',") + + def test_str(self): + # Arrange + username = "RJ" + user = self.network.get_user(username) + + # Act + string = str(user) + + # Assert + self.assertEqual(string, "RJ") + + def test_equality(self): + # Arrange + username = "RJ" + user = self.network.get_user(username) + not_a_user = self.network + + # Act / Assert + self.assertNotEqual(user, not_a_user) + + def test_get_name(self): + # Arrange + username = "RJ" + user = self.network.get_user(username) + + # Act + name = user.get_name(properly_capitalized=True) + + # Assert + self.assertEqual(name, "RJ") + def test_get_user_registration(self): # Arrange username = "RJ" From 2ef88dfd4c0c26b3af042d090352e07be0644f1b Mon Sep 17 00:00:00 2001 From: hugovk Date: Thu, 19 Oct 2017 00:22:06 +0300 Subject: [PATCH 48/66] Remove dead or broken Last.fm user functions --- pylast/__init__.py | 56 ---------------------------------------------- 1 file changed, 56 deletions(-) diff --git a/pylast/__init__.py b/pylast/__init__.py index 0470a17..270ae69 100644 --- a/pylast/__init__.py +++ b/pylast/__init__.py @@ -97,9 +97,6 @@ IMAGES_ORDER_POPULARITY = "popularity" IMAGES_ORDER_DATE = "dateadded" -USER_MALE = 'Male' -USER_FEMALE = 'Female' - SCROBBLE_SOURCE_USER = "P" SCROBBLE_SOURCE_NON_PERSONALIZED_BROADCAST = "R" SCROBBLE_SOURCE_PERSONALIZED_BROADCAST = "E" @@ -2255,24 +2252,6 @@ class User(_BaseObject, _Chartable): return seq - def get_neighbours(self, limit=50, cacheable=True): - """Returns a list of the user's friends.""" - - params = self._get_params() - if limit: - params['limit'] = limit - - doc = self._request( - self.ws_prefix + '.getNeighbours', cacheable, params) - - seq = [] - names = _extract_all(doc, 'name') - - for name in names: - seq.append(User(name, self.network)) - - return seq - def get_now_playing(self): """ Returns the currently playing track, or None if nothing is playing. @@ -2350,20 +2329,6 @@ class User(_BaseObject, _Chartable): return seq - def get_id(self): - """Returns the user ID.""" - - doc = self._request(self.ws_prefix + ".getInfo", True) - - return _extract(doc, "id") - - def get_language(self): - """Returns the language code of the language used by the user.""" - - doc = self._request(self.ws_prefix + ".getInfo", True) - - return _extract(doc, "lang") - def get_country(self): """Returns the name of the country of the user.""" @@ -2376,27 +2341,6 @@ class User(_BaseObject, _Chartable): else: return Country(country, self.network) - def get_age(self): - """Returns the user's age.""" - - doc = self._request(self.ws_prefix + ".getInfo", True) - - return _number(_extract(doc, "age")) - - def get_gender(self): - """Returns the user's gender. Either USER_MALE or USER_FEMALE.""" - - doc = self._request(self.ws_prefix + ".getInfo", True) - - value = _extract(doc, "gender") - - if value == 'm': - return USER_MALE - elif value == 'f': - return USER_FEMALE - - return None - def is_subscriber(self): """Returns whether the user is a subscriber or not. True or False.""" From 5fd9e4c8c5232c76b4879d26f9a166a105dd4f34 Mon Sep 17 00:00:00 2001 From: hugovk Date: Thu, 19 Oct 2017 00:33:02 +0300 Subject: [PATCH 49/66] Remove dead or broken Last.fm user functions --- pylast/__init__.py | 33 --------------------------------- tests/test_pylast_user.py | 11 ----------- 2 files changed, 44 deletions(-) diff --git a/pylast/__init__.py b/pylast/__init__.py index 270ae69..a1a23d2 100644 --- a/pylast/__init__.py +++ b/pylast/__init__.py @@ -2489,39 +2489,6 @@ class User(_BaseObject, _Chartable): return self._get_things( "getTopTracks", "track", Track, params, cacheable) - def compare_with_user(self, user, shared_artists_limit=None): - """ - Compare this user with another Last.fm user. - Returns a sequence: - (tasteometer_score, (shared_artist1, shared_artist2, ...)) - user: A User object or a username string/unicode object. - """ - - if isinstance(user, User): - user = user.get_name() - - params = self._get_params() - if shared_artists_limit: - params['limit'] = shared_artists_limit - params['type1'] = 'user' - params['type2'] = 'user' - params['value1'] = self.get_name() - params['value2'] = user - - doc = self._request('tasteometer.compare', False, params) - - score = _extract(doc, 'score') - - artists = doc.getElementsByTagName('artists')[0] - shared_artists_names = _extract_all(artists, 'name') - - shared_artists_seq = [] - - for name in shared_artists_names: - shared_artists_seq.append(Artist(name, self.network)) - - return (score, shared_artists_seq) - def get_image(self): """Returns the user's avatar.""" diff --git a/tests/test_pylast_user.py b/tests/test_pylast_user.py index 36ef7bd..9121dbe 100755 --- a/tests/test_pylast_user.py +++ b/tests/test_pylast_user.py @@ -85,17 +85,6 @@ class TestPyLastUser(PyLastTestCase): # Just check date because of timezones self.assertEqual(unixtime_registered, u"1037793040") - def test_get_genderless_user(self): - # Arrange - # Currently test_user has no gender set: - lastfm_user = self.network.get_user("test_user") - - # Act - gender = lastfm_user.get_gender() - - # Assert - self.assertIsNone(gender) - def test_get_countryless_user(self): # Arrange # Currently test_user has no country set: From ebc5b29f5cdf719c1a35b4c48deb064a793874b6 Mon Sep 17 00:00:00 2001 From: hugovk Date: Thu, 19 Oct 2017 00:46:23 +0300 Subject: [PATCH 50/66] Remove dead or broken Last.fm user functions --- pylast/__init__.py | 44 --------------------------------------- tests/test_pylast_user.py | 17 --------------- 2 files changed, 61 deletions(-) diff --git a/pylast/__init__.py b/pylast/__init__.py index a1a23d2..e74c247 100644 --- a/pylast/__init__.py +++ b/pylast/__init__.py @@ -1074,37 +1074,6 @@ class _BaseObject(object): return seq - def share(self, users, message=None): - """ - Shares this (sends out recommendations). - Parameters: - * users [User|str,]: A list that can contain usernames, emails, - User objects, or all of them. - * message str: A message to include in the recommendation message. - Only for Artist/Track. - """ - - # Last.fm currently accepts a max of 10 recipient at a time - while len(users) > 10: - section = users[0:9] - users = users[9:] - self.share(section, message) - - user_names = [] - for user in users: - if isinstance(user, User): - user_names.append(user.get_name()) - else: - user_names.append(user) - - params = self._get_params() - recipients = ','.join(user_names) - params['recipient'] = recipients - if message: - params['message'] = message - - self._request(self.ws_prefix + '.share', False, params) - def get_wiki_published_date(self): """ Returns the summary of the wiki. @@ -2538,19 +2507,6 @@ class AuthenticatedUser(User): self.name = _extract(doc, "name") return self.name - def get_recommended_artists(self, limit=50, cacheable=False): - """ - Returns a sequence of Artist objects - if limit==None it will return all - """ - - seq = [] - for node in _collect_nodes( - limit, self, "user.getRecommendedArtists", cacheable): - seq.append(Artist(_extract(node, "name"), self.network)) - - return seq - class _Search(_BaseObject): """An abstract class. Use one of its derivatives.""" diff --git a/tests/test_pylast_user.py b/tests/test_pylast_user.py index 9121dbe..26876bf 100755 --- a/tests/test_pylast_user.py +++ b/tests/test_pylast_user.py @@ -256,23 +256,6 @@ class TestPyLastUser(PyLastTestCase): # Act/Assert self.helper_get_assert_charts(lastfm_user, dates[0]) - # Commented out to avoid spamming - # def test_share_spam(self): - # # Arrange - # users_to_spam = [TODO_ENTER_SPAMEES_HERE] - # spam_message = "Dig the krazee sound!" - # artist = self.network.get_top_artists(limit=1)[0].item - # track = artist.get_top_tracks(limit=1)[0].item - - # # Act - # artist.share(users_to_spam, spam_message) - # track.share(users_to_spam, spam_message) - - # Assert - # Check inbox for spam! - - # album/artist/track/user - def test_user_top_artists(self): # Arrange lastfm_user = self.network.get_user(self.username) From f9a8bf3daee5a4fd4fa59dd0318e7172eff9c5f9 Mon Sep 17 00:00:00 2001 From: hugovk Date: Thu, 19 Oct 2017 00:55:20 +0300 Subject: [PATCH 51/66] More tests --- tests/test_pylast_album.py | 11 +++++++ tests/test_pylast_artist.py | 11 +++++++ tests/test_pylast_user.py | 64 ++++++++++++++++++++++++++++--------- 3 files changed, 71 insertions(+), 15 deletions(-) diff --git a/tests/test_pylast_album.py b/tests/test_pylast_album.py index a93c27d..53581a5 100755 --- a/tests/test_pylast_album.py +++ b/tests/test_pylast_album.py @@ -99,6 +99,17 @@ class TestPyLastAlbum(PyLastTestCase): # Act / Assert self.assertTrue(album1 != album2) + def test_get_cover_image(self): + # Arrange + album = self.network.get_album("Test Artist", "Test Album") + + # Act + image = album.get_cover_image() + + # Assert + self.assertTrue(image.startswith("https://")) + self.assertTrue(image.endswith(".png")) + if __name__ == '__main__': unittest.main(failfast=True) diff --git a/tests/test_pylast_artist.py b/tests/test_pylast_artist.py index 4fe15a2..bec278e 100755 --- a/tests/test_pylast_artist.py +++ b/tests/test_pylast_artist.py @@ -11,6 +11,17 @@ from .test_pylast import PyLastTestCase class TestPyLastArtist(PyLastTestCase): + def test_repr(self): + # Arrange + artist = pylast.Artist("Test Artist", self.network) + + # Act + representation = repr(artist) + + # Assert + self.assertTrue( + representation.startswith("pylast.Artist('Test Artist',")) + def test_artist_is_hashable(self): # Arrange test_artist = self.network.get_artist("Test Artist") diff --git a/tests/test_pylast_user.py b/tests/test_pylast_user.py index 26876bf..7613345 100755 --- a/tests/test_pylast_user.py +++ b/tests/test_pylast_user.py @@ -16,20 +16,17 @@ class TestPyLastUser(PyLastTestCase): def test_repr(self): # Arrange - username = "RJ" - user = self.network.get_user(username) + user = self.network.get_user("RJ") # Act representation = repr(user) # Assert - self.assertTrue( - representation.startswith(representation), "pylast.User('RJ',") + self.assertTrue(representation.startswith("pylast.User('RJ',")) def test_str(self): # Arrange - username = "RJ" - user = self.network.get_user(username) + user = self.network.get_user("RJ") # Act string = str(user) @@ -39,17 +36,27 @@ class TestPyLastUser(PyLastTestCase): def test_equality(self): # Arrange - username = "RJ" - user = self.network.get_user(username) + user_1a = self.network.get_user("RJ") + user_1b = self.network.get_user("RJ") + user_2 = self.network.get_user("Test User") not_a_user = self.network # Act / Assert - self.assertNotEqual(user, not_a_user) + self.assertEqual(user_1a, user_1b) + self.assertTrue(user_1a == user_1b) + self.assertFalse(user_1a != user_1b) + + self.assertNotEqual(user_1a, user_2) + self.assertTrue(user_1a != user_2) + self.assertFalse(user_1a == user_2) + + self.assertNotEqual(user_1a, not_a_user) + self.assertTrue(user_1a != not_a_user) + self.assertFalse(user_1a == not_a_user) def test_get_name(self): # Arrange - username = "RJ" - user = self.network.get_user(username) + user = self.network.get_user("RJ") # Act name = user.get_name(properly_capitalized=True) @@ -59,8 +66,7 @@ class TestPyLastUser(PyLastTestCase): def test_get_user_registration(self): # Arrange - username = "RJ" - user = self.network.get_user(username) + user = self.network.get_user("RJ") # Act registered = user.get_registered() @@ -75,8 +81,7 @@ class TestPyLastUser(PyLastTestCase): def test_get_user_unixtime_registration(self): # Arrange - username = "RJ" - user = self.network.get_user(username) + user = self.network.get_user("RJ") # Act unixtime_registered = user.get_unixtime_registered() @@ -458,6 +463,35 @@ class TestPyLastUser(PyLastTestCase): # Assert self.assertEqual(mbid, None) + def test_get_playcount(self): + # Arrange + user = self.network.get_user("RJ") + + # Act + playcount = user.get_playcount() + + # Assert + self.assertGreaterEqual(playcount, 128387) + + def test_get_image(self): + # Arrange + user = self.network.get_user("RJ") + + # Act / Assert + image = user.get_image() + + self.assertTrue(image.startswith("https://")) + self.assertTrue(image.endswith(".png")) + + def test_get_url(self): + # Arrange + user = self.network.get_user("RJ") + + # Act / Assert + url = user.get_url() + + self.assertEqual(url, "https://www.last.fm/user/rj") + if __name__ == '__main__': unittest.main(failfast=True) From 85bf9f9928272a0c3c95b964413c4a83677d1ac1 Mon Sep 17 00:00:00 2001 From: hugovk Date: Thu, 19 Oct 2017 23:36:00 +0300 Subject: [PATCH 52/66] Remove dead Last.fm track ban --- pylast/__init__.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pylast/__init__.py b/pylast/__init__.py index e74c247..06fe302 100644 --- a/pylast/__init__.py +++ b/pylast/__init__.py @@ -2052,11 +2052,6 @@ class Track(_Opus): self._request(self.ws_prefix + '.unlove') - def ban(self): - """Ban this track from ever playing on the radio. """ - - self._request(self.ws_prefix + '.ban') - def get_similar(self): """ Returns similar tracks for this track on the network, From 8f3628a6386fe55c1271332010d4a3141a9bf30d Mon Sep 17 00:00:00 2001 From: hugovk Date: Thu, 19 Oct 2017 23:37:10 +0300 Subject: [PATCH 53/66] More tests --- tests/test_pylast_artist.py | 11 ++++++++ tests/test_pylast_track.py | 56 +++++++++++++++++++++++++++++++++++++ tests/test_pylast_user.py | 10 +++++++ 3 files changed, 77 insertions(+) diff --git a/tests/test_pylast_artist.py b/tests/test_pylast_artist.py index bec278e..75c951d 100755 --- a/tests/test_pylast_artist.py +++ b/tests/test_pylast_artist.py @@ -268,6 +268,17 @@ class TestPyLastArtist(PyLastTestCase): # Assert self.assertEqual(corrected_artist_name, "Guns N' Roses") + def test_get_userplaycount(self): + # Arrange + artist = pylast.Artist("John Lennon", self.network, + username=self.username) + + # Act + playcount = artist.get_userplaycount() + + # Assert + self.assertGreaterEqual(playcount, 0) + if __name__ == '__main__': unittest.main(failfast=True) diff --git a/tests/test_pylast_track.py b/tests/test_pylast_track.py index 9f446da..61ef132 100755 --- a/tests/test_pylast_track.py +++ b/tests/test_pylast_track.py @@ -104,6 +104,62 @@ class TestPyLastTrack(PyLastTestCase): self.assertIsNotNone(wiki) self.assertGreaterEqual(len(wiki), 1) + def test_track_get_duration(self): + # Arrange + track = pylast.Track("Nirvana", "Lithium", self.network) + + # Act + duration = track.get_duration() + + # Assert + self.assertGreaterEqual(duration, 200000) + + def test_track_is_streamable(self): + # Arrange + track = pylast.Track("Nirvana", "Lithium", self.network) + + # Act + streamable = track.is_streamable() + + # Assert + self.assertFalse(streamable) + + def test_track_is_fulltrack_available(self): + # Arrange + track = pylast.Track("Nirvana", "Lithium", self.network) + + # Act + fulltrack_available = track.is_fulltrack_available() + + # Assert + self.assertFalse(fulltrack_available) + + def test_track_get_album(self): + # Arrange + track = pylast.Track("Nirvana", "Lithium", self.network) + + # Act + album = track.get_album() + print(album) + + # Assert + self.assertEqual(str(album), "Nirvana - Nevermind") + + def test_track_get_similar(self): + # Arrange + track = pylast.Track("Cher", "Believe", self.network) + + # Act + similar = track.get_similar() + + # Assert + found = False + for track in similar: + if str(track.item) == "Madonna - Vogue": + found = True + break + self.assertTrue(found) + if __name__ == '__main__': unittest.main(failfast=True) diff --git a/tests/test_pylast_user.py b/tests/test_pylast_user.py index 7613345..b64e0ee 100755 --- a/tests/test_pylast_user.py +++ b/tests/test_pylast_user.py @@ -101,6 +101,16 @@ class TestPyLastUser(PyLastTestCase): # Assert self.assertIsNone(country) + def test_user_get_country(self): + # Arrange + lastfm_user = self.network.get_user("RJ") + + # Act + country = lastfm_user.get_country() + + # Assert + self.assertEqual(str(country), "United Kingdom") + def test_user_equals_none(self): # Arrange lastfm_user = self.network.get_user(self.username) From c0a25fbabe2cfc265613739eabe7e42427d2f8ec Mon Sep 17 00:00:00 2001 From: hugovk Date: Fri, 20 Oct 2017 00:29:57 +0300 Subject: [PATCH 54/66] Remove dead Last.fm artist band members --- pylast/__init__.py | 11 ----------- tests/test_pylast_artist.py | 21 --------------------- 2 files changed, 32 deletions(-) diff --git a/pylast/__init__.py b/pylast/__init__.py index 06fe302..8a57787 100644 --- a/pylast/__init__.py +++ b/pylast/__init__.py @@ -1738,17 +1738,6 @@ class Artist(_BaseObject, _Taggable): return self.network._get_url( domain_name, "artist") % {'artist': artist} - def get_band_members(self): - """Returns a list of band members or None if unknown.""" - - names = None - doc = self._request(self.ws_prefix + ".getInfo", True) - - for node in doc.getElementsByTagName("bandmembers"): - names = _extract_all(node, "name") - - return names - class Country(_BaseObject): """A country at Last.fm.""" diff --git a/tests/test_pylast_artist.py b/tests/test_pylast_artist.py index 75c951d..0d45f64 100755 --- a/tests/test_pylast_artist.py +++ b/tests/test_pylast_artist.py @@ -237,27 +237,6 @@ class TestPyLastArtist(PyLastTestCase): # Act / Assert self.assertTrue(artist1 != artist2) - def test_band_members(self): - # Arrange - artist = pylast.Artist("The Beatles", self.network) - - # Act - band_members = artist.get_band_members() - - # Assert - self.skip_if_lastfm_api_broken(band_members) - self.assertGreaterEqual(len(band_members), 4) - - def test_no_band_members(self): - # Arrange - artist = pylast.Artist("John Lennon", self.network) - - # Act - band_members = artist.get_band_members() - - # Assert - self.assertIsNone(band_members) - def test_artist_get_correction(self): # Arrange artist = pylast.Artist("guns and roses", self.network) From 00b6c0a61901411c09955e6d452aba2627b6b3a3 Mon Sep 17 00:00:00 2001 From: hugovk Date: Fri, 20 Oct 2017 00:32:40 +0300 Subject: [PATCH 55/66] Remove dead Last.fm tag.getSimilar --- pylast/__init__.py | 12 ------------ tests/test_pylast_tag.py | 16 ---------------- 2 files changed, 28 deletions(-) diff --git a/pylast/__init__.py b/pylast/__init__.py index 8a57787..f7d99dc 100644 --- a/pylast/__init__.py +++ b/pylast/__init__.py @@ -1904,18 +1904,6 @@ class Tag(_BaseObject, _Chartable): return self.name - def get_similar(self): - """Returns the tags similar to this one, ordered by similarity. """ - - doc = self._request(self.ws_prefix + '.getSimilar', True) - - seq = [] - names = _extract_all(doc, 'name') - for name in names: - seq.append(Tag(name, self.network)) - - return seq - def get_top_albums(self, limit=None, cacheable=True): """Returns a list of the top albums.""" params = self._get_params() diff --git a/tests/test_pylast_tag.py b/tests/test_pylast_tag.py index d1a5f72..8d5440e 100755 --- a/tests/test_pylast_tag.py +++ b/tests/test_pylast_tag.py @@ -58,22 +58,6 @@ class TestPyLastTag(PyLastTestCase): self.assertTrue(tag1 != tag2) self.assertEqual(url, "https://www.last.fm/tag/blues") - def test_tags_similar(self): - # Arrange - tag = self.network.get_tag("blues") - - # Act - similar = tag.get_similar() - - # Assert - self.skip_if_lastfm_api_broken(similar) - found = False - for tag in similar: - if tag.name == "delta blues": - found = True - break - self.assertTrue(found) - if __name__ == '__main__': unittest.main(failfast=True) From 666181df506dd4be1075ab1f21a5ffaada7ae1f4 Mon Sep 17 00:00:00 2001 From: hugovk Date: Fri, 20 Oct 2017 00:39:02 +0300 Subject: [PATCH 56/66] Last.fm API broken but allow either yyyy-mm-dd or Unix timestamp --- tests/test_pylast_user.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/test_pylast_user.py b/tests/test_pylast_user.py index b64e0ee..3cc9969 100755 --- a/tests/test_pylast_user.py +++ b/tests/test_pylast_user.py @@ -72,12 +72,13 @@ class TestPyLastUser(PyLastTestCase): registered = user.get_registered() # Assert - # Last.fm API broken? Should be yyyy-mm-dd not Unix timestamp if int(registered): - pytest.skip("Last.fm API is broken.") - - # Just check date because of timezones - self.assertIn(u"2002-11-20 ", registered) + # Last.fm API broken? Used to be yyyy-mm-dd not Unix timestamp + self.assertEqual(registered, "1037793040") + else: + # Old way + # Just check date because of timezones + self.assertIn(u"2002-11-20 ", registered) def test_get_user_unixtime_registration(self): # Arrange From b55b40c3fe720a30887542838261eb2c1e241b5b Mon Sep 17 00:00:00 2001 From: hugovk Date: Fri, 20 Oct 2017 00:45:17 +0300 Subject: [PATCH 57/66] Remove dead Last.fm album/track get ID --- pylast/__init__.py | 6 ------ tests/test_pylast_user.py | 13 ------------- 2 files changed, 19 deletions(-) diff --git a/pylast/__init__.py b/pylast/__init__.py index f7d99dc..df23ad6 100644 --- a/pylast/__init__.py +++ b/pylast/__init__.py @@ -1429,12 +1429,6 @@ class _Opus(_BaseObject, _Taggable): return self.get_title(properly_capitalized) - def get_id(self): - """Returns the ID on the network.""" - - return _extract( - self._request(self.ws_prefix + ".getInfo", cacheable=True), "id") - def get_playcount(self): """Returns the number of plays on the network""" diff --git a/tests/test_pylast_user.py b/tests/test_pylast_user.py index 3cc9969..53bb1fc 100755 --- a/tests/test_pylast_user.py +++ b/tests/test_pylast_user.py @@ -5,8 +5,6 @@ Integration (not unit) tests for pylast.py import os import unittest -import pytest - import pylast from .test_pylast import PyLastTestCase @@ -393,17 +391,6 @@ class TestPyLastUser(PyLastTestCase): # Assert self.assertNotEqual(track1, track2) - def test_track_id(self): - # Arrange - track = pylast.Track("Test Artist", "test title", self.network) - - # Act - id = track.get_id() - - # Assert - self.skip_if_lastfm_api_broken(id) - self.assertEqual(id, "14053327") - def test_track_title_prop_caps(self): # Arrange track = pylast.Track("test artist", "test title", self.network) From b55949536650a66e996a99428fead672c6ac93e7 Mon Sep 17 00:00:00 2001 From: hugovk Date: Fri, 20 Oct 2017 00:51:34 +0300 Subject: [PATCH 58/66] Assume no illegal XML and no more skipping broken Last.fm --- pylast/__init__.py | 15 --------------- tests/test_pylast.py | 5 ----- tests/test_pylast_network.py | 14 -------------- tests/test_pylast_user.py | 1 - 4 files changed, 35 deletions(-) diff --git a/pylast/__init__.py b/pylast/__init__.py index df23ad6..3d1a219 100644 --- a/pylast/__init__.py +++ b/pylast/__init__.py @@ -22,7 +22,6 @@ from xml.dom import minidom, Node import collections -import re import hashlib import shelve import six @@ -108,18 +107,6 @@ SCROBBLE_MODE_LOVED = "L" SCROBBLE_MODE_BANNED = "B" SCROBBLE_MODE_SKIPPED = "S" -# From http://boodebr.org/main/python/all-about-python-and-unicode#UNI_XML -RE_XML_ILLEGAL = (u'([\u0000-\u0008\u000b-\u000c\u000e-\u001f\ufffe-\uffff])' + - u'|' + - u'([%s-%s][^%s-%s])|([^%s-%s][%s-%s])|([%s-%s]$)|(^[%s-%s])' - % - (unichr(0xd800), unichr(0xdbff), unichr(0xdc00), - unichr(0xdfff), unichr(0xd800), unichr(0xdbff), - unichr(0xdc00), unichr(0xdfff), unichr(0xd800), - unichr(0xdbff), unichr(0xdc00), unichr(0xdfff))) - -XML_ILLEGAL = re.compile(RE_XML_ILLEGAL) - # Python >3.4 and >2.7.9 has sane defaults SSL_CONTEXT = ssl.create_default_context() @@ -862,8 +849,6 @@ class _Request(object): except Exception as e: raise MalformedResponseError(self.network, e) - response_text = XML_ILLEGAL.sub("?", response_text) - self._check_response_for_errors(response_text) conn.close() return response_text diff --git a/tests/test_pylast.py b/tests/test_pylast.py index 9279112..9ebbcc1 100755 --- a/tests/test_pylast.py +++ b/tests/test_pylast.py @@ -52,11 +52,6 @@ class PyLastTestCase(unittest.TestCase): api_key=API_KEY, api_secret=API_SECRET, username=self.username, password_hash=password_hash) - def skip_if_lastfm_api_broken(self, value): - """Skip things not yet restored in Last.fm's broken API""" - if value is None or len(value) == 0: - pytest.skip("Last.fm API is broken.") - def helper_is_thing_hashable(self, thing): # Arrange things = set() diff --git a/tests/test_pylast_network.py b/tests/test_pylast_network.py index 7ea6432..733a80e 100755 --- a/tests/test_pylast_network.py +++ b/tests/test_pylast_network.py @@ -47,20 +47,6 @@ class TestPyLastNetwork(PyLastTestCase): self.assertEqual(str(current_track.title), "test title") self.assertEqual(str(current_track.artist), "Test Artist") - def test_invalid_xml(self): - # Arrange - # Currently causes PCDATA invalid Char value 25 - artist = "Blind Willie Johnson" - title = "It's nobody's fault but mine" - - # Act - search = self.network.search_for_track(artist, title) - total = search.get_total_result_count() - - # Assert - self.skip_if_lastfm_api_broken(total) - self.assertGreaterEqual(int(total), 0) - def test_enable_rate_limiting(self): # Arrange self.assertFalse(self.network.is_rate_limited()) diff --git a/tests/test_pylast_user.py b/tests/test_pylast_user.py index 53bb1fc..66beb40 100755 --- a/tests/test_pylast_user.py +++ b/tests/test_pylast_user.py @@ -218,7 +218,6 @@ class TestPyLastUser(PyLastTestCase): tags = user.get_top_tags(limit=1) # Assert - self.skip_if_lastfm_api_broken(tags) self.helper_only_one_thing_in_top_list(tags, pylast.Tag) def test_user_top_tracks(self): From 68db2da2e96865f4bab185bafba49640d81aeaa6 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 22 Oct 2017 23:17:27 +0300 Subject: [PATCH 59/66] pep8 is now pycodestyle https://github.com/PyCQA/pycodestyle/issues/466 --- setup.py | 5 +++-- tox.ini | 12 ++++++------ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/setup.py b/setup.py index 6d21e2a..2d9375c 100755 --- a/setup.py +++ b/setup.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -from setuptools import setup, find_packages +from setuptools import find_packages, setup setup( @@ -7,7 +7,8 @@ setup( version="1.9.0", author="Amr Hassan ", install_requires=['six'], - tests_require=['mock', 'pytest', 'coverage', 'pep8', 'pyyaml', 'pyflakes'], + tests_require=['mock', 'pytest', 'coverage', 'pycodestyle', 'pyyaml', + 'pyflakes'], description=("A Python interface to Last.fm and Libre.fm"), author_email="amr.hassan@gmail.com", url="https://github.com/pylast/pylast", diff --git a/tox.ini b/tox.ini index 33c73be..8300509 100644 --- a/tox.ini +++ b/tox.ini @@ -23,22 +23,22 @@ commands = {posargs} [testenv:py2lint] deps = - pep8 + pycodestyle pyflakes clonedigger commands = pyflakes pylast pyflakes tests - pep8 pylast - pep8 tests + pycodestyle pylast + pycodestyle tests ./clonedigger.sh [testenv:py3lint] deps = - pep8 + pycodestyle pyflakes commands = pyflakes pylast pyflakes tests - pep8 pylast - pep8 tests + pycodestyle pylast + pycodestyle tests From 52636b67643ef0066b90dd89f30b0e71fbaa3620 Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 24 Oct 2017 00:04:05 +0300 Subject: [PATCH 60/66] Work around Last.fm's 'Namespace prefix opensearch on totalResults is not defined' XML error --- pylast/__init__.py | 2 +- tests/test_pylast_network.py | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/pylast/__init__.py b/pylast/__init__.py index 3d1a219..1db3d88 100644 --- a/pylast/__init__.py +++ b/pylast/__init__.py @@ -2483,7 +2483,7 @@ class _Search(_BaseObject): doc = self._request(self._ws_prefix + ".search", True) - return _extract(doc, "opensearch:totalResults") + return _extract(doc, "totalResults") def _retrieve_page(self, page_index): """Returns the node of matches to be processed""" diff --git a/tests/test_pylast_network.py b/tests/test_pylast_network.py index 733a80e..2906b92 100755 --- a/tests/test_pylast_network.py +++ b/tests/test_pylast_network.py @@ -339,6 +339,18 @@ class TestPyLastNetwork(PyLastTestCase): self.assertIsInstance(results, list) self.assertIsInstance(results[0], pylast.Track) + def test_search_get_total_result_count(self): + # Arrange + artist = "Nirvana" + track = "Smells Like Teen Spirit" + search = self.network.search_for_track(artist, track) + + # Act + total = search.get_total_result_count() + + # Assert + self.assertGreater(int(total), 10000) + if __name__ == '__main__': unittest.main(failfast=True) From cb1f7607316dcbf70324089fab96d456093c3ee6 Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 24 Oct 2017 00:11:49 +0300 Subject: [PATCH 61/66] http -> https --- COPYING | 2 +- pylast/__init__.py | 2 +- tests/test_pylast_artist.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/COPYING b/COPYING index eec88ff..c4ff845 100644 --- a/COPYING +++ b/COPYING @@ -1,6 +1,6 @@ Apache License Version 2.0, January 2004 -http://www.apache.org/licenses/ +https://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION diff --git a/pylast/__init__.py b/pylast/__init__.py index 1db3d88..1dc7547 100644 --- a/pylast/__init__.py +++ b/pylast/__init__.py @@ -830,7 +830,7 @@ class _Request(object): try: conn.request( - method='POST', url="http://" + HOST_NAME + HOST_SUBDIR, + method='POST', url="https://" + HOST_NAME + HOST_SUBDIR, body=data, headers=headers) except Exception as e: raise NetworkError(self.network, e) diff --git a/tests/test_pylast_artist.py b/tests/test_pylast_artist.py index 0d45f64..66dbb49 100755 --- a/tests/test_pylast_artist.py +++ b/tests/test_pylast_artist.py @@ -213,7 +213,7 @@ class TestPyLastArtist(PyLastTestCase): name_cap = artist1.get_name(properly_capitalized=True) # Assert - self.assertIn("http", image) + self.assertIn("https", image) self.assertGreater(playcount, 1) self.assertTrue(artist1 != artist2) self.assertEqual(name.lower(), name_cap.lower()) From c601d2f365323290bbb94a62ddec0daab8fd4a39 Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 24 Oct 2017 00:54:34 +0300 Subject: [PATCH 62/66] Fix minor things from code inspection --- pylast/__init__.py | 2 +- setup.py | 4 ++-- tests/test_pylast_network.py | 9 +++++---- tests/test_pylast_user.py | 1 + 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/pylast/__init__.py b/pylast/__init__.py index 1dc7547..96ca64b 100644 --- a/pylast/__init__.py +++ b/pylast/__init__.py @@ -244,7 +244,7 @@ class _Network(object): """ Returns an (API_KEY, API_SECRET, SESSION_KEY) tuple. """ - return (self.api_key, self.api_secret, self.session_key) + return self.api_key, self.api_secret, self.session_key def _delay_call(self): """ diff --git a/setup.py b/setup.py index 2d9375c..2a0de0d 100755 --- a/setup.py +++ b/setup.py @@ -8,8 +8,8 @@ setup( author="Amr Hassan ", install_requires=['six'], tests_require=['mock', 'pytest', 'coverage', 'pycodestyle', 'pyyaml', - 'pyflakes'], - description=("A Python interface to Last.fm and Libre.fm"), + 'pyflakes', 'flaky'], + description="A Python interface to Last.fm and Libre.fm", author_email="amr.hassan@gmail.com", url="https://github.com/pylast/pylast", classifiers=[ diff --git a/tests/test_pylast_network.py b/tests/test_pylast_network.py index 2906b92..23d2f20 100755 --- a/tests/test_pylast_network.py +++ b/tests/test_pylast_network.py @@ -174,7 +174,7 @@ class TestPyLastNetwork(PyLastTestCase): # Act stringed = str(thing) - repr = thing.__repr__() + rep = thing.__repr__() title = thing.get_title() name = thing.get_name() playcount = thing.get_playcount() @@ -182,7 +182,7 @@ class TestPyLastNetwork(PyLastTestCase): # Assert self.assertEqual(stringed, "Test Artist - Test Album") - self.assertIn("pylast.Album('Test Artist', 'Test Album',", repr) + self.assertIn("pylast.Album('Test Artist', 'Test Album',", rep) self.assertEqual(title, name) self.assertIsInstance(playcount, int) self.assertGreater(playcount, 1) @@ -195,7 +195,7 @@ class TestPyLastNetwork(PyLastTestCase): # Act stringed = str(thing) - repr = thing.__repr__() + rep = thing.__repr__() title = thing.get_title() name = thing.get_name() playcount = thing.get_playcount() @@ -203,7 +203,7 @@ class TestPyLastNetwork(PyLastTestCase): # Assert self.assertEqual(stringed, "Test Artist - test title") - self.assertIn("pylast.Track('Test Artist', 'test title',", repr) + self.assertIn("pylast.Track('Test Artist', 'test title',", rep) self.assertEqual(title, "test title") self.assertEqual(title, name) self.assertIsInstance(playcount, int) @@ -275,6 +275,7 @@ class TestPyLastNetwork(PyLastTestCase): def test_init_with_token(self): # Arrange/Act + msg = None try: pylast.LastFMNetwork( api_key=self.__class__.secrets["api_key"], diff --git a/tests/test_pylast_user.py b/tests/test_pylast_user.py index 66beb40..1169f41 100755 --- a/tests/test_pylast_user.py +++ b/tests/test_pylast_user.py @@ -239,6 +239,7 @@ class TestPyLastUser(PyLastTestCase): def helper_get_assert_charts(self, thing, date): # Arrange + album_chart, track_chart = None, None (from_date, to_date) = date # Act From b2f58fde636cf422b356ba320fb19694ef4766bd Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 24 Oct 2017 00:57:33 +0300 Subject: [PATCH 63/66] Remove commented-out, broken downloads badge --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 8e55b29..87e259e 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,6 @@ pyLast [![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/) - [![Coverage (Codecov)](https://codecov.io/gh/pylast/pylast/branch/develop/graph/badge.svg)](https://codecov.io/gh/pylast/pylast) [![Coverage (Coveralls)](https://coveralls.io/repos/github/pylast/pylast/badge.svg?branch=develop)](https://coveralls.io/github/pylast/pylast?branch=develop) [![Code health](https://landscape.io/github/pylast/pylast/develop/landscape.svg)](https://landscape.io/github/hugovk/pylast/develop) From dc5e0ce8435e6c53f425bb39b3951d519e396e85 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sat, 28 Oct 2017 09:56:12 +0300 Subject: [PATCH 64/66] Add supported Python versions badge [CI skip] --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 87e259e..2af6559 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ pyLast [![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/) +[![Supported Python versions](https://img.shields.io/pypi/pyversions/pylast.svg)](https://pypi.python.org/pypi/pylast/) [![Coverage (Codecov)](https://codecov.io/gh/pylast/pylast/branch/develop/graph/badge.svg)](https://codecov.io/gh/pylast/pylast) [![Coverage (Coveralls)](https://coveralls.io/repos/github/pylast/pylast/badge.svg?branch=develop)](https://coveralls.io/github/pylast/pylast?branch=develop) [![Code health](https://landscape.io/github/pylast/pylast/develop/landscape.svg)](https://landscape.io/github/hugovk/pylast/develop) From ec647d181cdad1b483debd315d44280f80005ce2 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sat, 28 Oct 2017 10:00:14 +0300 Subject: [PATCH 65/66] Update tests info [CI skip] --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2af6559..697ae2f 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ pyLast A Python interface to [Last.fm](https://www.last.fm/) and other API-compatible websites such as [Libre.fm](https://libre.fm/). -Try using the pydoc utility for help on usage or see [test_pylast.py](tests/test_pylast.py) for examples. +Use the pydoc utility for help on usage or see [tests/](tests/) for examples. Installation ------------ @@ -74,12 +74,12 @@ track.add_tags(("awesome", "favorite")) # to get more help about anything and see examples of how it works ``` -More examples in hugovk/lastfm-tools and [test_pylast.py](test_pylast.py). +More examples in hugovk/lastfm-tools and [tests/](tests/). Testing ------- -[tests/test_pylast.py](tests/test_pylast.py) contains integration tests with Last.fm, and plenty of code examples. Unit tests are also in the [tests/](tests/) directory. +The [tests/](tests/) directory contains integration and unit tests with Last.fm, and plenty of code examples. For integration tests you need a test account at Last.fm that will become cluttered with test data, and an API key and secret. Either copy [example_test_pylast.yaml](example_test_pylast.yaml) to test_pylast.yaml and fill out the credentials, or set them as environment variables like: From 79fa0c6f9d57375fcb412b9d75f5aefb51ad275c Mon Sep 17 00:00:00 2001 From: Hugo Date: Sat, 28 Oct 2017 10:01:47 +0300 Subject: [PATCH 66/66] pytest is recommended entry point --- README.md | 6 +++--- tox.ini | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 697ae2f..53b5d12 100644 --- a/README.md +++ b/README.md @@ -93,17 +93,17 @@ export PYLAST_API_SECRET=TODO_ENTER_YOURS_HERE To run all unit and integration tests: ```sh pip install pytest flaky mock -py.test +pytest ``` Or run just one test case: ```sh -py.test -k test_scrobble +pytest -k test_scrobble ``` To run with coverage: ```sh -py.test -v --cov pylast --cov-report term-missing +pytest -v --cov pylast --cov-report term-missing coverage report # for command-line report coverage html # for HTML report open htmlcov/index.html diff --git a/tox.ini b/tox.ini index 8300509..6347575 100644 --- a/tox.ini +++ b/tox.ini @@ -15,7 +15,7 @@ deps = ipdb pytest-cov flaky -commands = py.test -v -s -W all --cov pylast --cov-report term-missing {posargs} +commands = pytest -v -s -W all --cov pylast --cov-report term-missing {posargs} [testenv:venv] deps = ipdb