Merge pull request #395 from pylast/cleanup

This commit is contained in:
Hugo van Kemenade 2022-04-06 18:05:20 +03:00 committed by GitHub
commit aefa7cef1b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 329 additions and 313 deletions

View file

@ -41,7 +41,7 @@ try:
import importlib.metadata as importlib_metadata import importlib.metadata as importlib_metadata
except ImportError: except ImportError:
# Python 3.7 and lower # Python 3.7 and lower
import importlib_metadata 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"
@ -156,7 +156,7 @@ class _Network:
domain_names, domain_names,
urls, urls,
token=None, token=None,
): ) -> None:
""" """
name: the name of the network name: the name of the network
homepage: the homepage URL homepage: the homepage URL
@ -196,7 +196,7 @@ class _Network:
self.cache_backend = None self.cache_backend = None
self.proxy = None self.proxy = None
self.last_call_time = 0 self.last_call_time: float = 0.0
self.limit_rate = False self.limit_rate = False
# Load session_key and username from authentication token if provided # Load session_key and username from authentication token if provided
@ -215,8 +215,8 @@ class _Network:
sk_gen = SessionKeyGenerator(self) sk_gen = SessionKeyGenerator(self)
self.session_key = sk_gen.get_session_key(self.username, self.password_hash) self.session_key = sk_gen.get_session_key(self.username, self.password_hash)
def __str__(self): def __str__(self) -> str:
return "%s Network" % self.name return f"{self.name} Network"
def get_artist(self, artist_name): def get_artist(self, artist_name):
""" """
@ -275,10 +275,8 @@ class _Network:
if domain_language in self.domain_names: if domain_language in self.domain_names:
return self.domain_names[domain_language] return self.domain_names[domain_language]
def _get_url(self, domain, url_type): def _get_url(self, domain, url_type) -> str:
return "https://{}/{}".format( return f"https://{self._get_language_domain(domain)}/{self.urls[url_type]}"
self._get_language_domain(domain), self.urls[url_type]
)
def _get_ws_auth(self): def _get_ws_auth(self):
""" """
@ -286,7 +284,7 @@ class _Network:
""" """
return self.api_key, self.api_secret, self.session_key return self.api_key, self.api_secret, self.session_key
def _delay_call(self): def _delay_call(self) -> None:
""" """
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.
""" """
@ -299,7 +297,7 @@ class _Network:
self.last_call_time = now self.last_call_time = now
def get_top_artists(self, limit=None, cacheable=True): def get_top_artists(self, limit=None, cacheable: bool = True):
"""Returns the most played artists as a sequence of TopItem objects.""" """Returns the most played artists as a sequence of TopItem objects."""
params = {} params = {}
@ -310,7 +308,7 @@ class _Network:
return _extract_top_artists(doc, self) return _extract_top_artists(doc, self)
def get_top_tracks(self, limit=None, cacheable=True): def get_top_tracks(self, limit=None, cacheable: bool = True):
"""Returns the most played tracks as a sequence of TopItem objects.""" """Returns the most played tracks as a sequence of TopItem objects."""
params = {} params = {}
@ -329,14 +327,14 @@ class _Network:
return seq return seq
def get_top_tags(self, limit=None, cacheable=True): def get_top_tags(self, limit=None, cacheable: bool = True):
"""Returns the most used tags as a sequence of TopItem objects.""" """Returns the most used tags as a sequence of TopItem objects."""
# Last.fm has no "limit" parameter for tag.getTopTags # Last.fm has no "limit" parameter for tag.getTopTags
# so we need to get all (250) and then limit locally # so we need to get all (250) and then limit locally
doc = _Request(self, "tag.getTopTags").execute(cacheable) doc = _Request(self, "tag.getTopTags").execute(cacheable)
seq = [] seq: list[TopItem] = []
for node in doc.getElementsByTagName("tag"): for node in doc.getElementsByTagName("tag"):
if limit and len(seq) >= limit: if limit and len(seq) >= limit:
break break
@ -346,7 +344,7 @@ class _Network:
return seq return seq
def get_geo_top_artists(self, country, limit=None, cacheable=True): def get_geo_top_artists(self, country, limit=None, cacheable: bool = True):
"""Get the most popular artists on Last.fm by country. """Get the most popular artists on Last.fm by country.
Parameters: Parameters:
country (Required) : A country name, as defined by the ISO 3166-1 country (Required) : A country name, as defined by the ISO 3166-1
@ -363,7 +361,9 @@ class _Network:
return _extract_top_artists(doc, self) return _extract_top_artists(doc, self)
def get_geo_top_tracks(self, country, location=None, limit=None, cacheable=True): def get_geo_top_tracks(
self, country, location=None, limit=None, cacheable: bool = True
):
"""Get the most popular tracks on Last.fm last week by country. """Get the most popular tracks on Last.fm last week by country.
Parameters: Parameters:
country (Required) : A country name, as defined by the ISO 3166-1 country (Required) : A country name, as defined by the ISO 3166-1
@ -409,59 +409,52 @@ class _Network:
"""Returns True if web proxy is enabled.""" """Returns True if web proxy is enabled."""
return self.proxy is not None return self.proxy is not None
def enable_rate_limit(self): def enable_rate_limit(self) -> None:
"""Enables rate limiting for this network""" """Enables rate limiting for this network"""
self.limit_rate = True self.limit_rate = True
def disable_rate_limit(self): def disable_rate_limit(self) -> None:
"""Disables rate limiting for this network""" """Disables rate limiting for this network"""
self.limit_rate = False self.limit_rate = False
def is_rate_limited(self): def is_rate_limited(self) -> bool:
"""Return True if web service calls are rate limited""" """Return True if web service calls are rate limited"""
return self.limit_rate return self.limit_rate
def enable_caching(self, file_path=None): def enable_caching(self, file_path=None) -> None:
"""Enables caching request-wide for all cacheable calls. """Enables caching request-wide for all cacheable calls.
* file_path: A file path for the backend storage file. If * file_path: A file path for the backend storage file. If
None set, a temp file would probably be created, according the backend. None set, a temp file would probably be created, according the backend.
""" """
if not file_path: if not file_path:
self.cache_backend = _ShelfCacheBackend.create_shelf() self.cache_backend = _ShelfCacheBackend.create_shelf()
return return
self.cache_backend = _ShelfCacheBackend(file_path) self.cache_backend = _ShelfCacheBackend(file_path)
def disable_caching(self): def disable_caching(self) -> None:
"""Disables all caching features.""" """Disables all caching features."""
self.cache_backend = None self.cache_backend = None
def is_caching_enabled(self): def is_caching_enabled(self) -> bool:
"""Returns True if caching is enabled.""" """Returns True if caching is enabled."""
return self.cache_backend is not None
return not (self.cache_backend is None)
def _get_cache_backend(self):
return self.cache_backend
def search_for_album(self, album_name): def search_for_album(self, album_name):
"""Searches for an album by its name. Returns a AlbumSearch object. """Searches for an album by its name. Returns an AlbumSearch object.
Use get_next_page() to retrieve sequences of results.""" Use get_next_page() to retrieve sequences of results."""
return AlbumSearch(album_name, self) return AlbumSearch(album_name, self)
def search_for_artist(self, artist_name): def search_for_artist(self, artist_name):
"""Searches of an artist by its name. Returns a ArtistSearch object. """Searches for an artist by its name. Returns an ArtistSearch object.
Use get_next_page() to retrieve sequences of results.""" Use get_next_page() to retrieve sequences of results."""
return ArtistSearch(artist_name, self) return ArtistSearch(artist_name, self)
def search_for_track(self, artist_name, track_name): def search_for_track(self, artist_name, track_name):
"""Searches of a track by its name and its artist. Set artist to an """Searches for a track by its name and its artist. Set artist to an
empty string if not available. empty string if not available.
Returns a TrackSearch object. Returns a TrackSearch object.
Use get_next_page() to retrieve sequences of results.""" Use get_next_page() to retrieve sequences of results."""
@ -505,7 +498,7 @@ class _Network:
track_number=None, track_number=None,
mbid=None, mbid=None,
context=None, context=None,
): ) -> None:
""" """
Used to notify Last.fm that a user has started listening to a track. Used to notify Last.fm that a user has started listening to a track.
@ -592,7 +585,7 @@ class _Network:
) )
) )
def scrobble_many(self, tracks): def scrobble_many(self, tracks) -> None:
""" """
Used to scrobble a batch of tracks at once. The parameter tracks is a Used to scrobble a batch of tracks at once. The parameter tracks is a
sequence of dicts per track containing the keyword arguments as if sequence of dicts per track containing the keyword arguments as if
@ -608,8 +601,8 @@ class _Network:
params = {} params = {}
for i in range(len(tracks_to_scrobble)): for i in range(len(tracks_to_scrobble)):
params["artist[%d]" % i] = tracks_to_scrobble[i]["artist"] params[f"artist[{i}]"] = tracks_to_scrobble[i]["artist"]
params["track[%d]" % i] = tracks_to_scrobble[i]["title"] params[f"track[{i}]"] = tracks_to_scrobble[i]["title"]
additional_args = ( additional_args = (
"timestamp", "timestamp",
@ -635,7 +628,7 @@ class _Network:
else: else:
maps_to = arg maps_to = arg
params["%s[%d]" % (maps_to, i)] = tracks_to_scrobble[i][arg] params[f"{maps_to}[{i}]"] = tracks_to_scrobble[i][arg]
_Request(self, "track.scrobble", params).execute() _Request(self, "track.scrobble", params).execute()
@ -667,13 +660,13 @@ class LastFMNetwork(_Network):
def __init__( def __init__(
self, self,
api_key="", api_key: str = "",
api_secret="", api_secret: str = "",
session_key="", session_key: str = "",
username="", username: str = "",
password_hash="", password_hash: str = "",
token="", token: str = "",
): ) -> None:
super().__init__( super().__init__(
name="Last.fm", name="Last.fm",
homepage="https://www.last.fm", homepage="https://www.last.fm",
@ -708,17 +701,15 @@ class LastFMNetwork(_Network):
}, },
) )
def __repr__(self): def __repr__(self) -> str:
return "pylast.LastFMNetwork(%s)" % ( return (
", ".join( "pylast.LastFMNetwork("
( f"'{self.api_key}', "
"'%s'" % self.api_key, f"'{self.api_secret}', "
"'%s'" % self.api_secret, f"'{self.session_key}', "
"'%s'" % self.session_key, f"'{self.username}', "
"'%s'" % self.username, f"'{self.password_hash}'"
"'%s'" % self.password_hash, ")"
)
)
) )
@ -738,8 +729,13 @@ class LibreFMNetwork(_Network):
""" """
def __init__( def __init__(
self, api_key="", api_secret="", session_key="", username="", password_hash="" self,
): api_key: str = "",
api_secret: str = "",
session_key: str = "",
username: str = "",
password_hash: str = "",
) -> None:
super().__init__( super().__init__(
name="Libre.fm", name="Libre.fm",
@ -774,31 +770,29 @@ class LibreFMNetwork(_Network):
}, },
) )
def __repr__(self): def __repr__(self) -> str:
return "pylast.LibreFMNetwork(%s)" % ( return (
", ".join( "pylast.LibreFMNetwork("
( f"'{self.api_key}', "
"'%s'" % self.api_key, f"'{self.api_secret}', "
"'%s'" % self.api_secret, f"'{self.session_key}', "
"'%s'" % self.session_key, f"'{self.username}', "
"'%s'" % self.username, f"'{self.password_hash}'"
"'%s'" % self.password_hash, ")"
)
)
) )
class _ShelfCacheBackend: class _ShelfCacheBackend:
"""Used as a backend for caching cacheable requests.""" """Used as a backend for caching cacheable requests."""
def __init__(self, file_path=None, flag=None): def __init__(self, file_path=None, flag=None) -> None:
if flag is not None: if flag is not None:
self.shelf = shelve.open(file_path, flag=flag) self.shelf = shelve.open(file_path, flag=flag)
else: else:
self.shelf = shelve.open(file_path) self.shelf = shelve.open(file_path)
self.cache_keys = set(self.shelf.keys()) self.cache_keys = set(self.shelf.keys())
def __contains__(self, key): def __contains__(self, key) -> bool:
return key in self.cache_keys return key in self.cache_keys
def __iter__(self): def __iter__(self):
@ -807,7 +801,7 @@ class _ShelfCacheBackend:
def get_xml(self, key): def get_xml(self, key):
return self.shelf[key] return self.shelf[key]
def set_xml(self, key, xml_string): def set_xml(self, key, xml_string) -> None:
self.cache_keys.add(key) self.cache_keys.add(key)
self.shelf[key] = xml_string self.shelf[key] = xml_string
@ -821,7 +815,7 @@ class _ShelfCacheBackend:
class _Request: class _Request:
"""Representing an abstract web service operation.""" """Representing an abstract web service operation."""
def __init__(self, network, method_name, params=None): def __init__(self, network, method_name, params=None) -> None:
logger.info(method_name) logger.info(method_name)
if params is None: if params is None:
@ -839,13 +833,13 @@ class _Request:
self.params["method"] = method_name self.params["method"] = method_name
if network.is_caching_enabled(): if network.is_caching_enabled():
self.cache = network._get_cache_backend() self.cache = network.cache_backend
if self.session_key: if self.session_key:
self.params["sk"] = self.session_key self.params["sk"] = self.session_key
self.sign_it() self.sign_it()
def sign_it(self): def sign_it(self) -> None:
"""Sign this request.""" """Sign this request."""
if "api_sig" not in self.params.keys(): if "api_sig" not in self.params.keys():
@ -961,13 +955,13 @@ class _Request:
except Exception as e: except Exception as e:
raise MalformedResponseError(self.network, e) from e raise MalformedResponseError(self.network, e) from e
e = doc.getElementsByTagName("lfm")[0] element = doc.getElementsByTagName("lfm")[0]
logger.debug(doc.toprettyxml()) logger.debug(doc.toprettyxml())
if e.getAttribute("status") != "ok": if element.getAttribute("status") != "ok":
e = doc.getElementsByTagName("error")[0] element = doc.getElementsByTagName("error")[0]
status = e.getAttribute("code") status = element.getAttribute("code")
details = e.firstChild.data.strip() details = element.firstChild.data.strip()
raise WSError(self.network, status, details) raise WSError(self.network, status, details)
@ -989,13 +983,13 @@ class SessionKeyGenerator:
A session key's lifetime is infinite, unless the user revokes the rights A session key's lifetime is infinite, unless the user revokes the rights
of the given API Key. of the given API Key.
If you create a Network object with just a API_KEY and API_SECRET and a If you create a Network object with just an API_KEY and API_SECRET and a
username and a password_hash, a SESSION_KEY will be automatically generated username and a password_hash, a SESSION_KEY will be automatically generated
for that network and stored in it so you don't have to do this manually, for that network and stored in it so you don't have to do this manually,
unless you want to. unless you want to.
""" """
def __init__(self, network): def __init__(self, network) -> None:
self.network = network self.network = network
self.web_auth_tokens = {} self.web_auth_tokens = {}
@ -1025,15 +1019,17 @@ class SessionKeyGenerator:
token = self._get_web_auth_token() token = self._get_web_auth_token()
url = "{homepage}/api/auth/?api_key={api}&token={token}".format( url = (
homepage=self.network.homepage, api=self.network.api_key, token=token f"{self.network.homepage}/api/auth/"
f"?api_key={self.network.api_key}"
f"&token={token}"
) )
self.web_auth_tokens[url] = token self.web_auth_tokens[url] = token
return url return url
def get_web_auth_session_key_username(self, url, token=""): def get_web_auth_session_key_username(self, url, token: str = ""):
""" """
Retrieves the session key/username of a web authorization process by its URL. Retrieves the session key/username of a web authorization process by its URL.
""" """
@ -1053,7 +1049,7 @@ class SessionKeyGenerator:
username = doc.getElementsByTagName("name")[0].firstChild.data username = doc.getElementsByTagName("name")[0].firstChild.data
return session_key, username return session_key, username
def get_web_auth_session_key(self, url, token=""): def get_web_auth_session_key(self, url, token: str = ""):
""" """
Retrieves the session key of a web authorization process by its URL. Retrieves the session key of a web authorization process by its URL.
""" """
@ -1105,11 +1101,11 @@ class _BaseObject:
network = None network = None
def __init__(self, network, ws_prefix): def __init__(self, network, ws_prefix) -> None:
self.network = network self.network = network
self.ws_prefix = ws_prefix self.ws_prefix = ws_prefix
def _request(self, method_name, cacheable=False, params=None): def _request(self, method_name, cacheable: bool = False, params=None):
if not params: if not params:
params = self._get_params() params = self._get_params()
@ -1140,7 +1136,12 @@ class _BaseObject:
return first_child.wholeText.strip() return first_child.wholeText.strip()
def _get_things( def _get_things(
self, method, thing_type, params=None, cacheable=True, stream=False self,
method,
thing_type,
params=None,
cacheable: bool = True,
stream: bool = False,
): ):
"""Returns a list of the most played thing_types by this thing.""" """Returns a list of the most played thing_types by this thing."""
@ -1205,7 +1206,7 @@ class _BaseObject:
class _Chartable(_BaseObject): class _Chartable(_BaseObject):
"""Common functions for classes with charts.""" """Common functions for classes with charts."""
def __init__(self, network, ws_prefix): def __init__(self, network, ws_prefix) -> None:
super().__init__(network=network, ws_prefix=ws_prefix) super().__init__(network=network, ws_prefix=ws_prefix)
def get_weekly_chart_dates(self): def get_weekly_chart_dates(self):
@ -1276,10 +1277,10 @@ class _Chartable(_BaseObject):
class _Taggable(_BaseObject): class _Taggable(_BaseObject):
"""Common functions for classes with tags.""" """Common functions for classes with tags."""
def __init__(self, network, ws_prefix): def __init__(self, network, ws_prefix) -> None:
super().__init__(network=network, ws_prefix=ws_prefix) super().__init__(network=network, ws_prefix=ws_prefix)
def add_tags(self, tags): def add_tags(self, tags) -> None:
"""Adds one or several tags. """Adds one or several tags.
* tags: A sequence of tag names or Tag objects. * tags: A sequence of tag names or Tag objects.
""" """
@ -1287,7 +1288,7 @@ class _Taggable(_BaseObject):
for tag in tags: for tag in tags:
self.add_tag(tag) self.add_tag(tag)
def add_tag(self, tag): def add_tag(self, tag) -> None:
"""Adds one tag. """Adds one tag.
* tag: a tag name or a Tag object. * tag: a tag name or a Tag object.
""" """
@ -1300,7 +1301,7 @@ class _Taggable(_BaseObject):
self._request(self.ws_prefix + ".addTags", False, params) self._request(self.ws_prefix + ".addTags", False, params)
def remove_tag(self, tag): def remove_tag(self, tag) -> None:
"""Remove a user's tag from this object.""" """Remove a user's tag from this object."""
if isinstance(tag, Tag): if isinstance(tag, Tag):
@ -1325,7 +1326,7 @@ class _Taggable(_BaseObject):
return tags return tags
def remove_tags(self, tags): def remove_tags(self, tags) -> None:
"""Removes one or several tags from this object. """Removes one or several tags from this object.
* tags: a sequence of tag names or Tag objects. * tags: a sequence of tag names or Tag objects.
""" """
@ -1333,12 +1334,12 @@ class _Taggable(_BaseObject):
for tag in tags: for tag in tags:
self.remove_tag(tag) self.remove_tag(tag)
def clear_tags(self): def clear_tags(self) -> None:
"""Clears all the user-set tags.""" """Clears all the user-set tags."""
self.remove_tags(*(self.get_tags())) self.remove_tags(*(self.get_tags()))
def set_tags(self, tags): def set_tags(self, tags) -> None:
"""Sets this object's tags to only those tags. """Sets this object's tags to only those tags.
* tags: a sequence of tag names or Tag objects. * tags: a sequence of tag names or Tag objects.
""" """
@ -1401,13 +1402,13 @@ class PyLastError(Exception):
class WSError(PyLastError): class WSError(PyLastError):
"""Exception related to the Network web service""" """Exception related to the Network web service"""
def __init__(self, network, status, details): def __init__(self, network, status, details) -> None:
self.status = status self.status = status
self.details = details self.details = details
self.network = network self.network = network
@_string_output @_string_output
def __str__(self): def __str__(self) -> str:
return self.details return self.details
def get_id(self): def get_id(self):
@ -1445,25 +1446,26 @@ class WSError(PyLastError):
class MalformedResponseError(PyLastError): class MalformedResponseError(PyLastError):
"""Exception conveying a malformed response from the music network.""" """Exception conveying a malformed response from the music network."""
def __init__(self, network, underlying_error): def __init__(self, network, underlying_error) -> None:
self.network = network self.network = network
self.underlying_error = underlying_error self.underlying_error = underlying_error
def __str__(self): def __str__(self) -> str:
return "Malformed response from {}. Underlying error: {}".format( return (
self.network.name, str(self.underlying_error) f"Malformed response from {self.network.name}. "
f"Underlying error: {self.underlying_error}"
) )
class NetworkError(PyLastError): class NetworkError(PyLastError):
"""Exception conveying a problem in sending a request to Last.fm""" """Exception conveying a problem in sending a request to Last.fm"""
def __init__(self, network, underlying_error): def __init__(self, network, underlying_error) -> None:
self.network = network self.network = network
self.underlying_error = underlying_error self.underlying_error = underlying_error
def __str__(self): def __str__(self) -> str:
return "NetworkError: %s" % str(self.underlying_error) return f"NetworkError: {self.underlying_error}"
class _Opus(_Taggable): class _Opus(_Taggable):
@ -1475,7 +1477,9 @@ class _Opus(_Taggable):
__hash__ = _BaseObject.__hash__ __hash__ = _BaseObject.__hash__
def __init__(self, artist, title, network, ws_prefix, username=None, info=None): def __init__(
self, artist, title, network, ws_prefix, username=None, info=None
) -> None:
""" """
Create an opus instance. Create an opus instance.
# Parameters: # Parameters:
@ -1500,17 +1504,15 @@ class _Opus(_Taggable):
) # Default to current user ) # Default to current user
self.info = info self.info = info
def __repr__(self): def __repr__(self) -> str:
return "pylast.{}({}, {}, {})".format( return (
self.ws_prefix.title(), f"pylast.{self.ws_prefix.title()}"
repr(self.artist.name), f"({repr(self.artist.name)}, {repr(self.title)}, {repr(self.network)})"
repr(self.title),
repr(self.network),
) )
@_string_output @_string_output
def __str__(self): def __str__(self) -> str:
return _unicode("%s - %s") % (self.get_artist().get_name(), self.get_title()) return f"{self.get_artist().get_name()} - {self.get_title()}"
def __eq__(self, other): def __eq__(self, other):
if type(self) != type(other): if type(self) != type(other):
@ -1550,7 +1552,7 @@ class _Opus(_Taggable):
) )
return self.info["image"][size] return self.info["image"][size]
def get_title(self, properly_capitalized=False): def get_title(self, properly_capitalized: bool = False):
"""Returns the artist or track title.""" """Returns the artist or track title."""
if properly_capitalized: if properly_capitalized:
self.title = _extract( self.title = _extract(
@ -1559,7 +1561,7 @@ class _Opus(_Taggable):
return self.title return self.title
def get_name(self, properly_capitalized=False): def get_name(self, properly_capitalized: bool = False):
"""Returns the album or track title (alias to get_title()).""" """Returns the album or track title (alias to get_title())."""
return self.get_title(properly_capitalized) return self.get_title(properly_capitalized)
@ -1620,7 +1622,7 @@ class Album(_Opus):
__hash__ = _Opus.__hash__ __hash__ = _Opus.__hash__
def __init__(self, artist, title, network, username=None, info=None): def __init__(self, artist, title, network, username=None, info=None) -> None:
super().__init__(artist, title, network, "album", username, info) super().__init__(artist, title, network, "album", username, info)
def get_tracks(self): def get_tracks(self):
@ -1665,7 +1667,7 @@ class Artist(_Taggable):
__hash__ = _BaseObject.__hash__ __hash__ = _BaseObject.__hash__
def __init__(self, name, network, username=None, info=None): def __init__(self, name, network, username=None, info=None) -> None:
"""Create an artist object. """Create an artist object.
# Parameters: # Parameters:
* name str: The artist's name. * name str: The artist's name.
@ -1680,14 +1682,14 @@ class Artist(_Taggable):
self.username = username self.username = username
self.info = info self.info = info
def __repr__(self): def __repr__(self) -> str:
return f"pylast.Artist({repr(self.get_name())}, {repr(self.network)})" return f"pylast.Artist({repr(self.get_name())}, {repr(self.network)})"
def __unicode__(self): def __unicode__(self):
return str(self.get_name()) return str(self.get_name())
@_string_output @_string_output
def __str__(self): def __str__(self) -> str:
return self.__unicode__() return self.__unicode__()
def __eq__(self, other): def __eq__(self, other):
@ -1702,7 +1704,7 @@ class Artist(_Taggable):
def _get_params(self): def _get_params(self):
return {self.ws_prefix: self.get_name()} return {self.ws_prefix: self.get_name()}
def get_name(self, properly_capitalized=False): def get_name(self, properly_capitalized: bool = False):
"""Returns the name of the artist. """Returns the name of the artist.
If properly_capitalized was asserted then the name would be downloaded If properly_capitalized was asserted then the name would be downloaded
overwriting the given one.""" overwriting the given one."""
@ -1809,7 +1811,7 @@ class Artist(_Taggable):
return artists return artists
def get_top_albums(self, limit=None, cacheable=True, stream=False): def get_top_albums(self, limit=None, cacheable: bool = True, stream: bool = False):
"""Returns a list of the top albums.""" """Returns a list of the top albums."""
params = self._get_params() params = self._get_params()
if limit: if limit:
@ -1817,7 +1819,7 @@ class Artist(_Taggable):
return self._get_things("getTopAlbums", Album, params, cacheable, stream=stream) return self._get_things("getTopAlbums", Album, params, cacheable, stream=stream)
def get_top_tracks(self, limit=None, cacheable=True, stream=False): def get_top_tracks(self, limit=None, cacheable: bool = True, stream: bool = False):
"""Returns a list of the most played Tracks by this artist.""" """Returns a list of the most played Tracks by this artist."""
params = self._get_params() params = self._get_params()
if limit: if limit:
@ -1855,16 +1857,16 @@ class Country(_BaseObject):
__hash__ = _BaseObject.__hash__ __hash__ = _BaseObject.__hash__
def __init__(self, name, network): def __init__(self, name, network) -> None:
super().__init__(network=network, ws_prefix="geo") super().__init__(network=network, ws_prefix="geo")
self.name = name self.name = name
def __repr__(self): def __repr__(self) -> str:
return f"pylast.Country({repr(self.name)}, {repr(self.network)})" return f"pylast.Country({repr(self.name)}, {repr(self.network)})"
@_string_output @_string_output
def __str__(self): def __str__(self) -> str:
return self.get_name() return self.get_name()
def __eq__(self, other): def __eq__(self, other):
@ -1881,7 +1883,7 @@ class Country(_BaseObject):
return self.name return self.name
def get_top_artists(self, limit=None, cacheable=True): def get_top_artists(self, limit=None, cacheable: bool = True):
"""Returns a sequence of the most played artists.""" """Returns a sequence of the most played artists."""
params = self._get_params() params = self._get_params()
if limit: if limit:
@ -1891,7 +1893,7 @@ class Country(_BaseObject):
return _extract_top_artists(doc, self) return _extract_top_artists(doc, self)
def get_top_tracks(self, limit=None, cacheable=True, stream=False): def get_top_tracks(self, limit=None, cacheable: bool = True, stream: bool = False):
"""Returns a sequence of the most played tracks""" """Returns a sequence of the most played tracks"""
params = self._get_params() params = self._get_params()
if limit: if limit:
@ -1930,7 +1932,7 @@ class Library(_BaseObject):
__hash__ = _BaseObject.__hash__ __hash__ = _BaseObject.__hash__
def __init__(self, user, network): def __init__(self, user, network) -> None:
super().__init__(network=network, ws_prefix="library") super().__init__(network=network, ws_prefix="library")
if isinstance(user, User): if isinstance(user, User):
@ -1938,11 +1940,11 @@ class Library(_BaseObject):
else: else:
self.user = User(user, self.network) self.user = User(user, self.network)
def __repr__(self): def __repr__(self) -> str:
return f"pylast.Library({repr(self.user)}, {repr(self.network)})" return f"pylast.Library({repr(self.user)}, {repr(self.network)})"
@_string_output @_string_output
def __str__(self): def __str__(self) -> str:
return repr(self.get_user()) + "'s Library" return repr(self.get_user()) + "'s Library"
def _get_params(self): def _get_params(self):
@ -1952,7 +1954,9 @@ class Library(_BaseObject):
"""Returns the user who owns this library.""" """Returns the user who owns this library."""
return self.user return self.user
def get_artists(self, limit=50, cacheable=True, stream=False): def get_artists(
self, limit: int = 50, cacheable: bool = True, stream: bool = False
):
""" """
Returns a sequence of Album objects Returns a sequence of Album objects
if limit==None it will return all (may take a while) if limit==None it will return all (may take a while)
@ -1979,16 +1983,16 @@ class Tag(_Chartable):
__hash__ = _BaseObject.__hash__ __hash__ = _BaseObject.__hash__
def __init__(self, name, network): def __init__(self, name, network) -> None:
super().__init__(network=network, ws_prefix="tag") super().__init__(network=network, ws_prefix="tag")
self.name = name self.name = name
def __repr__(self): def __repr__(self) -> str:
return f"pylast.Tag({repr(self.name)}, {repr(self.network)})" return f"pylast.Tag({repr(self.name)}, {repr(self.network)})"
@_string_output @_string_output
def __str__(self): def __str__(self) -> str:
return self.get_name() return self.get_name()
def __eq__(self, other): def __eq__(self, other):
@ -2000,7 +2004,7 @@ class Tag(_Chartable):
def _get_params(self): def _get_params(self):
return {self.ws_prefix: self.get_name()} return {self.ws_prefix: self.get_name()}
def get_name(self, properly_capitalized=False): def get_name(self, properly_capitalized: bool = False):
"""Returns the name of the tag.""" """Returns the name of the tag."""
if properly_capitalized: if properly_capitalized:
@ -2010,7 +2014,7 @@ class Tag(_Chartable):
return self.name return self.name
def get_top_albums(self, limit=None, cacheable=True): def get_top_albums(self, limit=None, cacheable: bool = True):
"""Returns a list of the top albums.""" """Returns a list of the top albums."""
params = self._get_params() params = self._get_params()
if limit: if limit:
@ -2020,7 +2024,7 @@ class Tag(_Chartable):
return _extract_top_albums(doc, self.network) return _extract_top_albums(doc, self.network)
def get_top_tracks(self, limit=None, cacheable=True, stream=False): def get_top_tracks(self, limit=None, cacheable: bool = True, stream: bool = False):
"""Returns a list of the most played Tracks for this tag.""" """Returns a list of the most played Tracks for this tag."""
params = self._get_params() params = self._get_params()
if limit: if limit:
@ -2028,7 +2032,7 @@ class Tag(_Chartable):
return self._get_things("getTopTracks", Track, params, cacheable, stream=stream) return self._get_things("getTopTracks", Track, params, cacheable, stream=stream)
def get_top_artists(self, limit=None, cacheable=True): def get_top_artists(self, limit=None, cacheable: bool = True):
"""Returns a sequence of the most played artists.""" """Returns a sequence of the most played artists."""
params = self._get_params() params = self._get_params()
@ -2066,7 +2070,7 @@ class Track(_Opus):
__hash__ = _Opus.__hash__ __hash__ = _Opus.__hash__
def __init__(self, artist, title, network, username=None, info=None): def __init__(self, artist, title, network, username=None, info=None) -> None:
super().__init__(artist, title, network, "track", username, info) super().__init__(artist, title, network, "track", username, info)
def get_correction(self): def get_correction(self):
@ -2109,12 +2113,12 @@ class Track(_Opus):
node = doc.getElementsByTagName("album")[0] node = doc.getElementsByTagName("album")[0]
return Album(_extract(node, "artist"), _extract(node, "title"), self.network) return Album(_extract(node, "artist"), _extract(node, "title"), self.network)
def love(self): def love(self) -> None:
"""Adds the track to the user's loved tracks.""" """Adds the track to the user's loved tracks."""
self._request(self.ws_prefix + ".love") self._request(self.ws_prefix + ".love")
def unlove(self): def unlove(self) -> None:
"""Remove the track to the user's loved tracks.""" """Remove the track to the user's loved tracks."""
self._request(self.ws_prefix + ".unlove") self._request(self.ws_prefix + ".unlove")
@ -2175,16 +2179,16 @@ class User(_Chartable):
__hash__ = _BaseObject.__hash__ __hash__ = _BaseObject.__hash__
def __init__(self, user_name, network): def __init__(self, user_name, network) -> None:
super().__init__(network=network, ws_prefix="user") super().__init__(network=network, ws_prefix="user")
self.name = user_name self.name = user_name
def __repr__(self): def __repr__(self) -> str:
return f"pylast.User({repr(self.name)}, {repr(self.network)})" return f"pylast.User({repr(self.name)}, {repr(self.network)})"
@_string_output @_string_output
def __str__(self): def __str__(self) -> str:
return self.get_name() return self.get_name()
def __eq__(self, other): def __eq__(self, other):
@ -2209,7 +2213,7 @@ class User(_Chartable):
Track(track_artist, title, self.network), album, date, timestamp Track(track_artist, title, self.network), album, date, timestamp
) )
def get_name(self, properly_capitalized=False): def get_name(self, properly_capitalized: bool = False):
"""Returns the user name.""" """Returns the user name."""
if properly_capitalized: if properly_capitalized:
@ -2219,7 +2223,9 @@ class User(_Chartable):
return self.name return self.name
def get_friends(self, limit=50, cacheable=False, stream=False): def get_friends(
self, limit: int = 50, cacheable: bool = False, stream: bool = False
):
"""Returns a list of the user's friends.""" """Returns a list of the user's friends."""
def _get_friends(): def _get_friends():
@ -2230,7 +2236,9 @@ class User(_Chartable):
return _get_friends() if stream else list(_get_friends()) return _get_friends() if stream else list(_get_friends())
def get_loved_tracks(self, limit=50, cacheable=True, stream=False): def get_loved_tracks(
self, limit: int = 50, cacheable: bool = True, stream: bool = False
):
""" """
Returns this user's loved track as a sequence of LovedTrack objects in Returns this user's loved track as a sequence of LovedTrack objects in
reverse order of their timestamp, all the way back to the first track. reverse order of their timestamp, all the way back to the first track.
@ -2295,12 +2303,12 @@ class User(_Chartable):
def get_recent_tracks( def get_recent_tracks(
self, self,
limit=10, limit: int = 10,
cacheable=True, cacheable: bool = True,
time_from=None, time_from=None,
time_to=None, time_to=None,
stream=False, stream: bool = False,
now_playing=False, now_playing: bool = False,
): ):
""" """
Returns this user's played track as a sequence of PlayedTrack objects Returns this user's played track as a sequence of PlayedTrack objects
@ -2390,7 +2398,7 @@ class User(_Chartable):
return int(doc.getElementsByTagName("registered")[0].getAttribute("unixtime")) return int(doc.getElementsByTagName("registered")[0].getAttribute("unixtime"))
def get_tagged_albums(self, tag, limit=None, cacheable=True): def get_tagged_albums(self, tag, limit=None, cacheable: bool = True):
"""Returns the albums tagged by a user.""" """Returns the albums tagged by a user."""
params = self._get_params() params = self._get_params()
@ -2412,7 +2420,7 @@ class User(_Chartable):
doc = self._request(self.ws_prefix + ".getpersonaltags", True, params) doc = self._request(self.ws_prefix + ".getpersonaltags", True, params)
return _extract_artists(doc, self.network) return _extract_artists(doc, self.network)
def get_tagged_tracks(self, tag, limit=None, cacheable=True): def get_tagged_tracks(self, tag, limit=None, cacheable: bool = True):
"""Returns the tracks tagged by a user.""" """Returns the tracks tagged by a user."""
params = self._get_params() params = self._get_params()
@ -2423,7 +2431,7 @@ class User(_Chartable):
doc = self._request(self.ws_prefix + ".getpersonaltags", cacheable, params) doc = self._request(self.ws_prefix + ".getpersonaltags", cacheable, params)
return _extract_tracks(doc, self.network) return _extract_tracks(doc, self.network)
def get_top_albums(self, period=PERIOD_OVERALL, limit=None, cacheable=True): def get_top_albums(self, period=PERIOD_OVERALL, limit=None, cacheable: bool = True):
"""Returns the top albums played by a user. """Returns the top albums played by a user.
* period: The period of time. Possible values: * period: The period of time. Possible values:
o PERIOD_OVERALL o PERIOD_OVERALL
@ -2463,7 +2471,7 @@ class User(_Chartable):
return _extract_top_artists(doc, self.network) return _extract_top_artists(doc, self.network)
def get_top_tags(self, limit=None, cacheable=True): def get_top_tags(self, limit=None, cacheable: bool = True):
""" """
Returns a sequence of the top tags used by this user with their counts Returns a sequence of the top tags used by this user with their counts
as TopItem objects. as TopItem objects.
@ -2488,7 +2496,11 @@ class User(_Chartable):
return seq return seq
def get_top_tracks( def get_top_tracks(
self, period=PERIOD_OVERALL, limit=None, cacheable=True, stream=False self,
period=PERIOD_OVERALL,
limit=None,
cacheable: bool = True,
stream: bool = False,
): ):
"""Returns the top tracks played by a user. """Returns the top tracks played by a user.
* period: The period of time. Possible values: * period: The period of time. Possible values:
@ -2506,7 +2518,9 @@ class User(_Chartable):
return self._get_things("getTopTracks", Track, params, cacheable, stream=stream) return self._get_things("getTopTracks", Track, params, cacheable, stream=stream)
def get_track_scrobbles(self, artist, track, cacheable=False, stream=False): def get_track_scrobbles(
self, artist, track, cacheable: bool = False, stream: bool = False
):
""" """
Get a list of this user's scrobbles of this artist's track, Get a list of this user's scrobbles of this artist's track,
including scrobble time. including scrobble time.
@ -2570,13 +2584,13 @@ class User(_Chartable):
class AuthenticatedUser(User): class AuthenticatedUser(User):
def __init__(self, network): def __init__(self, network) -> None:
super().__init__(user_name=network.username, network=network) super().__init__(user_name=network.username, network=network)
def _get_params(self): def _get_params(self):
return {"user": self.get_name()} return {"user": self.get_name()}
def get_name(self, properly_capitalized=False): def get_name(self, properly_capitalized: bool = False):
"""Returns the name of the authenticated user.""" """Returns the name of the authenticated user."""
return super().get_name(properly_capitalized=properly_capitalized) return super().get_name(properly_capitalized=properly_capitalized)
@ -2584,7 +2598,7 @@ class AuthenticatedUser(User):
class _Search(_BaseObject): class _Search(_BaseObject):
"""An abstract class. Use one of its derivatives.""" """An abstract class. Use one of its derivatives."""
def __init__(self, ws_prefix, search_terms, network): def __init__(self, ws_prefix, search_terms, network) -> None:
super().__init__(network, ws_prefix) super().__init__(network, ws_prefix)
self._ws_prefix = ws_prefix self._ws_prefix = ws_prefix
@ -2624,7 +2638,7 @@ class _Search(_BaseObject):
class AlbumSearch(_Search): class AlbumSearch(_Search):
"""Search for an album by name.""" """Search for an album by name."""
def __init__(self, album_name, network): def __init__(self, album_name, network) -> None:
super().__init__( super().__init__(
ws_prefix="album", search_terms={"album": album_name}, network=network ws_prefix="album", search_terms={"album": album_name}, network=network
) )
@ -2651,7 +2665,7 @@ class AlbumSearch(_Search):
class ArtistSearch(_Search): class ArtistSearch(_Search):
"""Search for an artist by artist name.""" """Search for an artist by artist name."""
def __init__(self, artist_name, network): def __init__(self, artist_name, network) -> None:
super().__init__( super().__init__(
ws_prefix="artist", search_terms={"artist": artist_name}, network=network ws_prefix="artist", search_terms={"artist": artist_name}, network=network
) )
@ -2680,7 +2694,7 @@ class TrackSearch(_Search):
down by specifying the artist name, set it to empty string. down by specifying the artist name, set it to empty string.
""" """
def __init__(self, artist_name, track_title, network): def __init__(self, artist_name, track_title, network) -> None:
super().__init__( super().__init__(
ws_prefix="track", ws_prefix="track",
search_terms={"track": track_title, "artist": artist_name}, search_terms={"track": track_title, "artist": artist_name},
@ -2732,7 +2746,9 @@ def cleanup_nodes(doc):
return doc return doc
def _collect_nodes(limit, sender, method_name, cacheable, params=None, stream=False): def _collect_nodes(
limit, sender, method_name, cacheable, params=None, stream: bool = False
):
""" """
Returns a sequence of dom.Node objects about as close to limit as possible Returns a sequence of dom.Node objects about as close to limit as possible
""" """
@ -2787,7 +2803,7 @@ def _collect_nodes(limit, sender, method_name, cacheable, params=None, stream=Fa
return _stream_collect_nodes() if stream else list(_stream_collect_nodes()) return _stream_collect_nodes() if stream else list(_stream_collect_nodes())
def _extract(node, name, index=0): def _extract(node, name, index: int = 0):
"""Extracts a value from the xml string""" """Extracts a value from the xml string"""
nodes = node.getElementsByTagName(name) nodes = node.getElementsByTagName(name)
@ -2888,7 +2904,7 @@ def _number(string):
def _unescape_htmlentity(string): def _unescape_htmlentity(string):
mapping = html.entities.name2codepoint mapping = html.entities.name2codepoint
for key in mapping: for key in mapping:
string = string.replace("&%s;" % key, chr(mapping[key])) string = string.replace(f"&{key};", chr(mapping[key]))
return string return string

View file

@ -8,7 +8,7 @@ from .test_pylast import TestPyLastWithLastFm
class TestPyLastAlbum(TestPyLastWithLastFm): class TestPyLastAlbum(TestPyLastWithLastFm):
def test_album_tags_are_topitems(self): def test_album_tags_are_topitems(self) -> None:
# Arrange # Arrange
album = self.network.get_album("Test Artist", "Test Album") album = self.network.get_album("Test Artist", "Test Album")
@ -19,14 +19,14 @@ class TestPyLastAlbum(TestPyLastWithLastFm):
assert len(tags) > 0 assert len(tags) > 0
assert isinstance(tags[0], pylast.TopItem) assert isinstance(tags[0], pylast.TopItem)
def test_album_is_hashable(self): def test_album_is_hashable(self) -> None:
# Arrange # Arrange
album = self.network.get_album("Test Artist", "Test Album") album = self.network.get_album("Test Artist", "Test Album")
# Act/Assert # Act/Assert
self.helper_is_thing_hashable(album) self.helper_is_thing_hashable(album)
def test_album_in_recent_tracks(self): def test_album_in_recent_tracks(self) -> None:
# Arrange # Arrange
lastfm_user = self.network.get_user(self.username) lastfm_user = self.network.get_user(self.username)
@ -37,7 +37,7 @@ class TestPyLastAlbum(TestPyLastWithLastFm):
# Assert # Assert
assert hasattr(track, "album") assert hasattr(track, "album")
def test_album_wiki_content(self): def test_album_wiki_content(self) -> None:
# Arrange # Arrange
album = pylast.Album("Test Artist", "Test Album", self.network) album = pylast.Album("Test Artist", "Test Album", self.network)
@ -48,7 +48,7 @@ class TestPyLastAlbum(TestPyLastWithLastFm):
assert wiki is not None assert wiki is not None
assert len(wiki) >= 1 assert len(wiki) >= 1
def test_album_wiki_published_date(self): def test_album_wiki_published_date(self) -> None:
# Arrange # Arrange
album = pylast.Album("Test Artist", "Test Album", self.network) album = pylast.Album("Test Artist", "Test Album", self.network)
@ -59,7 +59,7 @@ class TestPyLastAlbum(TestPyLastWithLastFm):
assert wiki is not None assert wiki is not None
assert len(wiki) >= 1 assert len(wiki) >= 1
def test_album_wiki_summary(self): def test_album_wiki_summary(self) -> None:
# Arrange # Arrange
album = pylast.Album("Test Artist", "Test Album", self.network) album = pylast.Album("Test Artist", "Test Album", self.network)
@ -70,7 +70,7 @@ class TestPyLastAlbum(TestPyLastWithLastFm):
assert wiki is not None assert wiki is not None
assert len(wiki) >= 1 assert len(wiki) >= 1
def test_album_eq_none_is_false(self): def test_album_eq_none_is_false(self) -> None:
# Arrange # Arrange
album1 = None album1 = None
album2 = pylast.Album("Test Artist", "Test Album", self.network) album2 = pylast.Album("Test Artist", "Test Album", self.network)
@ -78,7 +78,7 @@ class TestPyLastAlbum(TestPyLastWithLastFm):
# Act / Assert # Act / Assert
assert album1 != album2 assert album1 != album2
def test_album_ne_none_is_true(self): def test_album_ne_none_is_true(self) -> None:
# Arrange # Arrange
album1 = None album1 = None
album2 = pylast.Album("Test Artist", "Test Album", self.network) album2 = pylast.Album("Test Artist", "Test Album", self.network)
@ -86,7 +86,7 @@ class TestPyLastAlbum(TestPyLastWithLastFm):
# Act / Assert # Act / Assert
assert album1 != album2 assert album1 != album2
def test_get_cover_image(self): def test_get_cover_image(self) -> None:
# Arrange # Arrange
album = self.network.get_album("Test Artist", "Test Album") album = self.network.get_album("Test Artist", "Test Album")
@ -97,7 +97,7 @@ class TestPyLastAlbum(TestPyLastWithLastFm):
self.assert_startswith(image, "https://") self.assert_startswith(image, "https://")
self.assert_endswith(image, ".gif") self.assert_endswith(image, ".gif")
def test_mbid(self): def test_mbid(self) -> None:
# Arrange # Arrange
album = self.network.get_album("Radiohead", "OK Computer") album = self.network.get_album("Radiohead", "OK Computer")
@ -107,7 +107,7 @@ class TestPyLastAlbum(TestPyLastWithLastFm):
# Assert # Assert
assert mbid == "0b6b4ba0-d36f-47bd-b4ea-6a5b91842d29" assert mbid == "0b6b4ba0-d36f-47bd-b4ea-6a5b91842d29"
def test_no_mbid(self): def test_no_mbid(self) -> None:
# Arrange # Arrange
album = self.network.get_album("Test Artist", "Test Album") album = self.network.get_album("Test Artist", "Test Album")

View file

@ -10,7 +10,7 @@ from .test_pylast import WRITE_TEST, TestPyLastWithLastFm
class TestPyLastArtist(TestPyLastWithLastFm): class TestPyLastArtist(TestPyLastWithLastFm):
def test_repr(self): def test_repr(self) -> None:
# Arrange # Arrange
artist = pylast.Artist("Test Artist", self.network) artist = pylast.Artist("Test Artist", self.network)
@ -20,7 +20,7 @@ class TestPyLastArtist(TestPyLastWithLastFm):
# Assert # Assert
assert representation.startswith("pylast.Artist('Test Artist',") assert representation.startswith("pylast.Artist('Test Artist',")
def test_artist_is_hashable(self): def test_artist_is_hashable(self) -> None:
# Arrange # Arrange
test_artist = self.network.get_artist("Radiohead") test_artist = self.network.get_artist("Radiohead")
artist = test_artist.get_similar(limit=2)[0].item artist = test_artist.get_similar(limit=2)[0].item
@ -29,7 +29,7 @@ class TestPyLastArtist(TestPyLastWithLastFm):
# Act/Assert # Act/Assert
self.helper_is_thing_hashable(artist) self.helper_is_thing_hashable(artist)
def test_bio_published_date(self): def test_bio_published_date(self) -> None:
# Arrange # Arrange
artist = pylast.Artist("Test Artist", self.network) artist = pylast.Artist("Test Artist", self.network)
@ -40,7 +40,7 @@ class TestPyLastArtist(TestPyLastWithLastFm):
assert bio is not None assert bio is not None
assert len(bio) >= 1 assert len(bio) >= 1
def test_bio_content(self): def test_bio_content(self) -> None:
# Arrange # Arrange
artist = pylast.Artist("Test Artist", self.network) artist = pylast.Artist("Test Artist", self.network)
@ -51,7 +51,7 @@ class TestPyLastArtist(TestPyLastWithLastFm):
assert bio is not None assert bio is not None
assert len(bio) >= 1 assert len(bio) >= 1
def test_bio_content_none(self): def test_bio_content_none(self) -> None:
# Arrange # Arrange
# An artist with no biography, with "<content/>" in the API XML # An artist with no biography, with "<content/>" in the API XML
artist = pylast.Artist("Mr Sizef + Unquote", self.network) artist = pylast.Artist("Mr Sizef + Unquote", self.network)
@ -62,7 +62,7 @@ class TestPyLastArtist(TestPyLastWithLastFm):
# Assert # Assert
assert bio is None assert bio is None
def test_bio_summary(self): def test_bio_summary(self) -> None:
# Arrange # Arrange
artist = pylast.Artist("Test Artist", self.network) artist = pylast.Artist("Test Artist", self.network)
@ -73,7 +73,7 @@ class TestPyLastArtist(TestPyLastWithLastFm):
assert bio is not None assert bio is not None
assert len(bio) >= 1 assert len(bio) >= 1
def test_artist_top_tracks(self): def test_artist_top_tracks(self) -> None:
# Arrange # Arrange
# Pick an artist with plenty of plays # Pick an artist with plenty of plays
artist = self.network.get_top_artists(limit=1)[0].item artist = self.network.get_top_artists(limit=1)[0].item
@ -84,7 +84,7 @@ class TestPyLastArtist(TestPyLastWithLastFm):
# Assert # Assert
self.helper_two_different_things_in_top_list(things, pylast.Track) self.helper_two_different_things_in_top_list(things, pylast.Track)
def test_artist_top_albums(self): def test_artist_top_albums(self) -> None:
# Arrange # Arrange
# Pick an artist with plenty of plays # Pick an artist with plenty of plays
artist = self.network.get_top_artists(limit=1)[0].item artist = self.network.get_top_artists(limit=1)[0].item
@ -107,7 +107,7 @@ class TestPyLastArtist(TestPyLastWithLastFm):
# Assert # Assert
assert len(things) == test_limit assert len(things) == test_limit
def test_artist_top_albums_limit_default(self): def test_artist_top_albums_limit_default(self) -> None:
# Arrange # Arrange
# Pick an artist with plenty of plays # Pick an artist with plenty of plays
artist = self.network.get_top_artists(limit=1)[0].item artist = self.network.get_top_artists(limit=1)[0].item
@ -118,7 +118,7 @@ class TestPyLastArtist(TestPyLastWithLastFm):
# Assert # Assert
assert len(things) == 50 assert len(things) == 50
def test_artist_listener_count(self): def test_artist_listener_count(self) -> None:
# Arrange # Arrange
artist = self.network.get_artist("Test Artist") artist = self.network.get_artist("Test Artist")
@ -130,7 +130,7 @@ class TestPyLastArtist(TestPyLastWithLastFm):
assert count > 0 assert count > 0
@pytest.mark.skipif(not WRITE_TEST, reason="Only test once to avoid collisions") @pytest.mark.skipif(not WRITE_TEST, reason="Only test once to avoid collisions")
def test_tag_artist(self): def test_tag_artist(self) -> None:
# Arrange # Arrange
artist = self.network.get_artist("Test Artist") artist = self.network.get_artist("Test Artist")
# artist.clear_tags() # artist.clear_tags()
@ -145,7 +145,7 @@ class TestPyLastArtist(TestPyLastWithLastFm):
assert found assert found
@pytest.mark.skipif(not WRITE_TEST, reason="Only test once to avoid collisions") @pytest.mark.skipif(not WRITE_TEST, reason="Only test once to avoid collisions")
def test_remove_tag_of_type_text(self): def test_remove_tag_of_type_text(self) -> None:
# Arrange # Arrange
tag = "testing" # text tag = "testing" # text
artist = self.network.get_artist("Test Artist") artist = self.network.get_artist("Test Artist")
@ -160,7 +160,7 @@ class TestPyLastArtist(TestPyLastWithLastFm):
assert not found assert not found
@pytest.mark.skipif(not WRITE_TEST, reason="Only test once to avoid collisions") @pytest.mark.skipif(not WRITE_TEST, reason="Only test once to avoid collisions")
def test_remove_tag_of_type_tag(self): def test_remove_tag_of_type_tag(self) -> None:
# Arrange # Arrange
tag = pylast.Tag("testing", self.network) # Tag tag = pylast.Tag("testing", self.network) # Tag
artist = self.network.get_artist("Test Artist") artist = self.network.get_artist("Test Artist")
@ -175,7 +175,7 @@ class TestPyLastArtist(TestPyLastWithLastFm):
assert not found assert not found
@pytest.mark.skipif(not WRITE_TEST, reason="Only test once to avoid collisions") @pytest.mark.skipif(not WRITE_TEST, reason="Only test once to avoid collisions")
def test_remove_tags(self): def test_remove_tags(self) -> None:
# Arrange # Arrange
tags = ["removetag1", "removetag2"] tags = ["removetag1", "removetag2"]
artist = self.network.get_artist("Test Artist") artist = self.network.get_artist("Test Artist")
@ -195,7 +195,7 @@ class TestPyLastArtist(TestPyLastWithLastFm):
assert not found2 assert not found2
@pytest.mark.skipif(not WRITE_TEST, reason="Only test once to avoid collisions") @pytest.mark.skipif(not WRITE_TEST, reason="Only test once to avoid collisions")
def test_set_tags(self): def test_set_tags(self) -> None:
# Arrange # Arrange
tags = ["sometag1", "sometag2"] tags = ["sometag1", "sometag2"]
artist = self.network.get_artist("Test Artist 2") artist = self.network.get_artist("Test Artist 2")
@ -219,7 +219,7 @@ class TestPyLastArtist(TestPyLastWithLastFm):
assert found1 assert found1
assert found2 assert found2
def test_artists(self): def test_artists(self) -> None:
# Arrange # Arrange
artist1 = self.network.get_artist("Radiohead") artist1 = self.network.get_artist("Radiohead")
artist2 = self.network.get_artist("Portishead") artist2 = self.network.get_artist("Portishead")
@ -239,7 +239,7 @@ class TestPyLastArtist(TestPyLastWithLastFm):
assert url == "https://www.last.fm/music/radiohead" assert url == "https://www.last.fm/music/radiohead"
assert mbid == "a74b1b7f-71a5-4011-9441-d0b5e4122711" assert mbid == "a74b1b7f-71a5-4011-9441-d0b5e4122711"
def test_artist_eq_none_is_false(self): def test_artist_eq_none_is_false(self) -> None:
# Arrange # Arrange
artist1 = None artist1 = None
artist2 = pylast.Artist("Test Artist", self.network) artist2 = pylast.Artist("Test Artist", self.network)
@ -247,7 +247,7 @@ class TestPyLastArtist(TestPyLastWithLastFm):
# Act / Assert # Act / Assert
assert artist1 != artist2 assert artist1 != artist2
def test_artist_ne_none_is_true(self): def test_artist_ne_none_is_true(self) -> None:
# Arrange # Arrange
artist1 = None artist1 = None
artist2 = pylast.Artist("Test Artist", self.network) artist2 = pylast.Artist("Test Artist", self.network)
@ -255,7 +255,7 @@ class TestPyLastArtist(TestPyLastWithLastFm):
# Act / Assert # Act / Assert
assert artist1 != artist2 assert artist1 != artist2
def test_artist_get_correction(self): def test_artist_get_correction(self) -> None:
# Arrange # Arrange
artist = pylast.Artist("guns and roses", self.network) artist = pylast.Artist("guns and roses", self.network)
@ -265,7 +265,7 @@ class TestPyLastArtist(TestPyLastWithLastFm):
# Assert # Assert
assert corrected_artist_name == "Guns N' Roses" assert corrected_artist_name == "Guns N' Roses"
def test_get_userplaycount(self): def test_get_userplaycount(self) -> None:
# Arrange # Arrange
artist = pylast.Artist("John Lennon", self.network, username=self.username) artist = pylast.Artist("John Lennon", self.network, username=self.username)

View file

@ -8,14 +8,14 @@ from .test_pylast import TestPyLastWithLastFm
class TestPyLastCountry(TestPyLastWithLastFm): class TestPyLastCountry(TestPyLastWithLastFm):
def test_country_is_hashable(self): def test_country_is_hashable(self) -> None:
# Arrange # Arrange
country = self.network.get_country("Italy") country = self.network.get_country("Italy")
# Act/Assert # Act/Assert
self.helper_is_thing_hashable(country) self.helper_is_thing_hashable(country)
def test_countries(self): def test_countries(self) -> None:
# Arrange # Arrange
country1 = pylast.Country("Italy", self.network) country1 = pylast.Country("Italy", self.network)
country2 = pylast.Country("Finland", self.network) country2 = pylast.Country("Finland", self.network)

View file

@ -8,7 +8,7 @@ from .test_pylast import TestPyLastWithLastFm
class TestPyLastLibrary(TestPyLastWithLastFm): class TestPyLastLibrary(TestPyLastWithLastFm):
def test_repr(self): def test_repr(self) -> None:
# Arrange # Arrange
library = pylast.Library(user=self.username, network=self.network) library = pylast.Library(user=self.username, network=self.network)
@ -18,7 +18,7 @@ class TestPyLastLibrary(TestPyLastWithLastFm):
# Assert # Assert
self.assert_startswith(representation, "pylast.Library(") self.assert_startswith(representation, "pylast.Library(")
def test_str(self): def test_str(self) -> None:
# Arrange # Arrange
library = pylast.Library(user=self.username, network=self.network) library = pylast.Library(user=self.username, network=self.network)
@ -28,21 +28,21 @@ class TestPyLastLibrary(TestPyLastWithLastFm):
# Assert # Assert
self.assert_endswith(string, "'s Library") self.assert_endswith(string, "'s Library")
def test_library_is_hashable(self): def test_library_is_hashable(self) -> None:
# Arrange # Arrange
library = pylast.Library(user=self.username, network=self.network) library = pylast.Library(user=self.username, network=self.network)
# Act/Assert # Act/Assert
self.helper_is_thing_hashable(library) self.helper_is_thing_hashable(library)
def test_cacheable_library(self): def test_cacheable_library(self) -> None:
# Arrange # Arrange
library = pylast.Library(self.username, self.network) library = pylast.Library(self.username, self.network)
# Act/Assert # Act/Assert
self.helper_validate_cacheable(library, "get_artists") self.helper_validate_cacheable(library, "get_artists")
def test_get_user(self): def test_get_user(self) -> None:
# Arrange # Arrange
library = pylast.Library(user=self.username, network=self.network) library = pylast.Library(user=self.username, network=self.network)
user_to_get = self.network.get_user(self.username) user_to_get = self.network.get_user(self.username)

View file

@ -13,7 +13,7 @@ from .test_pylast import PyLastTestCase, load_secrets
class TestPyLastWithLibreFm(PyLastTestCase): class TestPyLastWithLibreFm(PyLastTestCase):
"""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): def test_libre_fm(self) -> None:
# Arrange # Arrange
secrets = load_secrets() secrets = load_secrets()
username = secrets["username"] username = secrets["username"]
@ -27,7 +27,7 @@ class TestPyLastWithLibreFm(PyLastTestCase):
# Assert # Assert
assert name == "Radiohead" assert name == "Radiohead"
def test_repr(self): def test_repr(self) -> None:
# Arrange # Arrange
secrets = load_secrets() secrets = load_secrets()
username = secrets["username"] username = secrets["username"]

View file

@ -13,7 +13,7 @@ from .test_pylast import WRITE_TEST, TestPyLastWithLastFm
class TestPyLastNetwork(TestPyLastWithLastFm): class TestPyLastNetwork(TestPyLastWithLastFm):
@pytest.mark.skipif(not WRITE_TEST, reason="Only test once to avoid collisions") @pytest.mark.skipif(not WRITE_TEST, reason="Only test once to avoid collisions")
def test_scrobble(self): def test_scrobble(self) -> None:
# Arrange # Arrange
artist = "test artist" artist = "test artist"
title = "test title" title = "test title"
@ -31,7 +31,7 @@ class TestPyLastNetwork(TestPyLastWithLastFm):
assert str(last_scrobble.track.title).lower() == title assert str(last_scrobble.track.title).lower() == title
@pytest.mark.skipif(not WRITE_TEST, reason="Only test once to avoid collisions") @pytest.mark.skipif(not WRITE_TEST, reason="Only test once to avoid collisions")
def test_update_now_playing(self): def test_update_now_playing(self) -> None:
# Arrange # Arrange
artist = "Test Artist" artist = "Test Artist"
title = "test title" title = "test title"
@ -55,7 +55,7 @@ class TestPyLastNetwork(TestPyLastWithLastFm):
assert len(current_track.info["image"]) assert len(current_track.info["image"])
assert re.search(r"^http.+$", current_track.info["image"][pylast.SIZE_LARGE]) assert re.search(r"^http.+$", current_track.info["image"][pylast.SIZE_LARGE])
def test_enable_rate_limiting(self): def test_enable_rate_limiting(self) -> None:
# Arrange # Arrange
assert not self.network.is_rate_limited() assert not self.network.is_rate_limited()
@ -72,7 +72,7 @@ class TestPyLastNetwork(TestPyLastWithLastFm):
assert self.network.is_rate_limited() assert self.network.is_rate_limited()
assert now - then >= 0.2 assert now - then >= 0.2
def test_disable_rate_limiting(self): def test_disable_rate_limiting(self) -> None:
# Arrange # Arrange
self.network.enable_rate_limit() self.network.enable_rate_limit()
assert self.network.is_rate_limited() assert self.network.is_rate_limited()
@ -87,14 +87,14 @@ class TestPyLastNetwork(TestPyLastWithLastFm):
# Assert # Assert
assert not self.network.is_rate_limited() assert not self.network.is_rate_limited()
def test_lastfm_network_name(self): def test_lastfm_network_name(self) -> None:
# Act # Act
name = str(self.network) name = str(self.network)
# Assert # Assert
assert name == "Last.fm Network" assert name == "Last.fm Network"
def test_geo_get_top_artists(self): def test_geo_get_top_artists(self) -> None:
# Arrange # Arrange
# Act # Act
artists = self.network.get_geo_top_artists(country="United Kingdom", limit=1) artists = self.network.get_geo_top_artists(country="United Kingdom", limit=1)
@ -104,7 +104,7 @@ class TestPyLastNetwork(TestPyLastWithLastFm):
assert isinstance(artists[0], pylast.TopItem) assert isinstance(artists[0], pylast.TopItem)
assert isinstance(artists[0].item, pylast.Artist) assert isinstance(artists[0].item, pylast.Artist)
def test_geo_get_top_tracks(self): def test_geo_get_top_tracks(self) -> None:
# Arrange # Arrange
# Act # Act
tracks = self.network.get_geo_top_tracks( tracks = self.network.get_geo_top_tracks(
@ -116,7 +116,7 @@ class TestPyLastNetwork(TestPyLastWithLastFm):
assert isinstance(tracks[0], pylast.TopItem) assert isinstance(tracks[0], pylast.TopItem)
assert isinstance(tracks[0].item, pylast.Track) assert isinstance(tracks[0].item, pylast.Track)
def test_network_get_top_artists_with_limit(self): def test_network_get_top_artists_with_limit(self) -> None:
# Arrange # Arrange
# Act # Act
artists = self.network.get_top_artists(limit=1) artists = self.network.get_top_artists(limit=1)
@ -124,7 +124,7 @@ class TestPyLastNetwork(TestPyLastWithLastFm):
# Assert # Assert
self.helper_only_one_thing_in_top_list(artists, pylast.Artist) self.helper_only_one_thing_in_top_list(artists, pylast.Artist)
def test_network_get_top_tags_with_limit(self): def test_network_get_top_tags_with_limit(self) -> None:
# Arrange # Arrange
# Act # Act
tags = self.network.get_top_tags(limit=1) tags = self.network.get_top_tags(limit=1)
@ -132,7 +132,7 @@ class TestPyLastNetwork(TestPyLastWithLastFm):
# Assert # Assert
self.helper_only_one_thing_in_top_list(tags, pylast.Tag) self.helper_only_one_thing_in_top_list(tags, pylast.Tag)
def test_network_get_top_tags_with_no_limit(self): def test_network_get_top_tags_with_no_limit(self) -> None:
# Arrange # Arrange
# Act # Act
tags = self.network.get_top_tags() tags = self.network.get_top_tags()
@ -140,7 +140,7 @@ class TestPyLastNetwork(TestPyLastWithLastFm):
# Assert # Assert
self.helper_at_least_one_thing_in_top_list(tags, pylast.Tag) self.helper_at_least_one_thing_in_top_list(tags, pylast.Tag)
def test_network_get_top_tracks_with_limit(self): def test_network_get_top_tracks_with_limit(self) -> None:
# Arrange # Arrange
# Act # Act
tracks = self.network.get_top_tracks(limit=1) tracks = self.network.get_top_tracks(limit=1)
@ -148,7 +148,7 @@ class TestPyLastNetwork(TestPyLastWithLastFm):
# Assert # Assert
self.helper_only_one_thing_in_top_list(tracks, pylast.Track) self.helper_only_one_thing_in_top_list(tracks, pylast.Track)
def test_country_top_tracks(self): def test_country_top_tracks(self) -> None:
# Arrange # Arrange
country = self.network.get_country("Croatia") country = self.network.get_country("Croatia")
@ -158,7 +158,7 @@ class TestPyLastNetwork(TestPyLastWithLastFm):
# Assert # Assert
self.helper_two_different_things_in_top_list(things, pylast.Track) self.helper_two_different_things_in_top_list(things, pylast.Track)
def test_country_network_top_tracks(self): def test_country_network_top_tracks(self) -> None:
# Arrange # Arrange
# Act # Act
things = self.network.get_geo_top_tracks("Croatia", limit=2) things = self.network.get_geo_top_tracks("Croatia", limit=2)
@ -166,7 +166,7 @@ class TestPyLastNetwork(TestPyLastWithLastFm):
# Assert # Assert
self.helper_two_different_things_in_top_list(things, pylast.Track) self.helper_two_different_things_in_top_list(things, pylast.Track)
def test_tag_top_tracks(self): def test_tag_top_tracks(self) -> None:
# Arrange # Arrange
tag = self.network.get_tag("blues") tag = self.network.get_tag("blues")
@ -176,7 +176,7 @@ class TestPyLastNetwork(TestPyLastWithLastFm):
# Assert # Assert
self.helper_two_different_things_in_top_list(things, pylast.Track) self.helper_two_different_things_in_top_list(things, pylast.Track)
def test_album_data(self): def test_album_data(self) -> None:
# Arrange # Arrange
thing = self.network.get_album("Test Artist", "Test Album") thing = self.network.get_album("Test Artist", "Test Album")
@ -196,7 +196,7 @@ class TestPyLastNetwork(TestPyLastWithLastFm):
assert playcount > 1 assert playcount > 1
assert "https://www.last.fm/music/test%2bartist/test%2balbum" == url assert "https://www.last.fm/music/test%2bartist/test%2balbum" == url
def test_track_data(self): def test_track_data(self) -> None:
# Arrange # Arrange
thing = self.network.get_track("Test Artist", "test title") thing = self.network.get_track("Test Artist", "test title")
@ -217,7 +217,7 @@ class TestPyLastNetwork(TestPyLastWithLastFm):
assert playcount > 1 assert playcount > 1
assert "https://www.last.fm/fr/music/test%2bartist/_/test%2btitle" == url assert "https://www.last.fm/fr/music/test%2bartist/_/test%2btitle" == url
def test_country_top_artists(self): def test_country_top_artists(self) -> None:
# Arrange # Arrange
country = self.network.get_country("Ukraine") country = self.network.get_country("Ukraine")
@ -227,7 +227,7 @@ class TestPyLastNetwork(TestPyLastWithLastFm):
# Assert # Assert
self.helper_only_one_thing_in_top_list(artists, pylast.Artist) self.helper_only_one_thing_in_top_list(artists, pylast.Artist)
def test_caching(self): def test_caching(self) -> None:
# Arrange # Arrange
user = self.network.get_user("RJ") user = self.network.get_user("RJ")
@ -242,7 +242,7 @@ class TestPyLastNetwork(TestPyLastWithLastFm):
self.network.disable_caching() self.network.disable_caching()
assert not self.network.is_caching_enabled() assert not self.network.is_caching_enabled()
def test_album_mbid(self): def test_album_mbid(self) -> None:
# Arrange # Arrange
mbid = "03c91c40-49a6-44a7-90e7-a700edf97a62" mbid = "03c91c40-49a6-44a7-90e7-a700edf97a62"
@ -255,7 +255,7 @@ class TestPyLastNetwork(TestPyLastWithLastFm):
assert album.title == "Believe" assert album.title == "Believe"
assert album_mbid == mbid assert album_mbid == mbid
def test_artist_mbid(self): def test_artist_mbid(self) -> None:
# Arrange # Arrange
mbid = "7e84f845-ac16-41fe-9ff8-df12eb32af55" mbid = "7e84f845-ac16-41fe-9ff8-df12eb32af55"
@ -266,7 +266,7 @@ class TestPyLastNetwork(TestPyLastWithLastFm):
assert isinstance(artist, pylast.Artist) assert isinstance(artist, pylast.Artist)
assert artist.name in ("MusicBrainz Test Artist", "MusicBrainzz Test Artist") assert artist.name in ("MusicBrainz Test Artist", "MusicBrainzz Test Artist")
def test_track_mbid(self): def test_track_mbid(self) -> None:
# Arrange # Arrange
mbid = "ebc037b1-cc9c-44f2-a21f-83c219f0e1e0" mbid = "ebc037b1-cc9c-44f2-a21f-83c219f0e1e0"
@ -279,7 +279,7 @@ class TestPyLastNetwork(TestPyLastWithLastFm):
assert track.title == "first" assert track.title == "first"
assert track_mbid == mbid assert track_mbid == mbid
def test_init_with_token(self): def test_init_with_token(self) -> None:
# Arrange/Act # Arrange/Act
msg = None msg = None
try: try:
@ -294,7 +294,7 @@ class TestPyLastNetwork(TestPyLastWithLastFm):
# Assert # Assert
assert msg == "Unauthorized Token - This token has not been issued" assert msg == "Unauthorized Token - This token has not been issued"
def test_proxy(self): def test_proxy(self) -> None:
# Arrange # Arrange
proxy = "http://example.com:1234" proxy = "http://example.com:1234"
@ -306,7 +306,7 @@ class TestPyLastNetwork(TestPyLastWithLastFm):
self.network.disable_proxy() self.network.disable_proxy()
assert not self.network.is_proxy_enabled() assert not self.network.is_proxy_enabled()
def test_album_search(self): def test_album_search(self) -> None:
# Arrange # Arrange
album = "Nevermind" album = "Nevermind"
@ -318,7 +318,7 @@ class TestPyLastNetwork(TestPyLastWithLastFm):
assert isinstance(results, list) assert isinstance(results, list)
assert isinstance(results[0], pylast.Album) assert isinstance(results[0], pylast.Album)
def test_album_search_images(self): def test_album_search_images(self) -> None:
# Arrange # Arrange
album = "Nevermind" album = "Nevermind"
search = self.network.search_for_album(album) search = self.network.search_for_album(album)
@ -338,7 +338,7 @@ class TestPyLastNetwork(TestPyLastWithLastFm):
self.assert_endswith(images[pylast.SIZE_EXTRA_LARGE], ".png") self.assert_endswith(images[pylast.SIZE_EXTRA_LARGE], ".png")
assert "/300x300/" in images[pylast.SIZE_EXTRA_LARGE] assert "/300x300/" in images[pylast.SIZE_EXTRA_LARGE]
def test_artist_search(self): def test_artist_search(self) -> None:
# Arrange # Arrange
artist = "Nirvana" artist = "Nirvana"
@ -350,7 +350,7 @@ class TestPyLastNetwork(TestPyLastWithLastFm):
assert isinstance(results, list) assert isinstance(results, list)
assert isinstance(results[0], pylast.Artist) assert isinstance(results[0], pylast.Artist)
def test_artist_search_images(self): def test_artist_search_images(self) -> None:
# Arrange # Arrange
artist = "Nirvana" artist = "Nirvana"
search = self.network.search_for_artist(artist) search = self.network.search_for_artist(artist)
@ -370,7 +370,7 @@ class TestPyLastNetwork(TestPyLastWithLastFm):
self.assert_endswith(images[pylast.SIZE_EXTRA_LARGE], ".png") self.assert_endswith(images[pylast.SIZE_EXTRA_LARGE], ".png")
assert "/300x300/" in images[pylast.SIZE_EXTRA_LARGE] assert "/300x300/" in images[pylast.SIZE_EXTRA_LARGE]
def test_track_search(self): def test_track_search(self) -> None:
# Arrange # Arrange
artist = "Nirvana" artist = "Nirvana"
track = "Smells Like Teen Spirit" track = "Smells Like Teen Spirit"
@ -383,7 +383,7 @@ class TestPyLastNetwork(TestPyLastWithLastFm):
assert isinstance(results, list) assert isinstance(results, list)
assert isinstance(results[0], pylast.Track) assert isinstance(results[0], pylast.Track)
def test_track_search_images(self): def test_track_search_images(self) -> None:
# Arrange # Arrange
artist = "Nirvana" artist = "Nirvana"
track = "Smells Like Teen Spirit" track = "Smells Like Teen Spirit"
@ -404,7 +404,7 @@ class TestPyLastNetwork(TestPyLastWithLastFm):
self.assert_endswith(images[pylast.SIZE_EXTRA_LARGE], ".png") self.assert_endswith(images[pylast.SIZE_EXTRA_LARGE], ".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): def test_search_get_total_result_count(self) -> None:
# Arrange # Arrange
artist = "Nirvana" artist = "Nirvana"
track = "Smells Like Teen Spirit" track = "Smells Like Teen Spirit"

View file

@ -33,14 +33,14 @@ def load_secrets(): # pragma: no cover
class PyLastTestCase: class PyLastTestCase:
def assert_startswith(self, s, prefix, start=None, end=None): def assert_startswith(self, s, prefix, start=None, end=None) -> None:
assert s.startswith(prefix, start, end) assert s.startswith(prefix, start, end)
def assert_endswith(self, s, suffix, start=None, end=None): def assert_endswith(self, s, suffix, start=None, end=None) -> None:
assert s.endswith(suffix, start, end) assert s.endswith(suffix, start, end)
def _no_xfail_rerun_filter(err, name, test, plugin): 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
@ -54,7 +54,7 @@ class TestPyLastWithLastFm(PyLastTestCase):
return int(time.time()) return int(time.time())
@classmethod @classmethod
def setup_class(cls): def setup_class(cls) -> None:
if cls.secrets is None: if cls.secrets is None:
cls.secrets = load_secrets() cls.secrets = load_secrets()
@ -71,7 +71,7 @@ class TestPyLastWithLastFm(PyLastTestCase):
password_hash=password_hash, password_hash=password_hash,
) )
def helper_is_thing_hashable(self, thing): def helper_is_thing_hashable(self, thing) -> None:
# Arrange # Arrange
things = set() things = set()
@ -82,7 +82,7 @@ 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): def helper_validate_results(self, 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
@ -93,7 +93,7 @@ class TestPyLastWithLastFm(PyLastTestCase):
assert a == b assert a == b
assert b == c assert b == c
def helper_validate_cacheable(self, thing, function_name): def helper_validate_cacheable(self, thing, function_name) -> None:
# Arrange # Arrange
# get thing.function_name() # get thing.function_name()
func = getattr(thing, function_name, None) func = getattr(thing, function_name, None)
@ -106,27 +106,27 @@ 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): def helper_at_least_one_thing_in_top_list(self, 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): def helper_only_one_thing_in_top_list(self, 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): def helper_only_one_thing_in_list(self, 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): def helper_two_different_things_in_top_list(self, things, expected_type) -> None:
# Assert # Assert
assert len(things) == 2 assert len(things) == 2
thing1 = things[0] thing1 = things[0]

View file

@ -8,14 +8,14 @@ from .test_pylast import TestPyLastWithLastFm
class TestPyLastTag(TestPyLastWithLastFm): class TestPyLastTag(TestPyLastWithLastFm):
def test_tag_is_hashable(self): def test_tag_is_hashable(self) -> None:
# Arrange # Arrange
tag = self.network.get_top_tags(limit=1)[0] tag = self.network.get_top_tags(limit=1)[0]
# Act/Assert # Act/Assert
self.helper_is_thing_hashable(tag) self.helper_is_thing_hashable(tag)
def test_tag_top_artists(self): def test_tag_top_artists(self) -> None:
# Arrange # Arrange
tag = self.network.get_tag("blues") tag = self.network.get_tag("blues")
@ -25,7 +25,7 @@ class TestPyLastTag(TestPyLastWithLastFm):
# Assert # Assert
self.helper_only_one_thing_in_top_list(artists, pylast.Artist) self.helper_only_one_thing_in_top_list(artists, pylast.Artist)
def test_tag_top_albums(self): def test_tag_top_albums(self) -> None:
# Arrange # Arrange
tag = self.network.get_tag("blues") tag = self.network.get_tag("blues")
@ -35,7 +35,7 @@ class TestPyLastTag(TestPyLastWithLastFm):
# Assert # Assert
self.helper_only_one_thing_in_top_list(albums, pylast.Album) self.helper_only_one_thing_in_top_list(albums, pylast.Album)
def test_tags(self): def test_tags(self) -> None:
# Arrange # Arrange
tag1 = self.network.get_tag("blues") tag1 = self.network.get_tag("blues")
tag2 = self.network.get_tag("rock") tag2 = self.network.get_tag("rock")

View file

@ -13,7 +13,7 @@ from .test_pylast import WRITE_TEST, TestPyLastWithLastFm
class TestPyLastTrack(TestPyLastWithLastFm): class TestPyLastTrack(TestPyLastWithLastFm):
@pytest.mark.skipif(not WRITE_TEST, reason="Only test once to avoid collisions") @pytest.mark.skipif(not WRITE_TEST, reason="Only test once to avoid collisions")
def test_love(self): def test_love(self) -> None:
# Arrange # Arrange
artist = "Test Artist" artist = "Test Artist"
title = "test title" title = "test title"
@ -29,7 +29,7 @@ class TestPyLastTrack(TestPyLastWithLastFm):
assert str(loved[0].track.title).lower() == "test title" assert str(loved[0].track.title).lower() == "test title"
@pytest.mark.skipif(not WRITE_TEST, reason="Only test once to avoid collisions") @pytest.mark.skipif(not WRITE_TEST, reason="Only test once to avoid collisions")
def test_unlove(self): def test_unlove(self) -> None:
# Arrange # Arrange
artist = pylast.Artist("Test Artist", self.network) artist = pylast.Artist("Test Artist", self.network)
title = "test title" title = "test title"
@ -47,7 +47,7 @@ class TestPyLastTrack(TestPyLastWithLastFm):
assert str(loved[0].track.artist) != "Test Artist" assert str(loved[0].track.artist) != "Test Artist"
assert str(loved[0].track.title) != "test title" assert str(loved[0].track.title) != "test title"
def test_user_play_count_in_track_info(self): def test_user_play_count_in_track_info(self) -> None:
# Arrange # Arrange
artist = "Test Artist" artist = "Test Artist"
title = "test title" title = "test title"
@ -61,7 +61,7 @@ class TestPyLastTrack(TestPyLastWithLastFm):
# Assert # Assert
assert count >= 0 assert count >= 0
def test_user_loved_in_track_info(self): def test_user_loved_in_track_info(self) -> None:
# Arrange # Arrange
artist = "Test Artist" artist = "Test Artist"
title = "test title" title = "test title"
@ -77,7 +77,7 @@ class TestPyLastTrack(TestPyLastWithLastFm):
assert isinstance(loved, bool) assert isinstance(loved, bool)
assert not isinstance(loved, str) assert not isinstance(loved, str)
def test_track_is_hashable(self): def test_track_is_hashable(self) -> None:
# Arrange # Arrange
artist = self.network.get_artist("Test Artist") artist = self.network.get_artist("Test Artist")
track = artist.get_top_tracks(stream=False)[0].item track = artist.get_top_tracks(stream=False)[0].item
@ -86,7 +86,7 @@ class TestPyLastTrack(TestPyLastWithLastFm):
# Act/Assert # Act/Assert
self.helper_is_thing_hashable(track) self.helper_is_thing_hashable(track)
def test_track_wiki_content(self): def test_track_wiki_content(self) -> None:
# Arrange # Arrange
track = pylast.Track("Test Artist", "test title", self.network) track = pylast.Track("Test Artist", "test title", self.network)
@ -97,7 +97,7 @@ class TestPyLastTrack(TestPyLastWithLastFm):
assert wiki is not None assert wiki is not None
assert len(wiki) >= 1 assert len(wiki) >= 1
def test_track_wiki_summary(self): def test_track_wiki_summary(self) -> None:
# Arrange # Arrange
track = pylast.Track("Test Artist", "test title", self.network) track = pylast.Track("Test Artist", "test title", self.network)
@ -108,7 +108,7 @@ class TestPyLastTrack(TestPyLastWithLastFm):
assert wiki is not None assert wiki is not None
assert len(wiki) >= 1 assert len(wiki) >= 1
def test_track_get_duration(self): def test_track_get_duration(self) -> None:
# Arrange # Arrange
track = pylast.Track("Cher", "Believe", self.network) track = pylast.Track("Cher", "Believe", self.network)
@ -118,7 +118,7 @@ class TestPyLastTrack(TestPyLastWithLastFm):
# Assert # Assert
assert duration >= 200000 assert duration >= 200000
def test_track_get_album(self): def test_track_get_album(self) -> None:
# Arrange # Arrange
track = pylast.Track("Nirvana", "Lithium", self.network) track = pylast.Track("Nirvana", "Lithium", self.network)
@ -128,7 +128,7 @@ class TestPyLastTrack(TestPyLastWithLastFm):
# Assert # Assert
assert str(album) == "Nirvana - Nevermind" assert str(album) == "Nirvana - Nevermind"
def test_track_get_similar(self): def test_track_get_similar(self) -> None:
# Arrange # Arrange
track = pylast.Track("Cher", "Believe", self.network) track = pylast.Track("Cher", "Believe", self.network)
@ -143,7 +143,7 @@ class TestPyLastTrack(TestPyLastWithLastFm):
break break
assert found assert found
def test_track_get_similar_limits(self): def test_track_get_similar_limits(self) -> None:
# Arrange # Arrange
track = pylast.Track("Cher", "Believe", self.network) track = pylast.Track("Cher", "Believe", self.network)
@ -153,7 +153,7 @@ class TestPyLastTrack(TestPyLastWithLastFm):
assert len(track.get_similar(limit=None)) >= 23 assert len(track.get_similar(limit=None)) >= 23
assert len(track.get_similar(limit=0)) >= 23 assert len(track.get_similar(limit=0)) >= 23
def test_tracks_notequal(self): def test_tracks_notequal(self) -> None:
# Arrange # Arrange
track1 = pylast.Track("Test Artist", "test title", self.network) track1 = pylast.Track("Test Artist", "test title", self.network)
track2 = pylast.Track("Test Artist", "Test Track", self.network) track2 = pylast.Track("Test Artist", "Test Track", self.network)
@ -162,7 +162,7 @@ class TestPyLastTrack(TestPyLastWithLastFm):
# Assert # Assert
assert track1 != track2 assert track1 != track2
def test_track_title_prop_caps(self): def test_track_title_prop_caps(self) -> None:
# Arrange # Arrange
track = pylast.Track("test artist", "test title", self.network) track = pylast.Track("test artist", "test title", self.network)
@ -172,7 +172,7 @@ class TestPyLastTrack(TestPyLastWithLastFm):
# Assert # Assert
assert title == "Test Title" assert title == "Test Title"
def test_track_listener_count(self): def test_track_listener_count(self) -> None:
# Arrange # Arrange
track = pylast.Track("test artist", "test title", self.network) track = pylast.Track("test artist", "test title", self.network)
@ -182,7 +182,7 @@ class TestPyLastTrack(TestPyLastWithLastFm):
# Assert # Assert
assert count > 21 assert count > 21
def test_album_tracks(self): def test_album_tracks(self) -> None:
# Arrange # Arrange
album = pylast.Album("Test Artist", "Test", self.network) album = pylast.Album("Test Artist", "Test", self.network)
@ -196,7 +196,7 @@ class TestPyLastTrack(TestPyLastWithLastFm):
assert len(tracks) == 1 assert len(tracks) == 1
assert url.startswith("https://www.last.fm/music/test") assert url.startswith("https://www.last.fm/music/test")
def test_track_eq_none_is_false(self): def test_track_eq_none_is_false(self) -> None:
# Arrange # Arrange
track1 = None track1 = None
track2 = pylast.Track("Test Artist", "test title", self.network) track2 = pylast.Track("Test Artist", "test title", self.network)
@ -204,7 +204,7 @@ class TestPyLastTrack(TestPyLastWithLastFm):
# Act / Assert # Act / Assert
assert track1 != track2 assert track1 != track2
def test_track_ne_none_is_true(self): def test_track_ne_none_is_true(self) -> None:
# Arrange # Arrange
track1 = None track1 = None
track2 = pylast.Track("Test Artist", "test title", self.network) track2 = pylast.Track("Test Artist", "test title", self.network)
@ -212,7 +212,7 @@ class TestPyLastTrack(TestPyLastWithLastFm):
# Act / Assert # Act / Assert
assert track1 != track2 assert track1 != track2
def test_track_get_correction(self): def test_track_get_correction(self) -> None:
# Arrange # Arrange
track = pylast.Track("Guns N' Roses", "mrbrownstone", self.network) track = pylast.Track("Guns N' Roses", "mrbrownstone", self.network)
@ -222,7 +222,7 @@ class TestPyLastTrack(TestPyLastWithLastFm):
# Assert # Assert
assert corrected_track_name == "Mr. Brownstone" assert corrected_track_name == "Mr. Brownstone"
def test_track_with_no_mbid(self): def test_track_with_no_mbid(self) -> None:
# Arrange # Arrange
track = pylast.Track("Static-X", "Set It Off", self.network) track = pylast.Track("Static-X", "Set It Off", self.network)

View file

@ -16,7 +16,7 @@ from .test_pylast import TestPyLastWithLastFm
class TestPyLastUser(TestPyLastWithLastFm): class TestPyLastUser(TestPyLastWithLastFm):
def test_repr(self): def test_repr(self) -> None:
# Arrange # Arrange
user = self.network.get_user("RJ") user = self.network.get_user("RJ")
@ -26,7 +26,7 @@ class TestPyLastUser(TestPyLastWithLastFm):
# Assert # Assert
self.assert_startswith(representation, "pylast.User('RJ',") self.assert_startswith(representation, "pylast.User('RJ',")
def test_str(self): def test_str(self) -> None:
# Arrange # Arrange
user = self.network.get_user("RJ") user = self.network.get_user("RJ")
@ -36,7 +36,7 @@ class TestPyLastUser(TestPyLastWithLastFm):
# Assert # Assert
assert string == "RJ" assert string == "RJ"
def test_equality(self): def test_equality(self) -> None:
# Arrange # Arrange
user_1a = self.network.get_user("RJ") user_1a = self.network.get_user("RJ")
user_1b = self.network.get_user("RJ") user_1b = self.network.get_user("RJ")
@ -48,7 +48,7 @@ class TestPyLastUser(TestPyLastWithLastFm):
assert user_1a != user_2 assert user_1a != user_2
assert user_1a != not_a_user assert user_1a != not_a_user
def test_get_name(self): def test_get_name(self) -> None:
# Arrange # Arrange
user = self.network.get_user("RJ") user = self.network.get_user("RJ")
@ -58,7 +58,7 @@ class TestPyLastUser(TestPyLastWithLastFm):
# Assert # Assert
assert name == "RJ" assert name == "RJ"
def test_get_user_registration(self): def test_get_user_registration(self) -> None:
# Arrange # Arrange
user = self.network.get_user("RJ") user = self.network.get_user("RJ")
@ -74,7 +74,7 @@ class TestPyLastUser(TestPyLastWithLastFm):
# Just check date because of timezones # Just check date because of timezones
assert "2002-11-20 " in registered assert "2002-11-20 " in registered
def test_get_user_unixtime_registration(self): def test_get_user_unixtime_registration(self) -> None:
# Arrange # Arrange
user = self.network.get_user("RJ") user = self.network.get_user("RJ")
@ -85,7 +85,7 @@ class TestPyLastUser(TestPyLastWithLastFm):
# Just check date because of timezones # Just check date because of timezones
assert unixtime_registered == 1037793040 assert unixtime_registered == 1037793040
def test_get_countryless_user(self): def test_get_countryless_user(self) -> None:
# Arrange # Arrange
# Currently test_user has no country set: # Currently test_user has no country set:
lastfm_user = self.network.get_user("test_user") lastfm_user = self.network.get_user("test_user")
@ -96,7 +96,7 @@ class TestPyLastUser(TestPyLastWithLastFm):
# Assert # Assert
assert country is None assert country is None
def test_user_get_country(self): def test_user_get_country(self) -> None:
# Arrange # Arrange
lastfm_user = self.network.get_user("RJ") lastfm_user = self.network.get_user("RJ")
@ -106,7 +106,7 @@ class TestPyLastUser(TestPyLastWithLastFm):
# Assert # Assert
assert str(country) == "United Kingdom" assert str(country) == "United Kingdom"
def test_user_equals_none(self): def test_user_equals_none(self) -> None:
# Arrange # Arrange
lastfm_user = self.network.get_user(self.username) lastfm_user = self.network.get_user(self.username)
@ -116,7 +116,7 @@ class TestPyLastUser(TestPyLastWithLastFm):
# Assert # Assert
assert not value assert not value
def test_user_not_equal_to_none(self): def test_user_not_equal_to_none(self) -> None:
# Arrange # Arrange
lastfm_user = self.network.get_user(self.username) lastfm_user = self.network.get_user(self.username)
@ -126,7 +126,7 @@ class TestPyLastUser(TestPyLastWithLastFm):
# Assert # Assert
assert value assert value
def test_now_playing_user_with_no_scrobbles(self): def test_now_playing_user_with_no_scrobbles(self) -> None:
# Arrange # Arrange
# Currently test-account has no scrobbles: # Currently test-account has no scrobbles:
user = self.network.get_user("test-account") user = self.network.get_user("test-account")
@ -137,7 +137,7 @@ class TestPyLastUser(TestPyLastWithLastFm):
# Assert # Assert
assert current_track is None assert current_track is None
def test_love_limits(self): def test_love_limits(self) -> None:
# Arrange # Arrange
# Currently test-account has at least 23 loved tracks: # Currently test-account has at least 23 loved tracks:
user = self.network.get_user("test-user") user = self.network.get_user("test-user")
@ -148,7 +148,7 @@ class TestPyLastUser(TestPyLastWithLastFm):
assert len(user.get_loved_tracks(limit=None)) >= 23 assert len(user.get_loved_tracks(limit=None)) >= 23
assert len(user.get_loved_tracks(limit=0)) >= 23 assert len(user.get_loved_tracks(limit=0)) >= 23
def test_user_is_hashable(self): def test_user_is_hashable(self) -> None:
# Arrange # Arrange
user = self.network.get_user(self.username) user = self.network.get_user(self.username)
@ -169,7 +169,7 @@ class TestPyLastUser(TestPyLastWithLastFm):
# # Assert # # Assert
# self.assertGreaterEqual(len(tracks), 0) # self.assertGreaterEqual(len(tracks), 0)
def test_pickle(self): def test_pickle(self) -> None:
# Arrange # Arrange
import pickle import pickle
@ -187,7 +187,7 @@ class TestPyLastUser(TestPyLastWithLastFm):
assert lastfm_user == loaded_user assert lastfm_user == loaded_user
@pytest.mark.xfail @pytest.mark.xfail
def test_cacheable_user(self): def test_cacheable_user(self) -> None:
# Arrange # Arrange
lastfm_user = self.network.get_authenticated_user() lastfm_user = self.network.get_authenticated_user()
@ -201,7 +201,7 @@ class TestPyLastUser(TestPyLastWithLastFm):
lastfm_user, "get_recent_tracks" lastfm_user, "get_recent_tracks"
) )
def test_user_get_top_tags_with_limit(self): def test_user_get_top_tags_with_limit(self) -> None:
# Arrange # Arrange
user = self.network.get_user("RJ") user = self.network.get_user("RJ")
@ -211,7 +211,7 @@ class TestPyLastUser(TestPyLastWithLastFm):
# Assert # Assert
self.helper_only_one_thing_in_top_list(tags, pylast.Tag) self.helper_only_one_thing_in_top_list(tags, pylast.Tag)
def test_user_top_tracks(self): def test_user_top_tracks(self) -> None:
# Arrange # Arrange
lastfm_user = self.network.get_user("RJ") lastfm_user = self.network.get_user("RJ")
@ -221,14 +221,14 @@ class TestPyLastUser(TestPyLastWithLastFm):
# Assert # Assert
self.helper_two_different_things_in_top_list(things, pylast.Track) self.helper_two_different_things_in_top_list(things, pylast.Track)
def helper_assert_chart(self, chart, expected_type): def helper_assert_chart(self, chart, expected_type) -> None:
# Assert # Assert
assert chart is not None assert chart is not None
assert len(chart) > 0 assert len(chart) > 0
assert isinstance(chart[0], pylast.TopItem) assert isinstance(chart[0], pylast.TopItem)
assert isinstance(chart[0].item, expected_type) assert isinstance(chart[0].item, expected_type)
def helper_get_assert_charts(self, thing, date): def helper_get_assert_charts(self, thing, date) -> None:
# Arrange # Arrange
album_chart, track_chart = None, None album_chart, track_chart = None, None
(from_date, to_date) = date (from_date, to_date) = date
@ -245,14 +245,14 @@ class TestPyLastUser(TestPyLastWithLastFm):
self.helper_assert_chart(album_chart, pylast.Album) self.helper_assert_chart(album_chart, pylast.Album)
self.helper_assert_chart(track_chart, pylast.Track) self.helper_assert_chart(track_chart, pylast.Track)
def helper_dates_valid(self, dates): def helper_dates_valid(self, dates) -> None:
# Assert # Assert
assert len(dates) >= 1 assert len(dates) >= 1
assert isinstance(dates[0], tuple) assert isinstance(dates[0], tuple)
(start, end) = dates[0] (start, end) = dates[0]
assert start < end assert start < end
def test_user_charts(self): def test_user_charts(self) -> None:
# Arrange # Arrange
lastfm_user = self.network.get_user("RJ") lastfm_user = self.network.get_user("RJ")
dates = lastfm_user.get_weekly_chart_dates() dates = lastfm_user.get_weekly_chart_dates()
@ -261,7 +261,7 @@ class TestPyLastUser(TestPyLastWithLastFm):
# Act/Assert # Act/Assert
self.helper_get_assert_charts(lastfm_user, dates[0]) self.helper_get_assert_charts(lastfm_user, dates[0])
def test_user_top_artists(self): def test_user_top_artists(self) -> None:
# Arrange # Arrange
lastfm_user = self.network.get_user(self.username) lastfm_user = self.network.get_user(self.username)
@ -271,7 +271,7 @@ class TestPyLastUser(TestPyLastWithLastFm):
# Assert # Assert
self.helper_only_one_thing_in_top_list(artists, pylast.Artist) self.helper_only_one_thing_in_top_list(artists, pylast.Artist)
def test_user_top_albums(self): def test_user_top_albums(self) -> None:
# Arrange # Arrange
user = self.network.get_user("RJ") user = self.network.get_user("RJ")
@ -285,7 +285,7 @@ class TestPyLastUser(TestPyLastWithLastFm):
assert len(top_album.info["image"]) assert len(top_album.info["image"])
assert re.search(r"^http.+$", top_album.info["image"][pylast.SIZE_LARGE]) assert re.search(r"^http.+$", top_album.info["image"][pylast.SIZE_LARGE])
def test_user_tagged_artists(self): def test_user_tagged_artists(self) -> None:
# Arrange # Arrange
lastfm_user = self.network.get_user(self.username) lastfm_user = self.network.get_user(self.username)
tags = ["artisttagola"] tags = ["artisttagola"]
@ -298,7 +298,7 @@ class TestPyLastUser(TestPyLastWithLastFm):
# Assert # Assert
self.helper_only_one_thing_in_list(artists, pylast.Artist) self.helper_only_one_thing_in_list(artists, pylast.Artist)
def test_user_tagged_albums(self): def test_user_tagged_albums(self) -> None:
# Arrange # Arrange
lastfm_user = self.network.get_user(self.username) lastfm_user = self.network.get_user(self.username)
tags = ["albumtagola"] tags = ["albumtagola"]
@ -311,7 +311,7 @@ class TestPyLastUser(TestPyLastWithLastFm):
# Assert # Assert
self.helper_only_one_thing_in_list(albums, pylast.Album) self.helper_only_one_thing_in_list(albums, pylast.Album)
def test_user_tagged_tracks(self): def test_user_tagged_tracks(self) -> None:
# Arrange # Arrange
lastfm_user = self.network.get_user(self.username) lastfm_user = self.network.get_user(self.username)
tags = ["tracktagola"] tags = ["tracktagola"]
@ -324,7 +324,7 @@ class TestPyLastUser(TestPyLastWithLastFm):
# Assert # Assert
self.helper_only_one_thing_in_list(tracks, pylast.Track) self.helper_only_one_thing_in_list(tracks, pylast.Track)
def test_user_subscriber(self): def test_user_subscriber(self) -> None:
# Arrange # Arrange
subscriber = self.network.get_user("RJ") subscriber = self.network.get_user("RJ")
non_subscriber = self.network.get_user("Test User") non_subscriber = self.network.get_user("Test User")
@ -337,7 +337,7 @@ class TestPyLastUser(TestPyLastWithLastFm):
assert subscriber_is_subscriber assert subscriber_is_subscriber
assert not non_subscriber_is_subscriber assert not non_subscriber_is_subscriber
def test_user_get_image(self): def test_user_get_image(self) -> None:
# Arrange # Arrange
user = self.network.get_user("RJ") user = self.network.get_user("RJ")
@ -347,7 +347,7 @@ class TestPyLastUser(TestPyLastWithLastFm):
# Assert # Assert
self.assert_startswith(url, "https://") self.assert_startswith(url, "https://")
def test_user_get_library(self): def test_user_get_library(self) -> None:
# Arrange # Arrange
user = self.network.get_user(self.username) user = self.network.get_user(self.username)
@ -357,7 +357,7 @@ class TestPyLastUser(TestPyLastWithLastFm):
# Assert # Assert
assert isinstance(library, pylast.Library) assert isinstance(library, pylast.Library)
def test_get_recent_tracks_from_to(self): def test_get_recent_tracks_from_to(self) -> None:
# Arrange # Arrange
lastfm_user = self.network.get_user("RJ") lastfm_user = self.network.get_user("RJ")
start = dt.datetime(2011, 7, 21, 15, 10) start = dt.datetime(2011, 7, 21, 15, 10)
@ -374,7 +374,7 @@ class TestPyLastUser(TestPyLastWithLastFm):
assert str(tracks[0].track.artist) == "Johnny Cash" assert str(tracks[0].track.artist) == "Johnny Cash"
assert str(tracks[0].track.title) == "Ring of Fire" assert str(tracks[0].track.title) == "Ring of Fire"
def test_get_recent_tracks_limit_none(self): def test_get_recent_tracks_limit_none(self) -> None:
# Arrange # Arrange
lastfm_user = self.network.get_user("bbc6music") lastfm_user = self.network.get_user("bbc6music")
start = dt.datetime(2020, 2, 15, 15, 00) start = dt.datetime(2020, 2, 15, 15, 00)
@ -393,7 +393,7 @@ class TestPyLastUser(TestPyLastWithLastFm):
assert str(tracks[0].track.artist) == "Seun Kuti & Egypt 80" assert str(tracks[0].track.artist) == "Seun Kuti & Egypt 80"
assert str(tracks[0].track.title) == "Struggles Sounds" assert str(tracks[0].track.title) == "Struggles Sounds"
def test_get_recent_tracks_is_streamable(self): def test_get_recent_tracks_is_streamable(self) -> None:
# Arrange # Arrange
lastfm_user = self.network.get_user("bbc6music") lastfm_user = self.network.get_user("bbc6music")
start = dt.datetime(2020, 2, 15, 15, 00) start = dt.datetime(2020, 2, 15, 15, 00)
@ -410,7 +410,7 @@ class TestPyLastUser(TestPyLastWithLastFm):
# Assert # Assert
assert inspect.isgenerator(tracks) assert inspect.isgenerator(tracks)
def test_get_playcount(self): def test_get_playcount(self) -> None:
# Arrange # Arrange
user = self.network.get_user("RJ") user = self.network.get_user("RJ")
@ -420,7 +420,7 @@ class TestPyLastUser(TestPyLastWithLastFm):
# Assert # Assert
assert playcount >= 128387 assert playcount >= 128387
def test_get_image(self): def test_get_image(self) -> None:
# Arrange # Arrange
user = self.network.get_user("RJ") user = self.network.get_user("RJ")
@ -431,7 +431,7 @@ class TestPyLastUser(TestPyLastWithLastFm):
self.assert_startswith(image, "https://") self.assert_startswith(image, "https://")
self.assert_endswith(image, ".png") self.assert_endswith(image, ".png")
def test_get_url(self): def test_get_url(self) -> None:
# Arrange # Arrange
user = self.network.get_user("RJ") user = self.network.get_user("RJ")
@ -441,7 +441,7 @@ class TestPyLastUser(TestPyLastWithLastFm):
# Assert # Assert
assert url == "https://www.last.fm/user/rj" assert url == "https://www.last.fm/user/rj"
def test_get_weekly_artist_charts(self): def test_get_weekly_artist_charts(self) -> None:
# Arrange # Arrange
user = self.network.get_user("bbc6music") user = self.network.get_user("bbc6music")
@ -453,7 +453,7 @@ class TestPyLastUser(TestPyLastWithLastFm):
assert artist is not None assert artist is not None
assert isinstance(artist.network, pylast.LastFMNetwork) assert isinstance(artist.network, pylast.LastFMNetwork)
def test_get_weekly_track_charts(self): def test_get_weekly_track_charts(self) -> None:
# Arrange # Arrange
user = self.network.get_user("bbc6music") user = self.network.get_user("bbc6music")
@ -465,7 +465,7 @@ class TestPyLastUser(TestPyLastWithLastFm):
assert track is not None assert track is not None
assert isinstance(track.network, pylast.LastFMNetwork) assert isinstance(track.network, pylast.LastFMNetwork)
def test_user_get_track_scrobbles(self): def test_user_get_track_scrobbles(self) -> None:
# Arrange # Arrange
artist = "France Gall" artist = "France Gall"
title = "Laisse Tomber Les Filles" title = "Laisse Tomber Les Filles"
@ -479,7 +479,7 @@ class TestPyLastUser(TestPyLastWithLastFm):
assert str(scrobbles[0].track.artist) == "France Gall" assert str(scrobbles[0].track.artist) == "France Gall"
assert scrobbles[0].track.title == "Laisse Tomber Les Filles" assert scrobbles[0].track.title == "Laisse Tomber Les Filles"
def test_cacheable_user_get_track_scrobbles(self): def test_cacheable_user_get_track_scrobbles(self) -> None:
# Arrange # Arrange
artist = "France Gall" artist = "France Gall"
title = "Laisse Tomber Les Filles" title = "Laisse Tomber Les Filles"

View file

@ -18,13 +18,13 @@ def mock_network():
"fdasfdsafsaf not unicode", "fdasfdsafsaf not unicode",
], ],
) )
def test_get_cache_key(artist): def test_get_cache_key(artist) -> None:
request = pylast._Request(mock_network(), "some_method", params={"artist": artist}) request = pylast._Request(mock_network(), "some_method", params={"artist": artist})
request._get_cache_key() request._get_cache_key()
@pytest.mark.parametrize("obj", [pylast.Artist("B\xe9l", mock_network())]) @pytest.mark.parametrize("obj", [pylast.Artist("B\xe9l", mock_network())])
def test_cast_and_hash(obj): def test_cast_and_hash(obj) -> None:
assert type(str(obj)) is str assert type(str(obj)) is str
assert isinstance(hash(obj), int) assert isinstance(hash(obj), int)