From e38d1c516b1e841dc24548f76c5ef51e5b7ec41b Mon Sep 17 00:00:00 2001 From: Hugo Date: Sat, 9 Feb 2019 23:18:51 +0200 Subject: [PATCH 1/5] Update error status codes from https://www.last.fm/api/errorcodes --- pylast/__init__.py | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/pylast/__init__.py b/pylast/__init__.py index 5b839c9..c4da3a6 100644 --- a/pylast/__init__.py +++ b/pylast/__init__.py @@ -72,13 +72,16 @@ else: from urllib.parse import quote_plus as url_quote_plus +# 1 : This error does not exist STATUS_INVALID_SERVICE = 2 STATUS_INVALID_METHOD = 3 STATUS_AUTH_FAILED = 4 STATUS_INVALID_FORMAT = 5 STATUS_INVALID_PARAMS = 6 STATUS_INVALID_RESOURCE = 7 -STATUS_TOKEN_ERROR = 8 +# DeprecationWarning: STATUS_TOKEN_ERROR is deprecated and will be +# removed in a future version. Use STATUS_OPERATION_FAILED instead. +STATUS_OPERATION_FAILED = STATUS_TOKEN_ERROR = 8 STATUS_INVALID_SK = 9 STATUS_INVALID_API_KEY = 10 STATUS_OFFLINE = 11 @@ -86,6 +89,20 @@ STATUS_SUBSCRIBERS_ONLY = 12 STATUS_INVALID_SIGNATURE = 13 STATUS_TOKEN_UNAUTHORIZED = 14 STATUS_TOKEN_EXPIRED = 15 +STATUS_TEMPORARILY_UNAVAILABLE = 16 +STATUS_LOGIN_REQUIRED = 17 +STATUS_TRIAL_EXPIRED = 18 +# 19 : This error does not exist +STATUS_NOT_ENOUGH_CONTENT = 20 +STATUS_NOT_ENOUGH_MEMBERS = 21 +STATUS_NOT_ENOUGH_FANS = 22 +STATUS_NOT_ENOUGH_NEIGHBOURS = 23 +STATUS_NO_PEAK_RADIO = 24 +STATUS_RADIO_NOT_FOUND = 25 +STATUS_API_KEY_SUSPENDED = 26 +STATUS_DEPRECATED = 27 +# 28 : This error is not documented +STATUS_RATE_LIMIT_EXCEEDED = 29 PERIOD_OVERALL = "overall" PERIOD_7DAYS = "7day" @@ -1414,13 +1431,25 @@ class WSError(Exception): STATUS_INVALID_FORMAT = 5 STATUS_INVALID_PARAMS = 6 STATUS_INVALID_RESOURCE = 7 - STATUS_TOKEN_ERROR = 8 + STATUS_OPERATION_FAILED = 8 STATUS_INVALID_SK = 9 STATUS_INVALID_API_KEY = 10 STATUS_OFFLINE = 11 STATUS_SUBSCRIBERS_ONLY = 12 STATUS_TOKEN_UNAUTHORIZED = 14 STATUS_TOKEN_EXPIRED = 15 + STATUS_TEMPORARILY_UNAVAILABLE = 16 + STATUS_LOGIN_REQUIRED = 17 + STATUS_TRIAL_EXPIRED = 18 + STATUS_NOT_ENOUGH_CONTENT = 20 + STATUS_NOT_ENOUGH_MEMBERS = 21 + STATUS_NOT_ENOUGH_FANS = 22 + STATUS_NOT_ENOUGH_NEIGHBOURS = 23 + STATUS_NO_PEAK_RADIO = 24 + STATUS_RADIO_NOT_FOUND = 25 + STATUS_API_KEY_SUSPENDED = 26 + STATUS_DEPRECATED = 27 + STATUS_RATE_LIMIT_EXCEEDED = 29 """ return self.status From bfd3ffe06cb1ebd22587d362292572cb2d957c4e Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 10 Feb 2019 11:31:42 +0200 Subject: [PATCH 2/5] Retry on temporary error when paging --- pylast/__init__.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/pylast/__init__.py b/pylast/__init__.py index c4da3a6..48f918a 100644 --- a/pylast/__init__.py +++ b/pylast/__init__.py @@ -2776,7 +2776,29 @@ def _collect_nodes(limit, sender, method_name, cacheable, params=None): while not end_of_pages and (not limit or (limit and len(nodes) < limit)): params["page"] = str(page) - doc = sender._request(method_name, cacheable, params) + + tries = 0 + while True: + tries += 1 + try: + doc = sender._request(method_name, cacheable, params) + break # success + except MalformedResponseError as e: + if tries < 3: + time.sleep(1) # wait and try again + else: + raise e + except WSError as e: + if tries < 3 and int(e.get_id()) in [ + # "Please try again" statuses + STATUS_OPERATION_FAILED, + STATUS_OFFLINE, + STATUS_TEMPORARILY_UNAVAILABLE, + ]: + time.sleep(1) # wait and try again + else: + raise e + doc = cleanup_nodes(doc) # break if there are no child nodes From f32848160cc87513b6f80fd7e3cd79fba1183b01 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 10 Feb 2019 11:58:40 +0200 Subject: [PATCH 3/5] Make test more reliable: check a static album not a dynamic one which may not have any tags --- tests/test_album.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_album.py b/tests/test_album.py index a2836fd..2a26849 100755 --- a/tests/test_album.py +++ b/tests/test_album.py @@ -12,10 +12,10 @@ from .test_pylast import TestPyLastWithLastFm class TestPyLastAlbum(TestPyLastWithLastFm): def test_album_tags_are_topitems(self): # Arrange - albums = self.network.get_user("RJ").get_top_albums() + album = self.network.get_album("Test Artist", "Test Album") # Act - tags = albums[0].item.get_top_tags(limit=1) + tags = album.get_top_tags(limit=1) # Assert self.assertGreater(len(tags), 0) From 9b2ada5dd03b1f8294497912320413c6735a9f2d Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 10 Feb 2019 16:06:58 +0200 Subject: [PATCH 4/5] Retry on any exception Also seen these when testing: WSError: User not found MalformedResponseError: Malformed response from Last.fm. Underlying error: mismatched tag: line 6, column 2 NetworkError: NetworkError: [Errno 8] nodename nor servname provided, or not known --- pylast/__init__.py | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/pylast/__init__.py b/pylast/__init__.py index 48f918a..dc71cf5 100644 --- a/pylast/__init__.py +++ b/pylast/__init__.py @@ -2777,27 +2777,17 @@ def _collect_nodes(limit, sender, method_name, cacheable, params=None): while not end_of_pages and (not limit or (limit and len(nodes) < limit)): params["page"] = str(page) - tries = 0 + tries = 1 while True: - tries += 1 try: doc = sender._request(method_name, cacheable, params) break # success - except MalformedResponseError as e: - if tries < 3: - time.sleep(1) # wait and try again - else: - raise e - except WSError as e: - if tries < 3 and int(e.get_id()) in [ - # "Please try again" statuses - STATUS_OPERATION_FAILED, - STATUS_OFFLINE, - STATUS_TEMPORARILY_UNAVAILABLE, - ]: - time.sleep(1) # wait and try again - else: + except Exception as e: + if tries >= 3: raise e + # Wait and try again + time.sleep(1) + tries += 1 doc = cleanup_nodes(doc) From 86c979204dd61d3f8f96706e794ca123ec5b2f04 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 10 Feb 2019 17:30:04 +0200 Subject: [PATCH 5/5] year++ --- pylast/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pylast/__init__.py b/pylast/__init__.py index dc71cf5..7308907 100644 --- a/pylast/__init__.py +++ b/pylast/__init__.py @@ -4,7 +4,7 @@ # A Python interface to Last.fm and Libre.fm # # Copyright 2008-2010 Amr Hassan -# Copyright 2013-2018 hugovk +# Copyright 2013-2019 hugovk # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -37,7 +37,7 @@ from . import version __author__ = "Amr Hassan, hugovk, Mice Pápai" __copyright__ = ( - "Copyright (C) 2008-2010 Amr Hassan, 2013-2018 hugovk, " "2017 Mice Pápai" + "Copyright (C) 2008-2010 Amr Hassan, 2013-2019 hugovk, " "2017 Mice Pápai" ) __license__ = "apache2" __email__ = "amr.hassan@gmail.com"