From 574476e44c394bf0f2268d911cc7f847d7c1db51 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sat, 22 Aug 2020 10:36:22 +0300 Subject: [PATCH 01/29] Include tests in coverage https://nedbatchelder.com/blog/202008/you_should_include_your_tests_in_coverage.html --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 87367e0..0a61ca1 100644 --- a/tox.ini +++ b/tox.ini @@ -8,7 +8,7 @@ setenv = PYLAST_PASSWORD_HASH={env:PYLAST_PASSWORD_HASH:} PYLAST_API_KEY={env:PYLAST_API_KEY:} PYLAST_API_SECRET={env:PYLAST_API_SECRET:} -commands = pytest -v -s -W all --cov pylast --cov-report term-missing --random-order {posargs} +commands = pytest -v -s -W all --cov pylast --cov tests --cov-report term-missing --random-order {posargs} [testenv:venv] deps = ipdb From 66f5ace9173a683d751df97e3b6440ca1e1fd434 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sat, 22 Aug 2020 10:38:42 +0300 Subject: [PATCH 02/29] pre-commit autoupdate --- .pre-commit-config.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index da1e286..4ddbb33 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/asottile/pyupgrade - rev: v2.6.1 + rev: v2.7.2 hooks: - id: pyupgrade args: ["--py3-plus"] @@ -26,17 +26,17 @@ repos: - id: seed-isort-config - repo: https://github.com/timothycrosley/isort - rev: 4.3.21 + rev: 5.4.2 hooks: - id: isort - repo: https://github.com/pre-commit/pygrep-hooks - rev: v1.5.1 + rev: v1.6.0 hooks: - id: python-check-blanket-noqa - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v3.1.0 + rev: v3.2.0 hooks: - id: check-merge-conflict - id: check-yaml From 48f4be0bcf26b277a724e41e80b5fa2941f9bfe2 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sat, 22 Aug 2020 11:45:33 +0300 Subject: [PATCH 03/29] Rewrite and add pragmas to improve coverage of non-runnable test code --- tests/test_artist.py | 28 ++++++---------------------- tests/test_pylast.py | 2 +- tests/test_user.py | 11 ++++++++--- 3 files changed, 15 insertions(+), 26 deletions(-) diff --git a/tests/test_artist.py b/tests/test_artist.py index 8250f5b..802a5e2 100755 --- a/tests/test_artist.py +++ b/tests/test_artist.py @@ -153,11 +153,7 @@ class TestPyLastArtist(TestPyLastWithLastFm): # Assert tags = artist.get_tags() assert len(tags) > 0 - found = False - for tag in tags: - if tag.name == "testing": - found = True - break + found = any(tag.name == "testing" for tag in tags) assert found @pytest.mark.skipif(not WRITE_TEST, reason="Only test once to avoid collisions") @@ -172,11 +168,7 @@ class TestPyLastArtist(TestPyLastWithLastFm): # Assert tags = artist.get_tags() - found = False - for tag in tags: - if tag.name == "testing": - found = True - break + found = any(tag.name == "testing" for tag in tags) assert not found @pytest.mark.skipif(not WRITE_TEST, reason="Only test once to avoid collisions") @@ -191,11 +183,7 @@ class TestPyLastArtist(TestPyLastWithLastFm): # Assert tags = artist.get_tags() - found = False - for tag in tags: - if tag.name == "testing": - found = True - break + found = any(tag.name == "testing" for tag in tags) assert not found @pytest.mark.skipif(not WRITE_TEST, reason="Only test once to avoid collisions") @@ -213,12 +201,8 @@ class TestPyLastArtist(TestPyLastWithLastFm): # Assert tags_after = artist.get_tags() assert 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 + found1 = any(tag.name == "removetag1" for tag in tags_after) + found2 = any(tag.name == "removetag2" for tag in tags_after) assert not found1 assert not found2 @@ -308,4 +292,4 @@ class TestPyLastArtist(TestPyLastWithLastFm): playcount = artist.get_userplaycount() # Assert - assert playcount >= 0 + assert playcount >= 0 # whilst xfail: # pragma: no cover diff --git a/tests/test_pylast.py b/tests/test_pylast.py index 83a64ad..730d39d 100755 --- a/tests/test_pylast.py +++ b/tests/test_pylast.py @@ -13,7 +13,7 @@ from flaky import flaky WRITE_TEST = sys.version_info[:2] == (3, 8) -def load_secrets(): +def load_secrets(): # pragma: no cover secrets_file = "test_pylast.yaml" if os.path.isfile(secrets_file): import yaml # pip install pyyaml diff --git a/tests/test_user.py b/tests/test_user.py index b0ae898..7f7e2e9 100755 --- a/tests/test_user.py +++ b/tests/test_user.py @@ -68,7 +68,7 @@ class TestPyLastUser(TestPyLastWithLastFm): if int(registered): # Last.fm API broken? Used to be yyyy-mm-dd not Unix timestamp assert registered == "1037793040" - else: + else: # pragma: no cover # Old way # Just check date because of timezones assert "2002-11-20 " in registered @@ -192,8 +192,13 @@ class TestPyLastUser(TestPyLastWithLastFm): # 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") + # no cover whilst xfail: + self.helper_validate_cacheable( # pragma: no cover + lastfm_user, "get_loved_tracks" + ) + self.helper_validate_cacheable( # pragma: no cover + lastfm_user, "get_recent_tracks" + ) def test_user_get_top_tags_with_limit(self): # Arrange From 3129d6052d973bcbf744d962232945c0872fc73f Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 23 Aug 2020 12:11:07 +0300 Subject: [PATCH 04/29] Run xfail tests only once: no point re-running --- tests/test_pylast.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/test_pylast.py b/tests/test_pylast.py index 730d39d..9ed1c7f 100755 --- a/tests/test_pylast.py +++ b/tests/test_pylast.py @@ -40,7 +40,12 @@ class PyLastTestCase: assert str.endswith(suffix, start, end) -@flaky(max_runs=3, min_passes=1) +def _no_xfail_rerun_filter(err, name, test, plugin): + for _ in test.iter_markers(name="xfail"): + return False + + +@flaky(max_runs=3, min_passes=1, rerun_filter=_no_xfail_rerun_filter) class TestPyLastWithLastFm(PyLastTestCase): secrets = None From 99e0cc734a2ae01b16f8c67fc7a773b0d9be8f0f Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Mon, 31 Aug 2020 23:03:10 +0300 Subject: [PATCH 05/29] Remove W503 from Flake8 ignore list (ignored by default) --- setup.cfg | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index bf5350d..632c39e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,4 @@ [flake8] -ignore = W503 max_line_length = 88 [tool:isort] From 2d570b97ffea7d979c9209914a814389dd48008e Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Fri, 11 Sep 2020 23:44:57 +0300 Subject: [PATCH 06/29] Update config --- .github/workflows/lint.yml | 5 ++++- .pre-commit-config.yaml | 15 +++++---------- setup.cfg | 2 +- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 13f3f43..fd4c7e6 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -2,9 +2,12 @@ name: Lint on: [push, pull_request] +env: + FORCE_COLOR: 1 + jobs: build: - runs-on: ubuntu-18.04 + runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4ddbb33..9a37fb9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,22 +14,17 @@ repos: files: \.pyi?$ types: [] + - repo: https://github.com/PyCQA/isort + rev: 5.5.1 + hooks: + - id: isort + - repo: https://gitlab.com/pycqa/flake8 rev: 3.8.3 hooks: - id: flake8 additional_dependencies: [flake8-2020, flake8-implicit-str-concat] - - repo: https://github.com/asottile/seed-isort-config - rev: v2.2.0 - hooks: - - id: seed-isort-config - - - repo: https://github.com/timothycrosley/isort - rev: 5.4.2 - hooks: - - id: isort - - repo: https://github.com/pre-commit/pygrep-hooks rev: v1.6.0 hooks: diff --git a/setup.cfg b/setup.cfg index 632c39e..191fac9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,4 +2,4 @@ max_line_length = 88 [tool:isort] -known_third_party = flaky,pkg_resources,pylast,pytest,setuptools +profile = black From 0f96fe58b1273fb21589e961a1635858039d4adb Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Fri, 11 Sep 2020 23:47:42 +0300 Subject: [PATCH 07/29] Format with Black and isort --- .pre-commit-config.yaml | 4 +- src/pylast/__init__.py | 114 ++++++++++++++++++++-------------------- tests/test_artist.py | 3 +- tests/test_librefm.py | 3 +- tests/test_network.py | 3 +- tests/test_pylast.py | 3 +- tests/test_track.py | 3 +- tests/test_user.py | 3 +- tests/unicode_test.py | 3 +- 9 files changed, 73 insertions(+), 66 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9a37fb9..58efd85 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,7 +6,7 @@ repos: args: ["--py3-plus"] - repo: https://github.com/psf/black - rev: 19.10b0 + rev: 20.8b1 hooks: - id: black args: ["--target-version", "py35"] @@ -15,7 +15,7 @@ repos: types: [] - repo: https://github.com/PyCQA/isort - rev: 5.5.1 + rev: 5.5.2 hooks: - id: isort diff --git a/src/pylast/__init__.py b/src/pylast/__init__.py index fb2de22..6e3c226 100644 --- a/src/pylast/__init__.py +++ b/src/pylast/__init__.py @@ -146,29 +146,29 @@ class _Network: token=None, ): """ - name: the name of the network - homepage: the homepage URL - ws_server: the URL of the webservices server - 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 - domain_names: a dict mapping each DOMAIN_* value to a string domain - name - urls: a dict mapping types to URLs - token: an authentication token to retrieve a session + name: the name of the network + homepage: the homepage URL + ws_server: the URL of the webservices server + 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 + domain_names: a dict mapping each DOMAIN_* value to a string domain + name + urls: a dict mapping types to URLs + 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. + if username and password_hash were provided and not session_key, + session_key will be generated automatically when needed. - Either a valid session_key or a combination of username and - password_hash must be present for scrobbling. + Either a valid session_key or a combination of username and + password_hash must be present for scrobbling. - You should use a preconfigured network object through a - get_*_network(...) method instead of creating an object - of this class, unless you know what you're doing. + You should use a preconfigured network object through a + get_*_network(...) method instead of creating an object + of this class, unless you know what you're doing. """ self.name = name @@ -209,56 +209,56 @@ class _Network: def get_artist(self, artist_name): """ - Return an Artist object + Return an Artist object """ return Artist(artist_name, self) def get_track(self, artist, title): """ - Return a Track object + Return a Track object """ return Track(artist, title, self) def get_album(self, artist, title): """ - Return an Album object + Return an Album object """ return Album(artist, title, self) def get_authenticated_user(self): """ - Returns the authenticated user + Returns the authenticated user """ return AuthenticatedUser(self) def get_country(self, country_name): """ - Returns a country object + Returns a country object """ return Country(country_name, self) def get_user(self, username): """ - Returns a user object + Returns a user object """ return User(username, self) def get_tag(self, name): """ - Returns a tag object + Returns a tag object """ return Tag(name, self) def _get_language_domain(self, domain_language): """ - Returns the mapped domain name of the network to a DOMAIN_* value + Returns the mapped domain name of the network to a DOMAIN_* value """ if domain_language in self.domain_names: @@ -271,13 +271,13 @@ class _Network: def _get_ws_auth(self): """ - Returns an (API_KEY, API_SECRET, SESSION_KEY) tuple. + Returns an (API_KEY, API_SECRET, SESSION_KEY) tuple. """ return self.api_key, self.api_secret, self.session_key def _delay_call(self): """ - Makes sure that web service calls are at least 0.2 seconds apart. + Makes sure that web service calls are at least 0.2 seconds apart. """ now = time.time() @@ -1408,31 +1408,31 @@ class WSError(Exception): def get_id(self): """Returns the exception ID, from one of the following: - 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_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 + 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_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 @@ -2937,8 +2937,8 @@ def _url_safe(text): def _number(string): """ - Extracts an int from a string. - Returns a 0 if None or an empty string was passed. + Extracts an int from a string. + Returns a 0 if None or an empty string was passed. """ if not string: diff --git a/tests/test_artist.py b/tests/test_artist.py index 802a5e2..309537f 100755 --- a/tests/test_artist.py +++ b/tests/test_artist.py @@ -2,9 +2,10 @@ """ Integration (not unit) tests for pylast.py """ -import pylast import pytest +import pylast + from .test_pylast import WRITE_TEST, TestPyLastWithLastFm diff --git a/tests/test_librefm.py b/tests/test_librefm.py index cb8ddcc..6b0f3dd 100755 --- a/tests/test_librefm.py +++ b/tests/test_librefm.py @@ -2,9 +2,10 @@ """ Integration (not unit) tests for pylast.py """ -import pylast from flaky import flaky +import pylast + from .test_pylast import PyLastTestCase, load_secrets diff --git a/tests/test_network.py b/tests/test_network.py index bad8c54..3416260 100755 --- a/tests/test_network.py +++ b/tests/test_network.py @@ -5,9 +5,10 @@ Integration (not unit) tests for pylast.py import re import time -import pylast import pytest +import pylast + from .test_pylast import WRITE_TEST, TestPyLastWithLastFm diff --git a/tests/test_pylast.py b/tests/test_pylast.py index 9ed1c7f..789afad 100755 --- a/tests/test_pylast.py +++ b/tests/test_pylast.py @@ -6,10 +6,11 @@ import os import sys import time -import pylast import pytest from flaky import flaky +import pylast + WRITE_TEST = sys.version_info[:2] == (3, 8) diff --git a/tests/test_track.py b/tests/test_track.py index 3bfe995..523498e 100755 --- a/tests/test_track.py +++ b/tests/test_track.py @@ -4,9 +4,10 @@ Integration (not unit) tests for pylast.py """ import time -import pylast import pytest +import pylast + from .test_pylast import WRITE_TEST, TestPyLastWithLastFm diff --git a/tests/test_user.py b/tests/test_user.py index 7f7e2e9..2428e69 100755 --- a/tests/test_user.py +++ b/tests/test_user.py @@ -8,9 +8,10 @@ import os import re import warnings -import pylast import pytest +import pylast + from .test_pylast import TestPyLastWithLastFm diff --git a/tests/unicode_test.py b/tests/unicode_test.py index 7efcfea..7b3c271 100644 --- a/tests/unicode_test.py +++ b/tests/unicode_test.py @@ -1,8 +1,9 @@ from unittest import mock -import pylast import pytest +import pylast + def mock_network(): return mock.Mock(_get_ws_auth=mock.Mock(return_value=("", "", ""))) From 08ff0085058c19723256c4561c61de00f675bbd5 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Fri, 11 Sep 2020 23:56:15 +0300 Subject: [PATCH 08/29] Drop support for EOL Python 3.5 --- .pre-commit-config.yaml | 4 ++-- .travis.yml | 1 - README.md | 13 +++++++------ setup.py | 3 +-- 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 58efd85..8f833d2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,13 +3,13 @@ repos: rev: v2.7.2 hooks: - id: pyupgrade - args: ["--py3-plus"] + args: ["--py36-plus"] - repo: https://github.com/psf/black rev: 20.8b1 hooks: - id: black - args: ["--target-version", "py35"] + args: ["--target-version", "py36"] # override until resolved: https://github.com/psf/black/issues/402 files: \.pyi?$ types: [] diff --git a/.travis.yml b/.travis.yml index f904565..688e4e4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,7 +23,6 @@ matrix: - python: 3.8 - python: 3.7 - python: 3.6 - - python: 3.5 - python: 3.9-dev - python: 3.10-dev - python: pypy3 diff --git a/README.md b/README.md index 806c718..6ddcf9f 100644 --- a/README.md +++ b/README.md @@ -31,11 +31,13 @@ Or from requirements.txt: Note: -* pylast 3.0.0+ supports Python 3.5+ ([#265](https://github.com/pylast/pylast/issues/265)) -* pyLast 2.2.0 - 2.4.0 supports Python 2.7.10+, 3.4, 3.5, 3.6, 3.7. -* pyLast 2.0.0 - 2.1.0 supports Python 2.7.10+, 3.4, 3.5, 3.6. -* pyLast 1.7.0 - 1.9.0 supports Python 2.7, 3.3, 3.4, 3.5, 3.6. -* pyLast 1.0.0 - 1.6.0 supports Python 2.7, 3.3, 3.4. +* pyLast 4.0.0+ supports Python 3.6+. +* pyLast 3.2.0 - 3.3.0 supports Python 3.5-3.8. +* pyLast 3.0.0 - 3.1.0 supports Python 3.5-3.7. +* pyLast 2.2.0 - 2.4.0 supports Python 2.7.10+, 3.4-3.7. +* pyLast 2.0.0 - 2.1.0 supports Python 2.7.10+, 3.4-3.6. +* pyLast 1.7.0 - 1.9.0 supports Python 2.7, 3.3-3.6. +* pyLast 1.0.0 - 1.6.0 supports Python 2.7, 3.3-3.4. * pyLast 0.5 supports Python 2, 3. * pyLast < 0.5 supports Python 2. @@ -49,7 +51,6 @@ Features * Proxy support. * Internal caching support for some web services calls (disabled by default). * Support for other API-compatible networks like Libre.fm. - * Python 3-friendly (Starting from 0.5). Getting started diff --git a/setup.py b/setup.py index 7238518..171283d 100755 --- a/setup.py +++ b/setup.py @@ -27,7 +27,7 @@ setup( extras_require={ "tests": ["flaky", "pytest", "pytest-cov", "pytest-random-order", "pyyaml"] }, - python_requires=">=3.5", + python_requires=">=3.6", classifiers=[ "Development Status :: 5 - Production/Stable", "License :: OSI Approved :: Apache Software License", @@ -35,7 +35,6 @@ setup( "Topic :: Multimedia :: Sound/Audio", "Topic :: Software Development :: Libraries :: Python Modules", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", From bf753884c4b125d4faef8fa7ccce8f00f3b5569b Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Sat, 12 Sep 2020 00:13:19 +0300 Subject: [PATCH 09/29] Add support for Python 3.9 --- README.md | 2 +- setup.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6ddcf9f..ceed792 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Or from requirements.txt: Note: -* pyLast 4.0.0+ supports Python 3.6+. +* pyLast 4.0.0+ supports Python 3.6-3.9. * pyLast 3.2.0 - 3.3.0 supports Python 3.5-3.8. * pyLast 3.0.0 - 3.1.0 supports Python 3.5-3.7. * pyLast 2.2.0 - 2.4.0 supports Python 2.7.10+, 3.4-3.7. diff --git a/setup.py b/setup.py index 171283d..a2d891f 100755 --- a/setup.py +++ b/setup.py @@ -38,6 +38,7 @@ setup( "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", From 85f58472a332156f14ab7f836eed42259ae61547 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Sat, 12 Sep 2020 00:21:12 +0300 Subject: [PATCH 10/29] Remove deprecated Artist.get_cover_image, User.get_artist_tracks and STATUS_TOKEN_ERROR --- src/pylast/__init__.py | 65 +----------------------------------------- tests/test_artist.py | 4 --- tests/test_user.py | 13 --------- 3 files changed, 1 insertion(+), 81 deletions(-) diff --git a/src/pylast/__init__.py b/src/pylast/__init__.py index 6e3c226..e02c764 100644 --- a/src/pylast/__init__.py +++ b/src/pylast/__init__.py @@ -27,7 +27,6 @@ import shelve import ssl import tempfile import time -import warnings import xml.dom from http.client import HTTPSConnection from urllib.parse import quote_plus @@ -49,9 +48,7 @@ STATUS_AUTH_FAILED = 4 STATUS_INVALID_FORMAT = 5 STATUS_INVALID_PARAMS = 6 STATUS_INVALID_RESOURCE = 7 -# 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_OPERATION_FAILED = 8 STATUS_INVALID_SK = 9 STATUS_INVALID_API_KEY = 10 STATUS_OFFLINE = 11 @@ -1715,32 +1712,6 @@ class Artist(_BaseObject, _Taggable): return _extract(self._request(self.ws_prefix + ".getCorrection"), "name") - def get_cover_image(self, size=SIZE_EXTRA_LARGE): - """ - Returns a URI to the cover image - size can be one of: - SIZE_MEGA - SIZE_EXTRA_LARGE - SIZE_LARGE - SIZE_MEDIUM - SIZE_SMALL - """ - - warnings.warn( - "Artist.get_cover_image is deprecated and will be removed in a future " - "version. In the meantime, only default star images are available. " - "See https://github.com/pylast/pylast/issues/317 and " - "https://support.last.fm/t/api-announcement/202", - DeprecationWarning, - stacklevel=2, - ) - - if "image" not in self.info: - self.info["image"] = _extract_all( - self._request(self.ws_prefix + ".getInfo", cacheable=True), "image" - ) - return self.info["image"][size] - def get_playcount(self): """Returns the number of plays on the network.""" @@ -2251,40 +2222,6 @@ class User(_BaseObject, _Chartable): return self.name - def get_artist_tracks(self, artist, cacheable=False): - """ - Deprecated by Last.fm. - Get a list of tracks by a given artist scrobbled by this user, - including scrobble time. - """ - - warnings.warn( - "User.get_artist_tracks is deprecated and will be removed in a future " - "version. User.get_track_scrobbles is a partial replacement. " - "See https://github.com/pylast/pylast/issues/298", - DeprecationWarning, - stacklevel=2, - ) - - params = self._get_params() - params["artist"] = artist - - seq = [] - for track in _collect_nodes( - None, self, self.ws_prefix + ".getArtistTracks", cacheable, params - ): - title = _extract(track, "name") - artist = _extract(track, "artist") - date = _extract(track, "date") - album = _extract(track, "album") - timestamp = track.getElementsByTagName("date")[0].getAttribute("uts") - - seq.append( - PlayedTrack(Track(artist, title, self.network), album, date, timestamp) - ) - - return seq - def get_friends(self, limit=50, cacheable=False): """Returns a list of the user's friends. """ diff --git a/tests/test_artist.py b/tests/test_artist.py index 309537f..679d917 100755 --- a/tests/test_artist.py +++ b/tests/test_artist.py @@ -241,16 +241,12 @@ class TestPyLastArtist(TestPyLastWithLastFm): url = artist1.get_url() mbid = artist1.get_mbid() - with pytest.warns(DeprecationWarning): - 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 - assert "https" in image assert playcount > 1 assert artist1 != artist2 assert name.lower() == name_cap.lower() diff --git a/tests/test_user.py b/tests/test_user.py index 2428e69..99766dc 100755 --- a/tests/test_user.py +++ b/tests/test_user.py @@ -6,7 +6,6 @@ import calendar import datetime as dt import os import re -import warnings import pytest @@ -475,15 +474,3 @@ class TestPyLastUser(TestPyLastWithLastFm): # Assert self.helper_validate_results(result1, result2, result3) - - def test_get_artist_tracks_deprecated(self): - # Arrange - lastfm_user = self.network.get_user(self.username) - - # Act / Assert - with warnings.catch_warnings(), pytest.raises( - pylast.WSError, - match="Deprecated - This type of request is no longer supported", - ): - warnings.filterwarnings("ignore", category=DeprecationWarning) - lastfm_user.get_artist_tracks(artist="Test Artist") From 6da916e78db26c7fdcd451d94c7dcf33a3a0ca9b Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Wed, 7 Oct 2020 20:55:58 +0300 Subject: [PATCH 11/29] tox-ini-fmt --- .pre-commit-config.yaml | 9 +++++++-- tox.ini | 32 ++++++++++++++++++++------------ 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8f833d2..c8a441a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,12 +15,12 @@ repos: types: [] - repo: https://github.com/PyCQA/isort - rev: 5.5.2 + rev: 5.5.4 hooks: - id: isort - repo: https://gitlab.com/pycqa/flake8 - rev: 3.8.3 + rev: 3.8.4 hooks: - id: flake8 additional_dependencies: [flake8-2020, flake8-implicit-str-concat] @@ -35,3 +35,8 @@ repos: hooks: - id: check-merge-conflict - id: check-yaml + + - repo: https://github.com/tox-dev/tox-ini-fmt + rev: 0.5.0 + hooks: + - id: tox-ini-fmt diff --git a/tox.ini b/tox.ini index 0a61ca1..331d4ef 100644 --- a/tox.ini +++ b/tox.ini @@ -1,21 +1,29 @@ [tox] -envlist = py{36, 37, 38, 39, 310, py3} +envlist = + py{py3, 310, 39, 38, 37, 36} [testenv] -extras = tests setenv = - PYLAST_USERNAME={env:PYLAST_USERNAME:} - PYLAST_PASSWORD_HASH={env:PYLAST_PASSWORD_HASH:} - PYLAST_API_KEY={env:PYLAST_API_KEY:} - PYLAST_API_SECRET={env:PYLAST_API_SECRET:} -commands = pytest -v -s -W all --cov pylast --cov tests --cov-report term-missing --random-order {posargs} + PYLAST_API_KEY = {env:PYLAST_API_KEY:} + PYLAST_API_SECRET = {env:PYLAST_API_SECRET:} + PYLAST_PASSWORD_HASH = {env:PYLAST_PASSWORD_HASH:} + PYLAST_USERNAME = {env:PYLAST_USERNAME:} +extras = + tests +commands = + pytest -v -s -W all --cov pylast --cov tests --cov-report term-missing --random-order {posargs} [testenv:venv] -deps = ipdb -commands = {posargs} +deps = + ipdb +commands = + {posargs} [testenv:lint] -deps = pre-commit -commands = pre-commit run --all-files --show-diff-on-failure +passenv = + PRE_COMMIT_COLOR skip_install = true -passenv = PRE_COMMIT_COLOR +deps = + pre-commit +commands = + pre-commit run --all-files --show-diff-on-failure From e888739b1480be76cdccc7b5b842b068c3fd09a3 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Wed, 7 Oct 2020 21:02:08 +0300 Subject: [PATCH 12/29] Add 4.0.0 to changelog --- CHANGELOG.md | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1d7042..64d7765 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [4.0.0] - 2020-10-07 +## Added + +* Add support for Python 3.9 (#347) @hugovk + +## Removed + +* Remove deprecated Artist.get_cover_image, User.get_artist_tracks and STATUS_TOKEN_ERROR (#348) @hugovk +* Drop support for EOL Python 3.5 (#346) @hugovk + ## [3.3.0] - 2020-06-25 ### Added @@ -86,10 +96,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Support for Python 2.7 ([#265]) -[3.3.0]: https://github.com/pylast/pylast/compare/v3.2.1...3.3.0 -[3.2.1]: https://github.com/pylast/pylast/compare/v3.2.0...3.2.1 -[3.2.0]: https://github.com/pylast/pylast/compare/v3.1.0...3.2.0 -[3.1.0]: https://github.com/pylast/pylast/compare/v3.0.0...3.1.0 +[4.0.0]: https://github.com/pylast/pylast/compare/3.3.0...4.0.0 +[3.3.0]: https://github.com/pylast/pylast/compare/3.2.1...3.3.0 +[3.2.1]: https://github.com/pylast/pylast/compare/3.2.0...3.2.1 +[3.2.0]: https://github.com/pylast/pylast/compare/3.1.0...3.2.0 +[3.1.0]: https://github.com/pylast/pylast/compare/3.0.0...3.1.0 [3.0.0]: https://github.com/pylast/pylast/compare/2.4.0...3.0.0 [2.4.0]: https://github.com/pylast/pylast/compare/2.3.0...2.4.0 [#265]: https://github.com/pylast/pylast/issues/265 @@ -105,3 +116,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#311]: https://github.com/pylast/pylast/issues/311 [#312]: https://github.com/pylast/pylast/issues/312 [#316]: https://github.com/pylast/pylast/issues/316 +[#346]: https://github.com/pylast/pylast/issues/346 +[#347]: https://github.com/pylast/pylast/issues/347 +[#348]: https://github.com/pylast/pylast/issues/348 From b5a9617cdfa959a0fd1ed8a832089e503df0b57f Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Wed, 7 Oct 2020 21:26:42 +0300 Subject: [PATCH 13/29] 3.9-dev first --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 688e4e4..77e8c45 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,10 +20,10 @@ matrix: include: - python: 3.8 env: TOXENV=lint + - python: 3.9-dev - python: 3.8 - python: 3.7 - python: 3.6 - - python: 3.9-dev - python: 3.10-dev - python: pypy3 From 49e2831cf607f6f7247ba63f03b51a495695e3c6 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Wed, 7 Oct 2020 21:27:17 +0300 Subject: [PATCH 14/29] Code formatting --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64d7765..b1c3a61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Removed -* Remove deprecated Artist.get_cover_image, User.get_artist_tracks and STATUS_TOKEN_ERROR (#348) @hugovk +* Remove deprecated `Artist.get_cover_image`, `User.get_artist_tracks` and `STATUS_TOKEN_ERROR` (#348) @hugovk * Drop support for EOL Python 3.5 (#346) @hugovk From b6eb1c8bafa4eae494de3a8e652f13e75865720b Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Wed, 28 Oct 2020 16:17:15 +0200 Subject: [PATCH 15/29] Add Hacktoberfest labels --- .github/labels.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/labels.yml b/.github/labels.yml index 6ea43df..13ae9e0 100644 --- a/.github/labels.yml +++ b/.github/labels.yml @@ -97,6 +97,12 @@ - color: 0366d6 description: "For dependencies" name: dependencies +- color: f4660e + description: "" + name: Hacktoberfest +- color: f4660e + description: "To credit accepted Hacktoberfest PRs" + name: hacktoberfest-accepted - color: fef2c0 description: "" name: test From c218aab4dd16d9b60972f5924ab944ee98481e77 Mon Sep 17 00:00:00 2001 From: sheetalsingala Date: Wed, 28 Oct 2020 12:42:46 -0400 Subject: [PATCH 16/29] Add Python 3.9 final to Travis CI --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 77e8c45..d7865e5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,7 +20,7 @@ matrix: include: - python: 3.8 env: TOXENV=lint - - python: 3.9-dev + - python: 3.9 - python: 3.8 - python: 3.7 - python: 3.6 From d7e1d70c34c094be63867bc61b32113f7fe9c651 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Mon, 16 Nov 2020 22:41:24 +0200 Subject: [PATCH 17/29] Use pre-commit/action --- .github/workflows/lint.yml | 32 ++------------------------------ 1 file changed, 2 insertions(+), 30 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index fd4c7e6..bda0c64 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -2,39 +2,11 @@ name: Lint on: [push, pull_request] -env: - FORCE_COLOR: 1 - jobs: build: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 - - - name: Cache - uses: actions/cache@v2 - with: - path: | - ~/.cache/pip - ~/.cache/pre-commit - key: - lint-v2-${{ hashFiles('**/setup.py') }}-${{ - hashFiles('**/.pre-commit-config.yaml') }} - restore-keys: | - lint-v2- - - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: 3.8 - - - name: Install dependencies - run: | - python -m pip install -U pip - python -m pip install -U tox - - - name: Lint - run: tox -e lint - env: - PRE_COMMIT_COLOR: always + - uses: actions/setup-python@v2 + - uses: pre-commit/action@v2.0.0 From b7e2cce725e2e3e77018bb94ff13c86caad731a5 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Mon, 16 Nov 2020 22:41:44 +0200 Subject: [PATCH 18/29] Remove Travis CI --- .travis.yml | 66 ----------------------------------------------------- 1 file changed, 66 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index d7865e5..0000000 --- a/.travis.yml +++ /dev/null @@ -1,66 +0,0 @@ -language: python -cache: - pip: true - directories: - - $HOME/.cache/pre-commit - -env: - global: - - secure: ivg6II471E9HV8xyqnawLIuP/sZ0J63Y+BC0BQcRVKtLn/K3zmD1ozM3TFL9S549Nxd0FqDKHXJvXsgaTGIDpK8sxE2AMKV5IojyM0iAVuN7YjPK9vwSlRw1u0EysPMFqxOZVQnoDyHrSGIUrP/VMdnhBu6dbUX0FyEkvZshXhY= - - secure: gDWNEYA1EUv4G230/KzcTgcmEST0nf2FeW/z/prsoQBu+TWw1rKKSJAJeMLvuI1z4aYqqNYdmqjWyNhhVK3p5wmFP2lxbhaBT1jDsxxFpePc0nUkdAQOOD0yBpbBGkqkjjxU34HjTX2NFNEbcM3izVVE9oQmS5r4oFFNJgdL91c= - - secure: RpsZblHFU7a5dnkO/JUgi70RkNJwoUh3jJqVo1oOLjL+lvuAmPXhI8MDk2diUk43X+XCBFBEnm7UCGnjUF+hDnobO4T+VrIFuVJWg3C7iKIT+YWvgG6A+CSeo/P0I0dAeUscTr5z4ylOq3EDx4MFSa8DmoWMmjKTAG1GAeTlY2k= - - secure: T5OKyd5Bs0nZbUr+YICbThC5GrFq/kUjX8FokzCv7NWsYaUWIwEmMXXzoYALoB3A+rAglOx6GABaupoNKKg3tFQyxXphuMKpZ8MasMAMFjFW0d7wsgGy0ylhVwrgoKzDbCQ5FKbohC+9ltLs+kKMCQ0L+MI70a/zTfF4/dVWO/o= - - secure: DxBvGGoIgbAeuuU3A6+J1HBbmUAEvqdmK73etw+yNKDLGvvukgTL33dNCr8CZXLKRRvfhrjU7Q01GUpOTxrVQ9nJgsD55kwx0wPtuBWIF80M2m4SPsiVLlwP/LFYD5JMDTDWjFTlVahma8P7qoLjCc7b/RgigWLidH19snQmjdY= - - secure: VPARlWNg/0Nit7a924vJlDfv7yiuTDtrcGZNFrZ6yN3dl8ZjVPizQXQNKA3yq0y2jW25nwjRwZYj3eY5MdM9F7Sw51d+/8AjFtdCuRgDvwlQFR/pCoyzqgJATkXKo7mlejvnA+5EKUzAmu3drIbboFgbLgRTMrG7b/ot9tazTHs= - - secure: CQYL7MH6tSVrCcluIfWfDSTo4E/p+9pF0eI7Vtf0oaZBzyulODHK8h/mzJp4HwezyfOu0RCedq6sloGQr1/29CvWWESaYyoGoGz9Mz2ZS+MpIcjGISfZa+x4vSp6QPFvd4i/1Z/1j2gJVVyswkrIVUwZIDJtfAKzZI5iHx2gH8Y= - - secure: SsKJoJwtDVWrL5xxl9C/gTRy6FhfRQQNNAFOogl9mTs/WeI2t9QTYoKsxLPXOdoRdu4MvT3h/B2sjwggt7zP81fBVxQRTkg4nq0zSHlj0NqclbFa6I5lUYdGwH9gPk/HWJJwXhKRDsqn/iRw2v+qBDs/j3kIgPQ0yjM58LEPXic= - -matrix: - fast_finish: true - include: - - python: 3.8 - env: TOXENV=lint - - python: 3.9 - - python: 3.8 - - python: 3.7 - - python: 3.6 - - python: 3.10-dev - - python: pypy3 - -install: -- travis_retry pip install -U pip -- travis_retry pip install -U tox-travis - -script: tox - -after_success: - - | - if [ "$TOXENV" != "lint" ]; then - travis_retry pip install -U coveralls && coveralls - travis_retry pip install -U codecov && codecov - fi - -deploy: - - provider: pypi - server: https://test.pypi.org/legacy/ - on: - tags: false - repo: pylast/pylast - branch: master - condition: $TOXENV = lint - user: hugovk - password: - secure: "OCNT7Sf7TpS6aKuqBXEWxJZjmEpdERTBp/yllOd9xnpFt2ZL96CyKtAhPA8zu5OP58QFEZSafZRfXYJoz78RDrx3gOdRXCFT00vXIMnjVvrAlieNEHCVAT0kRW9lYK1Cf5baHYsOYIs6EZf2fEAhdzvmh83G4Y1Y+FPR9tA6uy8=" - distributions: sdist --format=gztar bdist_wheel - skip_existing: true - - provider: pypi - on: - tags: true - repo: pylast/pylast - branch: master - condition: $TOXENV = lint - user: hugovk - password: - secure: "OCNT7Sf7TpS6aKuqBXEWxJZjmEpdERTBp/yllOd9xnpFt2ZL96CyKtAhPA8zu5OP58QFEZSafZRfXYJoz78RDrx3gOdRXCFT00vXIMnjVvrAlieNEHCVAT0kRW9lYK1Cf5baHYsOYIs6EZf2fEAhdzvmh83G4Y1Y+FPR9tA6uy8=" - distributions: sdist --format=gztar bdist_wheel - skip_existing: true From 815dc62dcd38091803b068ec0002478529734955 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Mon, 16 Nov 2020 22:51:34 +0200 Subject: [PATCH 19/29] Test on GitHub Actions --- .github/workflows/test.yml | 63 ++++++++++++++++++++++++++++++++++++++ tox.ini | 10 +++--- 2 files changed, 68 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..12e6357 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,63 @@ +name: Test + +on: [push, pull_request] + +env: + FORCE_COLOR: 1 + +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + python-version: ["3.6", "3.7", "3.8", "3.9", "3.10-dev", "pypy3"] + os: [ubuntu-20.04] + include: + # Include new variables for Codecov + - { codecov-flag: GHA_Ubuntu2004, os: ubuntu-20.04 } + + steps: + - uses: actions/checkout@v2 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Get pip cache dir + id: pip-cache + run: | + echo "::set-output name=dir::$(pip cache dir)" + + - name: Cache + uses: actions/cache@v2 + with: + path: ${{ steps.pip-cache.outputs.dir }} + key: + ${{ matrix.os }}-${{ matrix.python-version }}-v3-${{ + hashFiles('**/setup.py') }} + restore-keys: | + ${{ matrix.os }}-${{ matrix.python-version }}-v3- + + - name: Install dependencies + run: | + python -m pip install -U pip + python -m pip install -U wheel + python -m pip install -U tox + + - name: Tox tests + shell: bash + run: | + tox -e py + env: + PYLAST_API_KEY: ${{ secrets.PYLAST_API_KEY }} + PYLAST_API_SECRET: ${{ secrets.PYLAST_API_SECRET }} + PYLAST_PASSWORD_HASH: ${{ secrets.PYLAST_PASSWORD_HASH }} + PYLAST_USERNAME: ${{ secrets.PYLAST_USERNAME }} + + - name: Upload coverage + uses: codecov/codecov-action@v1 + with: + flags: ${{ matrix.codecov-flag }} + name: ${{ matrix.os }} Python ${{ matrix.python-version }} diff --git a/tox.ini b/tox.ini index 331d4ef..c19e202 100644 --- a/tox.ini +++ b/tox.ini @@ -3,11 +3,11 @@ envlist = py{py3, 310, 39, 38, 37, 36} [testenv] -setenv = - PYLAST_API_KEY = {env:PYLAST_API_KEY:} - PYLAST_API_SECRET = {env:PYLAST_API_SECRET:} - PYLAST_PASSWORD_HASH = {env:PYLAST_PASSWORD_HASH:} - PYLAST_USERNAME = {env:PYLAST_USERNAME:} +passenv = + PYLAST_API_KEY + PYLAST_API_SECRET + PYLAST_PASSWORD_HASH + PYLAST_USERNAME extras = tests commands = From 5413d636ce107f7291ada9cb80a6ea6a6cbff6a5 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Mon, 16 Nov 2020 23:45:25 +0200 Subject: [PATCH 20/29] Fix test --- tests/test_network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_network.py b/tests/test_network.py index 3416260..702b9a9 100755 --- a/tests/test_network.py +++ b/tests/test_network.py @@ -64,7 +64,7 @@ class TestPyLastNetwork(TestPyLastWithLastFm): self.network.enable_rate_limit() then = time.time() # Make some network call, limit not applied first time - self.network.get_user(self.username) + self.network.get_top_artists() # Make a second network call, limiting should be applied self.network.get_top_artists() now = time.time() From c979b72cf3ba656e6896953f56e20c21fbd93c53 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Tue, 17 Nov 2020 17:39:48 +0200 Subject: [PATCH 21/29] Label sync: don't delete existing labels not found in manifest --- .github/workflows/labels.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml index 2303846..e84c13e 100644 --- a/.github/workflows/labels.yml +++ b/.github/workflows/labels.yml @@ -11,5 +11,7 @@ jobs: steps: - uses: actions/checkout@v2 - uses: micnncim/action-label-syncer@v1 + with: + prune: false env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From e98f493c39a81f62765c4f6266d9013c4d5e1d74 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Tue, 17 Nov 2020 17:40:56 +0200 Subject: [PATCH 22/29] Use version resolver with Release Drafter --- .github/release-drafter.yml | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index 402bfbf..c121b37 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -1,5 +1,5 @@ -name-template: "$NEXT_PATCH_VERSION" -tag-template: "$NEXT_PATCH_VERSION" +name-template: "Release $RESOLVED_VERSION" +tag-template: "$RESOLVED_VERSION" categories: - title: "Added" @@ -26,3 +26,20 @@ template: | ## Changes $CHANGES + +version-resolver: + major: + labels: + - "changelog: Removed" + minor: + labels: + - "changelog: Added" + - "changelog: Changed" + - "changelog: Deprecated" + - "enhancement" + + patch: + labels: + - "changelog: Fixed" + - "bug" + default: minor From 0ba17ecfff960f23e19d2dfae656bcd661f3b9e5 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Tue, 17 Nov 2020 17:46:50 +0200 Subject: [PATCH 23/29] Deploy to TestPyPI on merges to master, to prod PyPI for tags --- .github/workflows/deploy.yml | 57 ++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 .github/workflows/deploy.yml diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..1f65cb8 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,57 @@ +name: Deploy + +on: + push: + branches: + - master + release: + types: + - published + +jobs: + build: + if: github.repository == 'pylast/pylast' + runs-on: ubuntu-20.04 + + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Cache + uses: actions/cache@v2 + with: + path: ~/.cache/pip + key: deploy-${{ hashFiles('**/setup.py') }} + restore-keys: | + deploy- + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: 3.9 + + - name: Install dependencies + run: | + python -m pip install -U pip + python -m pip install -U setuptools twine wheel + + - name: Build package + run: | + python setup.py --version + python setup.py sdist --format=gztar bdist_wheel + twine check dist/* + + - name: Publish package to PyPI + if: github.event.action == 'published' + uses: pypa/gh-action-pypi-publish@master + with: + user: __token__ + password: ${{ secrets.pypi_password }} + + - name: Publish package to TestPyPI + uses: pypa/gh-action-pypi-publish@master + with: + user: __token__ + password: ${{ secrets.test_pypi_password }} + repository_url: https://test.pypi.org/legacy/ From e4ca881c951148bbcde39719353dd85599711368 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Tue, 17 Nov 2020 17:50:28 +0200 Subject: [PATCH 24/29] pre-commit autoupdate --- .pre-commit-config.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c8a441a..147a7e0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/asottile/pyupgrade - rev: v2.7.2 + rev: v2.7.4 hooks: - id: pyupgrade args: ["--py36-plus"] @@ -15,7 +15,7 @@ repos: types: [] - repo: https://github.com/PyCQA/isort - rev: 5.5.4 + rev: 5.6.4 hooks: - id: isort @@ -26,12 +26,12 @@ repos: additional_dependencies: [flake8-2020, flake8-implicit-str-concat] - repo: https://github.com/pre-commit/pygrep-hooks - rev: v1.6.0 + rev: v1.7.0 hooks: - id: python-check-blanket-noqa - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v3.2.0 + rev: v3.3.0 hooks: - id: check-merge-conflict - id: check-yaml From 9033debfcd756e2c1ff39e8db910d68da777de5f Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Tue, 17 Nov 2020 17:58:43 +0200 Subject: [PATCH 25/29] Replace Travis CI with GitHub Actions --- .mergify.yml | 2 -- README.md | 5 ++--- RELEASING.md | 7 ++++--- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/.mergify.yml b/.mergify.yml index f2aad55..dad8639 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -3,8 +3,6 @@ pull_request_rules: conditions: - label=automerge - status-success=build - - status-success=continuous-integration/travis-ci/pr - - status-success=continuous-integration/travis-ci/push actions: merge: method: merge diff --git a/README.md b/README.md index ceed792..69872b5 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,9 @@ pyLast [![PyPI version](https://img.shields.io/pypi/v/pylast.svg)](https://pypi.org/project/pylast/) [![Supported Python versions](https://img.shields.io/pypi/pyversions/pylast.svg)](https://pypi.org/project/pylast/) [![PyPI downloads](https://img.shields.io/pypi/dm/pylast.svg)](https://pypistats.org/packages/pylast) -[![Build status](https://travis-ci.org/pylast/pylast.svg?branch=master)](https://travis-ci.org/pylast/pylast) +[![Test](https://github.com/pylast/pylast/workflows/Test/badge.svg)](https://github.com/pylast/pylast/actions) [![Coverage (Codecov)](https://codecov.io/gh/pylast/pylast/branch/master/graph/badge.svg)](https://codecov.io/gh/pylast/pylast) -[![Coverage (Coveralls)](https://coveralls.io/repos/github/pylast/pylast/badge.svg?branch=master)](https://coveralls.io/github/pylast/pylast?branch=master) -[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/python/black) +[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![DOI](https://zenodo.org/badge/7803088.svg)](https://zenodo.org/badge/latestdoi/7803088) A Python interface to [Last.fm](https://www.last.fm/) and other API-compatible websites such as [Libre.fm](https://libre.fm/). diff --git a/RELEASING.md b/RELEASING.md index 4224482..7e3cdfc 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -1,8 +1,9 @@ # Release Checklist * [ ] Get master to the appropriate code release state. - [Travis CI](https://travis-ci.org/pylast/pylast) should be running cleanly for + [GitHub Actions](https://github.com/pylast/pylast/actions) should be running cleanly for all merges to master. + [![Test](https://github.com/pylast/pylast/workflows/Test/badge.svg)](https://github.com/pylast/pylast/actions) * [ ] Edit release draft, adjust text if needed: https://github.com/pylast/pylast/releases @@ -13,8 +14,8 @@ * [ ] Publish release -* [ ] Check the tagged [Travis CI build](https://travis-ci.org/pylast/pylast) has - deployed to [PyPI](https://pypi.org/project/pylast/#history) +* [ ] Check the tagged [GitHub Actions build](https://github.com/pylast/pylast/actions?query=workflow%3ADeploy) + has deployed to [PyPI](https://pypi.org/project/pylast/#history) * [ ] Check installation: From 08274028ebf1f3c611e61e23440df88334377455 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Sun, 27 Dec 2020 14:01:12 +0200 Subject: [PATCH 26/29] Set limit to 50 by default, not 1 --- src/pylast/__init__.py | 2 +- tests/test_artist.py | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/pylast/__init__.py b/src/pylast/__init__.py index e02c764..2f527c0 100644 --- a/src/pylast/__init__.py +++ b/src/pylast/__init__.py @@ -1149,7 +1149,7 @@ class _BaseObject: def _get_things(self, method, thing, thing_type, params=None, cacheable=True): """Returns a list of the most played thing_types by this thing.""" - limit = params.get("limit", 1) + limit = params.get("limit", 50) seq = [] for node in _collect_nodes( limit, self, self.ws_prefix + "." + method, cacheable, params diff --git a/tests/test_artist.py b/tests/test_artist.py index 679d917..69dafb9 100755 --- a/tests/test_artist.py +++ b/tests/test_artist.py @@ -131,6 +131,17 @@ class TestPyLastArtist(TestPyLastWithLastFm): # Assert assert len(things) == 100 + def test_artist_top_albums_limit_default(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() + + # Assert + assert len(things) == 50 + def test_artist_listener_count(self): # Arrange artist = self.network.get_artist("Test Artist") From a5034c43c043f0ff8caf9463c02b76bff30a4001 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Sun, 27 Dec 2020 14:05:52 +0200 Subject: [PATCH 27/29] Refactor with pytest.mark.parametrize --- tests/test_artist.py | 32 ++++---------------------------- 1 file changed, 4 insertions(+), 28 deletions(-) diff --git a/tests/test_artist.py b/tests/test_artist.py index 69dafb9..c99b04e 100755 --- a/tests/test_artist.py +++ b/tests/test_artist.py @@ -95,41 +95,17 @@ class TestPyLastArtist(TestPyLastWithLastFm): # Assert self.helper_two_different_things_in_top_list(things, pylast.Album) - def test_artist_top_albums_limit_1(self): + @pytest.mark.parametrize("test_limit", [1, 50, 100]) + def test_artist_top_albums_limit(self, test_limit: int) -> None: # Arrange - limit = 1 # Pick an artist with plenty of plays artist = self.network.get_top_artists(limit=1)[0].item # Act - things = artist.get_top_albums(limit=limit) + things = artist.get_top_albums(limit=test_limit) # Assert - assert len(things) == 1 - - def test_artist_top_albums_limit_50(self): - # Arrange - limit = 50 - # Pick an artist with plenty of plays - artist = self.network.get_top_artists(limit=1)[0].item - - # Act - things = artist.get_top_albums(limit=limit) - - # Assert - assert len(things) == 50 - - def test_artist_top_albums_limit_100(self): - # Arrange - limit = 100 - # Pick an artist with plenty of plays - artist = self.network.get_top_artists(limit=1)[0].item - - # Act - things = artist.get_top_albums(limit=limit) - - # Assert - assert len(things) == 100 + assert len(things) == test_limit def test_artist_top_albums_limit_default(self): # Arrange From 23503a72128c93b726324a345a47a1f9f03e6c3a Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Sun, 27 Dec 2020 14:22:20 +0200 Subject: [PATCH 28/29] Refactor to remove unused parameter --- src/pylast/__init__.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/pylast/__init__.py b/src/pylast/__init__.py index 2f527c0..e53d9da 100644 --- a/src/pylast/__init__.py +++ b/src/pylast/__init__.py @@ -1146,8 +1146,8 @@ class _BaseObject: return first_child.wholeText.strip() - def _get_things(self, method, thing, thing_type, params=None, cacheable=True): - """Returns a list of the most played thing_types by this thing.""" + def _get_things(self, method, thing_type, params=None, cacheable=True): + """Returns a list of the most played thing_types.""" limit = params.get("limit", 50) seq = [] @@ -1812,7 +1812,7 @@ class Artist(_BaseObject, _Taggable): if limit: params["limit"] = limit - return self._get_things("getTopAlbums", "album", Album, params, cacheable) + return self._get_things("getTopAlbums", Album, params, cacheable) def get_top_tracks(self, limit=None, cacheable=True): """Returns a list of the most played Tracks by this artist.""" @@ -1820,7 +1820,7 @@ class Artist(_BaseObject, _Taggable): if limit: params["limit"] = limit - return self._get_things("getTopTracks", "track", Track, params, cacheable) + return self._get_things("getTopTracks", Track, params, cacheable) def get_url(self, domain_name=DOMAIN_ENGLISH): """Returns the URL of the artist page on the network. @@ -1894,7 +1894,7 @@ class Country(_BaseObject): if limit: params["limit"] = limit - return self._get_things("getTopTracks", "track", Track, params, cacheable) + return self._get_things("getTopTracks", Track, params, cacheable) def get_url(self, domain_name=DOMAIN_ENGLISH): """Returns the URL of the country page on the network. @@ -2024,7 +2024,7 @@ class Tag(_BaseObject, _Chartable): if limit: params["limit"] = limit - return self._get_things("getTopTracks", "track", Track, params, cacheable) + return self._get_things("getTopTracks", Track, params, cacheable) def get_top_artists(self, limit=None, cacheable=True): """Returns a sequence of the most played artists.""" @@ -2498,7 +2498,7 @@ class User(_BaseObject, _Chartable): if limit: params["limit"] = limit - return self._get_things("getTopTracks", "track", Track, params, cacheable) + return self._get_things("getTopTracks", Track, params, cacheable) def get_track_scrobbles(self, artist, track, cacheable=False): """ From 7327b303371a653c71bae3a77254b2166028fe82 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Sun, 27 Dec 2020 14:22:41 +0200 Subject: [PATCH 29/29] Refactor to avoid shadowing built-in --- tests/test_pylast.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_pylast.py b/tests/test_pylast.py index 789afad..c734d32 100755 --- a/tests/test_pylast.py +++ b/tests/test_pylast.py @@ -34,11 +34,11 @@ def load_secrets(): # pragma: no cover class PyLastTestCase: - def assert_startswith(self, str, prefix, start=None, end=None): - assert str.startswith(prefix, start, end) + def assert_startswith(self, s, prefix, start=None, end=None): + assert s.startswith(prefix, start, end) - def assert_endswith(self, str, suffix, start=None, end=None): - assert str.endswith(suffix, start, end) + def assert_endswith(self, s, suffix, start=None, end=None): + assert s.endswith(suffix, start, end) def _no_xfail_rerun_filter(err, name, test, plugin):