From: Marcel van der Veldt Date: Fri, 11 Sep 2020 23:41:23 +0000 (+0200) Subject: more linting X-Git-Url: https://git.kitaultman.com/?a=commitdiff_plain;h=56471bda1b1baabdf9560d7d5474c246d7314773;p=music-assistant-server.git more linting --- diff --git a/.vscode/settings.json b/.vscode/settings.json index 17f512aa..9cbb6974 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,6 @@ { - "python.linting.pylintEnabled": false, + "python.linting.pylintEnabled": true, "python.linting.enabled": true, "python.pythonPath": "venv/bin/python3", - "python.linting.flake8Enabled": true + "python.linting.flake8Enabled": false } \ No newline at end of file diff --git a/music_assistant.egg-info/SOURCES.txt b/music_assistant.egg-info/SOURCES.txt index 09131263..2cc1352c 100644 --- a/music_assistant.egg-info/SOURCES.txt +++ b/music_assistant.egg-info/SOURCES.txt @@ -133,7 +133,7 @@ music_assistant/providers/chromecast/player.py music_assistant/providers/demo/__init__.py music_assistant/providers/demo/demo_musicprovider.py music_assistant/providers/demo/demo_playerprovider.py -music_assistant/providers/file/file.py +music_assistant/providers/file/__init__.py music_assistant/providers/home_assistant/__init__.py music_assistant/providers/qobuz/__init__.py music_assistant/providers/sonos/__init__.py @@ -149,7 +149,7 @@ music_assistant/providers/squeezebox/constants.py music_assistant/providers/squeezebox/discovery.py music_assistant/providers/squeezebox/socket_client.py music_assistant/providers/tunein/__init__.py -music_assistant/providers/webplayer/todo.py +music_assistant/providers/webplayer/__init__.py venv/lib/python3.7/site-packages/Pillow-7.2.0.dist-info/top_level.txt venv/lib/python3.7/site-packages/PyChromecast-7.2.1.dist-info/top_level.txt venv/lib/python3.7/site-packages/PyJWT-1.7.1.dist-info/entry_points.txt @@ -214,11 +214,6 @@ venv/lib/python3.7/site-packages/mccabe-0.6.1.dist-info/top_level.txt venv/lib/python3.7/site-packages/memory_tempfile-2.2.3.dist-info/LICENSE.txt venv/lib/python3.7/site-packages/more_itertools-8.5.0.dist-info/top_level.txt venv/lib/python3.7/site-packages/multidict-4.7.6.dist-info/top_level.txt -venv/lib/python3.7/site-packages/music_assistant-1.0.0-py3.7.egg/EGG-INFO/SOURCES.txt -venv/lib/python3.7/site-packages/music_assistant-1.0.0-py3.7.egg/EGG-INFO/dependency_links.txt -venv/lib/python3.7/site-packages/music_assistant-1.0.0-py3.7.egg/EGG-INFO/entry_points.txt -venv/lib/python3.7/site-packages/music_assistant-1.0.0-py3.7.egg/EGG-INFO/requires.txt -venv/lib/python3.7/site-packages/music_assistant-1.0.0-py3.7.egg/EGG-INFO/top_level.txt venv/lib/python3.7/site-packages/mypy-0.770.dist-info/entry_points.txt venv/lib/python3.7/site-packages/mypy-0.770.dist-info/top_level.txt venv/lib/python3.7/site-packages/mypy_extensions-0.4.3.dist-info/top_level.txt diff --git a/music_assistant/cache.py b/music_assistant/cache.py index 97e28c74..7c36d047 100644 --- a/music_assistant/cache.py +++ b/music_assistant/cache.py @@ -13,7 +13,7 @@ from music_assistant.utils import run_periodic LOGGER = logging.getLogger("mass") -class Cache(object): +class Cache: """Basic stateless caching system.""" _db = None @@ -100,8 +100,7 @@ class Cache(object): """Get int checksum from string.""" if not stringinput: return 0 - else: - stringinput = str(stringinput) + stringinput = str(stringinput) return reduce(lambda x, y: x + y, map(ord, stringinput)) @@ -150,15 +149,14 @@ def async_use_cache(cache_days=14, cache_checksum=None): cachedata = await method_class.cache.async_get(cache_str) if cachedata is not None: return cachedata - else: - result = await func(*args, **kwargs) - await method_class.cache.async_set( - cache_str, - result, - checksum=cache_checksum, - expiration=(86400 * cache_days), - ) - return result + result = await func(*args, **kwargs) + await method_class.cache.async_set( + cache_str, + result, + checksum=cache_checksum, + expiration=(86400 * cache_days), + ) + return result return async_wrapped diff --git a/music_assistant/config.py b/music_assistant/config.py index cc2a1e30..b82264e4 100755 --- a/music_assistant/config.py +++ b/music_assistant/config.py @@ -8,7 +8,7 @@ from enum import Enum from typing import List from cryptography.fernet import Fernet, InvalidToken -from music_assistant.app_vars import get_app_var # noqa +from music_assistant.app_vars import get_app_var # noqa # pylint: disable=all from music_assistant.constants import ( CONF_CROSSFADE_DURATION, CONF_ENABLED, diff --git a/music_assistant/http_streamer.py b/music_assistant/http_streamer.py index 0edceaa7..31b90b75 100755 --- a/music_assistant/http_streamer.py +++ b/music_assistant/http_streamer.py @@ -13,6 +13,7 @@ import shlex import signal import subprocess import threading +import urllib from contextlib import suppress import pyloudnorm @@ -298,18 +299,17 @@ class HTTPStreamer: if cancelled.is_set(): # break out the loop if the http session is cancelled break - else: - # update actual duration to the queue for more accurate now playing info - accurate_duration = bytes_written / int(sample_rate * 4 * 2) - queue_track.duration = accurate_duration - LOGGER.debug( - "Finished Streaming queue track: %s (%s) on player %s", - queue_track.item_id, - queue_track.name, - player_id, - ) - # run garbage collect manually to avoid too much memory fragmentation - gc.collect() + # update actual duration to the queue for more accurate now playing info + accurate_duration = bytes_written / int(sample_rate * 4 * 2) + queue_track.duration = accurate_duration + LOGGER.debug( + "Finished Streaming queue track: %s (%s) on player %s", + queue_track.item_id, + queue_track.name, + player_id, + ) + # run garbage collect manually to avoid too much memory fragmentation + gc.collect() # end of queue reached, pass last fadeout bits to final output if last_fadeout_data and not cancelled.is_set(): sox_proc.stdin.write(last_fadeout_data) @@ -412,10 +412,9 @@ class HTTPStreamer: # last chunk yield (True, prev_chunk + chunk) break - else: - if prev_chunk: - yield (False, prev_chunk) - prev_chunk = chunk + if prev_chunk: + yield (False, prev_chunk) + prev_chunk = chunk # fire event that streaming has ended self.mass.signal_event(EVENT_STREAM_ENDED, streamdetails) # send task to background to analyse the audio @@ -460,8 +459,6 @@ class HTTPStreamer: # only when needed we do the analyze stuff LOGGER.debug("Start analyzing track %s", item_key) if streamdetails.type == StreamType.URL: - import urllib - audio_data = urllib.request.urlopen(streamdetails.path).read() elif streamdetails.type == StreamType.EXECUTABLE: audio_data = subprocess.check_output(streamdetails.path, shell=True) diff --git a/music_assistant/mass.py b/music_assistant/mass.py index a9a21449..e542e6cd 100644 --- a/music_assistant/mass.py +++ b/music_assistant/mass.py @@ -226,7 +226,8 @@ class MusicAssistant: task = self.loop.run_in_executor(None, target, *args) # type: ignore return task - def __handle_exception(self, loop, context): + @staticmethod + def __handle_exception(loop, context): """Global exception handler.""" LOGGER.error("Caught exception: %s", context) loop.default_exception_handler(context) diff --git a/music_assistant/models/media_types.py b/music_assistant/models/media_types.py index a05afb2b..10f95de5 100755 --- a/music_assistant/models/media_types.py +++ b/music_assistant/models/media_types.py @@ -80,7 +80,7 @@ class ExternalId(str, Enum): @dataclass -class MediaItem(object): +class MediaItem: """Representation of a media item.""" item_id: str = "" diff --git a/music_assistant/models/player_queue.py b/music_assistant/models/player_queue.py index 3ea790e5..b6aaef9d 100755 --- a/music_assistant/models/player_queue.py +++ b/music_assistant/models/player_queue.py @@ -179,13 +179,12 @@ class PlayerQueue: if self.cur_index is None: # playback started return 0 - else: - # player already playing (or paused) so return the next item - if len(self.items) > (self.cur_index + 1): - return self.cur_index + 1 - if self._repeat_enabled: - # repeat enabled, start queue at beginning - return 0 + # player already playing (or paused) so return the next item + if len(self.items) > (self.cur_index + 1): + return self.cur_index + 1 + if self._repeat_enabled: + # repeat enabled, start queue at beginning + return 0 return None @property @@ -237,10 +236,9 @@ class PlayerQueue: return if self.use_queue_stream: return await self.async_play_index(self.cur_index + 1) - else: - return await self.mass.player_manager.get_player_provider( - self.player_id - ).async_cmd_next(self.player_id) + return await self.mass.player_manager.get_player_provider( + self.player_id + ).async_cmd_next(self.player_id) async def async_previous(self): """Play the previous track in the queue.""" @@ -248,8 +246,7 @@ class PlayerQueue: return if self.use_queue_stream: return await self.async_play_index(self.cur_index - 1) - else: - return await self.mass.player_manager.async_cmd_previous(self.player_id) + return await self.mass.player_manager.async_cmd_previous(self.player_id) async def async_resume(self): """Resume previous queue.""" @@ -288,7 +285,7 @@ class PlayerQueue: return await player_prov.async_cmd_play_uri( self.player_id, queue_stream_uri ) - elif supports_queue: + if supports_queue: try: return await player_prov.async_cmd_queue_play_index( self.player_id, index @@ -507,11 +504,12 @@ class PlayerQueue: if not music_prov: continue # provider temporary unavailable ? - streamdetails = await music_prov.async_get_stream_details( + streamdetails: StreamDetails = await music_prov.async_get_stream_details( prov_media.item_id ) if streamdetails: + streamdetails.player_id = player_id # set streamdetails as attribute on the queue_item queue_item.streamdetails = streamdetails return streamdetails diff --git a/music_assistant/models/playerprovider.py b/music_assistant/models/playerprovider.py index a4cfe8fb..ed89ecbf 100755 --- a/music_assistant/models/playerprovider.py +++ b/music_assistant/models/playerprovider.py @@ -30,7 +30,7 @@ class PlayerProvider(Provider): raise NotImplementedError @abstractmethod - async def async_cmd_stop(self, player_id: str): + async def async_cmd_stop(self, player_id: str) -> None: """ Send STOP command to given player. @@ -39,7 +39,7 @@ class PlayerProvider(Provider): raise NotImplementedError @abstractmethod - async def async_cmd_play(self, player_id: str): + async def async_cmd_play(self, player_id: str) -> None: """ Send PLAY command to given player. @@ -75,7 +75,7 @@ class PlayerProvider(Provider): raise NotImplementedError @abstractmethod - async def async_cmd_power_on(self, player_id: str): + async def async_cmd_power_on(self, player_id: str) -> None: """ Send POWER ON command to given player. @@ -84,7 +84,7 @@ class PlayerProvider(Provider): raise NotImplementedError @abstractmethod - async def async_cmd_power_off(self, player_id: str): + async def async_cmd_power_off(self, player_id: str) -> None: """ Send POWER OFF command to given player. @@ -93,7 +93,7 @@ class PlayerProvider(Provider): raise NotImplementedError @abstractmethod - async def async_cmd_volume_set(self, player_id: str, volume_level: int): + async def async_cmd_volume_set(self, player_id: str, volume_level: int) -> None: """ Send volume level command to given player. diff --git a/music_assistant/music_manager.py b/music_assistant/music_manager.py index 2c88381f..bfefcfa2 100755 --- a/music_assistant/music_manager.py +++ b/music_assistant/music_manager.py @@ -922,7 +922,7 @@ class MusicManager: if track_version.provider == playlist_prov.provider: track_ids_to_add.append(track_version.item_id) break - elif playlist_prov.provider == "file": + if playlist_prov.provider == "file": # the file provider can handle uri's from all providers so simply add the uri uri = f"{track_version.provider}://{track_version.item_id}" track_ids_to_add.append(uri) diff --git a/music_assistant/player_manager.py b/music_assistant/player_manager.py index e29da1a7..94b92ed7 100755 --- a/music_assistant/player_manager.py +++ b/music_assistant/player_manager.py @@ -251,7 +251,7 @@ class PlayerManager: if queue_opt == QueueOption.Add: return await player_queue.async_append(queue_items) - async def async_cmd_stop(self, player_id: str): + async def async_cmd_stop(self, player_id: str) -> None: """ Send STOP command to given player. @@ -260,7 +260,7 @@ class PlayerManager: # TODO: redirect playback related commands to parent player? return await self.get_player_provider(player_id).async_cmd_stop(player_id) - async def async_cmd_play(self, player_id: str): + async def async_cmd_play(self, player_id: str) -> None: """ Send PLAY command to given player. @@ -311,7 +311,7 @@ class PlayerManager: """ return await self.get_player_queue(player_id).async_previous() - async def async_cmd_power_on(self, player_id: str): + async def async_cmd_power_on(self, player_id: str) -> None: """ Send POWER ON command to given player. @@ -327,7 +327,7 @@ class PlayerManager: if control: self.mass.add_job(control.set_state, control.id, True) - async def async_cmd_power_off(self, player_id: str): + async def async_cmd_power_off(self, player_id: str) -> None: """ Send POWER OFF command to given player. @@ -360,7 +360,7 @@ class PlayerManager: return await self.async_cmd_power_off(player_id) return await self.async_cmd_power_on(player_id) - async def async_cmd_volume_set(self, player_id: str, volume_level: int): + async def async_cmd_volume_set(self, player_id: str, volume_level: int) -> None: """ Send volume level command to given player. @@ -559,7 +559,8 @@ class PlayerManager: return player.state @callback - def __get_player_mute_state(self, player: Player): + @classmethod + def __get_player_mute_state(cls, player: Player): """Get final/calculated player's mute state.""" # TODO: Handle VolumeControl plugin for mute state? return player.muted diff --git a/music_assistant/providers/chromecast/__init__.py b/music_assistant/providers/chromecast/__init__.py index 02e6ad88..feace029 100644 --- a/music_assistant/providers/chromecast/__init__.py +++ b/music_assistant/providers/chromecast/__init__.py @@ -82,7 +82,7 @@ class ChromecastProvider(PlayerProvider): """ self.mass.add_job(self._players[player_id].play_uri, uri) - async def async_cmd_stop(self, player_id: str): + async def async_cmd_stop(self, player_id: str) -> None: """ Send STOP command to given player. @@ -90,7 +90,7 @@ class ChromecastProvider(PlayerProvider): """ self.mass.add_job(self._players[player_id].stop) - async def async_cmd_play(self, player_id: str): + async def async_cmd_play(self, player_id: str) -> None: """ Send STOP command to given player. @@ -122,7 +122,7 @@ class ChromecastProvider(PlayerProvider): """ self.mass.add_job(self._players[player_id].previous) - async def async_cmd_power_on(self, player_id: str): + async def async_cmd_power_on(self, player_id: str) -> None: """ Send POWER ON command to given player. @@ -130,7 +130,7 @@ class ChromecastProvider(PlayerProvider): """ self.mass.add_job(self._players[player_id].power_on) - async def async_cmd_power_off(self, player_id: str): + async def async_cmd_power_off(self, player_id: str) -> None: """ Send POWER OFF command to given player. @@ -138,7 +138,7 @@ class ChromecastProvider(PlayerProvider): """ self.mass.add_job(self._players[player_id].power_off) - async def async_cmd_volume_set(self, player_id: str, volume_level: int): + async def async_cmd_volume_set(self, player_id: str, volume_level: int) -> None: """ Send volume level command to given player. diff --git a/music_assistant/providers/chromecast/player.py b/music_assistant/providers/chromecast/player.py index 4849a042..d39b6458 100644 --- a/music_assistant/providers/chromecast/player.py +++ b/music_assistant/providers/chromecast/player.py @@ -342,8 +342,7 @@ class ChromecastPlayer: queue_item.name = "Music Assistant" queue_item.uri = uri return self.queue_load([queue_item, queue_item]) - else: - self._chromecast.play_media(uri, "audio/flac") + self._chromecast.play_media(uri, "audio/flac") def queue_load(self, queue_items: List[QueueItem]): """Load (overwrite) queue with new items.""" diff --git a/music_assistant/providers/demo/demo_playerprovider.py b/music_assistant/providers/demo/demo_playerprovider.py index 45d91f0a..b8bd64b4 100644 --- a/music_assistant/providers/demo/demo_playerprovider.py +++ b/music_assistant/providers/demo/demo_playerprovider.py @@ -129,7 +129,7 @@ class DemoPlayerProvider(PlayerProvider): self.mass.add_job(self._players[player_id].vlc_player.set_media, media) self.mass.add_job(self._players[player_id].vlc_player.play) - async def async_cmd_stop(self, player_id: str): + async def async_cmd_stop(self, player_id: str) -> None: """ Send STOP command to given player. @@ -137,7 +137,7 @@ class DemoPlayerProvider(PlayerProvider): """ self.mass.add_job(self._players[player_id].vlc_player.stop) - async def async_cmd_play(self, player_id: str): + async def async_cmd_play(self, player_id: str) -> None: """ Send PLAY command to given player. @@ -170,7 +170,7 @@ class DemoPlayerProvider(PlayerProvider): """ self.mass.add_job(self._players[player_id].vlc_player.previous_chapter) - async def async_cmd_power_on(self, player_id: str): + async def async_cmd_power_on(self, player_id: str) -> None: """ Send POWER ON command to given player. @@ -181,7 +181,7 @@ class DemoPlayerProvider(PlayerProvider): self.mass.player_manager.async_update_player(self._players[player_id]) ) - async def async_cmd_power_off(self, player_id: str): + async def async_cmd_power_off(self, player_id: str) -> None: """ Send POWER OFF command to given player. @@ -193,7 +193,7 @@ class DemoPlayerProvider(PlayerProvider): self.mass.player_manager.async_update_player(self._players[player_id]) ) - async def async_cmd_volume_set(self, player_id: str, volume_level: int): + async def async_cmd_volume_set(self, player_id: str, volume_level: int) -> None: """ Send volume level command to given player. diff --git a/music_assistant/providers/file/__init__.py b/music_assistant/providers/file/__init__.py index 32279e50..f32559b8 100644 --- a/music_assistant/providers/file/__init__.py +++ b/music_assistant/providers/file/__init__.py @@ -59,6 +59,9 @@ class FileProvider(MusicProvider): Should be compatible with LMS """ + # pylint chokes on taglib so ignore these + # pylint: disable=unsubscriptable-object,unsupported-membership-test + _music_dir = None _playlists_dir = None @@ -114,34 +117,34 @@ class FileProvider(MusicProvider): async def async_get_library_artists(self) -> List[Artist]: """Retrieve all library artists.""" if not os.path.isdir(self._music_dir): - LOGGER.error("music path does not exist: %s" % self._music_dir) + LOGGER.error("music path does not exist: %s", self._music_dir) + yield None return - yield for dirname in os.listdir(self._music_dir): dirpath = os.path.join(self._music_dir, dirname) if os.path.isdir(dirpath) and not dirpath.startswith("."): - artist = await self.get_artist(dirpath) + artist = await self.async_get_artist(dirpath) if artist: yield artist async def async_get_library_albums(self) -> List[Album]: """Get album folders recursively.""" - async for artist in self.get_library_artists(): - async for album in self.get_artist_albums(artist.item_id): + async for artist in self.async_get_library_artists(): + async for album in self.async_get_artist_albums(artist.item_id): yield album async def async_get_library_tracks(self) -> List[Track]: """Get all tracks recursively.""" # TODO: support disk subfolders - async for album in self.get_library_albums(): - async for track in self.get_album_tracks(album.item_id): + async for album in self.async_get_library_albums(): + async for track in self.async_get_album_tracks(album.item_id): yield track async def async_get_library_playlists(self) -> List[Playlist]: """Retrieve playlists from disk.""" if not self._playlists_dir: + yield None return - yield for filename in os.listdir(self._playlists_dir): filepath = os.path.join(self._playlists_dir, filename) if ( @@ -149,23 +152,23 @@ class FileProvider(MusicProvider): and not filename.startswith(".") and filename.lower().endswith(".m3u") ): - playlist = await self.get_playlist(filepath) + playlist = await self.async_get_playlist(filepath) if playlist: yield playlist - async def async_get_artist(self, prov_item_id: str) -> Artist: + async def async_get_artist(self, prov_artist_id: str) -> Artist: """Get full artist details by id.""" - if os.sep not in prov_item_id: - itempath = base64.b64decode(prov_item_id).decode("utf-8") + if os.sep not in prov_artist_id: + itempath = base64.b64decode(prov_artist_id).decode("utf-8") else: - itempath = prov_item_id - prov_item_id = base64.b64encode(itempath.encode("utf-8")).decode("utf-8") + itempath = prov_artist_id + prov_artist_id = base64.b64encode(itempath.encode("utf-8")).decode("utf-8") if not os.path.isdir(itempath): - LOGGER.error("Artist path does not exist: %s" % itempath) + LOGGER.error("Artist path does not exist: %s", itempath) return None name = itempath.split(os.sep)[-1] artist = Artist() - artist.item_id = prov_item_id + artist.item_id = prov_artist_id artist.provider = PROV_ID artist.name = name artist.provider_ids.append( @@ -173,58 +176,60 @@ class FileProvider(MusicProvider): ) return artist - async def async_get_album(self, prov_item_id: str) -> Album: + async def async_get_album(self, prov_album_id: str) -> Album: """Get full album details by id.""" - if os.sep not in prov_item_id: - itempath = base64.b64decode(prov_item_id).decode("utf-8") + if os.sep not in prov_album_id: + itempath = base64.b64decode(prov_album_id).decode("utf-8") else: - itempath = prov_item_id - prov_item_id = base64.b64encode(itempath.encode("utf-8")).decode("utf-8") + itempath = prov_album_id + prov_album_id = base64.b64encode(itempath.encode("utf-8")).decode("utf-8") if not os.path.isdir(itempath): - LOGGER.error("album path does not exist: %s" % itempath) + LOGGER.error("album path does not exist: %s", itempath) return None name = itempath.split(os.sep)[-1] artistpath = itempath.rsplit(os.sep, 1)[0] album = Album() - album.item_id = prov_item_id - album.provider = self.prov_id + album.item_id = prov_album_id + album.provider = PROV_ID album.name, album.version = parse_title_and_version(name) - album.artist = await self.get_artist(artistpath) + album.artist = await self.async_get_artist(artistpath) if not album.artist: raise Exception("No album artist ! %s" % artistpath) album.provider_ids.append( - MediaItemProviderId(provider=PROV_ID, item_id=prov_item_id) + MediaItemProviderId(provider=PROV_ID, item_id=prov_album_id) ) return album - async def async_get_track(self, prov_item_id: str) -> Track: + async def async_get_track(self, prov_track_id: str) -> Track: """Get full track details by id.""" - if os.sep not in prov_item_id: - itempath = base64.b64decode(prov_item_id).decode("utf-8") + if os.sep not in prov_track_id: + itempath = base64.b64decode(prov_track_id).decode("utf-8") else: - itempath = prov_item_id + itempath = prov_track_id if not os.path.isfile(itempath): LOGGER.error("track path does not exist: %s", itempath) return None - return await self.__parse_track(itempath) + return await self.__async_parse_track(itempath) - async def async_get_playlist(self, prov_item_id: str) -> Playlist: + async def async_get_playlist(self, prov_playlist_id: str) -> Playlist: """Get full playlist details by id.""" - if os.sep not in prov_item_id: - itempath = base64.b64decode(prov_item_id).decode("utf-8") + if os.sep not in prov_playlist_id: + itempath = base64.b64decode(prov_playlist_id).decode("utf-8") else: - itempath = prov_item_id - prov_item_id = base64.b64encode(itempath.encode("utf-8")).decode("utf-8") + itempath = prov_playlist_id + prov_playlist_id = base64.b64encode(itempath.encode("utf-8")).decode( + "utf-8" + ) if not os.path.isfile(itempath): - LOGGER.error("playlist path does not exist: %s" % itempath) + LOGGER.error("playlist path does not exist: %s", itempath) return None playlist = Playlist() - playlist.item_id = prov_item_id - playlist.provider = self.prov_id + playlist.item_id = prov_playlist_id + playlist.provider = PROV_ID playlist.name = itempath.split(os.sep)[-1].replace(".m3u", "") playlist.is_editable = True playlist.provider_ids.append( - MediaItemProviderId(provider=PROV_ID, item_id=prov_item_id) + MediaItemProviderId(provider=PROV_ID, item_id=prov_playlist_id) ) playlist.owner = "disk" playlist.checksum = os.path.getmtime(itempath) @@ -237,42 +242,35 @@ class FileProvider(MusicProvider): else: albumpath = prov_album_id if not os.path.isdir(albumpath): - LOGGER.error("album path does not exist: %s" % albumpath) + LOGGER.error("album path does not exist: %s", albumpath) return - album = await self.get_album(albumpath) + album = await self.async_get_album(albumpath) for filename in os.listdir(albumpath): filepath = os.path.join(albumpath, filename) if os.path.isfile(filepath) and not filepath.startswith("."): - track = await self.__parse_track(filepath) + track = await self.__async_parse_track(filepath) if track: track.album = album yield track - async def async_get_playlist_tracks( - self, prov_playlist_id: str, limit: int = 50, offset: int = 0 - ) -> List[Track]: + async def async_get_playlist_tracks(self, prov_playlist_id: str) -> List[Track]: """Get playlist tracks for given playlist id.""" if os.sep not in prov_playlist_id: itempath = base64.b64decode(prov_playlist_id).decode("utf-8") else: itempath = prov_playlist_id if not os.path.isfile(itempath): - LOGGER.error("playlist path does not exist: %s" % itempath) + LOGGER.error("playlist path does not exist: %s", itempath) return - counter = 0 index = 0 - with open(itempath) as f: - for line in f.readlines(): + with open(itempath) as _file: + for line in _file.readlines(): line = line.strip() if line and not line.startswith("#"): - counter += 1 - if counter > offset: - track = await self.__parse_track_from_uri(line) - if track: - yield track - index += 1 - if limit and index == limit: - break + track = await self.__async_parse_track_from_uri(line) + if track: + yield track + index += 1 async def async_get_artist_albums(self, prov_artist_id: str) -> List[Album]: """Get a list of albums for the given artist.""" @@ -281,34 +279,34 @@ class FileProvider(MusicProvider): else: artistpath = prov_artist_id if not os.path.isdir(artistpath): - LOGGER.error("artist path does not exist: %s" % artistpath) + LOGGER.error("artist path does not exist: %s", artistpath) return for dirname in os.listdir(artistpath): dirpath = os.path.join(artistpath, dirname) if os.path.isdir(dirpath) and not dirpath.startswith("."): - album = await self.get_album(dirpath) + album = await self.async_get_album(dirpath) if album: yield album async def async_get_artist_toptracks(self, prov_artist_id: str) -> List[Track]: """Get a list of random tracks as we have no clue about preference.""" - async for album in self.get_artist_albums(prov_artist_id): - async for track in self.get_album_tracks(album.item_id): + async for album in self.async_get_artist_albums(prov_artist_id): + async for track in self.async_get_album_tracks(album.item_id): yield track - async def async_get_stream_details(self, track_id): + async def async_get_stream_details(self, item_id: str) -> StreamDetails: """Return the content details for the given track when it will be streamed.""" - if os.sep not in track_id: - track_id = base64.b64decode(track_id).decode("utf-8") + if os.sep not in item_id: + track_id = base64.b64decode(item_id).decode("utf-8") if not os.path.isfile(track_id): return None # TODO: retrieve sanple rate and bitdepth return StreamDetails( type=StreamType.FILE, provider=PROV_ID, - item_id=track_id, - content_type=ContentType(track_id.split(".")[-1]), - path=track_id, + item_id=item_id, + content_type=ContentType(item_id.split(".")[-1]), + path=item_id, sample_rate=44100, bit_depth=16, ) @@ -324,16 +322,16 @@ class FileProvider(MusicProvider): prov_item_id = base64.b64encode(filename.encode("utf-8")).decode("utf-8") track.duration = song.length track.item_id = prov_item_id - track.provider = self.prov_id + track.provider = PROV_ID name = song.tags["TITLE"][0] track.name, track.version = parse_title_and_version(name) albumpath = filename.rsplit(os.sep, 1)[0] - track.album = await self.get_album(albumpath) + track.album = await self.async_get_album(albumpath) artists = [] for artist_str in song.tags["ARTIST"]: local_artist_path = os.path.join(self._music_dir, artist_str) if os.path.isfile(local_artist_path): - artist = await self.get_artist(local_artist_path) + artist = await self.async_get_artist(local_artist_path) else: artist = Artist() artist.name = artist_str @@ -389,6 +387,7 @@ class FileProvider(MusicProvider): async def __async_parse_track_from_uri(self, uri): """Try to parse a track from an uri found in playlist.""" + # pylint: disable=broad-except if "://" in uri: # track is uri from external provider? prov_id = uri.split("://")[0] @@ -398,7 +397,7 @@ class FileProvider(MusicProvider): prov_item_id, prov_id, lazy=False ) except Exception as exc: - LOGGER.warning("Could not parse uri %s to track: %s" % (uri, str(exc))) + LOGGER.warning("Could not parse uri %s to track: %s", uri, str(exc)) return None # try to treat uri as filename # TODO: filename could be related to musicdir or full path diff --git a/music_assistant/providers/home_assistant/__init__.py b/music_assistant/providers/home_assistant/__init__.py index 7e10da23..346138e0 100644 --- a/music_assistant/providers/home_assistant/__init__.py +++ b/music_assistant/providers/home_assistant/__init__.py @@ -232,7 +232,7 @@ class HomeAssistantPlugin(Provider): return await self.mass.player_manager.async_play_media( player_id, media_items, queue_opt ) - elif "spotify://playlist" in media_content_id: + if "spotify://playlist" in media_content_id: # TODO: handle parsing of other uri's here playlist = await self.mass.music_manager.async_getplaylist( "spotify", media_content_id.split(":")[-1] diff --git a/music_assistant/providers/qobuz/__init__.py b/music_assistant/providers/qobuz/__init__.py index ab157f8b..23cffe93 100644 --- a/music_assistant/providers/qobuz/__init__.py +++ b/music_assistant/providers/qobuz/__init__.py @@ -7,7 +7,7 @@ from typing import List, Optional import aiohttp from asyncio_throttle import Throttler -from music_assistant.app_vars import get_app_var # noqa +from music_assistant.app_vars import get_app_var # noqa # pylint: disable=all from music_assistant.constants import ( CONF_PASSWORD, CONF_USERNAME, diff --git a/music_assistant/providers/sonos/sonos.py b/music_assistant/providers/sonos/sonos.py index f6921849..bd771639 100644 --- a/music_assistant/providers/sonos/sonos.py +++ b/music_assistant/providers/sonos/sonos.py @@ -67,7 +67,7 @@ class SonosProvider(PlayerProvider): else: LOGGER.warning("Received command for unavailable player: %s", player_id) - async def async_cmd_stop(self, player_id: str): + async def async_cmd_stop(self, player_id: str) -> None: """ Send STOP command to given player. @@ -79,7 +79,7 @@ class SonosProvider(PlayerProvider): else: LOGGER.warning("Received command for unavailable player: %s", player_id) - async def async_cmd_play(self, player_id: str): + async def async_cmd_play(self, player_id: str) -> None: """ Send STOP command to given player. @@ -127,7 +127,7 @@ class SonosProvider(PlayerProvider): else: LOGGER.warning("Received command for unavailable player: %s", player_id) - async def async_cmd_power_on(self, player_id: str): + async def async_cmd_power_on(self, player_id: str) -> None: """ Send POWER ON command to given player. @@ -141,7 +141,7 @@ class SonosProvider(PlayerProvider): else: LOGGER.warning("Received command for unavailable player: %s", player_id) - async def async_cmd_power_off(self, player_id: str): + async def async_cmd_power_off(self, player_id: str) -> None: """ Send POWER OFF command to given player. @@ -156,7 +156,7 @@ class SonosProvider(PlayerProvider): else: LOGGER.warning("Received command for unavailable player: %s", player_id) - async def async_cmd_volume_set(self, player_id: str, volume_level: int): + async def async_cmd_volume_set(self, player_id: str, volume_level: int) -> None: """ Send volume level command to given player. @@ -244,8 +244,7 @@ class SonosProvider(PlayerProvider): return await self.async_cmd_queue_insert( player_id, queue_items, len(player_queue.items) ) - else: - LOGGER.warning("Received command for unavailable player: %s", player_id) + LOGGER.warning("Received command for unavailable player: %s", player_id) async def async_cmd_queue_clear(self, player_id: str): """ diff --git a/music_assistant/providers/spotify/__init__.py b/music_assistant/providers/spotify/__init__.py index 8d1c9d58..f3645197 100644 --- a/music_assistant/providers/spotify/__init__.py +++ b/music_assistant/providers/spotify/__init__.py @@ -9,7 +9,7 @@ from typing import List, Optional import aiohttp from asyncio_throttle import Throttler -from music_assistant.app_vars import get_app_var # noqa +from music_assistant.app_vars import get_app_var # noqa # pylint: disable=all from music_assistant.constants import CONF_PASSWORD, CONF_USERNAME from music_assistant.models.config_entry import ConfigEntry, ConfigEntryType from music_assistant.models.media_types import ( diff --git a/music_assistant/providers/squeezebox/__init__.py b/music_assistant/providers/squeezebox/__init__.py index 3e1d1c35..a9336f4a 100644 --- a/music_assistant/providers/squeezebox/__init__.py +++ b/music_assistant/providers/squeezebox/__init__.py @@ -86,7 +86,7 @@ class PySqueezeProvider(PlayerProvider): else: LOGGER.warning("Received command for unavailable player: %s", player_id) - async def async_cmd_stop(self, player_id: str): + async def async_cmd_stop(self, player_id: str) -> None: """ Send STOP command to given player. @@ -98,7 +98,7 @@ class PySqueezeProvider(PlayerProvider): else: LOGGER.warning("Received command for unavailable player: %s", player_id) - async def async_cmd_play(self, player_id: str): + async def async_cmd_play(self, player_id: str) -> None: """ Send PLAY command to given player. @@ -146,7 +146,7 @@ class PySqueezeProvider(PlayerProvider): if new_track: await self.async_cmd_play_uri(player_id, new_track.uri) - async def async_cmd_power_on(self, player_id: str): + async def async_cmd_power_on(self, player_id: str) -> None: """ Send POWER ON command to given player. @@ -163,7 +163,7 @@ class PySqueezeProvider(PlayerProvider): else: LOGGER.warning("Received command for unavailable player: %s", player_id) - async def async_cmd_power_off(self, player_id: str): + async def async_cmd_power_off(self, player_id: str) -> None: """ Send POWER OFF command to given player. @@ -181,7 +181,7 @@ class PySqueezeProvider(PlayerProvider): else: LOGGER.warning("Received command for unavailable player: %s", player_id) - async def async_cmd_volume_set(self, player_id: str, volume_level: int): + async def async_cmd_volume_set(self, player_id: str, volume_level: int) -> None: """ Send volume level command to given player. @@ -261,7 +261,7 @@ class PySqueezeProvider(PlayerProvider): :param player_id: player_id of the player to handle the command. :param queue_items: a list of QueueItems """ - pass # automagically handled by built-in queue controller + # automagically handled by built-in queue controller async def async_cmd_queue_update( self, player_id: str, queue_items: List[QueueItem] @@ -272,7 +272,7 @@ class PySqueezeProvider(PlayerProvider): :param player_id: player_id of the player to handle the command. :param queue_items: a list of QueueItems """ - pass # automagically handled by built-in queue controller + # automagically handled by built-in queue controller async def async_cmd_queue_clear(self, player_id: str): """ @@ -285,7 +285,7 @@ class PySqueezeProvider(PlayerProvider): async def async_start_discovery(self): """Start discovery for players.""" - transport, protocol = await self.mass.loop.create_datagram_endpoint( + transport, _ = await self.mass.loop.create_datagram_endpoint( lambda: DiscoveryProtocol(self.mass.web.http_port), local_addr=("0.0.0.0", 3483), ) diff --git a/music_assistant/providers/squeezebox/discovery.py b/music_assistant/providers/squeezebox/discovery.py index d5ae0b80..39bcc44d 100644 --- a/music_assistant/providers/squeezebox/discovery.py +++ b/music_assistant/providers/squeezebox/discovery.py @@ -18,17 +18,17 @@ class Datagram: """Decode a datagram message.""" if data[0] == "e": return TLVDiscoveryRequestDatagram(data) - elif data[0] == "E": + if data[0] == "E": return TLVDiscoveryResponseDatagram(data) - elif data[0] == "d": + if data[0] == "d": return ClientDiscoveryDatagram(data) - elif data[0] == "h": + if data[0] == "h": pass # Hello! - elif data[0] == "i": + if data[0] == "i": pass # IR - elif data[0] == "2": + if data[0] == "2": pass # i2c? - elif data[0] == "a": + if data[0] == "a": pass # ack! @@ -127,11 +127,13 @@ class DiscoveryProtocol: mreq = struct.pack("4sL", group, socket.INADDR_ANY) sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq) - def error_received(self, exc): + @classmethod + def error_received(cls, exc): """Call on Error.""" LOGGER.error(exc) - def connection_lost(self, *args, **kwargs): + @classmethod + def connection_lost(cls, *args, **kwargs): """Call on Connection lost.""" # pylint: disable=unused-argument LOGGER.debug("Connection lost to discovery") diff --git a/music_assistant/providers/squeezebox/socket_client.py b/music_assistant/providers/squeezebox/socket_client.py index 9c1930ab..89c3b5c6 100644 --- a/music_assistant/providers/squeezebox/socket_client.py +++ b/music_assistant/providers/squeezebox/socket_client.py @@ -265,8 +265,8 @@ class SqueezeSocketClient: self.close() @callback + @staticmethod def __pack_stream( - self, command, autostart=b"1", formatbyte=b"o", @@ -360,7 +360,8 @@ class SqueezeSocketClient: asyncio.create_task(self._event_callback(Event.EVENT_UPDATED, self)) @callback - def _process_stat_stmo(self, data): + @classmethod + def _process_stat_stmo(cls, data): """ Process incoming stat STMo message. @@ -448,7 +449,7 @@ class SqueezeSocketClient: asyncio.create_task(self._event_callback(Event.EVENT_UPDATED, self)) -class PySqueezeVolume(object): +class PySqueezeVolume: """Represents a sound volume. This is an awful lot more complex than it sounds.""" minimum = 0 diff --git a/music_assistant/providers/webplayer/__init__.py b/music_assistant/providers/webplayer/__init__.py index 528913e3..c623d26b 100644 --- a/music_assistant/providers/webplayer/__init__.py +++ b/music_assistant/providers/webplayer/__init__.py @@ -79,26 +79,26 @@ class WebPlayerProvider(PlayerProvider): await self.mass.player_manager.async_add_player(player) elif msg == EVENT_WEBPLAYER_STATE: - await self.__handle_player_state(msg_details) + await self.__async_handle_player_state(msg_details) @run_periodic(30) - async def async_check_players(self): + async def async_check_players(self) -> None: """Invalidate players that did not send a heartbeat message in a while.""" cur_time = time.time() offline_players = [] for player in self._players.values(): - if cur_time - player._last_message > 30: + if cur_time - player.last_message > 30: offline_players.append(player.player_id) for player_id in offline_players: await self.mass.player_manager.async_remove_player(player_id) self._players.pop(player_id, None) - async def async_cmd_stop(self, player_id: str): + async def async_cmd_stop(self, player_id: str) -> None: """Send stop command to player.""" data = {"player_id": player_id, "cmd": "stop"} self.mass.signal_event(EVENT_WEBPLAYER_CMD, data) - async def async_cmd_play(self, player_id: str): + async def async_cmd_play(self, player_id: str) -> None: """Send play command to player.""" data = {"player_id": player_id, "cmd": "play"} self.mass.signal_event(EVENT_WEBPLAYER_CMD, data) @@ -108,18 +108,18 @@ class WebPlayerProvider(PlayerProvider): data = {"player_id": player_id, "cmd": "pause"} self.mass.signal_event(EVENT_WEBPLAYER_CMD, data) - async def async_cmd_power_on(self, player_id: str): + async def async_cmd_power_on(self, player_id: str) -> None: """Send power ON command to player.""" self._players[player_id].powered = True # not supported on webplayer data = {"player_id": player_id, "cmd": "stop"} self.mass.signal_event(EVENT_WEBPLAYER_CMD, data) - async def async_cmd_power_off(self, player_id: str): + async def async_cmd_power_off(self, player_id: str) -> None: """Send power OFF command to player.""" await self.async_cmd_stop(player_id) self._players[player_id].powered = False - async def async_cmd_volume_set(self, volume_level, player_id: str): + async def async_cmd_volume_set(self, player_id: str, volume_level: int) -> None: """Send new volume level command to player.""" data = { "player_id": player_id, @@ -156,5 +156,5 @@ class WebPlayerProvider(PlayerProvider): player.powered = data["powered"] if "name" in data: player.name = data["name"] - player._last_message = time.time() + player.last_message = time.time() self.mass.add_job(self.mass.player_manager.async_update_player(player)) diff --git a/music_assistant/utils.py b/music_assistant/utils.py index 0117a161..5b330a92 100755 --- a/music_assistant/utils.py +++ b/music_assistant/utils.py @@ -127,8 +127,7 @@ def try_parse_bool(possible_bool): """Try to parse a bool.""" if isinstance(possible_bool, bool): return possible_bool - else: - return possible_bool in ["true", "True", "1", "on", "ON", 1] + return possible_bool in ["true", "True", "1", "on", "ON", 1] def parse_title_and_version(track_title, track_version=None): diff --git a/pylintrc b/pylintrc index c3e21eae..0b60c452 100644 --- a/pylintrc +++ b/pylintrc @@ -1,9 +1,12 @@ [MASTER] ignore=tests +ignore-patterns=app_vars # Use a conservative default here; 2 should speed up most setups and not hurt # any too bad. Override on command line as appropriate. jobs=2 persistent=no +suggestion-mode=yes +extension-pkg-whitelist=taglib [BASIC] good-names=id,i,j,k,ex,Run,_,fp,T,ev @@ -22,6 +25,7 @@ good-names=id,i,j,k,ex,Run,_,fp,T,ev # inconsistent-return-statements - doesn't handle raise # too-many-ancestors - it's too strict. # wrong-import-order - isort guards this +# fixme - project is in development phase disable= format, abstract-class-little-used, @@ -43,7 +47,8 @@ disable= too-many-statements, too-many-boolean-expressions, unused-argument, - wrong-import-order + wrong-import-order, + fixme # enable useless-suppression temporarily every now and then to clean them up enable= use-symbolic-message-instead @@ -51,6 +56,11 @@ enable= [REPORTS] score=no +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=15 + [TYPECHECK] # For attrs ignored-classes=_CountingAttr diff --git a/setup.cfg b/setup.cfg index c8ed9243..c2987645 100644 --- a/setup.cfg +++ b/setup.cfg @@ -16,6 +16,7 @@ ignore = E266 [isort] +profile = black multi_line_output = 3 include_trailing_comma = True force_grid_wrap = 0 @@ -23,15 +24,10 @@ use_parentheses = True line_length = 88 [mypy] -follow_imports = skip +python_version = 3.7 +ignore_errors = true +follow_imports = silent ignore_missing_imports = true -check_untyped_defs = true -disallow_incomplete_defs = true -disallow_untyped_calls = true -disallow_untyped_defs = true -warn_return_any = true -warn_unreachable = true -warn_unused_ignores = true warn_incomplete_stub = true warn_redundant_casts = true warn_unused_configs = true