Merge pull request #430 from pylast/rm-3.7
This commit is contained in:
commit
5f302e0813
3
.github/workflows/test.yml
vendored
3
.github/workflows/test.yml
vendored
|
@ -11,7 +11,7 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ["pypy3.9", "3.7", "3.8", "3.9", "3.10", "3.11", "3.12-dev"]
|
python-version: ["pypy3.9", "3.8", "3.9", "3.10", "3.11", "3.12"]
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
@ -21,6 +21,7 @@ jobs:
|
||||||
uses: actions/setup-python@v4
|
uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
|
allow-prereleases: true
|
||||||
cache: pip
|
cache: pip
|
||||||
cache-dependency-path: pyproject.toml
|
cache-dependency-path: pyproject.toml
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/asottile/pyupgrade
|
- repo: https://github.com/asottile/pyupgrade
|
||||||
rev: v3.3.1
|
rev: v3.4.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: pyupgrade
|
- id: pyupgrade
|
||||||
args: [--py37-plus]
|
args: [--py38-plus]
|
||||||
|
|
||||||
- repo: https://github.com/psf/black
|
- repo: https://github.com/psf/black
|
||||||
rev: 23.3.0
|
rev: 23.3.0
|
||||||
|
@ -14,7 +14,7 @@ repos:
|
||||||
rev: 1.13.0
|
rev: 1.13.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: blacken-docs
|
- id: blacken-docs
|
||||||
args: [--target-version=py37]
|
args: [--target-version=py38]
|
||||||
additional_dependencies: [black==23.3.0]
|
additional_dependencies: [black==23.3.0]
|
||||||
|
|
||||||
- repo: https://github.com/PyCQA/isort
|
- repo: https://github.com/PyCQA/isort
|
||||||
|
@ -46,12 +46,12 @@ repos:
|
||||||
- id: requirements-txt-fixer
|
- id: requirements-txt-fixer
|
||||||
|
|
||||||
- repo: https://github.com/tox-dev/pyproject-fmt
|
- repo: https://github.com/tox-dev/pyproject-fmt
|
||||||
rev: 0.9.2
|
rev: 0.10.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: pyproject-fmt
|
- id: pyproject-fmt
|
||||||
|
|
||||||
- repo: https://github.com/abravalheri/validate-pyproject
|
- repo: https://github.com/abravalheri/validate-pyproject
|
||||||
rev: v0.12.2
|
rev: v0.13
|
||||||
hooks:
|
hooks:
|
||||||
- id: validate-pyproject
|
- id: validate-pyproject
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,7 @@ Or from requirements.txt:
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
|
|
||||||
* pyLast 5.2+ supports Python 3.7-3.12.
|
* pyLast 5.2+ supports Python 3.8-3.12.
|
||||||
* pyLast 5.1 supports Python 3.7-3.11.
|
* pyLast 5.1 supports Python 3.7-3.11.
|
||||||
* pyLast 5.0 supports Python 3.7-3.10.
|
* pyLast 5.0 supports Python 3.7-3.10.
|
||||||
* pyLast 4.3 - 4.5 supports Python 3.6-3.10.
|
* pyLast 4.3 - 4.5 supports Python 3.6-3.10.
|
||||||
|
|
|
@ -18,30 +18,28 @@ keywords = [
|
||||||
license = {text = "Apache-2.0"}
|
license = {text = "Apache-2.0"}
|
||||||
maintainers = [{name = "Hugo van Kemenade"}]
|
maintainers = [{name = "Hugo van Kemenade"}]
|
||||||
authors = [{name = "Amr Hassan <amr.hassan@gmail.com> and Contributors", email = "amr.hassan@gmail.com"}]
|
authors = [{name = "Amr Hassan <amr.hassan@gmail.com> and Contributors", email = "amr.hassan@gmail.com"}]
|
||||||
requires-python = ">=3.7"
|
requires-python = ">=3.8"
|
||||||
classifiers = [
|
classifiers = [
|
||||||
"Development Status :: 5 - Production/Stable",
|
"Development Status :: 5 - Production/Stable",
|
||||||
"License :: OSI Approved :: Apache Software License",
|
"License :: OSI Approved :: Apache Software License",
|
||||||
"Programming Language :: Python :: 3",
|
"Programming Language :: Python :: 3",
|
||||||
"Programming Language :: Python :: 3 :: Only",
|
"Programming Language :: Python :: 3 :: Only",
|
||||||
"Programming Language :: Python :: 3.7",
|
"Programming Language :: Python :: 3.8",
|
||||||
"Programming Language :: Python :: 3.8",
|
"Programming Language :: Python :: 3.9",
|
||||||
"Programming Language :: Python :: 3.9",
|
"Programming Language :: Python :: 3.10",
|
||||||
"Programming Language :: Python :: 3.10",
|
"Programming Language :: Python :: 3.11",
|
||||||
"Programming Language :: Python :: 3.11",
|
"Programming Language :: Python :: 3.12",
|
||||||
"Programming Language :: Python :: 3.12",
|
"Programming Language :: Python :: Implementation :: CPython",
|
||||||
"Programming Language :: Python :: Implementation :: CPython",
|
"Programming Language :: Python :: Implementation :: PyPy",
|
||||||
"Programming Language :: Python :: Implementation :: PyPy",
|
"Topic :: Internet",
|
||||||
"Topic :: Internet",
|
"Topic :: Multimedia :: Sound/Audio",
|
||||||
"Topic :: Multimedia :: Sound/Audio",
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
||||||
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
||||||
]
|
]
|
||||||
dynamic = [
|
dynamic = [
|
||||||
"version",
|
"version",
|
||||||
]
|
]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"httpx",
|
"httpx",
|
||||||
'importlib-metadata; python_version < "3.8"',
|
|
||||||
]
|
]
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
tests = [
|
tests = [
|
||||||
|
|
|
@ -23,6 +23,7 @@ from __future__ import annotations
|
||||||
import collections
|
import collections
|
||||||
import hashlib
|
import hashlib
|
||||||
import html.entities
|
import html.entities
|
||||||
|
import importlib.metadata
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
@ -36,18 +37,11 @@ from xml.dom import Node, minidom
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
|
|
||||||
try:
|
|
||||||
# Python 3.8+
|
|
||||||
import importlib.metadata as importlib_metadata
|
|
||||||
except ImportError:
|
|
||||||
# Python 3.7 and lower
|
|
||||||
import importlib_metadata # type: ignore
|
|
||||||
|
|
||||||
__author__ = "Amr Hassan, hugovk, Mice Pápai"
|
__author__ = "Amr Hassan, hugovk, Mice Pápai"
|
||||||
__copyright__ = "Copyright (C) 2008-2010 Amr Hassan, 2013-2021 hugovk, 2017 Mice Pápai"
|
__copyright__ = "Copyright (C) 2008-2010 Amr Hassan, 2013-2021 hugovk, 2017 Mice Pápai"
|
||||||
__license__ = "apache2"
|
__license__ = "apache2"
|
||||||
__email__ = "amr.hassan@gmail.com"
|
__email__ = "amr.hassan@gmail.com"
|
||||||
__version__ = importlib_metadata.version(__name__)
|
__version__ = importlib.metadata.version(__name__)
|
||||||
|
|
||||||
|
|
||||||
# 1 : This error does not exist
|
# 1 : This error does not exist
|
||||||
|
|
|
@ -94,8 +94,8 @@ class TestPyLastAlbum(TestPyLastWithLastFm):
|
||||||
image = album.get_cover_image()
|
image = album.get_cover_image()
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
self.assert_startswith(image, "https://")
|
assert image.startswith("https://")
|
||||||
self.assert_endswith(image, ".gif")
|
assert image.endswith(".gif") or image.endswith(".png")
|
||||||
|
|
||||||
def test_mbid(self) -> None:
|
def test_mbid(self) -> None:
|
||||||
# Arrange
|
# Arrange
|
||||||
|
|
|
@ -16,7 +16,7 @@ class TestPyLastLibrary(TestPyLastWithLastFm):
|
||||||
representation = repr(library)
|
representation = repr(library)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
self.assert_startswith(representation, "pylast.Library(")
|
assert representation.startswith("pylast.Library(")
|
||||||
|
|
||||||
def test_str(self) -> None:
|
def test_str(self) -> None:
|
||||||
# Arrange
|
# Arrange
|
||||||
|
@ -26,7 +26,7 @@ class TestPyLastLibrary(TestPyLastWithLastFm):
|
||||||
string = str(library)
|
string = str(library)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
self.assert_endswith(string, "'s Library")
|
assert string.endswith("'s Library")
|
||||||
|
|
||||||
def test_library_is_hashable(self) -> None:
|
def test_library_is_hashable(self) -> None:
|
||||||
# Arrange
|
# Arrange
|
||||||
|
|
|
@ -6,11 +6,11 @@ from flaky import flaky
|
||||||
|
|
||||||
import pylast
|
import pylast
|
||||||
|
|
||||||
from .test_pylast import PyLastTestCase, load_secrets
|
from .test_pylast import load_secrets
|
||||||
|
|
||||||
|
|
||||||
@flaky(max_runs=3, min_passes=1)
|
@flaky(max_runs=3, min_passes=1)
|
||||||
class TestPyLastWithLibreFm(PyLastTestCase):
|
class TestPyLastWithLibreFm:
|
||||||
"""Own class for Libre.fm because we don't need the Last.fm setUp"""
|
"""Own class for Libre.fm because we don't need the Last.fm setUp"""
|
||||||
|
|
||||||
def test_libre_fm(self) -> None:
|
def test_libre_fm(self) -> None:
|
||||||
|
@ -38,4 +38,4 @@ class TestPyLastWithLibreFm(PyLastTestCase):
|
||||||
representation = repr(network)
|
representation = repr(network)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
self.assert_startswith(representation, "pylast.LibreFMNetwork(")
|
assert representation.startswith("pylast.LibreFMNetwork(")
|
||||||
|
|
|
@ -330,12 +330,12 @@ class TestPyLastNetwork(TestPyLastWithLastFm):
|
||||||
# Assert
|
# Assert
|
||||||
assert len(images) == 4
|
assert len(images) == 4
|
||||||
|
|
||||||
self.assert_startswith(images[pylast.SIZE_SMALL], "https://")
|
assert images[pylast.SIZE_SMALL].startswith("https://")
|
||||||
self.assert_endswith(images[pylast.SIZE_SMALL], ".png")
|
assert images[pylast.SIZE_SMALL].endswith(".png")
|
||||||
assert "/34s/" in images[pylast.SIZE_SMALL]
|
assert "/34s/" in images[pylast.SIZE_SMALL]
|
||||||
|
|
||||||
self.assert_startswith(images[pylast.SIZE_EXTRA_LARGE], "https://")
|
assert images[pylast.SIZE_EXTRA_LARGE].startswith("https://")
|
||||||
self.assert_endswith(images[pylast.SIZE_EXTRA_LARGE], ".png")
|
assert images[pylast.SIZE_EXTRA_LARGE].endswith(".png")
|
||||||
assert "/300x300/" in images[pylast.SIZE_EXTRA_LARGE]
|
assert "/300x300/" in images[pylast.SIZE_EXTRA_LARGE]
|
||||||
|
|
||||||
def test_artist_search(self) -> None:
|
def test_artist_search(self) -> None:
|
||||||
|
@ -362,12 +362,12 @@ class TestPyLastNetwork(TestPyLastWithLastFm):
|
||||||
# Assert
|
# Assert
|
||||||
assert len(images) == 5
|
assert len(images) == 5
|
||||||
|
|
||||||
self.assert_startswith(images[pylast.SIZE_SMALL], "https://")
|
assert images[pylast.SIZE_SMALL].startswith("https://")
|
||||||
self.assert_endswith(images[pylast.SIZE_SMALL], ".png")
|
assert images[pylast.SIZE_SMALL].endswith(".png")
|
||||||
assert "/34s/" in images[pylast.SIZE_SMALL]
|
assert "/34s/" in images[pylast.SIZE_SMALL]
|
||||||
|
|
||||||
self.assert_startswith(images[pylast.SIZE_EXTRA_LARGE], "https://")
|
assert images[pylast.SIZE_EXTRA_LARGE].startswith("https://")
|
||||||
self.assert_endswith(images[pylast.SIZE_EXTRA_LARGE], ".png")
|
assert images[pylast.SIZE_EXTRA_LARGE].endswith(".png")
|
||||||
assert "/300x300/" in images[pylast.SIZE_EXTRA_LARGE]
|
assert "/300x300/" in images[pylast.SIZE_EXTRA_LARGE]
|
||||||
|
|
||||||
def test_track_search(self) -> None:
|
def test_track_search(self) -> None:
|
||||||
|
@ -396,12 +396,12 @@ class TestPyLastNetwork(TestPyLastWithLastFm):
|
||||||
# Assert
|
# Assert
|
||||||
assert len(images) == 4
|
assert len(images) == 4
|
||||||
|
|
||||||
self.assert_startswith(images[pylast.SIZE_SMALL], "https://")
|
assert images[pylast.SIZE_SMALL].startswith("https://")
|
||||||
self.assert_endswith(images[pylast.SIZE_SMALL], ".png")
|
assert images[pylast.SIZE_SMALL].endswith(".png")
|
||||||
assert "/34s/" in images[pylast.SIZE_SMALL]
|
assert "/34s/" in images[pylast.SIZE_SMALL]
|
||||||
|
|
||||||
self.assert_startswith(images[pylast.SIZE_EXTRA_LARGE], "https://")
|
assert images[pylast.SIZE_EXTRA_LARGE].startswith("https://")
|
||||||
self.assert_endswith(images[pylast.SIZE_EXTRA_LARGE], ".png")
|
assert images[pylast.SIZE_EXTRA_LARGE].endswith(".png")
|
||||||
assert "/300x300/" in images[pylast.SIZE_EXTRA_LARGE]
|
assert "/300x300/" in images[pylast.SIZE_EXTRA_LARGE]
|
||||||
|
|
||||||
def test_search_get_total_result_count(self) -> None:
|
def test_search_get_total_result_count(self) -> None:
|
||||||
|
|
|
@ -32,24 +32,17 @@ def load_secrets(): # pragma: no cover
|
||||||
return doc
|
return doc
|
||||||
|
|
||||||
|
|
||||||
class PyLastTestCase:
|
|
||||||
def assert_startswith(self, s, prefix, start=None, end=None) -> None:
|
|
||||||
assert s.startswith(prefix, start, end)
|
|
||||||
|
|
||||||
def assert_endswith(self, s, suffix, start=None, end=None) -> None:
|
|
||||||
assert s.endswith(suffix, start, end)
|
|
||||||
|
|
||||||
|
|
||||||
def _no_xfail_rerun_filter(err, name, test, plugin) -> bool:
|
def _no_xfail_rerun_filter(err, name, test, plugin) -> bool:
|
||||||
for _ in test.iter_markers(name="xfail"):
|
for _ in test.iter_markers(name="xfail"):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
@flaky(max_runs=3, min_passes=1, rerun_filter=_no_xfail_rerun_filter)
|
@flaky(max_runs=3, min_passes=1, rerun_filter=_no_xfail_rerun_filter)
|
||||||
class TestPyLastWithLastFm(PyLastTestCase):
|
class TestPyLastWithLastFm:
|
||||||
secrets = None
|
secrets = None
|
||||||
|
|
||||||
def unix_timestamp(self):
|
@staticmethod
|
||||||
|
def unix_timestamp() -> int:
|
||||||
return int(time.time())
|
return int(time.time())
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -70,7 +63,8 @@ class TestPyLastWithLastFm(PyLastTestCase):
|
||||||
password_hash=password_hash,
|
password_hash=password_hash,
|
||||||
)
|
)
|
||||||
|
|
||||||
def helper_is_thing_hashable(self, thing) -> None:
|
@staticmethod
|
||||||
|
def helper_is_thing_hashable(thing) -> None:
|
||||||
# Arrange
|
# Arrange
|
||||||
things = set()
|
things = set()
|
||||||
|
|
||||||
|
@ -81,7 +75,8 @@ class TestPyLastWithLastFm(PyLastTestCase):
|
||||||
assert thing is not None
|
assert thing is not None
|
||||||
assert len(things) == 1
|
assert len(things) == 1
|
||||||
|
|
||||||
def helper_validate_results(self, a, b, c) -> None:
|
@staticmethod
|
||||||
|
def helper_validate_results(a, b, c) -> None:
|
||||||
# Assert
|
# Assert
|
||||||
assert a is not None
|
assert a is not None
|
||||||
assert b is not None
|
assert b is not None
|
||||||
|
@ -105,27 +100,31 @@ class TestPyLastWithLastFm(PyLastTestCase):
|
||||||
# Assert
|
# Assert
|
||||||
self.helper_validate_results(result1, result2, result3)
|
self.helper_validate_results(result1, result2, result3)
|
||||||
|
|
||||||
def helper_at_least_one_thing_in_top_list(self, things, expected_type) -> None:
|
@staticmethod
|
||||||
|
def helper_at_least_one_thing_in_top_list(things, expected_type) -> None:
|
||||||
# Assert
|
# Assert
|
||||||
assert len(things) > 1
|
assert len(things) > 1
|
||||||
assert isinstance(things, list)
|
assert isinstance(things, list)
|
||||||
assert isinstance(things[0], pylast.TopItem)
|
assert isinstance(things[0], pylast.TopItem)
|
||||||
assert isinstance(things[0].item, expected_type)
|
assert isinstance(things[0].item, expected_type)
|
||||||
|
|
||||||
def helper_only_one_thing_in_top_list(self, things, expected_type) -> None:
|
@staticmethod
|
||||||
|
def helper_only_one_thing_in_top_list(things, expected_type) -> None:
|
||||||
# Assert
|
# Assert
|
||||||
assert len(things) == 1
|
assert len(things) == 1
|
||||||
assert isinstance(things, list)
|
assert isinstance(things, list)
|
||||||
assert isinstance(things[0], pylast.TopItem)
|
assert isinstance(things[0], pylast.TopItem)
|
||||||
assert isinstance(things[0].item, expected_type)
|
assert isinstance(things[0].item, expected_type)
|
||||||
|
|
||||||
def helper_only_one_thing_in_list(self, things, expected_type) -> None:
|
@staticmethod
|
||||||
|
def helper_only_one_thing_in_list(things, expected_type) -> None:
|
||||||
# Assert
|
# Assert
|
||||||
assert len(things) == 1
|
assert len(things) == 1
|
||||||
assert isinstance(things, list)
|
assert isinstance(things, list)
|
||||||
assert isinstance(things[0], expected_type)
|
assert isinstance(things[0], expected_type)
|
||||||
|
|
||||||
def helper_two_different_things_in_top_list(self, things, expected_type) -> None:
|
@staticmethod
|
||||||
|
def helper_two_different_things_in_top_list(things, expected_type) -> None:
|
||||||
# Assert
|
# Assert
|
||||||
assert len(things) == 2
|
assert len(things) == 2
|
||||||
thing1 = things[0]
|
thing1 = things[0]
|
||||||
|
|
|
@ -24,7 +24,7 @@ class TestPyLastUser(TestPyLastWithLastFm):
|
||||||
representation = repr(user)
|
representation = repr(user)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
self.assert_startswith(representation, "pylast.User('RJ',")
|
assert representation.startswith("pylast.User('RJ',")
|
||||||
|
|
||||||
def test_str(self) -> None:
|
def test_str(self) -> None:
|
||||||
# Arrange
|
# Arrange
|
||||||
|
@ -345,7 +345,7 @@ class TestPyLastUser(TestPyLastWithLastFm):
|
||||||
url = user.get_image()
|
url = user.get_image()
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
self.assert_startswith(url, "https://")
|
assert url.startswith("https://")
|
||||||
|
|
||||||
def test_user_get_library(self) -> None:
|
def test_user_get_library(self) -> None:
|
||||||
# Arrange
|
# Arrange
|
||||||
|
@ -428,8 +428,8 @@ class TestPyLastUser(TestPyLastWithLastFm):
|
||||||
image = user.get_image()
|
image = user.get_image()
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
self.assert_startswith(image, "https://")
|
assert image.startswith("https://")
|
||||||
self.assert_endswith(image, ".png")
|
assert image.endswith(".png")
|
||||||
|
|
||||||
def test_get_url(self) -> None:
|
def test_get_url(self) -> None:
|
||||||
# Arrange
|
# Arrange
|
||||||
|
|
Loading…
Reference in a new issue