linting
authorMarcel van der Veldt <m.vanderveldt@outlook.com>
Fri, 11 Sep 2020 18:17:00 +0000 (20:17 +0200)
committerMarcel van der Veldt <m.vanderveldt@outlook.com>
Fri, 11 Sep 2020 18:17:00 +0000 (20:17 +0200)
45 files changed:
.vscode/settings.json
music_assistant/__main__.py
music_assistant/app_vars.py
music_assistant/cache.py
music_assistant/config.py
music_assistant/constants.py
music_assistant/database.py
music_assistant/http_streamer.py
music_assistant/mass.py
music_assistant/metadata.py
music_assistant/models/__init__.py
music_assistant/models/config_entry.py
music_assistant/models/media_types.py
music_assistant/models/musicprovider.py
music_assistant/models/player.py
music_assistant/models/player_queue.py
music_assistant/models/playerprovider.py
music_assistant/models/provider.py
music_assistant/models/streamdetails.py
music_assistant/music_manager.py
music_assistant/player_manager.py
music_assistant/providers/__init__.py
music_assistant/providers/chromecast/__init__.py
music_assistant/providers/chromecast/const.py
music_assistant/providers/chromecast/models.py
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/home_assistant/__init__.py
music_assistant/providers/qobuz/__init__.py
music_assistant/providers/sonos/__init__.py
music_assistant/providers/sonos/sonos.py
music_assistant/providers/spotify/__init__.py
music_assistant/providers/squeezebox/__init__.py
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/utils.py
music_assistant/web.py
setup.cfg
setup.py

index 42b4250bf9dd1500d35ef9f1007ae7f2c9296e59..17f512aa3f6f09588260248eb96181071a94daa5 100644 (file)
@@ -1,5 +1,6 @@
 {
-    "python.linting.pylintEnabled": true,
+    "python.linting.pylintEnabled": false,
     "python.linting.enabled": true,
-    "python.pythonPath": "venv/bin/python3"
+    "python.pythonPath": "venv/bin/python3",
+    "python.linting.flake8Enabled": true
 }
\ No newline at end of file
index af48b8185e457093e3554df28ab371f98408932f..6868b79f1f3be55a23afb25a736c5b7bd7b467b5 100755 (executable)
@@ -1,10 +1,7 @@
 """Start Music Assistant."""
 import argparse
-import asyncio
 import logging
 import os
-import platform
-import sys
 
 from aiorun import run
 from music_assistant.mass import MusicAssistant
@@ -27,7 +24,9 @@ def get_arguments():
         help="Directory that contains the MusicAssistant configuration",
     )
     parser.add_argument(
-        "--debug", action="store_true", help="Start MusicAssistant with verbose debug logging"
+        "--debug",
+        action="store_true",
+        help="Start MusicAssistant with verbose debug logging",
     )
     arguments = parser.parse_args()
     return arguments
@@ -65,8 +64,9 @@ def main():
     def on_shutdown(loop):
         logger.info("shutdown requested!")
         loop.run_until_complete(mass.async_stop())
-        
+
     run(mass.async_start(), use_uvloop=True, shutdown_callback=on_shutdown)
 
+
 if __name__ == "__main__":
     main()
index 9d99597f9a1a71a921ce2487f4d15f1a716fef80..b010396988890957b8d539b4e18ad2ba4dc87d35 100644 (file)
@@ -1 +1,33 @@
-(lambda __g: [[[[None for __g['get_app_var'], get_app_var.__name__ in [(lambda index: (lambda __l: [APP_VARS[__l['index']] for __l['index'] in [(index)]][0])({}), 'get_app_var')]][0] for __g['APP_VARS'] in [(base64.b64decode(VARS_ENC).decode('utf-8').split(','))]][0] for __g['VARS_ENC'] in [(b'OTQyODUyNTY3LDc2MTczMGQzZjk1ZTRhZjA5YWM2M2I5YTM3Y2NjOTZhLDJlYjk2ZjliMzc0OTRiZTE4MjQ5OTlkNTgwMjhhMzA1LFNTcnRNMnhlM2wwMDNnOEh4RmVUUUtub3BaNklCaUwzRTlPc1QxODFYMDA9')]][0] for __g['base64'] in [(__import__('base64', __g, __g))]][0])(globals())
+"""Some magic to store some appvars."""
+# pylint: skip-file
+# flake8: noqa
+(
+    lambda __g: [
+        [
+            [
+                [
+                    None
+                    for __g["get_app_var"], get_app_var.__name__ in [
+                        (
+                            lambda index: (
+                                lambda __l: [
+                                    APP_VARS[__l["index"]] for __l["index"] in [(index)]
+                                ][0]
+                            )({}),
+                            "get_app_var",
+                        )
+                    ]
+                ][0]
+                for __g["APP_VARS"] in [
+                    (base64.b64decode(VARS_ENC).decode("utf-8").split(","))
+                ]
+            ][0]
+            for __g["VARS_ENC"] in [
+                (
+                    b"OTQyODUyNTY3LDc2MTczMGQzZjk1ZTRhZjA5YWM2M2I5YTM3Y2NjOTZhLDJlYjk2ZjliMzc0OTRiZTE4MjQ5OTlkNTgwMjhhMzA1LFNTcnRNMnhlM2wwMDNnOEh4RmVUUUtub3BaNklCaUwzRTlPc1QxODFYMDA9"
+                )
+            ]
+        ][0]
+        for __g["base64"] in [(__import__("base64", __g, __g))]
+    ][0]
+)(globals())
index 84436be042e4a1b5393f85db40e4148123267feb..97e28c74104913489358e7f041257531c3eb541f 100644 (file)
@@ -14,7 +14,7 @@ LOGGER = logging.getLogger("mass")
 
 
 class Cache(object):
-    """basic stateless caching system."""
+    """Basic stateless caching system."""
 
     _db = None
 
@@ -35,13 +35,13 @@ class Cache(object):
             await db_conn.commit()
         self.mass.add_job(self.async_auto_cleanup())
 
-
     async def async_get(self, cache_key, checksum=""):
         """
-            get object from cache and return the results
-            cache_key: the (unique) name of the cache object as reference
-            checkum: optional argument to check if the checksum in the
-                     cacheobject matches the checkum provided
+        Get object from cache and return the results.
+
+        cache_key: the (unique) name of the cache object as reference
+        checkum: optional argument to check if the checksum in the
+                    cacheobject matches the checkum provided
         """
         result = None
         cur_time = int(time.time())
@@ -64,9 +64,7 @@ class Cache(object):
         return result
 
     async def async_set(self, cache_key, data, checksum="", expiration=(86400 * 30)):
-        """
-            set data in cache
-        """
+        """Set data in cache."""
         checksum = self._get_checksum(checksum)
         expires = int(time.time() + expiration)
         data = pickle.dumps(data)
@@ -78,7 +76,7 @@ class Cache(object):
 
     @run_periodic(3600)
     async def async_auto_cleanup(self):
-        """(scheduled) auto cleanup task"""
+        """Sceduled auto cleanup task."""
         cur_timestamp = int(time.time())
         LOGGER.debug("Running cleanup...")
         sql_query = "SELECT id, expires FROM simplecache"
@@ -99,7 +97,7 @@ class Cache(object):
 
     @staticmethod
     def _get_checksum(stringinput):
-        """get int checksum from string"""
+        """Get int checksum from string."""
         if not stringinput:
             return 0
         else:
@@ -107,8 +105,10 @@ class Cache(object):
         return reduce(lambda x, y: x + y, map(ord, stringinput))
 
 
-async def async_cached_generator(cache, cache_key, coro_func, expires=(86400 * 30), checksum=None):
-    """Helper method to store results of a async generator in the cache."""
+async def async_cached_generator(
+    cache, cache_key, coro_func, expires=(86400 * 30), checksum=None
+):
+    """Return helper method to store results of a async generator in the cache."""
     cache_result = await cache.async_get(cache_key, checksum)
     if cache_result is not None:
         for item in cache_result:
@@ -123,8 +123,10 @@ async def async_cached_generator(cache, cache_key, coro_func, expires=(86400 * 3
         await cache.async_set(cache_key, cache_result, checksum, expires)
 
 
-async def async_cached(cache, cache_key, coro_func, expires=(86400 * 30), checksum=None):
-    """Helper method to store results of a coroutine in the cache."""
+async def async_cached(
+    cache, cache_key, coro_func, expires=(86400 * 30), checksum=None
+):
+    """Return helper method to store results of a coroutine in the cache."""
     cache_result = await cache.async_get(cache_key, checksum)
     # normal async function
     if cache_result is not None:
@@ -135,7 +137,7 @@ async def async_cached(cache, cache_key, coro_func, expires=(86400 * 30), checks
 
 
 def async_use_cache(cache_days=14, cache_checksum=None):
-    """Decorator that can be used to cache a method's result."""
+    """Return decorator that can be used to cache a method's result."""
 
     def wrapper(func):
         @functools.wraps(func)
index 6084cf00c092a87df5d119b74cd2dd3a4a2ec0df..cc2a1e301f4f04daa735dd378b408bc778be19e0 100755 (executable)
@@ -1,6 +1,5 @@
 """All classes and helpers for the Configuration."""
 
-import base64
 import logging
 import os
 import shutil
@@ -9,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
+from music_assistant.app_vars import get_app_var  # noqa
 from music_assistant.constants import (
     CONF_CROSSFADE_DURATION,
     CONF_ENABLED,
@@ -20,8 +19,6 @@ from music_assistant.constants import (
     CONF_NAME,
     EVENT_CONFIG_CHANGED,
 )
-
-# from music_assistant.mass import MusicAssistant
 from music_assistant.models.config_entry import ConfigEntry, ConfigEntryType
 from music_assistant.utils import get_external_ip, json, try_load_json_file
 from passlib.hash import pbkdf2_sha256
@@ -150,16 +147,19 @@ class ConfigBaseType(Enum):
 class ConfigItem:
     """
     Configuration Item connected to Config Entries.
+
     Returns default value from config entry if no value present.
     """
 
     def __init__(self, mass, parent_item_key: str, base_type: ConfigBaseType):
+        """Initialize class."""
         self._parent_item_key = parent_item_key
         self._base_type = base_type
         self.mass = mass
         self.stored_config = OrderedDict()
 
     def __repr__(self):
+        """Print class."""
         return f"{OrderedDict}({self.items()})"
 
     def items(self) -> dict:
@@ -218,12 +218,16 @@ class ConfigItem:
                     raise ValueError
             else:
                 # single value item
-                if entry.entry_type == ConfigEntryType.STRING and not isinstance(value, str):
+                if entry.entry_type == ConfigEntryType.STRING and not isinstance(
+                    value, str
+                ):
                     if not value:
                         value = ""
                     else:
                         raise ValueError
-                if entry.entry_type == ConfigEntryType.BOOL and not isinstance(value, bool):
+                if entry.entry_type == ConfigEntryType.BOOL and not isinstance(
+                    value, bool
+                ):
                     raise ValueError
                 if entry.entry_type == ConfigEntryType.FLOAT and not isinstance(
                     value, (float, int)
@@ -248,7 +252,9 @@ class ConfigItem:
 
                     player = self.mass.player_manager.get_player(self._parent_item_key)
                     if player:
-                        self.mass.add_job(self.mass.player_manager.async_update_player(player))
+                        self.mass.add_job(
+                            self.mass.player_manager.async_update_player(player)
+                        )
             return
         # raise KeyError if we're trying to set a value not defined as ConfigEntry
         raise KeyError
@@ -266,15 +272,18 @@ class ConfigBase(OrderedDict):
     """Configuration class with ConfigItem items."""
 
     def __init__(self, mass, base_type=ConfigBaseType):
+        """Initialize class."""
         self.mass = mass
         self._base_type = base_type
         super().__init__()
 
     def __getitem__(self, item_key):
-        """Convenience method for get."""
-        if not item_key in self:
+        """Return convenience method for get."""
+        if item_key not in self:
             # create new ConfigDictItem on the fly
-            super().__setitem__(item_key, ConfigItem(self.mass, item_key, self._base_type))
+            super().__setitem__(
+                item_key, ConfigItem(self.mass, item_key, self._base_type)
+            )
         return super().__getitem__(item_key)
 
 
@@ -282,6 +291,7 @@ class MassConfig:
     """Class which holds our configuration."""
 
     def __init__(self, mass, data_path: str):
+        """Initialize class."""
         self._data_path = data_path
         self.loading = False
         self.mass = mass
@@ -347,7 +357,7 @@ class MassConfig:
         return pbkdf2_sha256.verify(password, self.base["security"]["password"])
 
     def __getitem__(self, item_key):
-        """Convenience method for get."""
+        """Return item value by key."""
         return getattr(self, item_key)
 
     async def async_close(self):
@@ -366,7 +376,11 @@ class MassConfig:
         if os.path.isfile(conf_file):
             shutil.move(conf_file, conf_file_backup)
         # create dict for stored config
-        stored_conf = {CONF_KEY_BASE: {}, CONF_KEY_PLAYERSETTINGS: {}, CONF_KEY_PROVIDERS: {}}
+        stored_conf = {
+            CONF_KEY_BASE: {},
+            CONF_KEY_PLAYERSETTINGS: {},
+            CONF_KEY_PROVIDERS: {},
+        }
         for conf_key in stored_conf:
             for key, value in self[conf_key].items():
                 stored_conf[conf_key][key] = value.stored_config
@@ -378,7 +392,7 @@ class MassConfig:
         self.loading = False
 
     def __load(self):
-        """load config from file"""
+        """Load config from file."""
         self.loading = True
         conf_file = os.path.join(self.data_path, "config.json")
         data = try_load_json_file(conf_file)
index 3056d156ffa50986e90763e04ace014209d972e6..034b664dad909b82a76e8f760255e8b9328a1a68 100755 (executable)
@@ -15,7 +15,6 @@ CONF_CROSSFADE_DURATION = "crossfade_duration"
 CONF_FALLBACK_GAIN_CORRECT = "fallback_gain_correct"
 
 
-
 CONF_KEY_BASE = "base"
 CONF_KEY_PLAYERSETTINGS = "player_settings"
 CONF_KEY_PROVIDERS = "providers"
@@ -34,4 +33,4 @@ EVENT_QUEUE_ITEMS_UPDATED = "queue items updated"
 EVENT_SHUTDOWN = "application shutdown"
 EVENT_PROVIDER_REGISTERED = "provider registered"
 EVENT_PLAYER_CONTROL_REGISTERED = "player control registered"
-EVENT_PLAYER_CONTROL_UPDATED = "player control updated"
\ No newline at end of file
+EVENT_PLAYER_CONTROL_UPDATED = "player control updated"
index 14829c4cbaef562867acdf72f61f38fbdc854d1a..2ce688754ebdf34811f7a17549d438c9f6128c64 100755 (executable)
@@ -1,11 +1,10 @@
 """Database logic."""
 # pylint: disable=too-many-lines
-import asyncio
 import logging
 import os
 import sqlite3
-from typing import List
 from functools import partial
+from typing import List
 
 import aiosqlite
 from music_assistant.models.media_types import (
@@ -22,25 +21,28 @@ from music_assistant.models.media_types import (
     Track,
     TrackQuality,
 )
-from music_assistant.utils import get_sort_name, try_parse_int, compare_strings
+from music_assistant.utils import compare_strings, get_sort_name, try_parse_int
 
 LOGGER = logging.getLogger("mass")
 
-import contextlib
-
 
 class DbConnect:
+    """Helper to initialize the db connection or utilize an existing one."""
+
     def __init__(self, dbfile: str, db_conn: sqlite3.Connection = None):
+        """Initialize class."""
         self._db_conn_provided = db_conn is not None
         self._db_conn = db_conn
         self._dbfile = dbfile
 
     async def __aenter__(self):
+        """Enter."""
         if not self._db_conn_provided:
             self._db_conn = await aiosqlite.connect(self._dbfile, timeout=120)
         return self._db_conn
 
     async def __aexit__(self, exc_type, exc_value, traceback):
+        """Exit."""
         if not self._db_conn_provided:
             await self._db_conn.close()
         return False
@@ -50,6 +52,7 @@ class Database:
     """Class that holds the (logic to the) database."""
 
     def __init__(self, mass):
+        """Initialize class."""
         self.mass = mass
         self._dbfile = os.path.join(mass.config.data_path, "database.db")
         self.db_conn = partial(DbConnect, self._dbfile)
@@ -177,7 +180,9 @@ class Database:
                 return item_id[0]
         return None
 
-    async def async_search(self, searchquery: str, media_types: List[MediaType]) -> SearchResult:
+    async def async_search(
+        self, searchquery: str, media_types: List[MediaType]
+    ) -> SearchResult:
         """Search library for the given searchphrase."""
         async with DbConnect(self._dbfile) as db_conn:
             result = SearchResult([], [], [], [], [])
@@ -185,27 +190,34 @@ class Database:
             if media_types is None or MediaType.Artist in media_types:
                 sql_query = ' WHERE name LIKE "%s"' % searchquery
                 result.artists = [
-                    item async for item in self.async_get_artists(sql_query, db_conn=db_conn)
+                    item
+                    async for item in self.async_get_artists(sql_query, db_conn=db_conn)
                 ]
             if media_types is None or MediaType.Album in media_types:
                 sql_query = ' WHERE name LIKE "%s"' % searchquery
                 result.albums = [
-                    item async for item in self.async_get_albums(sql_query, db_conn=db_conn)
+                    item
+                    async for item in self.async_get_albums(sql_query, db_conn=db_conn)
                 ]
             if media_types is None or MediaType.Track in media_types:
                 sql_query = ' WHERE name LIKE "%s"' % searchquery
                 result.tracks = [
-                    item async for item in self.async_get_tracks(sql_query, db_conn=db_conn)
+                    item
+                    async for item in self.async_get_tracks(sql_query, db_conn=db_conn)
                 ]
             if media_types is None or MediaType.Playlist in media_types:
                 sql_query = ' WHERE name LIKE "%s"' % searchquery
                 result.playlists = [
-                    item async for item in self.async_get_playlists(sql_query, db_conn=db_conn)
+                    item
+                    async for item in self.async_get_playlists(
+                        sql_query, db_conn=db_conn
+                    )
                 ]
             if media_types is None or MediaType.Radio in media_types:
                 sql_query = ' WHERE name LIKE "%s"' % searchquery
                 result.radios = [
-                    item async for item in self.async_get_radios(sql_query, db_conn=db_conn)
+                    item
+                    async for item in self.async_get_radios(sql_query, db_conn=db_conn)
                 ]
             return result
 
@@ -218,9 +230,11 @@ class Database:
                 provider = "{provider_id}" AND media_type = {int(MediaType.Artist)})"""
         else:
             sql_query = f"""WHERE artist_id in
-                    (SELECT item_id FROM library_items 
+                    (SELECT item_id FROM library_items
                     WHERE media_type = {int(MediaType.Artist)})"""
-        async for item in self.async_get_artists(sql_query, orderby=orderby, fulldata=True):
+        async for item in self.async_get_artists(
+            sql_query, orderby=orderby, fulldata=True
+        ):
             yield item
 
     async def async_get_library_albums(
@@ -233,7 +247,9 @@ class Database:
         else:
             sql_query = f"""WHERE album_id in
                 (SELECT item_id FROM library_items WHERE media_type = {int(MediaType.Album)})"""
-        async for item in self.async_get_albums(sql_query, orderby=orderby, fulldata=True):
+        async for item in self.async_get_albums(
+            sql_query, orderby=orderby, fulldata=True
+        ):
             yield item
 
     async def async_get_library_tracks(
@@ -242,7 +258,7 @@ class Database:
         """Get all library tracks, optionally filtered by provider."""
         if provider_id is not None:
             sql_query = f"""WHERE track_id in
-                (SELECT item_id FROM library_items WHERE provider = "{provider_id}" 
+                (SELECT item_id FROM library_items WHERE provider = "{provider_id}"
                 AND media_type = {int(MediaType.Track)})"""
         else:
             sql_query = f"""WHERE track_id in
@@ -279,7 +295,10 @@ class Database:
             yield item
 
     async def async_get_playlists(
-        self, filter_query: str = None, orderby: str = "name", db_conn: sqlite3.Connection = None
+        self,
+        filter_query: str = None,
+        orderby: str = "name",
+        db_conn: sqlite3.Connection = None,
     ) -> List[Playlist]:
         """Get all playlists from database."""
         async with DbConnect(self._dbfile, db_conn) as db_conn:
@@ -321,12 +340,17 @@ class Database:
     async def async_get_playlist(self, playlist_id: int) -> Playlist:
         """Get playlist record by id."""
         playlist_id = try_parse_int(playlist_id)
-        async for item in self.async_get_playlists(f"WHERE playlist_id = {playlist_id}"):
+        async for item in self.async_get_playlists(
+            f"WHERE playlist_id = {playlist_id}"
+        ):
             return item
         return None
 
     async def async_get_radios(
-        self, filter_query: str = None, orderby: str = "name", db_conn: sqlite3.Connection = None
+        self,
+        filter_query: str = None,
+        orderby: str = "name",
+        db_conn: sqlite3.Connection = None,
     ) -> List[Radio]:
         """Fetch radio records from database."""
         sql_query = "SELECT * FROM radios"
@@ -345,7 +369,9 @@ class Database:
                     metadata=await self.__async_get_metadata(
                         db_row["radio_id"], MediaType.Radio, db_conn
                     ),
-                    tags=await self.__async_get_tags(db_row["radio_id"], MediaType.Radio, db_conn),
+                    tags=await self.__async_get_tags(
+                        db_row["radio_id"], MediaType.Radio, db_conn
+                    ),
                     external_ids=await self.__async_get_external_ids(
                         db_row["radio_id"], MediaType.Radio, db_conn
                     ),
@@ -402,7 +428,9 @@ class Database:
                 async with db_conn.execute(sql_query, (last_row_id,)) as cursor:
                     playlist_id = await cursor.fetchone()
                     playlist_id = playlist_id[0]
-                LOGGER.debug("added playlist %s to database: %s", playlist.name, playlist_id)
+                LOGGER.debug(
+                    "added playlist %s to database: %s", playlist.name, playlist_id
+                )
             # add/update metadata
             await self.__async_add_prov_ids(
                 playlist_id, MediaType.Playlist, playlist.provider_ids, db_conn
@@ -415,7 +443,7 @@ class Database:
         return playlist_id
 
     async def async_add_radio(self, radio: Radio):
-        """add a new radio record to the database."""
+        """Add a new radio record to the database."""
         assert radio.name
         async with DbConnect(self._dbfile) as db_conn:
             async with db_conn.execute(
@@ -435,15 +463,23 @@ class Database:
                 async with db_conn.execute(sql_query, (last_row_id,)) as cursor:
                     radio_id = await cursor.fetchone()
                     radio_id = radio_id[0]
-                LOGGER.debug("added radio station %s to database: %s", radio.name, radio_id)
+                LOGGER.debug(
+                    "added radio station %s to database: %s", radio.name, radio_id
+                )
             # add/update metadata
-            await self.__async_add_prov_ids(radio_id, MediaType.Radio, radio.provider_ids, db_conn)
-            await self.__async_add_metadata(radio_id, MediaType.Radio, radio.metadata, db_conn)
+            await self.__async_add_prov_ids(
+                radio_id, MediaType.Radio, radio.provider_ids, db_conn
+            )
+            await self.__async_add_metadata(
+                radio_id, MediaType.Radio, radio.metadata, db_conn
+            )
             # save
             await db_conn.commit()
         return radio_id
 
-    async def async_add_to_library(self, item_id: int, media_type: MediaType, provider: str):
+    async def async_add_to_library(
+        self, item_id: int, media_type: MediaType, provider: str
+    ):
         """Add an item to the library (item must already be present in the db!)."""
         async with DbConnect(self._dbfile) as db_conn:
             item_id = try_parse_int(item_id)
@@ -452,7 +488,9 @@ class Database:
             await db_conn.execute(sql_query, (item_id, provider, media_type))
             await db_conn.commit()
 
-    async def async_remove_from_library(self, item_id: int, media_type: MediaType, provider: str):
+    async def async_remove_from_library(
+        self, item_id: int, media_type: MediaType, provider: str
+    ):
         """Remove item from the library."""
         async with DbConnect(self._dbfile) as db_conn:
             item_id = try_parse_int(item_id)
@@ -545,8 +583,12 @@ class Database:
             await self.__async_add_prov_ids(
                 artist_id, MediaType.Artist, artist.provider_ids, db_conn
             )
-            await self.__async_add_metadata(artist_id, MediaType.Artist, artist.metadata, db_conn)
-            await self.__async_add_tags(artist_id, MediaType.Artist, artist.tags, db_conn)
+            await self.__async_add_metadata(
+                artist_id, MediaType.Artist, artist.metadata, db_conn
+            )
+            await self.__async_add_tags(
+                artist_id, MediaType.Artist, artist.tags, db_conn
+            )
             await self.__async_add_external_ids(
                 artist_id, MediaType.Artist, artist.external_ids, db_conn
             )
@@ -602,13 +644,15 @@ class Database:
                     album.tags = await self.__async_get_tags(
                         album.item_id, MediaType.Album, db_conn
                     )
-                    album.labels = await self.__async_get_album_labels(album.item_id, db_conn)
+                    album.labels = await self.__async_get_album_labels(
+                        album.item_id, db_conn
+                    )
                 yield album
 
     async def async_get_album(
         self, album_id: int, fulldata=True, db_conn: sqlite3.Connection = None
     ) -> Album:
-        """get album record by id"""
+        """Get album record by id."""
         album_id = try_parse_int(album_id)
         async for item in self.async_get_albums(
             "WHERE album_id = %d" % album_id, fulldata=fulldata, db_conn=db_conn
@@ -646,7 +690,9 @@ class Database:
             if not album_id:
                 sql_query = """SELECT album_id, year, version, albumtype FROM
                     albums WHERE artist_id=? AND name=?"""
-                async with db_conn.execute(sql_query, (album.artist.item_id, album.name)) as cursor:
+                async with db_conn.execute(
+                    sql_query, (album.artist.item_id, album.name)
+                ) as cursor:
                     albums = await cursor.fetchall()
                 for result in albums:
                     if (not album.version and result["year"] == album.year) or (
@@ -675,8 +721,12 @@ class Database:
                 await db_conn.commit()
             # always add metadata and tags etc. because we might have received
             # additional info or a match from other provider
-            await self.__async_add_prov_ids(album_id, MediaType.Album, album.provider_ids, db_conn)
-            await self.__async_add_metadata(album_id, MediaType.Album, album.metadata, db_conn)
+            await self.__async_add_prov_ids(
+                album_id, MediaType.Album, album.provider_ids, db_conn
+            )
+            await self.__async_add_metadata(
+                album_id, MediaType.Album, album.metadata, db_conn
+            )
             await self.__async_add_tags(album_id, MediaType.Album, album.tags, db_conn)
             await self.__async_add_album_labels(album_id, album.labels, db_conn)
             await self.__async_add_external_ids(
@@ -738,7 +788,9 @@ class Database:
                     )
                 yield track
 
-    async def async_get_track(self, track_id: int, fulldata=True, db_conn: sqlite3.Connection = None) -> Track:
+    async def async_get_track(
+        self, track_id: int, fulldata=True, db_conn: sqlite3.Connection = None
+    ) -> Track:
         """Get track record by id."""
         track_id = try_parse_int(track_id)
         async for item in self.async_get_tracks(
@@ -760,14 +812,18 @@ class Database:
             track_id = await self.__async_get_item_by_external_id(track, db_conn)
             # fallback to matching on album_id, name and version
             if not track_id:
-                sql_query = (
-                    "SELECT track_id, duration, version FROM tracks WHERE album_id=? AND name=?"
-                )
-                async with db_conn.execute(sql_query, (track.album.item_id, track.name)) as cursor:
+                sql_query = "SELECT track_id, duration, version \
+                    FROM tracks WHERE album_id=? AND name=?"
+                async with db_conn.execute(
+                    sql_query, (track.album.item_id, track.name)
+                ) as cursor:
                     results = await cursor.fetchall()
                 for result in results:
                     # we perform an additional safety check on the duration or version
-                    if (track.version and compare_strings(result["version"], track.version)) or (
+                    if (
+                        track.version
+                        and compare_strings(result["version"], track.version)
+                    ) or (
                         (
                             not track.version
                             and not result["version"]
@@ -779,9 +835,8 @@ class Database:
             # no match found: insert track
             if not track_id:
                 assert track.name and track.album.item_id
-                sql_query = (
-                    "INSERT INTO tracks (name, album_id, duration, version) VALUES(?,?,?,?);"
-                )
+                sql_query = "INSERT INTO tracks (name, album_id, duration, version) \
+                        VALUES(?,?,?,?);"
                 query_params = (
                     track.name,
                     track.album.item_id,
@@ -802,8 +857,12 @@ class Database:
             for artist in track.artists:
                 sql_query = "INSERT or IGNORE INTO track_artists (track_id, artist_id) VALUES(?,?);"
                 await db_conn.execute(sql_query, (track_id, artist.item_id))
-            await self.__async_add_prov_ids(track_id, MediaType.Track, track.provider_ids, db_conn)
-            await self.__async_add_metadata(track_id, MediaType.Track, track.metadata, db_conn)
+            await self.__async_add_prov_ids(
+                track_id, MediaType.Track, track.provider_ids, db_conn
+            )
+            await self.__async_add_metadata(
+                track_id, MediaType.Track, track.metadata, db_conn
+            )
             await self.__async_add_tags(track_id, MediaType.Track, track.tags, db_conn)
             await self.__async_add_external_ids(
                 track_id, MediaType.Track, track.external_ids, db_conn
@@ -818,28 +877,40 @@ class Database:
             )
         return track_id
 
-    async def async_update_playlist(self, playlist_id: int, column_key: str, column_value: str):
+    async def async_update_playlist(
+        self, playlist_id: int, column_key: str, column_value: str
+    ):
         """Update column of existing playlist."""
         async with DbConnect(self._dbfile) as db_conn:
             sql_query = f"UPDATE playlists SET {column_key}=? WHERE playlist_id=?;"
             await db_conn.execute(sql_query, (column_value, playlist_id))
             await db_conn.commit()
 
-    async def async_get_artist_tracks(self, artist_id: int, orderby: str = "name") -> List[Track]:
-        """get all library tracks for the given artist"""
+    async def async_get_artist_tracks(
+        self, artist_id: int, orderby: str = "name"
+    ) -> List[Track]:
+        """Get all library tracks for the given artist."""
         artist_id = try_parse_int(artist_id)
         sql_query = f"""WHERE track_id in
             (SELECT track_id FROM track_artists WHERE artist_id = {artist_id})"""
-        async for item in self.async_get_tracks(sql_query, orderby=orderby, fulldata=False):
+        async for item in self.async_get_tracks(
+            sql_query, orderby=orderby, fulldata=False
+        ):
             yield item
 
-    async def async_get_artist_albums(self, artist_id: int, orderby: str = "name") -> List[Album]:
-        """get all library albums for the given artist"""
+    async def async_get_artist_albums(
+        self, artist_id: int, orderby: str = "name"
+    ) -> List[Album]:
+        """Get all library albums for the given artist."""
         sql_query = " WHERE artist_id = %s" % artist_id
-        async for item in self.async_get_albums(sql_query, orderby=orderby, fulldata=False):
+        async for item in self.async_get_albums(
+            sql_query, orderby=orderby, fulldata=False
+        ):
             yield item
 
-    async def async_set_track_loudness(self, provider_track_id: str, provider: str, loudness: int):
+    async def async_set_track_loudness(
+        self, provider_track_id: str, provider: str, loudness: int
+    ):
         """Set integrated loudness for a track in db."""
         async with DbConnect(self._dbfile) as db_conn:
             sql_query = """INSERT or REPLACE INTO track_loudness
@@ -852,14 +923,20 @@ class Database:
         async with DbConnect(self._dbfile) as db_conn:
             sql_query = """SELECT loudness FROM track_loudness WHERE
                 provider_track_id = ? AND provider = ?"""
-            async with db_conn.execute(sql_query, (provider_track_id, provider)) as cursor:
+            async with db_conn.execute(
+                sql_query, (provider_track_id, provider)
+            ) as cursor:
                 result = await cursor.fetchone()
             if result:
                 return result[0]
         return None
 
     async def __async_add_metadata(
-        self, item_id: int, media_type: MediaType, metadata: dict, db_conn: sqlite3.Connection
+        self,
+        item_id: int,
+        media_type: MediaType,
+        metadata: dict,
+        db_conn: sqlite3.Connection,
     ):
         """Add or update metadata."""
         for key, value in metadata.items():
@@ -877,7 +954,9 @@ class Database:
     ) -> dict:
         """Get metadata for media item."""
         metadata = {}
-        sql_query = "SELECT key, value FROM metadata WHERE item_id = ? AND media_type = ?"
+        sql_query = (
+            "SELECT key, value FROM metadata WHERE item_id = ? AND media_type = ?"
+        )
         if filter_key:
             sql_query += ' AND key = "%s"' % filter_key
         async with db_conn.execute(sql_query, (item_id, media_type)) as cursor:
@@ -889,9 +968,13 @@ class Database:
         return metadata
 
     async def __async_add_tags(
-        self, item_id: int, media_type: MediaType, tags: List[str], db_conn: sqlite3.Connection
+        self,
+        item_id: int,
+        media_type: MediaType,
+        tags: List[str],
+        db_conn: sqlite3.Connection,
     ):
-        """add tags to db"""
+        """Add tags to db."""
         for tag in tags:
             sql_query = "INSERT or IGNORE INTO tags (name) VALUES(?);"
             async with db_conn.execute(sql_query, (tag,)) as cursor:
@@ -903,7 +986,7 @@ class Database:
     async def __async_get_tags(
         self, item_id: int, media_type: MediaType, db_conn: sqlite3.Connection
     ) -> List[str]:
-        """get tags for media item"""
+        """Get tags for media item."""
         tags = []
         sql_query = """SELECT name FROM tags INNER JOIN media_tags ON
             tags.tag_id = media_tags.tag_id WHERE item_id = ? AND media_type = ?"""
@@ -916,18 +999,20 @@ class Database:
     async def __async_add_album_labels(
         self, album_id: int, labels: List[str], db_conn: sqlite3.Connection
     ):
-        """add labels to album in db"""
+        """Add labels to album in db."""
         for label in labels:
             sql_query = "INSERT or IGNORE INTO labels (name) VALUES(?);"
             async with db_conn.execute(sql_query, (label,)) as cursor:
                 label_id = cursor.lastrowid
-            sql_query = "INSERT or IGNORE INTO album_labels (album_id, label_id) VALUES(?,?);"
+            sql_query = (
+                "INSERT or IGNORE INTO album_labels (album_id, label_id) VALUES(?,?);"
+            )
             await db_conn.execute(sql_query, (album_id, label_id))
 
     async def __async_get_album_labels(
         self, album_id: int, db_conn: sqlite3.Connection
     ) -> List[str]:
-        """get labels for album item"""
+        """Get labels for album item."""
         labels = []
         sql_query = """SELECT name FROM labels INNER JOIN album_labels
             ON labels.label_id = album_labels.label_id WHERE album_id = ?"""
@@ -940,20 +1025,26 @@ class Database:
     async def __async_get_track_artists(
         self, track_id: int, db_conn: sqlite3.Connection, fulldata: bool = False
     ) -> List[Artist]:
-        """get artists for track"""
+        """Get artists for track."""
         sql_query = (
             "WHERE artist_id in (SELECT artist_id FROM track_artists WHERE track_id = %s)"
             % track_id
         )
         return [
             item
-            async for item in self.async_get_artists(sql_query, fulldata=fulldata, db_conn=db_conn)
+            async for item in self.async_get_artists(
+                sql_query, fulldata=fulldata, db_conn=db_conn
+            )
         ]
 
     async def __async_add_external_ids(
-        self, item_id: int, media_type: MediaType, external_ids: dict, db_conn: sqlite3.Connection
+        self,
+        item_id: int,
+        media_type: MediaType,
+        external_ids: dict,
+        db_conn: sqlite3.Connection,
     ):
-        """add or update external_ids"""
+        """Add or update external_ids."""
         for key, value in external_ids.items():
             sql_query = """INSERT or REPLACE INTO external_ids
                 (item_id, media_type, key, value) VALUES(?,?,?,?);"""
@@ -962,9 +1053,11 @@ class Database:
     async def __async_get_external_ids(
         self, item_id: int, media_type: MediaType, db_conn: sqlite3.Connection
     ) -> dict:
-        """get external_ids for media item"""
+        """Get external_ids for media item."""
         external_ids = {}
-        sql_query = "SELECT key, value FROM external_ids WHERE item_id = ? AND media_type = ?"
+        sql_query = (
+            "SELECT key, value FROM external_ids WHERE item_id = ? AND media_type = ?"
+        )
         for db_row in await db_conn.execute_fetchall(sql_query, (item_id, media_type)):
             external_ids[db_row[0]] = db_row[1]
         return external_ids
@@ -1017,8 +1110,12 @@ class Database:
     ) -> List[str]:
         """Get the providers that have this media_item added to the library."""
         providers = []
-        sql_query = "SELECT provider FROM library_items WHERE item_id = ? AND media_type = ?"
-        for db_row in await db_conn.execute_fetchall(sql_query, (db_item_id, media_type)):
+        sql_query = (
+            "SELECT provider FROM library_items WHERE item_id = ? AND media_type = ?"
+        )
+        for db_row in await db_conn.execute_fetchall(
+            sql_query, (db_item_id, media_type)
+        ):
             providers.append(db_row[0])
         return providers
 
@@ -1027,9 +1124,8 @@ class Database:
     ) -> int:
         """Try to get existing item in db by matching the new item's external id's."""
         for key, value in media_item.external_ids.items():
-            sql_query = (
-                "SELECT (item_id) FROM external_ids WHERE media_type=? AND key=? AND value=?;"
-            )
+            sql_query = "SELECT (item_id) FROM external_ids \
+                    WHERE media_type=? AND key=? AND value=?;"
             for db_row in await db_conn.execute_fetchall(
                 sql_query, (media_item.media_type, key, value)
             ):
index 2e5bfad7f086bc7fb2f9cf30825045e6fb8df16d..0edceaa7165720689fbffe5615a357a4d77f2058 100755 (executable)
@@ -1,10 +1,10 @@
 """
-    HTTPStreamer: handles all audio streaming to players,
-    either by sending tracks one by one or send one continuous stream
-    of music with crossfade/gapless support (queue stream).
+HTTPStreamer: handles all audio streaming to players.
+
+Either by sending tracks one by one or send one continuous stream
+of music with crossfade/gapless support (queue stream).
 """
 import asyncio
-import concurrent
 import gc
 import io
 import logging
@@ -13,7 +13,6 @@ import shlex
 import signal
 import subprocess
 import threading
-from asyncio import CancelledError
 from contextlib import suppress
 
 import pyloudnorm
@@ -32,6 +31,7 @@ class HTTPStreamer:
     """Built-in streamer using sox and webserver."""
 
     def __init__(self, mass):
+        """Initialize class."""
         self.mass = mass
         self.local_ip = get_ip()
         self.analyze_jobs = {}
@@ -39,9 +39,7 @@ class HTTPStreamer:
 
     @require_local_subnet
     async def async_stream(self, http_request):
-        """
-        start stream for a player
-        """
+        """Start stream for a player."""
         # make sure we have valid params
         player_id = http_request.match_info.get("player_id", "")
         player_queue = self.mass.player_manager.get_player_queue(player_id)
@@ -53,7 +51,9 @@ class HTTPStreamer:
             if not queue_item:
                 return web.Response(status=404, reason="Invalid Queue item Id")
         # prepare headers as audio/flac content
-        resp = web.StreamResponse(status=200, reason="OK", headers={"Content-Type": "audio/flac"})
+        resp = web.StreamResponse(
+            status=200, reason="OK", headers={"Content-Type": "audio/flac"}
+        )
         await resp.prepare(http_request)
         # run the streamer in executor to prevent the subprocess locking up our eventloop
         cancelled = threading.Event()
@@ -63,7 +63,12 @@ class HTTPStreamer:
             )
         else:
             bg_task = self.mass.loop.run_in_executor(
-                None, self.__get_queue_item_stream, player_id, queue_item, resp, cancelled
+                None,
+                self.__get_queue_item_stream,
+                player_id,
+                queue_item,
+                resp,
+                cancelled,
             )
         # let the streaming begin!
         try:
@@ -74,21 +79,25 @@ class HTTPStreamer:
         return resp
 
     def __get_queue_item_stream(self, player_id, queue_item, buffer, cancelled):
-        """start streaming single queue track"""
+        """Start streaming single queue track."""
         # pylint: disable=unused-variable
         LOGGER.debug(
             "stream single queue track started for track %s on player %s",
             queue_item.name,
             player_id,
         )
-        for is_last_chunk, audio_chunk in self.__get_audio_stream(player_id, queue_item, cancelled):
+        for is_last_chunk, audio_chunk in self.__get_audio_stream(
+            player_id, queue_item, cancelled
+        ):
             if cancelled.is_set():
                 # http session ended
                 # we must consume the data to prevent hanging subprocess instances
                 continue
             # put chunk in buffer
             with suppress((BrokenPipeError, ConnectionResetError)):
-                asyncio.run_coroutine_threadsafe(buffer.write(audio_chunk), self.mass.loop).result()
+                asyncio.run_coroutine_threadsafe(
+                    buffer.write(audio_chunk), self.mass.loop
+                ).result()
         # all chunks received: streaming finished
         if cancelled.is_set():
             LOGGER.debug(
@@ -99,10 +108,14 @@ class HTTPStreamer:
         else:
             # indicate EOF if no more data
             with suppress((BrokenPipeError, ConnectionResetError)):
-                asyncio.run_coroutine_threadsafe(buffer.write_eof(), self.mass.loop).result()
+                asyncio.run_coroutine_threadsafe(
+                    buffer.write_eof(), self.mass.loop
+                ).result()
 
             LOGGER.debug(
-                "stream single track finished for track %s on player %s", queue_item.name, player_id
+                "stream single track finished for track %s on player %s",
+                queue_item.name,
+                player_id,
             )
 
     def __get_queue_stream(self, player_id, buffer, cancelled):
@@ -127,16 +140,11 @@ class HTTPStreamer:
 
         def fill_buffer():
             while True:
-                chunk = sox_proc.stdout.read(128000)
+                chunk = sox_proc.stdout.read(128000)  # noqa
                 if not chunk:
                     break
                 if chunk and not cancelled.is_set():
-                    with suppress(
-                        (
-                            BrokenPipeError,
-                            ConnectionResetError,
-                        )
-                    ):
+                    with suppress((BrokenPipeError, ConnectionResetError)):
                         asyncio.run_coroutine_threadsafe(
                             buffer.write(chunk), self.mass.loop
                         ).result()
@@ -144,7 +152,9 @@ class HTTPStreamer:
             # indicate EOF if no more data
             if not cancelled.is_set():
                 with suppress((BrokenPipeError, ConnectionResetError)):
-                    asyncio.run_coroutine_threadsafe(buffer.write_eof(), self.mass.loop).result()
+                    asyncio.run_coroutine_threadsafe(
+                        buffer.write_eof(), self.mass.loop
+                    ).result()
 
         # start fill buffer task in background
         fill_buffer_thread = threading.Thread(target=fill_buffer)
@@ -159,7 +169,9 @@ class HTTPStreamer:
             # get the (next) track in queue
             if is_start:
                 # report start of queue playback so we can calculate current track/duration etc.
-                queue_track = self.mass.add_job(player_queue.async_start_queue_stream()).result()
+                queue_track = self.mass.add_job(
+                    player_queue.async_start_queue_stream()
+                ).result()
                 is_start = False
             else:
                 queue_track = player_queue.next_item
@@ -247,8 +259,13 @@ class HTTPStreamer:
                         # so we just use the entire original data
                         last_part = prev_chunk + chunk
                         if len(last_part) < fade_bytes:
-                            LOGGER.warning("Not enough data for crossfade: %s", len(last_part))
-                    if not player_queue.crossfade_enabled or len(last_part) < fade_bytes:
+                            LOGGER.warning(
+                                "Not enough data for crossfade: %s", len(last_part)
+                            )
+                    if (
+                        not player_queue.crossfade_enabled
+                        or len(last_part) < fade_bytes
+                    ):
                         # crossfading is not enabled so just pass the (stripped) audio data
                         sox_proc.stdin.write(last_part)
                         bytes_written += len(last_part)
@@ -309,8 +326,11 @@ class HTTPStreamer:
         else:
             LOGGER.info("streaming of queue for player %s completed", player_id)
 
-    def __get_audio_stream(self, player_id, queue_item, cancelled, chunksize=128000, resample=None):
+    def __get_audio_stream(
+        self, player_id, queue_item, cancelled, chunksize=128000, resample=None
+    ):
         """Get audio stream from provider and apply additional effects/processing if needed."""
+        # pylint: disable=subprocess-popen-preexec-fn
         player_queue = self.mass.player_manager.get_player_queue(player_id)
         streamdetails = self.mass.add_job(
             player_queue.async_get_stream_details(player_id, queue_item)
@@ -335,7 +355,11 @@ class HTTPStreamer:
                 sox_options,
             )
             process = subprocess.Popen(
-                args, shell=True, stdout=subprocess.PIPE, bufsize=chunksize, preexec_fn=os.setsid
+                args,
+                shell=True,
+                stdout=subprocess.PIPE,
+                bufsize=chunksize,
+                preexec_fn=os.setsid,
             )
         elif streamdetails.type in [StreamType.URL, StreamType.FILE]:
             args = 'sox -t %s "%s" -t %s - %s' % (
@@ -346,7 +370,11 @@ class HTTPStreamer:
             )
             args = shlex.split(args)
             process = subprocess.Popen(
-                args, shell=False, stdout=subprocess.PIPE, bufsize=chunksize, preexec_fn=os.setsid
+                args,
+                shell=False,
+                stdout=subprocess.PIPE,
+                bufsize=chunksize,
+                preexec_fn=os.setsid,
             )
         elif streamdetails.type == StreamType.EXECUTABLE:
             args = "%s | sox -t %s - -t %s - %s" % (
@@ -356,7 +384,11 @@ class HTTPStreamer:
                 sox_options,
             )
             process = subprocess.Popen(
-                args, shell=True, stdout=subprocess.PIPE, bufsize=chunksize, preexec_fn=os.setsid
+                args,
+                shell=True,
+                stdout=subprocess.PIPE,
+                bufsize=chunksize,
+                preexec_fn=os.setsid,
             )
         else:
             LOGGER.warning("no streaming options for %s", queue_item.name)
@@ -390,7 +422,9 @@ class HTTPStreamer:
         if queue_item.media_type == MediaType.Track:
             self.mass.loop.run_in_executor(None, self.__analyze_audio, streamdetails)
 
-    def __get_player_sox_options(self, player_id: str, streamdetails: StreamDetails) -> str:
+    def __get_player_sox_options(
+        self, player_id: str, streamdetails: StreamDetails
+    ) -> str:
         """Get player specific sox effect options."""
         sox_options = []
         player_conf = self.mass.config.get_player_config(player_id)
@@ -451,7 +485,7 @@ class HTTPStreamer:
 
     @staticmethod
     def __crossfade_pcm_parts(fade_in_part, fade_out_part, pcm_args, fade_length):
-        """crossfade two chunks of audio using sox"""
+        """Crossfade two chunks of audio using sox."""
         # create fade-in part
         fadeinfile = create_tempfile()
         args = "sox --ignore-length -t %s - -t %s %s fade t %s" % (
@@ -472,7 +506,9 @@ class HTTPStreamer:
             fade_length,
         )
         args = shlex.split(args)
-        process = subprocess.Popen(args, shell=False, stdout=subprocess.PIPE, stdin=subprocess.PIPE)
+        process = subprocess.Popen(
+            args, shell=False, stdout=subprocess.PIPE, stdin=subprocess.PIPE
+        )
         process.communicate(fade_out_part)
         # create crossfade using sox and some temp files
         # TODO: figure out how to make this less complex and without the tempfiles
@@ -484,7 +520,9 @@ class HTTPStreamer:
             pcm_args,
         )
         args = shlex.split(args)
-        process = subprocess.Popen(args, shell=False, stdout=subprocess.PIPE, stdin=subprocess.PIPE)
+        process = subprocess.Popen(
+            args, shell=False, stdout=subprocess.PIPE, stdin=subprocess.PIPE
+        )
         crossfade_part, _ = process.communicate()
         fadeinfile.close()
         fadeoutfile.close()
index edf7b147b2e11d460478369080ac299e39bcdd73..a9a21449c03d921c2eae8c5db778007778a89e0b 100644 (file)
@@ -23,17 +23,7 @@ from music_assistant.music_manager import MusicManager
 from music_assistant.player_manager import PlayerManager
 from music_assistant.utils import callback, get_hostname, get_ip_pton, is_callback
 from music_assistant.web import Web
-from zeroconf import DNSPointer, DNSRecord
-from zeroconf import Error as ZeroconfError
-from zeroconf import (
-    InterfaceChoice,
-    IPVersion,
-    NonUniqueNameException,
-    ServiceBrowser,
-    ServiceInfo,
-    ServiceStateChange,
-    Zeroconf,
-)
+from zeroconf import NonUniqueNameException, ServiceInfo, Zeroconf
 
 LOGGER = logging.getLogger("mass")
 
@@ -44,7 +34,8 @@ class MusicAssistant:
 
     def __init__(self, datapath):
         """
-        Create an instance of MusicAssistant
+        Create an instance of MusicAssistant.
+
             :param datapath: file location to store the data
         """
 
@@ -80,7 +71,7 @@ class MusicAssistant:
         await self.__async_setup_discovery()
 
     async def async_stop(self):
-        """stop running the music assistant server"""
+        """Stop running the music assistant server."""
         LOGGER.info("Application shutdown")
         self.signal_event(EVENT_SHUTDOWN)
         self._exit = True
@@ -111,13 +102,15 @@ class MusicAssistant:
     @callback
     def get_provider(self, provider_id: str) -> Provider:
         """Return provider/plugin by id."""
-        if not provider_id in self._providers:
+        if provider_id not in self._providers:
             LOGGER.warning("Provider %s is not available", provider_id)
             return None
         return self._providers[provider_id]
 
     @callback
-    def get_providers(self, filter_type: Optional[ProviderType] = None) -> List[Provider]:
+    def get_providers(
+        self, filter_type: Optional[ProviderType] = None
+    ) -> List[Provider]:
         """Return all providers, optionally filtered by type."""
         return [
             item
@@ -160,6 +153,7 @@ class MusicAssistant:
     def signal_event(self, event_msg: str, event_details: Any = None):
         """
         Signal (systemwide) event.
+
             :param event_msg: the eventmessage to signal
             :param event_details: optional details to send with the event.
         """
@@ -177,6 +171,7 @@ class MusicAssistant:
     ) -> Callable:
         """
         Add callback to event listeners.
+
         Returns function to remove the listener.
             :param cb_func: callback function or coroutine
             :param event_filter: Optionally only listen for these events
@@ -189,8 +184,11 @@ class MusicAssistant:
 
         return remove_listener
 
-    def add_job(self, target: Callable[..., Any], *args: Any) -> Optional[asyncio.Future]:
+    def add_job(
+        self, target: Callable[..., Any], *args: Any
+    ) -> Optional[asyncio.Future]:
         """Add a job/task to the event loop.
+
         target: target to call.
         args: parameters for method to call.
         """
@@ -207,7 +205,9 @@ class MusicAssistant:
         if threading.current_thread() is not threading.main_thread():
             # called from other thread
             if asyncio.iscoroutine(check_target):
-                task = asyncio.run_coroutine_threadsafe(target, self.loop)  # type: ignore
+                task = asyncio.run_coroutine_threadsafe(
+                    target, self.loop
+                )  # type: ignore
             elif asyncio.iscoroutinefunction(check_target):
                 task = asyncio.run_coroutine_threadsafe(target(*args), self.loop)
             elif is_callback(check_target):
index 5c082e54ac46845b1731bd6b90ec4848af38d02c..099ee345e1d1d6a2e46c68df68306103bc442553 100755 (executable)
@@ -1,7 +1,7 @@
 """All logic for metadata retrieval."""
+# TODO: split up into (optional) providers
 import json
 import logging
-# TODO: split up into (optional) providers
 import re
 from typing import Optional
 
@@ -16,23 +16,24 @@ LOGGER = logging.getLogger("mass")
 
 
 class MetaData:
-    """several helpers to search and store metadata for mediaitems"""
+    """Several helpers to search and store metadata for mediaitems."""
 
     # TODO: create periodic task to search for missing metadata
     def __init__(self, mass):
+        """Initialize class."""
         self.mass = mass
         self.musicbrainz = MusicBrainz(mass)
         self.fanarttv = FanartTv(mass)
 
     async def async_setup(self):
-        """async initialize of metadata module"""
+        """Async setup of metadata module."""
         await self.musicbrainz.async_setup()
         await self.fanarttv.async_setup()
 
     async def async_get_artist_metadata(self, mb_artist_id, cur_metadata):
-        """get/update rich metadata for an artist by providing the musicbrainz artist id"""
+        """Get/update rich metadata for an artist by providing the musicbrainz artist id."""
         metadata = cur_metadata
-        if not "fanart" in metadata:
+        if "fanart" not in metadata:
             res = await self.fanarttv.async_get_artist_images(mb_artist_id)
             if res:
                 self.merge_metadata(cur_metadata, res)
@@ -46,7 +47,7 @@ class MetaData:
         trackname=None,
         track_isrc=None,
     ):
-        """retrieve musicbrainz artist id for the given details"""
+        """Retrieve musicbrainz artist id for the given details."""
         LOGGER.debug(
             "searching musicbrainz for %s \
                 (albumname: %s - album_upc: %s - trackname: %s - track_isrc: %s)",
@@ -105,7 +106,7 @@ class MetaData:
 
     @staticmethod
     def merge_metadata(cur_metadata, new_values):
-        """merge new info into the metadata dict without overwiteing existing values"""
+        """Merge new info into the metadata dict without overwriting existing values."""
         for key, value in new_values.items():
             if not cur_metadata.get(key):
                 cur_metadata[key] = value
@@ -113,21 +114,26 @@ class MetaData:
 
 
 class MusicBrainz:
+    """Handle getting Id's from MusicBrainz."""
+
     def __init__(self, mass):
+        """Initialize class."""
         self.mass = mass
         self.cache = mass.cache
         self.throttler = None
         self._http_session = None
 
     async def async_setup(self):
-        """perform async setup"""
+        """Perform async setup."""
         self._http_session = aiohttp.ClientSession(
             loop=self.mass.loop, connector=aiohttp.TCPConnector()
         )
         self.throttler = Throttler(rate_limit=1, period=1)
 
-    async def async_search_artist_by_album(self, artistname, albumname=None, album_upc=None):
-        """retrieve musicbrainz artist id by providing the artist name and albumname or upc"""
+    async def async_search_artist_by_album(
+        self, artistname, albumname=None, album_upc=None
+    ):
+        """Retrieve musicbrainz artist id by providing the artist name and albumname or upc."""
         for searchartist in [
             re.sub(LUCENE_SPECIAL, r"\\\1", artistname),
             get_compare_string(artistname),
@@ -161,8 +167,10 @@ class MusicBrainz:
                                         return artist["id"]
         return ""
 
-    async def async_search_artist_by_track(self, artistname, trackname=None, track_isrc=None):
-        """retrieve artist id by providing the artist name and trackname or track isrc"""
+    async def async_search_artist_by_track(
+        self, artistname, trackname=None, track_isrc=None
+    ):
+        """Retrieve artist id by providing the artist name and trackname or track isrc."""
         endpoint = "recording"
         searchartist = re.sub(LUCENE_SPECIAL, r"\\\1", artistname)
         # searchartist = searchartist.replace('/','').replace('\\','').replace('-', '')
@@ -206,7 +214,10 @@ class MusicBrainz:
             ) as response:
                 try:
                     result = await response.json()
-                except Exception as exc:
+                except (
+                    aiohttp.client_exceptions.ContentTypeError,
+                    json.decoder.JSONDecodeError,
+                ) as exc:
                     msg = await response.text()
                     LOGGER.exception("%s - %s", str(exc), msg)
                     result = None
@@ -214,21 +225,24 @@ class MusicBrainz:
 
 
 class FanartTv:
+    """FanartTv support for metadata retrieval."""
+
     def __init__(self, mass):
+        """Initialize class."""
         self.mass = mass
         self.cache = mass.cache
         self._http_session = None
         self.throttler = None
 
     async def async_setup(self):
-        """perform async setup"""
+        """Perform async setup."""
         self._http_session = aiohttp.ClientSession(
             loop=self.mass.loop, connector=aiohttp.TCPConnector()
         )
         self.throttler = Throttler(rate_limit=1, period=2)
 
     async def async_get_artist_images(self, mb_artist_id):
-        """retrieve images by musicbrainz artist id"""
+        """Retrieve images by musicbrainz artist id."""
         metadata = {}
         data = await self.async_get_data("music/%s" % mb_artist_id)
         if data:
@@ -243,7 +257,7 @@ class FanartTv:
                     metadata[key] = item["url"]
             if data.get("artistthumb"):
                 url = data["artistthumb"][0]["url"]
-                if not "2a96cbd8b46e442fc41c2b86b821562f" in url:
+                if "2a96cbd8b46e442fc41c2b86b821562f" not in url:
                     metadata["image"] = url
             if data.get("musicbanner"):
                 metadata["banner"] = data["musicbanner"][0]["url"]
@@ -251,7 +265,7 @@ class FanartTv:
 
     @async_use_cache(30)
     async def async_get_data(self, endpoint, params=None):
-        """get data from api"""
+        """Get data from api."""
         if params is None:
             params = {}
         url = "http://webservice.fanart.tv/v3/%s" % endpoint
@@ -262,7 +276,10 @@ class FanartTv:
             ) as response:
                 try:
                     result = await response.json()
-                except (aiohttp.client_exceptions.ContentTypeError, json.decoder.JSONDecodeError):
+                except (
+                    aiohttp.client_exceptions.ContentTypeError,
+                    json.decoder.JSONDecodeError,
+                ):
                     LOGGER.error("Failed to retrieve %s", endpoint)
                     text_result = await response.text()
                     LOGGER.debug(text_result)
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..67f0447179d6a1a4b4d20bea05f33653905ac0fb 100644 (file)
@@ -0,0 +1 @@
+"""Models."""
index aa7781aea645ec8a036ba88df71560bbca2aa0dc..1797dbdb558566ff4e9110cc8fa369dd3bc44cf9 100644 (file)
@@ -17,7 +17,6 @@ class ConfigEntryType(str, Enum):
     HEADER = "header"
 
 
-
 @dataclass
 class ConfigEntry:
     """Model for a Config Entry."""
@@ -33,4 +32,4 @@ class ConfigEntry:
     depends_on: str = ""  # entry_key that needs to be set before this setting shows up in frontend
     hidden: bool = False  # hide from UI
     value: Optional[Any] = None  # set by the configuration manager
-    store_hashed: bool = False # value will be hashed, non reversible
+    store_hashed: bool = False  # value will be hashed, non reversible
index 8c6afd75f57cd6620e17bc86e49af25d533bedd0..de0e8f794f6b5274dc41b272dd46b86590e256af 100755 (executable)
@@ -1,12 +1,13 @@
 """Models and helpers for media items."""
 
 from dataclasses import dataclass, field
-from enum import Enum, Enum
+from enum import Enum
 from typing import List, Optional
 
 
 class MediaType(int, Enum):
     """Enum for MediaType."""
+
     Artist = 1
     Album = 2
     Track = 3
@@ -32,6 +33,7 @@ def media_type_from_string(media_type_str: str) -> MediaType:
 
 class ContributorRole(int, Enum):
     """Enum for Contributor Role."""
+
     Artist = 1
     Writer = 2
     Producer = 3
@@ -39,6 +41,7 @@ class ContributorRole(int, Enum):
 
 class AlbumType(int, Enum):
     """Enum for Album type."""
+
     Album = 1
     Single = 2
     Compilation = 3
@@ -46,6 +49,7 @@ class AlbumType(int, Enum):
 
 class TrackQuality(int, Enum):
     """Enum for Track Quality."""
+
     LOSSY_MP3 = 0
     LOSSY_OGG = 1
     LOSSY_AAC = 2
@@ -58,8 +62,9 @@ class TrackQuality(int, Enum):
 
 
 @dataclass
-class MediaItemProviderId():
+class MediaItemProviderId:
     """Model for a MediaItem's provider id."""
+
     provider: str
     item_id: str
     quality: Optional[TrackQuality] = TrackQuality.UNKNOWN
@@ -68,15 +73,16 @@ class MediaItemProviderId():
 
 class ExternalId(str, Enum):
     """Enum with external id's."""
+
     MUSICBRAINZ = "musicbrainz"
     UPC = "upc"
     ISRC = "isrc"
 
 
-
 @dataclass
 class MediaItem(object):
     """Representation of a media item."""
+
     item_id: str = ""
     provider: str = ""
     name: str = ""
@@ -91,14 +97,16 @@ class MediaItem(object):
 
 @dataclass
 class Artist(MediaItem):
-    """Model for an artist"""
+    """Model for an artist."""
+
     media_type: MediaType = MediaType.Artist
     sort_name: str = ""
 
 
 @dataclass
 class Album(MediaItem):
-    """Model for an album"""
+    """Model for an album."""
+
     media_type: MediaType = MediaType.Album
     version: str = ""
     year: int = 0
@@ -109,7 +117,8 @@ class Album(MediaItem):
 
 @dataclass
 class Track(MediaItem):
-    """Model for a track"""
+    """Model for a track."""
+
     media_type: MediaType = MediaType.Track
     duration: int = 0
     version: str = ""
@@ -121,7 +130,8 @@ class Track(MediaItem):
 
 @dataclass
 class Playlist(MediaItem):
-    """Model for a playlist"""
+    """Model for a playlist."""
+
     media_type: MediaType = MediaType.Playlist
     owner: str = ""
     checksum: [Optional[str]] = None  # some value to detect playlist track changes
@@ -130,14 +140,16 @@ class Playlist(MediaItem):
 
 @dataclass
 class Radio(MediaItem):
-    """Model for a radio station"""
+    """Model for a radio station."""
+
     media_type: MediaType = MediaType.Radio
     duration: int = 86400
 
 
 @dataclass
-class SearchResult():
+class SearchResult:
     """Model for Media Item Search result."""
+
     artists: List[Artist] = field(default_factory=list)
     albums: List[Album] = field(default_factory=list)
     tracks: List[Track] = field(default_factory=list)
index 21634542e2b84b45aff9d762b98677918dfe70ab..a24db70b738ad16932e99fdb4f670950840ec42b 100755 (executable)
@@ -13,14 +13,15 @@ from music_assistant.models.media_types import (
     SearchResult,
     Track,
 )
-from music_assistant.models.streamdetails import StreamDetails
 from music_assistant.models.provider import Provider, ProviderType
+from music_assistant.models.streamdetails import StreamDetails
 
 
 @dataclass
 class MusicProvider(Provider):
     """
     Base class for a Musicprovider.
+
     Should be overriden in the provider specific implementation.
     """
 
@@ -43,6 +44,7 @@ class MusicProvider(Provider):
     ) -> SearchResult:
         """
         Perform search on musicprovider.
+
             :param search_query: Search query.
             :param media_types: A list of media_types to include. All types if None.
             :param limit: Number of items to return in the search (per type).
@@ -76,7 +78,7 @@ class MusicProvider(Provider):
 
     @abstractmethod
     async def async_get_artist(self, prov_artist_id: str) -> Artist:
-        """Get full artist details by id"""
+        """Get full artist details by id."""
         raise NotImplementedError
 
     @abstractmethod
@@ -101,12 +103,12 @@ class MusicProvider(Provider):
 
     @abstractmethod
     async def async_get_playlist(self, prov_playlist_id: str) -> Playlist:
-        """Get full playlist details by id"""
+        """Get full playlist details by id."""
         raise NotImplementedError
 
     @abstractmethod
     async def async_get_radio(self, prov_radio_id: str) -> Radio:
-        """Get full radio details by id"""
+        """Get full radio details by id."""
         raise NotImplementedError
 
     @abstractmethod
@@ -125,7 +127,9 @@ class MusicProvider(Provider):
         raise NotImplementedError
 
     @abstractmethod
-    async def async_library_remove(self, prov_item_id: str, media_type: MediaType) -> bool:
+    async def async_library_remove(
+        self, prov_item_id: str, media_type: MediaType
+    ) -> bool:
         """Remove item from provider's library. Return true on succes."""
         raise NotImplementedError
 
index 78f985e72863c2000fa1abd8e5b1e78114dc9fc3..acd945cf129e4ae071fadbd5fa19c8eabbda3c2e 100755 (executable)
@@ -6,7 +6,6 @@ from enum import Enum
 from typing import Any, Awaitable, Callable, List, Optional, Union
 
 from music_assistant.models.config_entry import ConfigEntry
-from music_assistant.constants import EVENT_PLAYER_CONTROL_UPDATED, EVENT_PLAYER_CHANGED
 
 
 class PlayerState(str, Enum):
@@ -57,11 +56,11 @@ class Player:
     config_entries: List[ConfigEntry] = field(default_factory=list)
     updated_at: datetime = datetime.utcnow()  # managed by playermanager!
     active_queue: str = ""  # managed by playermanager!
-    group_parents: List[str] = field(default_factory=list) # managed by playermanager!
-    cur_queue_item_id: str = None # managed by playermanager!
+    group_parents: List[str] = field(default_factory=list)  # managed by playermanager!
+    cur_queue_item_id: str = None  # managed by playermanager!
 
     def __setattr__(self, name, value):
-        """Event when control is updated. Do not override"""
+        """Event when control is updated. Do not override."""
         if name == "updated_at":
             # updated at is set by the on_update callback
             # make sure we do not hit an endless loop
@@ -84,8 +83,12 @@ class PlayerControlType(int, Enum):
 
 @dataclass
 class PlayerControl:
-    """Model for a player control which allows for a
-    plugin-like structure to override common player commands."""
+    """
+    Model for a player control.
+
+    Allows for a plugin-like
+    structure to override common player commands.
+    """
 
     type: PlayerControlType = PlayerControlType.UNKNOWN
     id: str = ""
index 65355d58e4d0ddcdba3988aa7871c352e3bce7d7..3ea790e53b2ec3b861042a94f5c97f0a1dbf8d59 100755 (executable)
@@ -1,6 +1,4 @@
-"""
-    Models and helpers for a player queue.
-"""
+"""Models and helpers for a player queue."""
 
 import logging
 import random
@@ -15,10 +13,10 @@ from music_assistant.constants import (
     EVENT_QUEUE_ITEMS_UPDATED,
     EVENT_QUEUE_UPDATED,
 )
-from music_assistant.models.media_types import Track, MediaType
+from music_assistant.models.media_types import MediaType, Track
 from music_assistant.models.player import PlayerFeature, PlayerState
 from music_assistant.models.streamdetails import StreamDetails
-from music_assistant.utils import callback, json_serializer
+from music_assistant.utils import callback
 
 # pylint: disable=too-many-instance-attributes
 # pylint: disable=too-many-public-methods
@@ -28,7 +26,7 @@ LOGGER = logging.getLogger("mass")
 
 
 class QueueOption(str, Enum):
-    """Enum representation of the queue (play) options"""
+    """Enum representation of the queue (play) options."""
 
     Play = "play"
     Replace = "replace"
@@ -45,6 +43,7 @@ class QueueItem(Track):
     queue_item_id: str = ""
 
     def __init__(self, media_item=None):
+        """Initialize class."""
         super().__init__()
         self.queue_item_id = str(uuid.uuid4())
         # if existing media_item given, load those values
@@ -54,11 +53,10 @@ class QueueItem(Track):
 
 
 class PlayerQueue:
-    """
-    Class that holds the queue items for a player.
-    """
+    """Class that holds the queue items for a player."""
 
     def __init__(self, mass, player_id):
+        """Initialize class."""
         self.mass = mass
         self.player_id = player_id
         self._items = []
@@ -75,7 +73,7 @@ class PlayerQueue:
         self.mass.add_job(self.__async_restore_saved_state())
 
     async def async_close(self):
-        """Call on shutdown/close."""
+        """Handle shutdown/close."""
         # pylint: disable=unused-argument
         await self.__async_save_state()
 
@@ -86,12 +84,12 @@ class PlayerQueue:
 
     @property
     def shuffle_enabled(self):
-        """Shuffle enabled property"""
+        """Return shuffle enabled property."""
         return self._shuffle_enabled
 
     @shuffle_enabled.setter
     def shuffle_enabled(self, enable_shuffle: bool):
-        """enable/disable shuffle"""
+        """Set shuffle."""
         if not self._shuffle_enabled and enable_shuffle:
             # shuffle requested
             self._shuffle_enabled = True
@@ -113,7 +111,7 @@ class PlayerQueue:
 
     @property
     def repeat_enabled(self):
-        """Returns if crossfade is enabled for this player."""
+        """Return if crossfade is enabled for this player."""
         return self._repeat_enabled
 
     @repeat_enabled.setter
@@ -126,13 +124,16 @@ class PlayerQueue:
 
     @property
     def crossfade_enabled(self):
-        """Returns if crossfade is enabled for this player's queue."""
-        return self.mass.config.player_settings[self.player_id]["crossfade_duration"] > 0
+        """Return if crossfade is enabled for this player's queue."""
+        return (
+            self.mass.config.player_settings[self.player_id]["crossfade_duration"] > 0
+        )
 
     @property
     def cur_index(self):
         """
-        Returns the current index of the queue.
+        Return the current index of the queue.
+
         Returns None if queue is empty.
         """
         if not self._items:
@@ -143,6 +144,7 @@ class PlayerQueue:
     def cur_item_id(self):
         """
         Return the queue item id of the current item in the queue.
+
         Returns None if queue is empty.
         """
         if self.cur_index is None or not len(self.items) > self.cur_index:
@@ -153,6 +155,7 @@ class PlayerQueue:
     def cur_item(self):
         """
         Return the current item in the queue.
+
         Returns None if queue is empty.
         """
         if self.cur_index is None or not len(self.items) > self.cur_index:
@@ -161,14 +164,14 @@ class PlayerQueue:
 
     @property
     def cur_item_time(self):
-        """Returns the time (progress) for current (playing) item."""
+        """Return the time (progress) for current (playing) item."""
         return self._cur_item_time
 
     @property
     def next_index(self):
-        """
-        Returns the next index for this player's queue.
-        Returns None if queue is empty or no more items.
+        """Return the next index for this player's queue.
+
+        Return None if queue is empty or no more items.
         """
         if not self.items:
             # queue is empty
@@ -187,8 +190,8 @@ class PlayerQueue:
 
     @property
     def next_item(self):
-        """
-        Returns the next item in the queue.
+        """Return the next item in the queue.
+
         Returns None if queue is empty or no more items.
         """
         if self.next_index is not None:
@@ -197,31 +200,30 @@ class PlayerQueue:
 
     @property
     def items(self):
-        """
-        Returns all queue items for this player's queue.
-        """
+        """Return all queue items for this player's queue."""
         return self._items
 
     @property
     def use_queue_stream(self):
         """
-        bool to indicate that we need to use the queue stream
-        for example if crossfading is requested but a player doesn't natively support it
-        it will send a constant stream of audio to the player with all tracks
+        Indicate that we need to use the queue stream.
+
+        For example if crossfading is requested but a player doesn't natively support it
+        it will send a constant stream of audio to the player with all tracks.
         """
         supports_crossfade = PlayerFeature.CROSSFADE in self.player.features
         return self.crossfade_enabled and not supports_crossfade
 
     @callback
     def get_item(self, index):
-        """get item by index from queue"""
+        """Get item by index from queue."""
         if index is not None and len(self.items) > index:
             return self.items[index]
         return None
 
     @callback
     def by_item_id(self, queue_item_id: str):
-        """get item by queue_item_id from queue"""
+        """Get item by queue_item_id from queue."""
         if not queue_item_id:
             return None
         for item in self.items:
@@ -259,11 +261,15 @@ class PlayerQueue:
             else:
                 # at this point we don't know if the queue is synced with the player
                 # so just to be safe we send the queue_items to the player
-                player_provider = self.mass.player_manager.get_player_provider(self.player_id)
+                player_provider = self.mass.player_manager.get_player_provider(
+                    self.player_id
+                )
                 await player_provider.async_cmd_queue_load(self.player_id, self.items)
                 await self.async_play_index(prev_index)
         else:
-            LOGGER.warning("resume queue requested for %s but queue is empty", self.player.name)
+            LOGGER.warning(
+                "resume queue requested for %s but queue is empty", self.player.name
+            )
 
     async def async_play_index(self, index):
         """Play item at index X in queue."""
@@ -279,10 +285,14 @@ class PlayerQueue:
                 self.mass.web.internal_url,
                 self.player.player_id,
             )
-            return await player_prov.async_cmd_play_uri(self.player_id, queue_stream_uri)
+            return await player_prov.async_cmd_play_uri(
+                self.player_id, queue_stream_uri
+            )
         elif supports_queue:
             try:
-                return await player_prov.async_cmd_queue_play_index(self.player_id, index)
+                return await player_prov.async_cmd_queue_play_index(
+                    self.player_id, index
+                )
             except NotImplementedError:
                 # not supported by player, use load queue instead
                 LOGGER.debug(
@@ -291,11 +301,14 @@ class PlayerQueue:
                 self._items = self._items[index:]
                 await player_prov.async_cmd_queue_load(self.player_id, self._items)
         else:
-            return await player_prov.async_cmd_play_uri(self.player_id, self._items[index].uri)
+            return await player_prov.async_cmd_play_uri(
+                self.player_id, self._items[index].uri
+            )
 
     async def async_move_item(self, queue_item_id, pos_shift=1):
         """
-        move queue item x up/down the queue
+        Move queue item x up/down the queue.
+
         param pos_shift: move item x positions down if positive value
                          move item x positions up if negative value
                          move item to top of queue as next item
@@ -317,7 +330,7 @@ class PlayerQueue:
             await self.async_play_index(new_index)
 
     async def async_load(self, queue_items: List[QueueItem]):
-        """load (overwrite) queue with new items"""
+        """Load (overwrite) queue with new items."""
         supports_queue = PlayerFeature.QUEUE in self.player.features
         for index, item in enumerate(queue_items):
             item.sort_index = index
@@ -334,8 +347,9 @@ class PlayerQueue:
 
     async def async_insert(self, queue_items: List[QueueItem], offset=0):
         """
-        insert new items at offset x from current position
-        keeps remaining items in queue
+        Insert new items at offset x from current position.
+
+        Keeps remaining items in queue.
         if offset 0, will start playing newly added item(s)
             :param queue_items: a list of QueueItem
             :param offset: offset from current queue position
@@ -354,7 +368,9 @@ class PlayerQueue:
             item.sort_index = insert_at_index + index
         if self.shuffle_enabled:
             queue_items = self.__shuffle_items(queue_items)
-        self._items = self._items[:insert_at_index] + queue_items + self._items[insert_at_index:]
+        self._items = (
+            self._items[:insert_at_index] + queue_items + self._items[insert_at_index:]
+        )
         if self.use_queue_stream or not supports_queue:
             if offset == 0:
                 await self.async_play_index(insert_at_index)
@@ -376,9 +392,7 @@ class PlayerQueue:
         self.mass.add_job(self.__async_save_state())
 
     async def async_append(self, queue_items: List[QueueItem]):
-        """
-        append new items at the end of the queue
-        """
+        """Append new items at the end of the queue."""
         supports_queue = PlayerFeature.QUEUE in self.player.features
         for index, item in enumerate(queue_items):
             item.sort_index = len(self.items) + index
@@ -405,9 +419,7 @@ class PlayerQueue:
         self.mass.add_job(self.__async_save_state())
 
     async def async_update(self, queue_items: List[QueueItem]):
-        """
-        update the existing queue items, mostly caused by reordering
-        """
+        """Update the existing queue items, mostly caused by reordering."""
         supports_queue = PlayerFeature.QUEUE in self.player.features
         self._items = queue_items
         if supports_queue and not self.use_queue_stream:
@@ -426,9 +438,7 @@ class PlayerQueue:
         self.mass.add_job(self.__async_save_state())
 
     async def async_clear(self):
-        """
-        clear all items in the queue
-        """
+        """Clear all items in the queue."""
         supports_queue = PlayerFeature.QUEUE in self.player.features
         await self.mass.player_manager.async_cmd_stop(self.player_id)
         self._items = []
@@ -465,7 +475,7 @@ class PlayerQueue:
         self.mass.signal_event(EVENT_QUEUE_UPDATED, self.to_dict())
 
     async def async_start_queue_stream(self):
-        """called by the queue streamer when it starts playing the queue stream"""
+        """Call when queue_streamer starts playing the queue stream."""
         self._last_queue_startindex = self._next_queue_startindex
         return self.get_item(self._next_queue_startindex)
 
@@ -474,6 +484,7 @@ class PlayerQueue:
     ) -> StreamDetails:
         """
         Get streamdetails for the given queue_item.
+
         This is called just-in-time when a player/queue wants a QueueItem to be played.
         Do not try to request streamdetails in advance as this is expiring data.
             param player_id: The id of the player that will be playing the stream.
@@ -488,13 +499,17 @@ class PlayerQueue:
                 queue_item.item_id, queue_item.provider, lazy=True, refresh=True
             )
         # sort by quality and check track availability
-        for prov_media in sorted(full_track.provider_ids, key=lambda x: x.quality, reverse=True):
+        for prov_media in sorted(
+            full_track.provider_ids, key=lambda x: x.quality, reverse=True
+        ):
             # get streamdetails from provider
             music_prov = self.mass.get_provider(prov_media.provider)
             if not music_prov:
                 continue  # provider temporary unavailable ?
 
-            streamdetails = await music_prov.async_get_stream_details(prov_media.item_id)
+            streamdetails = await music_prov.async_get_stream_details(
+                prov_media.item_id
+            )
 
             if streamdetails:
                 # set streamdetails as attribute on the queue_item
@@ -503,7 +518,7 @@ class PlayerQueue:
         return None
 
     def to_dict(self):
-        """instance attributes as dict so it can be serialized to json"""
+        """Instance attributes as dict so it can be serialized to json."""
         return {
             "player_id": self.player.player_id,
             "shuffle_enabled": self.shuffle_enabled,
@@ -521,13 +536,16 @@ class PlayerQueue:
 
     @callback
     def __get_queue_stream_index(self):
+        """Get index of queue stream."""
         # player is playing a constant stream of the queue so we need to do this the hard way
         queue_index = 0
         elapsed_time_queue = self.player.elapsed_time
         total_time = 0
         track_time = 0
         if self.items and len(self.items) > self._last_queue_startindex:
-            queue_index = self._last_queue_startindex  # holds the last starting position
+            queue_index = (
+                self._last_queue_startindex
+            )  # holds the last starting position
             queue_track = None
             while len(self.items) > queue_index:
                 queue_track = self.items[queue_index]
@@ -541,14 +559,16 @@ class PlayerQueue:
         return queue_index, track_time
 
     async def async_process_queue_update(self, new_index, track_time):
-        """compare the queue index to determine if playback changed"""
+        """Compare the queue index to determine if playback changed."""
         new_track = self.get_item(new_index)
         if (not self._last_track and new_track) or self._last_track != new_track:
             # queue track updated
             # account for track changing state so trigger track change after 1 second
             if self._last_track and self._last_track.streamdetails:
                 self._last_track.streamdetails.seconds_played = self._last_item_time
-                self.mass.signal_event(EVENT_PLAYBACK_STOPPED, self._last_track.streamdetails)
+                self.mass.signal_event(
+                    EVENT_PLAYBACK_STOPPED, self._last_track.streamdetails
+                )
             if new_track and new_track.streamdetails:
                 self.mass.signal_event(EVENT_PLAYBACK_STARTED, new_track.streamdetails)
                 self._last_track = new_track
@@ -560,7 +580,9 @@ class PlayerQueue:
             ]:
                 # player stopped playing
                 if self._last_track:
-                    self.mass.signal_event(EVENT_PLAYBACK_STOPPED, self._last_track.streamdetails)
+                    self.mass.signal_event(
+                        EVENT_PLAYBACK_STOPPED, self._last_track.streamdetails
+                    )
         # update vars
         if track_time > 2:
             # account for track changing state so keep this a few seconds behind
@@ -570,13 +592,13 @@ class PlayerQueue:
 
     @staticmethod
     def __shuffle_items(queue_items):
-        """shuffle a list of tracks"""
+        """Shuffle a list of tracks."""
         # for now we use default python random function
         # can be extended with some more magic last_played and stuff
         return random.sample(queue_items, len(queue_items))
 
     def __index_by_id(self, queue_item_id):
-        """get index by queue_item_id"""
+        """Get index by queue_item_id."""
         item_index = None
         for index, item in enumerate(self.items):
             if item.queue_item_id == queue_item_id:
@@ -584,7 +606,7 @@ class PlayerQueue:
         return item_index
 
     async def __async_restore_saved_state(self):
-        """try to load the saved queue for this player from cache file"""
+        """Try to load the saved queue for this player from cache file."""
         cache_str = "queue_state_%s" % self.player.player_id
         cache_data = await self.mass.cache.async_get(cache_str)
         if cache_data:
@@ -597,7 +619,7 @@ class PlayerQueue:
     # pylint: enable=unused-argument
 
     async def __async_save_state(self):
-        """save current queue settings to file"""
+        """Save current queue settings to file."""
         cache_str = "queue_state_%s" % self.player_id
         cache_data = {
             "shuffle_enabled": self._shuffle_enabled,
index 0061140c6438fa9d3b6cdffdabe66b5b3db28a28..a4cfe8fb8c9d4cbef315c9564be84c3534c212bf 100755 (executable)
@@ -7,12 +7,15 @@ from typing import List
 from music_assistant.models.player_queue import QueueItem
 from music_assistant.models.provider import Provider, ProviderType
 
+
 @dataclass
 class PlayerProvider(Provider):
     """
-        Base class for a Playerprovider
-        Should be overridden/subclassed by provider specific implementation.
+    Base class for a Playerprovider.
+
+    Should be overridden/subclassed by provider specific implementation.
     """
+
     type: ProviderType = ProviderType.PLAYER_PROVIDER
 
     # SERVICE CALLS / PLAYER COMMANDS
@@ -20,82 +23,92 @@ class PlayerProvider(Provider):
     @abstractmethod
     async def async_cmd_play_uri(self, player_id: str, uri: str):
         """
-            Play the specified uri/url on the goven player.
-                :param player_id: player_id of the player to handle the command.
+        Play the specified uri/url on the given player.
+
+            :param player_id: player_id of the player to handle the command.
         """
         raise NotImplementedError
 
     @abstractmethod
     async def async_cmd_stop(self, player_id: str):
         """
-            Send STOP command to given player.
-                :param player_id: player_id of the player to handle the command.
+        Send STOP command to given player.
+
+            :param player_id: player_id of the player to handle the command.
         """
         raise NotImplementedError
 
     @abstractmethod
     async def async_cmd_play(self, player_id: str):
         """
-            Send STOP command to given player.
-                :param player_id: player_id of the player to handle the command.
+        Send PLAY command to given player.
+
+            :param player_id: player_id of the player to handle the command.
         """
         raise NotImplementedError
 
     @abstractmethod
     async def async_cmd_pause(self, player_id: str):
         """
-            Send PAUSE command to given player.
-                :param player_id: player_id of the player to handle the command.
+        Send PAUSE command to given player.
+
+            :param player_id: player_id of the player to handle the command.
         """
         raise NotImplementedError
 
     @abstractmethod
     async def async_cmd_next(self, player_id: str):
         """
-            Send NEXT TRACK command to given player.
-                :param player_id: player_id of the player to handle the command.
+        Send NEXT TRACK command to given player.
+
+            :param player_id: player_id of the player to handle the command.
         """
         raise NotImplementedError
 
     @abstractmethod
     async def async_cmd_previous(self, player_id: str):
         """
-            Send PREVIOUS TRACK command to given player.
-                :param player_id: player_id of the player to handle the command.
+        Send PREVIOUS TRACK command to given player.
+
+            :param player_id: player_id of the player to handle the command.
         """
         raise NotImplementedError
 
     @abstractmethod
     async def async_cmd_power_on(self, player_id: str):
         """
-            Send POWER ON command to given player.
-                :param player_id: player_id of the player to handle the command.
+        Send POWER ON command to given player.
+
+            :param player_id: player_id of the player to handle the command.
         """
         raise NotImplementedError
 
     @abstractmethod
     async def async_cmd_power_off(self, player_id: str):
         """
-            Send POWER OFF command to given player.
-                :param player_id: player_id of the player to handle the command.
+        Send POWER OFF command to given player.
+
+            :param player_id: player_id of the player to handle the command.
         """
         raise NotImplementedError
 
     @abstractmethod
     async def async_cmd_volume_set(self, player_id: str, volume_level: int):
         """
-            Send volume level command to given player.
-                :param player_id: player_id of the player to handle the command.
-                :param volume_level: volume level to set (0..100).
+        Send volume level command to given player.
+
+            :param player_id: player_id of the player to handle the command.
+            :param volume_level: volume level to set (0..100).
         """
         raise NotImplementedError
 
     @abstractmethod
     async def async_cmd_volume_mute(self, player_id: str, is_muted=False):
         """
-            Send volume MUTE command to given player.
-                :param player_id: player_id of the player to handle the command.
-                :param is_muted: bool with new mute state.
+        Send volume MUTE command to given player.
+
+            :param player_id: player_id of the player to handle the command.
+            :param is_muted: bool with new mute state.
         """
         raise NotImplementedError
 
@@ -104,52 +117,65 @@ class PlayerProvider(Provider):
 
     async def async_cmd_queue_play_index(self, player_id: str, index: int):
         """
-            Play item at index X on player's queue
-                :param player_id: player_id of the player to handle the command.
-                :param index: (int) index of the queue item that should start playing
+        Play item at index X on player's queue.
+
+            :param player_id: player_id of the player to handle the command.
+            :param index: (int) index of the queue item that should start playing
         """
         raise NotImplementedError
 
     async def async_cmd_queue_load(self, player_id: str, queue_items: List[QueueItem]):
         """
-            Load/overwrite given items in the player's queue implementation
-                :param player_id: player_id of the player to handle the command.
-                :param queue_items: a list of QueueItems
+        Load/overwrite given items in the player's queue implementation.
+
+            :param player_id: player_id of the player to handle the command.
+            :param queue_items: a list of QueueItems
         """
         raise NotImplementedError
 
-    async def async_cmd_queue_insert(self,
-                                     player_id: str,
-                                     queue_items: List[QueueItem],
-                                     insert_at_index: int):
+    async def async_cmd_queue_insert(
+        self, player_id: str, queue_items: List[QueueItem], insert_at_index: int
+    ):
         """
-            Insert new items at position X into existing queue.
-            If insert_at_index 0 or None, will start playing newly added item(s)
-                :param player_id: player_id of the player to handle the command.
-                :param queue_items: a list of QueueItems
-                :param insert_at_index: queue position to insert new items
+        Insert new items at position X into existing queue.
+
+        If insert_at_index 0 or None, will start playing newly added item(s)
+            :param player_id: player_id of the player to handle the command.
+            :param queue_items: a list of QueueItems
+            :param insert_at_index: queue position to insert new items
         """
+        # pylint: disable=abstract-method
         raise NotImplementedError
 
-    async def async_cmd_queue_append(self, player_id: str, queue_items: List[QueueItem]):
+    async def async_cmd_queue_append(
+        self, player_id: str, queue_items: List[QueueItem]
+    ):
         """
-            Append new items at the end of the queue.
-                :param player_id: player_id of the player to handle the command.
-                :param queue_items: a list of QueueItems
+        Append new items at the end of the queue.
+
+            :param player_id: player_id of the player to handle the command.
+            :param queue_items: a list of QueueItems
         """
+        # pylint: disable=abstract-method
         raise NotImplementedError
 
-    async def async_cmd_queue_update(self, player_id: str, queue_items: List[QueueItem]):
+    async def async_cmd_queue_update(
+        self, player_id: str, queue_items: List[QueueItem]
+    ):
         """
-            Overwrite the existing items in the queue, used for reordering.
-                :param player_id: player_id of the player to handle the command.
-                :param queue_items: a list of QueueItems
+        Overwrite the existing items in the queue, used for reordering.
+
+            :param player_id: player_id of the player to handle the command.
+            :param queue_items: a list of QueueItems
         """
+        # pylint: disable=abstract-method
         raise NotImplementedError
 
     async def async_cmd_queue_clear(self, player_id: str):
         """
-            Clear the player's queue.
-                :param player_id: player_id of the player to handle the command.
+        Clear the player's queue.
+
+            :param player_id: player_id of the player to handle the command.
         """
+        # pylint: disable=abstract-method
         raise NotImplementedError
index 1628079edac8c0597cba5bacb6cc2db487a1eb45..35530c191462ef10bc33e0ef5dc5461f22abadba 100644 (file)
@@ -3,7 +3,7 @@
 from abc import abstractmethod
 from dataclasses import dataclass
 from enum import Enum
-from typing import TYPE_CHECKING, Any, List, Optional
+from typing import TYPE_CHECKING, List
 
 from music_assistant.models.config_entry import ConfigEntry
 
@@ -44,17 +44,19 @@ class Provider:
 
     @abstractmethod
     async def async_on_start(self) -> bool:
-        """Called on startup.
+        """
         Handle initialization of the provider based on config.
-        Return bool if start was succesfull"""
+
+        Return bool if start was succesfull. Called on startup.
+        """
         raise NotImplementedError
 
     @abstractmethod
     async def async_on_stop(self):
-        """Called on shutdown. Handle correct close/cleanup of the provider on exit."""
+        """Handle correct close/cleanup of the provider on exit. Called on shutdown."""
         raise NotImplementedError
 
     async def async_on_reload(self):
-        """Called on reload. Handle configuration changes for this provider."""
+        """Handle configuration changes for this provider. Called on reload."""
         await self.async_on_stop()
         await self.async_on_start()
index 1911205cc4912adedf9e287033a46a1bf704821d..8f42bd31c1d494c0ce4f40e05b221d31e4dfc584 100644 (file)
@@ -1,6 +1,4 @@
-"""
-    Models and helpers for the streamdetails of a MediaItem.
-"""
+"""Models and helpers for the streamdetails of a MediaItem."""
 
 from dataclasses import dataclass
 from enum import Enum
@@ -9,6 +7,7 @@ from typing import Any, Optional
 
 class StreamType(str, Enum):
     """Enum with stream types."""
+
     EXECUTABLE = "executable"
     URL = "url"
     FILE = "file"
@@ -16,6 +15,7 @@ class StreamType(str, Enum):
 
 class ContentType(str, Enum):
     """Enum with stream content types."""
+
     OGG = "ogg"
     FLAC = "flac"
     MP3 = "mp3"
@@ -24,8 +24,9 @@ class ContentType(str, Enum):
 
 
 @dataclass
-class StreamDetails():
+class StreamDetails:
     """Model for streamdetails."""
+
     type: StreamType
     provider: str
     item_id: str
index d2b80da11aad0b46a763472847188df5804e7776..2c88381f0f8020d505772ede42a6f4e32c6ee48f 100755 (executable)
@@ -9,7 +9,6 @@ import time
 from typing import List, Optional
 
 import aiohttp
-from PIL import Image
 from music_assistant.cache import async_cached, async_cached_generator
 from music_assistant.constants import EVENT_MUSIC_SYNC_STATUS
 from music_assistant.models.media_types import (
@@ -26,13 +25,13 @@ from music_assistant.models.media_types import (
 from music_assistant.models.musicprovider import MusicProvider
 from music_assistant.models.provider import ProviderType
 from music_assistant.utils import compare_strings, run_periodic
-
+from PIL import Image
 
 LOGGER = logging.getLogger("mass")
 
 
 def sync_task(desc):
-    """Decorator to report a sync task."""
+    """Return decorator to report a sync task."""
 
     def wrapper(func):
         @functools.wraps(func)
@@ -42,16 +41,22 @@ def sync_task(desc):
             # check if this sync task is not already running
             for sync_prov_id, sync_desc in method_class.running_sync_jobs:
                 if sync_prov_id == prov_id and sync_desc == desc:
-                    LOGGER.debug("Syncjob %s for provider %s is already running!", desc, prov_id)
+                    LOGGER.debug(
+                        "Syncjob %s for provider %s is already running!", desc, prov_id
+                    )
                     return
             LOGGER.debug("Start syncjob %s for provider %s.", desc, prov_id)
             sync_job = (prov_id, desc)
             method_class.running_sync_jobs.append(sync_job)
-            method_class.mass.signal_event(EVENT_MUSIC_SYNC_STATUS, method_class.running_sync_jobs)
+            method_class.mass.signal_event(
+                EVENT_MUSIC_SYNC_STATUS, method_class.running_sync_jobs
+            )
             await func(*args)
             LOGGER.info("Finished syncing %s for provider %s", desc, prov_id)
             method_class.running_sync_jobs.remove(sync_job)
-            method_class.mass.signal_event(EVENT_MUSIC_SYNC_STATUS, method_class.running_sync_jobs)
+            method_class.mass.signal_event(
+                EVENT_MUSIC_SYNC_STATUS, method_class.running_sync_jobs
+            )
 
         return async_wrapped
 
@@ -62,6 +67,7 @@ class MusicManager:
     """Several helpers around the musicproviders."""
 
     def __init__(self, mass):
+        """Initialize class."""
         self.running_sync_jobs = []
         self.mass = mass
         self.cache = mass.cache
@@ -95,7 +101,9 @@ class MusicManager:
             return await self.async_get_radio(item_id, provider_id)
         return None
 
-    async def async_get_artist(self, item_id: str, provider_id: str, lazy: bool = True) -> Artist:
+    async def async_get_artist(
+        self, item_id: str, provider_id: str, lazy: bool = True
+    ) -> Artist:
         """Return artist details for the given provider artist id."""
         assert item_id and provider_id
         db_id = await self.mass.database.async_get_database_id(
@@ -107,9 +115,13 @@ class MusicManager:
             if not provider.available:
                 return None
             cache_key = f"{provider_id}.get_artist.{item_id}"
-            artist = await async_cached(self.cache, cache_key, provider.async_get_artist(item_id))
+            artist = await async_cached(
+                self.cache, cache_key, provider.async_get_artist(item_id)
+            )
             if not artist:
-                raise Exception("Artist %s not found on provider %s" % (item_id, provider_id))
+                raise Exception(
+                    "Artist %s not found on provider %s" % (item_id, provider_id)
+                )
             if lazy:
                 self.mass.add_job(self.__async_add_artist(artist))
                 artist.is_lazy = True
@@ -118,7 +130,11 @@ class MusicManager:
         return await self.mass.database.async_get_artist(db_id)
 
     async def async_get_album(
-        self, item_id: str, provider_id: str, lazy=True, album_details: Optional[Album] = None
+        self,
+        item_id: str,
+        provider_id: str,
+        lazy=True,
+        album_details: Optional[Album] = None,
     ) -> Album:
         """Return album details for the given provider album id."""
         assert item_id and provider_id
@@ -136,7 +152,9 @@ class MusicManager:
                     self.cache, cache_key, provider.async_get_album(item_id)
                 )
             if not album_details:
-                raise Exception("Album %s not found on provider %s" % (item_id, provider_id))
+                raise Exception(
+                    "Album %s not found on provider %s" % (item_id, provider_id)
+                )
             if lazy:
                 self.mass.add_job(self.__async_add_album(album_details))
                 album_details.is_lazy = True
@@ -159,8 +177,8 @@ class MusicManager:
         )
         if db_id and refresh:
             # in some cases (e.g. at playback time or requesting full track info)
-            # it's useful to have the track refreshed from the provider instead of the database cache
-            # this is to make sure that the track is available and perhaps
+            # it's useful to have the track refreshed from the provider instead of
+            # the database cache to make sure that the track is available and perhaps
             # another or a higher quality version is available.
             if lazy:
                 self.mass.add_job(self.__async_match_track(db_id))
@@ -177,7 +195,9 @@ class MusicManager:
                     self.cache, cache_key, provider.async_get_track(item_id)
                 )
             if not track_details:
-                raise Exception("Track %s not found on provider %s" % (item_id, provider_id))
+                raise Exception(
+                    "Track %s not found on provider %s" % (item_id, provider_id)
+                )
             if lazy:
                 self.mass.add_job(self.__async_add_track(track_details))
                 track_details.is_lazy = True
@@ -215,8 +235,10 @@ class MusicManager:
             db_id = await self.mass.database.async_add_radio(item_details)
         return await self.mass.database.async_get_radio(db_id)
 
-    async def async_get_album_tracks(self, item_id: str, provider_id: str) -> List[Track]:
-        """Return album tracks for the given provider album id. Generator!"""
+    async def async_get_album_tracks(
+        self, item_id: str, provider_id: str
+    ) -> List[Track]:
+        """Return album tracks for the given provider album id. Generator."""
         assert item_id and provider_id
         if provider_id == "database":
             # album tracks are not stored in db, we always fetch them (cached) from the provider.
@@ -236,15 +258,19 @@ class MusicManager:
             )
             if db_id:
                 # return database track instead if we have a match
-                db_item = await self.mass.database.async_get_track(db_id, fulldata=False)
+                db_item = await self.mass.database.async_get_track(
+                    db_id, fulldata=False
+                )
                 db_item.disc_number = item.disc_number
                 db_item.track_number = item.track_number
                 yield db_item
             else:
                 yield item
 
-    async def async_get_playlist_tracks(self, item_id: str, provider_id: str) -> List[Track]:
-        """Return playlist tracks for the given provider playlist id. Generator!"""
+    async def async_get_playlist_tracks(
+        self, item_id: str, provider_id: str
+    ) -> List[Track]:
+        """Return playlist tracks for the given provider playlist id. Generator."""
         assert item_id and provider_id
         if provider_id == "database":
             # playlist tracks are not stored in db, we always fetch them (cached) from the provider.
@@ -278,8 +304,10 @@ class MusicManager:
                 pos += 1
                 yield item
 
-    async def async_get_artist_toptracks(self, artist_id: str, provider_id: str) -> List[Track]:
-        """Return top tracks for an artist. Generator!"""
+    async def async_get_artist_toptracks(
+        self, artist_id: str, provider_id: str
+    ) -> List[Track]:
+        """Return top tracks for an artist. Generator."""
         async with self.mass.database.db_conn() as db_conn:
             if provider_id == "database":
                 # tracks from all providers
@@ -289,12 +317,15 @@ class MusicManager:
                 )
                 for prov_id in artist.provider_ids:
                     provider = self.mass.get_provider(prov_id.provider)
-                    if not provider or not MediaType.Track in provider.supported_mediatypes:
+                    if (
+                        not provider
+                        or MediaType.Track not in provider.supported_mediatypes
+                    ):
                         continue
                     async for item in self.async_get_artist_toptracks(
                         prov_id.item_id, prov_id.provider
                     ):
-                        if not item.item_id in item_ids:
+                        if item.item_id not in item_ids:
                             yield item
                             item_ids.append(item.item_id)
             else:
@@ -302,34 +333,48 @@ class MusicManager:
                 provider = self.mass.get_provider(provider_id)
                 cache_key = f"{provider_id}.artist_toptracks.{artist_id}"
                 async for item in async_cached_generator(
-                    self.cache, cache_key, provider.async_get_artist_toptracks(artist_id)
+                    self.cache,
+                    cache_key,
+                    provider.async_get_artist_toptracks(artist_id),
                 ):
                     if item:
                         assert item.item_id and item.provider
                         db_id = await self.mass.database.async_get_database_id(
-                            item.provider, item.item_id, MediaType.Track, db_conn=db_conn
+                            item.provider,
+                            item.item_id,
+                            MediaType.Track,
+                            db_conn=db_conn,
                         )
                         if db_id:
                             # return database track instead if we have a match
-                            yield await self.mass.database.async_get_track(db_id, fulldata=False, db_conn=db_conn)
+                            yield await self.mass.database.async_get_track(
+                                db_id, fulldata=False, db_conn=db_conn
+                            )
                         else:
                             yield item
 
-    async def async_get_artist_albums(self, artist_id: str, provider_id: str) -> List[Album]:
-        """Return (all) albums for an artist. Generator!"""
+    async def async_get_artist_albums(
+        self, artist_id: str, provider_id: str
+    ) -> List[Album]:
+        """Return (all) albums for an artist. Generator."""
         async with self.mass.database.db_conn() as db_conn:
             if provider_id == "database":
                 # albums from all providers
                 item_ids = []
-                artist = await self.mass.database.async_get_artist(artist_id, True, db_conn=db_conn)
+                artist = await self.mass.database.async_get_artist(
+                    artist_id, True, db_conn=db_conn
+                )
                 for prov_id in artist.provider_ids:
                     provider = self.mass.get_provider(prov_id.provider)
-                    if not provider or not MediaType.Album in provider.supported_mediatypes:
+                    if (
+                        not provider
+                        or MediaType.Album not in provider.supported_mediatypes
+                    ):
                         continue
                     async for item in self.async_get_artist_albums(
                         prov_id.item_id, prov_id.provider
                     ):
-                        if not item.item_id in item_ids:
+                        if item.item_id not in item_ids:
                             yield item
                             item_ids.append(item.item_id)
             else:
@@ -345,7 +390,9 @@ class MusicManager:
                     )
                     if db_id:
                         # return database album instead if we have a match
-                        yield await self.mass.database.async_get_album(db_id, db_conn=db_conn)
+                        yield await self.mass.database.async_get_album(
+                            db_id, db_conn=db_conn
+                        )
                     else:
                         yield item
 
@@ -354,7 +401,7 @@ class MusicManager:
     async def async_get_library_artists(
         self, orderby: str = "name", provider_filter: str = None
     ) -> List[Artist]:
-        """Return all library artists, optionally filtered by provider. Generator!"""
+        """Return all library artists, optionally filtered by provider. Generator."""
         async for item in self.mass.database.async_get_library_artists(
             provider_id=provider_filter, orderby=orderby
         ):
@@ -363,7 +410,7 @@ class MusicManager:
     async def async_get_library_albums(
         self, orderby: str = "name", provider_filter: str = None
     ) -> List[Album]:
-        """Return all library albums, optionally filtered by provider. Generator!"""
+        """Return all library albums, optionally filtered by provider. Generator."""
         async for item in self.mass.database.async_get_library_albums(
             provider_id=provider_filter, orderby=orderby
         ):
@@ -372,7 +419,7 @@ class MusicManager:
     async def async_get_library_tracks(
         self, orderby: str = "name", provider_filter: str = None
     ) -> List[Track]:
-        """Return all library tracks, optionally filtered by provider. Generator!"""
+        """Return all library tracks, optionally filtered by provider. Generator."""
         async for item in self.mass.database.async_get_library_tracks(
             provider_id=provider_filter, orderby=orderby
         ):
@@ -381,7 +428,7 @@ class MusicManager:
     async def async_get_library_playlists(
         self, orderby: str = "name", provider_filter: str = None
     ) -> List[Playlist]:
-        """Return all library playlists, optionally filtered by provider. Generator!"""
+        """Return all library playlists, optionally filtered by provider. Generator."""
         async for item in self.mass.database.async_get_library_playlists(
             provider_id=provider_filter, orderby=orderby
         ):
@@ -390,7 +437,7 @@ class MusicManager:
     async def async_get_library_radios(
         self, orderby: str = "name", provider_filter: str = None
     ) -> List[Playlist]:
-        """Return all library radios, optionally filtered by provider. Generator!"""
+        """Return all library radios, optionally filtered by provider. Generator."""
         async for item in self.mass.database.async_get_library_radios(
             provider_id=provider_filter, orderby=orderby
         ):
@@ -424,7 +471,9 @@ class MusicManager:
         await self.__async_match_album(db_id)
         return db_id
 
-    async def __async_add_track(self, track: Track, album_id: Optional[str] = None) -> int:
+    async def __async_add_track(
+        self, track: Track, album_id: Optional[str] = None
+    ) -> int:
         """Add track to local db and return the new database id."""
         track_artists = []
         # we need to fetch track artists too
@@ -437,7 +486,9 @@ class MusicManager:
         track.artists = track_artists
         # fetch album details - prefer optional provided album_id
         if album_id:
-            album_details = await self.async_get_album(album_id, track.provider, lazy=False)
+            album_details = await self.async_get_album(
+                album_id, track.provider, lazy=False
+            )
             if album_details:
                 track.album = album_details
         # make sure we have a database album
@@ -454,7 +505,9 @@ class MusicManager:
     async def __async_get_artist_musicbrainz_id(self, artist: Artist):
         """Fetch musicbrainz id by performing search using the artist name, albums and tracks."""
         # try with album first
-        async for lookup_album in self.async_get_artist_albums(artist.item_id, artist.provider):
+        async for lookup_album in self.async_get_artist_albums(
+            artist.item_id, artist.provider
+        ):
             if not lookup_album:
                 continue
             musicbrainz_id = await self.mass.metadata.async_get_mb_artist_id(
@@ -465,7 +518,9 @@ class MusicManager:
             if musicbrainz_id:
                 return musicbrainz_id
         # fallback to track
-        async for lookup_track in self.async_get_artist_toptracks(artist.item_id, artist.provider):
+        async for lookup_track in self.async_get_artist_toptracks(
+            artist.item_id, artist.provider
+        ):
             if not lookup_track:
                 continue
             musicbrainz_id = await self.mass.metadata.async_get_mb_artist_id(
@@ -482,6 +537,7 @@ class MusicManager:
     async def __async_match_artist(self, db_artist_id: int):
         """
         Try to find matching artists on all providers for the provided (database) artist_id.
+
         This is used to link objects of different providers together.
             :attrib db_artist_id: Database artist_id.
         """
@@ -494,10 +550,14 @@ class MusicManager:
         for provider in self.mass.get_providers(ProviderType.MUSIC_PROVIDER):
             if provider.id in cur_providers:
                 continue
-            LOGGER.debug("Trying to match artist %s on provider %s", artist.name, provider.name)
+            LOGGER.debug(
+                "Trying to match artist %s on provider %s", artist.name, provider.name
+            )
             match_found = False
             # try to get a match with some reference albums of this artist
-            async for ref_album in self.async_get_artist_albums(artist.item_id, artist.provider):
+            async for ref_album in self.async_get_artist_albums(
+                artist.item_id, artist.provider
+            ):
                 if match_found:
                     break
                 searchstr = "%s - %s" % (artist.name, ref_album.name)
@@ -516,7 +576,9 @@ class MusicManager:
                             continue
                         # double safety check - artist must match exactly !
                         if not compare_strings(
-                            search_result_item.artist.name, artist.name, strict=strictness
+                            search_result_item.artist.name,
+                            artist.name,
+                            strict=strictness,
                         ):
                             continue
                         # just load this item in the database where it will be strictly matched
@@ -547,7 +609,9 @@ class MusicManager:
                             if not search_result_item:
                                 continue
                             if not compare_strings(
-                                search_result_item.name, search_track.name, strict=strictness
+                                search_result_item.name,
+                                search_track.name,
+                                strict=strictness,
                             ):
                                 continue
                             # double safety check - artist must match exactly !
@@ -558,20 +622,29 @@ class MusicManager:
                                     continue
                                 # load this item in the database where it will be strictly matched
                                 await self.async_get_artist(
-                                    match_artist.item_id, match_artist.provider, lazy=False
+                                    match_artist.item_id,
+                                    match_artist.provider,
+                                    lazy=False,
                                 )
                                 match_found = True
                                 break
             if match_found:
-                LOGGER.debug("Found match for Artist %s on provider %s", artist.name, provider.name)
+                LOGGER.debug(
+                    "Found match for Artist %s on provider %s",
+                    artist.name,
+                    provider.name,
+                )
             else:
                 LOGGER.warning(
-                    "Could not find match for Artist %s on provider %s", artist.name, provider.name
+                    "Could not find match for Artist %s on provider %s",
+                    artist.name,
+                    provider.name,
                 )
 
     async def __async_match_album(self, db_album_id: int):
         """
         Try to find matching album on all providers for the provided (database) album_id.
+
         This is used to link objects of different providers/qualities together.
             :attrib db_album_id: Database album_id.
         """
@@ -585,7 +658,9 @@ class MusicManager:
         for provider in providers:
             if provider.id in cur_providers:
                 continue
-            LOGGER.debug("Trying to match album %s on provider %s", album.name, provider.name)
+            LOGGER.debug(
+                "Trying to match album %s on provider %s", album.name, provider.name
+            )
             match_found = False
             searchstr = "%s - %s" % (album.artist.name, album.name)
             if album.version:
@@ -616,15 +691,20 @@ class MusicManager:
                 )
                 match_found = True
             if match_found:
-                LOGGER.debug("Found match for Album %s on provider %s", album.name, provider.name)
+                LOGGER.debug(
+                    "Found match for Album %s on provider %s", album.name, provider.name
+                )
             else:
                 LOGGER.warning(
-                    "Could not find match for Album %s on provider %s", album.name, provider.name
+                    "Could not find match for Album %s on provider %s",
+                    album.name,
+                    provider.name,
                 )
 
     async def __async_match_track(self, db_track_id: int):
         """
         Try to find matching track on all providers for the provided (database) track_id.
+
         This is used to link objects of different providers/qualities together.
             :attrib db_track_id: Database track_id.
         """
@@ -634,7 +714,9 @@ class MusicManager:
         self._match_jobs.append(match_job_id)
         track = await self.mass.database.async_get_track(db_track_id, fulldata=False)
         for provider in self.mass.get_providers(ProviderType.MUSIC_PROVIDER):
-            LOGGER.debug("Trying to match track %s on provider %s", track.name, provider.name)
+            LOGGER.debug(
+                "Trying to match track %s on provider %s", track.name, provider.name
+            )
             match_found = False
             searchstr = "%s - %s" % (track.artists[0].name, track.name)
             if track.version:
@@ -660,7 +742,9 @@ class MusicManager:
                     if artist_match_found:
                         break
                     for search_item_artist in search_result_item.artists:
-                        if not compare_strings(artist.name, search_item_artist.name, strict=False):
+                        if not compare_strings(
+                            artist.name, search_item_artist.name, strict=False
+                        ):
                             continue
                         # just load this item in the database where it will be strictly matched
                         await self.async_get_track(
@@ -673,10 +757,14 @@ class MusicManager:
                         artist_match_found = True
                         break
             if match_found:
-                LOGGER.debug("Found match for Track %s on provider %s", track.name, provider.name)
+                LOGGER.debug(
+                    "Found match for Track %s on provider %s", track.name, provider.name
+                )
             else:
                 LOGGER.warning(
-                    "Could not find match for Track %s on provider %s", track.name, provider.name
+                    "Could not find match for Track %s on provider %s",
+                    track.name,
+                    provider.name,
                 )
 
     ################ Various convenience/helper methods ################
@@ -704,6 +792,7 @@ class MusicManager:
     ) -> SearchResult:
         """
         Perform search on given provider.
+
             :param search_query: Search query
             :param provider_id: provider_id of the provider to perform the search on.
             :param media_types: A list of media_types to include. All types if None.
@@ -715,7 +804,9 @@ class MusicManager:
         provider = self.mass.get_provider(provider_id)
         cache_key = f"{provider_id}.search.{search_query}.{media_types}.{limit}"
         return await async_cached(
-            self.cache, cache_key, provider.async_search(search_query, media_types, limit)
+            self.cache,
+            cache_key,
+            provider.async_search(search_query, media_types, limit),
         )
 
     async def async_global_search(
@@ -723,6 +814,7 @@ class MusicManager:
     ) -> SearchResult:
         """
         Perform global search for media items on all providers.
+
             :param search_query: Search query.
             :param media_types: A list of media_types to include. All types if None.
             :param limit: number of items to return in the search (per type).
@@ -750,7 +842,10 @@ class MusicManager:
         for media_item in media_items:
             # make sure we have a database item
             db_item = await self.async_get_item(
-                media_item.item_id, media_item.provider, media_item.media_type, lazy=False
+                media_item.item_id,
+                media_item.provider,
+                media_item.media_type,
+                lazy=False,
             )
             if not db_item:
                 continue
@@ -758,7 +853,9 @@ class MusicManager:
             for prov in db_item.provider_ids:
                 provider = self.mass.get_provider(prov.provider)
                 if provider:
-                    result = await provider.async_library_add(prov.item_id, media_item.media_type)
+                    result = await provider.async_library_add(
+                        prov.item_id, media_item.media_type
+                    )
                 # mark as library item in internal db
                 await self.mass.database.async_add_to_library(
                     db_item.item_id, db_item.media_type, prov.provider
@@ -771,7 +868,10 @@ class MusicManager:
         for media_item in media_items:
             # make sure we have a database item
             db_item = await self.async_get_item(
-                media_item.item_id, media_item.provider, media_item.media_type, lazy=False
+                media_item.item_id,
+                media_item.provider,
+                media_item.media_type,
+                lazy=False,
             )
             if not db_item:
                 continue
@@ -816,7 +916,9 @@ class MusicManager:
             # this should all be handled in the frontend but these checks are here just to be safe
             # a track can contain multiple versions on the same provider
             # simply sort by quality and just add the first one (assuming track is still available)
-            for track_version in sorted(track.provider_ids, key=lambda x: x.quality, reverse=True):
+            for track_version in sorted(
+                track.provider_ids, key=lambda x: x.quality, reverse=True
+            ):
                 if track_version.provider == playlist_prov.provider:
                     track_ids_to_add.append(track_version.item_id)
                     break
@@ -833,7 +935,9 @@ class MusicManager:
             )
             # return result of the action on the provider
             provider = self.mass.get_provider(playlist_prov.provider)
-            return await provider.async_add_playlist_tracks(playlist_prov.item_id, track_ids_to_add)
+            return await provider.async_add_playlist_tracks(
+                playlist_prov.item_id, track_ids_to_add
+            )
         return False
 
     async def async_remove_playlist_tracks(self, db_playlist_id, tracks: List[Track]):
@@ -878,7 +982,9 @@ class MusicManager:
         img_url = ""
         # we only retrieve items that we already have in cache
         item = None
-        if await self.mass.database.async_get_database_id(provider_id, item_id, media_type):
+        if await self.mass.database.async_get_database_id(
+            provider_id, item_id, media_type
+        ):
             item = await self.async_get_item(item_id, provider_id, media_type)
         if not item:
             return ""
@@ -930,6 +1036,7 @@ class MusicManager:
     async def async_music_provider_sync(self, prov_id: str):
         """
         Sync a music provider.
+
         param prov_id: {string} -- provider id to sync
         """
         provider = self.mass.get_provider(prov_id)
@@ -952,13 +1059,15 @@ class MusicManager:
         music_provider = self.mass.get_provider(provider_id)
         prev_db_ids = [
             item.item_id
-            async for item in self.async_get_library_artists(provider_filter=provider_id)
+            async for item in self.async_get_library_artists(
+                provider_filter=provider_id
+            )
         ]
         cur_db_ids = []
         async for item in music_provider.async_get_library_artists():
             db_item = await self.async_get_artist(item.item_id, provider_id, lazy=False)
             cur_db_ids.append(db_item.item_id)
-            if not db_item.item_id in prev_db_ids:
+            if db_item.item_id not in prev_db_ids:
                 await self.mass.database.async_add_to_library(
                     db_item.item_id, MediaType.Artist, provider_id
                 )
@@ -986,7 +1095,7 @@ class MusicManager:
             if not db_album:
                 LOGGER.error("provider %s album: %s", provider_id, str(item))
             cur_db_ids.append(db_album.item_id)
-            if not db_album.item_id in prev_db_ids:
+            if db_album.item_id not in prev_db_ids:
                 await self.mass.database.async_add_to_library(
                     db_album.item_id, MediaType.Album, provider_id
                 )
@@ -1007,9 +1116,11 @@ class MusicManager:
         ]
         cur_db_ids = []
         async for item in music_provider.async_get_library_tracks():
-            db_item = await self.async_get_track(item.item_id, provider_id=provider_id, lazy=False)
+            db_item = await self.async_get_track(
+                item.item_id, provider_id=provider_id, lazy=False
+            )
             cur_db_ids.append(db_item.item_id)
-            if not db_item.item_id in prev_db_ids:
+            if db_item.item_id not in prev_db_ids:
                 await self.mass.database.async_add_to_library(
                     db_item.item_id, MediaType.Track, provider_id
                 )
@@ -1026,7 +1137,9 @@ class MusicManager:
         music_provider = self.mass.get_provider(provider_id)
         prev_db_ids = [
             item.item_id
-            async for item in self.async_get_library_playlists(provider_filter=provider_id)
+            async for item in self.async_get_library_playlists(
+                provider_filter=provider_id
+            )
         ]
         cur_db_ids = []
         async for playlist in music_provider.async_get_library_playlists():
@@ -1035,7 +1148,7 @@ class MusicManager:
             # always add to db because playlist attributes could have changed
             db_id = await self.mass.database.async_add_playlist(playlist)
             cur_db_ids.append(db_id)
-            if not db_id in prev_db_ids:
+            if db_id not in prev_db_ids:
                 await self.mass.database.async_add_to_library(
                     db_id, MediaType.Playlist, playlist.provider
                 )
@@ -1049,7 +1162,7 @@ class MusicManager:
 
     @sync_task("radios")
     async def async_library_radios_sync(self, provider_id: str):
-        """sync library radios for given provider"""
+        """Sync library radios for given provider."""
         music_provider = self.mass.get_provider(provider_id)
         prev_db_ids = [
             item.item_id
@@ -1065,8 +1178,10 @@ class MusicManager:
             if not db_id:
                 db_id = await self.mass.database.async_add_radio(item)
             cur_db_ids.append(db_id)
-            if not db_id in prev_db_ids:
-                await self.mass.database.async_add_to_library(db_id, MediaType.Radio, provider_id)
+            if db_id not in prev_db_ids:
+                await self.mass.database.async_add_to_library(
+                    db_id, MediaType.Radio, provider_id
+                )
         # process deletions
         for db_id in prev_db_ids:
             if db_id not in cur_db_ids:
index 9c7af47c7ee6facb7ed021365caae3ca8bed520c..e29da1a72cc12c315ded4d8d50f531dc3402e590 100755 (executable)
@@ -1,11 +1,8 @@
-"""
-    PlayerManager:
-    Orchestrates all players from player providers and forwarding of commands and states.
-"""
+"""PlayerManager: Orchestrates all players from player providers."""
 
-from datetime import datetime
 import logging
-from typing import List, Optional, Any
+from datetime import datetime
+from typing import Any, List, Optional
 
 from music_assistant.constants import (
     CONF_ENABLED,
@@ -42,9 +39,10 @@ LOGGER = logging.getLogger("mass")
 
 
 class PlayerManager:
-    """several helpers to handle playback through player providers"""
+    """Several helpers to handle playback through player providers."""
 
     def __init__(self, mass):
+        """Initialize class."""
         self.mass = mass
         self._players = {}
         self._org_players = {}
@@ -55,7 +53,7 @@ class PlayerManager:
         self._player_controls_config_entries = []
 
     async def async_setup(self):
-        """Async initialize of module"""
+        """Async initialize of module."""
         self.mass.add_job(self.poll_task())
 
     async def async_close(self):
@@ -67,8 +65,8 @@ class PlayerManager:
     async def poll_task(self):
         """Check for updates on players that need to be polled."""
         for player in self._org_players.values():
-            if (player.should_poll
-                and (self._poll_ticks >= POLL_INTERVAL or player.state == PlayerState.Playing)
+            if player.should_poll and (
+                self._poll_ticks >= POLL_INTERVAL or player.state == PlayerState.Playing
             ):
                 # Just request update, value checking for changes is handled
                 await self.async_update_player(player)
@@ -101,7 +99,7 @@ class PlayerManager:
     @callback
     def get_player_queue(self, player_id: str) -> PlayerQueue:
         """Return player's queue by player_id or None if player does not exist."""
-        if not player_id in self._players:
+        if player_id not in self._players:
             return None
         player = self._players[player_id]
         return self._player_queues.get(player.active_queue)
@@ -109,7 +107,7 @@ class PlayerManager:
     @callback
     def get_player_control(self, control_id: str) -> PlayerControl:
         """Return PlayerControl by id."""
-        if not control_id in self._controls:
+        if control_id not in self._controls:
             LOGGER.warning("PlayerControl %s is not available", control_id)
             return None
         return self._controls[control_id]
@@ -135,11 +133,15 @@ class PlayerManager:
         await self.__async_create_player_state(player)
         if is_new_player:
             # create player queue
-            if not player.player_id in self._player_queues:
-                self._player_queues[player.player_id] = PlayerQueue(self.mass, player.player_id)
+            if player.player_id not in self._player_queues:
+                self._player_queues[player.player_id] = PlayerQueue(
+                    self.mass, player.player_id
+                )
             # TODO: turn on player if it was previously turned on ?
             LOGGER.info(
-                "New player added: %s/%s", player.provider_id, self._players[player.player_id].name
+                "New player added: %s/%s",
+                player.provider_id,
+                self._players[player.player_id].name,
             )
             self.mass.signal_event(EVENT_PLAYER_ADDED, self._players[player.player_id])
 
@@ -154,7 +156,7 @@ class PlayerManager:
         """Update an existing player (or register as new if non existing)."""
         if not player:
             return
-        if not player.player_id in self._players:
+        if player.player_id not in self._players:
             return await self.async_add_player(player)
         await self.__async_create_player_state(player)
 
@@ -179,7 +181,10 @@ class PlayerManager:
         # update all players using this playercontrol
         for player_id, player in self._players.items():
             conf = self.mass.config.player_settings[player_id]
-            if control.id in [conf.get(CONF_POWER_CONTROL), conf.get(CONF_VOLUME_CONTROL)]:
+            if control.id in [
+                conf.get(CONF_POWER_CONTROL),
+                conf.get(CONF_VOLUME_CONTROL),
+            ]:
                 self.mass.add_job(self.async_update_player(player))
 
     # SERVICE CALLS / PLAYER COMMANDS
@@ -191,7 +196,8 @@ class PlayerManager:
         queue_opt: QueueOption = QueueOption.Play,
     ):
         """
-        Play media item(s) on the given player
+        Play media item(s) on the given player.
+
             :param player_id: player_id of the player to handle the command.
             :param media_item: media item(s) that should be played (single item or list of items)
             :param queue_opt:
@@ -248,6 +254,7 @@ class PlayerManager:
     async def async_cmd_stop(self, player_id: str):
         """
         Send STOP command to given player.
+
             :param player_id: player_id of the player to handle the command.
         """
         # TODO: redirect playback related commands to parent player?
@@ -256,6 +263,7 @@ class PlayerManager:
     async def async_cmd_play(self, player_id: str):
         """
         Send PLAY command to given player.
+
             :param player_id: player_id of the player to handle the command.
         """
         # power on at play request
@@ -270,6 +278,7 @@ class PlayerManager:
     async def async_cmd_pause(self, player_id: str):
         """
         Send PAUSE command to given player.
+
             :param player_id: player_id of the player to handle the command.
         """
         return await self.get_player_provider(player_id).async_cmd_pause(player_id)
@@ -278,6 +287,7 @@ class PlayerManager:
     async def async_cmd_play_pause(self, player_id: str):
         """
         Toggle play/pause on given player.
+
             :param player_id: player_id of the player to handle the command.
         """
         player = self.get_player(player_id)
@@ -288,6 +298,7 @@ class PlayerManager:
     async def async_cmd_next(self, player_id: str):
         """
         Send NEXT TRACK command to given player.
+
             :param player_id: player_id of the player to handle the command.
         """
         return await self.get_player_queue(player_id).async_next()
@@ -295,6 +306,7 @@ class PlayerManager:
     async def async_cmd_previous(self, player_id: str):
         """
         Send PREVIOUS TRACK command to given player.
+
             :param player_id: player_id of the player to handle the command.
         """
         return await self.get_player_queue(player_id).async_previous()
@@ -302,6 +314,7 @@ class PlayerManager:
     async def async_cmd_power_on(self, player_id: str):
         """
         Send POWER ON command to given player.
+
             :param player_id: player_id of the player to handle the command.
         """
         player = self._players[player_id]
@@ -317,6 +330,7 @@ class PlayerManager:
     async def async_cmd_power_off(self, player_id: str):
         """
         Send POWER OFF command to given player.
+
             :param player_id: player_id of the player to handle the command.
         """
         player = self._players[player_id]
@@ -338,6 +352,7 @@ class PlayerManager:
     async def async_cmd_power_toggle(self, player_id: str):
         """
         Send POWER TOGGLE command to given player.
+
             :param player_id: player_id of the player to handle the command.
         """
         player = self._players[player_id]
@@ -348,6 +363,7 @@ class PlayerManager:
     async def async_cmd_volume_set(self, player_id: str, volume_level: int):
         """
         Send volume level command to given player.
+
             :param player_id: player_id of the player to handle the command.
             :param volume_level: volume level to set (0..100).
         """
@@ -381,7 +397,9 @@ class PlayerManager:
                 child_player = self._players.get(child_player_id)
                 if child_player and child_player.available and child_player.powered:
                     cur_child_volume = child_player.volume_level
-                    new_child_volume = cur_child_volume + (cur_child_volume * volume_dif_percent)
+                    new_child_volume = cur_child_volume + (
+                        cur_child_volume * volume_dif_percent
+                    )
                     await self.async_cmd_volume_set(child_player_id, new_child_volume)
         # regular volume command
         else:
@@ -390,6 +408,7 @@ class PlayerManager:
     async def async_cmd_volume_up(self, player_id: str):
         """
         Send volume UP command to given player.
+
             :param player_id: player_id of the player to handle the command.
         """
         player = self._players[player_id]
@@ -401,6 +420,7 @@ class PlayerManager:
     async def async_cmd_volume_down(self, player_id: str):
         """
         Send volume DOWN command to given player.
+
             :param player_id: player_id of the player to handle the command.
         """
         player = self._players[player_id]
@@ -412,6 +432,7 @@ class PlayerManager:
     async def async_cmd_volume_mute(self, player_id: str, is_muted=False):
         """
         Send MUTE command to given player.
+
             :param player_id: player_id of the player to handle the command.
             :param is_muted: bool with the new mute state.
         """
@@ -421,14 +442,18 @@ class PlayerManager:
 
     # OTHER/HELPER FUNCTIONS
 
-    async def async_get_gain_correct(self, player_id: str, item_id: str, provider_id: str):
+    async def async_get_gain_correct(
+        self, player_id: str, item_id: str, provider_id: str
+    ):
         """Get gain correction for given player / track combination."""
         player_conf = self.mass.config.get_player_config(player_id)
         if not player_conf["volume_normalisation"]:
             return 0
         target_gain = int(player_conf["target_volume"])
         fallback_gain = int(player_conf["fallback_gain_correct"])
-        track_loudness = await self.mass.database.async_get_track_loudness(item_id, provider_id)
+        track_loudness = await self.mass.database.async_get_track_loudness(
+            item_id, provider_id
+        )
         if track_loudness is None:
             gain_correct = fallback_gain
         else:
@@ -446,7 +471,9 @@ class PlayerManager:
     async def __async_create_player_state(self, player: Player):
         """Create/update internal Player object with all calculated properties."""
         self._org_players[player.player_id] = player
-        player_enabled = bool(self.mass.config.get_player_config(player.player_id)[CONF_ENABLED])
+        player_enabled = bool(
+            self.mass.config.get_player_config(player.player_id)[CONF_ENABLED]
+        )
         if player.player_id in self._players:
             player_state = self._players[player.player_id]
         else:
@@ -475,7 +502,9 @@ class PlayerManager:
         player_state.config_entries = self.__get_player_config_entries(player)
         player_state.active_queue = active_queue
         if active_queue in self._player_queues:
-            player_state.cur_queue_item_id = self._player_queues[active_queue].cur_item_id
+            player_state.cur_queue_item_id = self._player_queues[
+                active_queue
+            ].cur_item_id
 
     @callback
     def __get_player_name(self, player: Player):
@@ -544,7 +573,7 @@ class PlayerManager:
         for group_player in self._players.values():
             if not group_player.is_group_player:
                 continue
-            if not player.player_id in group_player.group_childs:
+            if player.player_id not in group_player.group_childs:
                 continue
             result.append(group_player.player_id)
         return result
@@ -571,7 +600,9 @@ class PlayerManager:
         # append power control config entries
         power_controls = self.get_player_controls(PlayerControlType.POWER)
         if power_controls:
-            controls = [{"text": item.name, "value": item.id} for item in power_controls]
+            controls = [
+                {"text": item.name, "value": item.id} for item in power_controls
+            ]
             entries.append(
                 ConfigEntry(
                     entry_key=CONF_POWER_CONTROL,
@@ -583,7 +614,9 @@ class PlayerManager:
         # append volume control config entries
         volume_controls = self.get_player_controls(PlayerControlType.VOLUME)
         if volume_controls:
-            controls = [{"text": item.name, "value": item.id} for item in volume_controls]
+            controls = [
+                {"text": item.name, "value": item.id} for item in volume_controls
+            ]
             entries.append(
                 ConfigEntry(
                     entry_key=CONF_VOLUME_CONTROL,
@@ -597,7 +630,7 @@ class PlayerManager:
     @callback
     def __player_updated(self, player_id: str, changed_value: str):
         """Call when player is updated."""
-        if not player_id in self._players:
+        if player_id not in self._players:
             return
         player = self._players[player_id]
         if not player.available and changed_value != "available":
@@ -618,5 +651,3 @@ class PlayerManager:
                         self.mass.add_job(self.async_update_player(child_player))
         if player_id in self._player_queues and player.active_queue == player_id:
             self.mass.add_job(self._player_queues[player_id].async_update_state())
-        
-
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..2209974fe6e0ade8d9011f649569b5077d72a821 100644 (file)
@@ -0,0 +1 @@
+"""Providers package."""
index 1dcb2fbe8a64a0f9ae27b6107dbd73f01727161e..02e6ad88a7870c49b9cf62d9a24434b44ebbcce4 100644 (file)
@@ -1,29 +1,14 @@
-#!/usr/bin/env python3
-# -*- coding:utf-8 -*-
+"""ChromeCast playerprovider."""
 
-import asyncio
 import logging
-import time
-import types
 import uuid
-from typing import List, Optional, Tuple
+from typing import List
 
-import aiohttp
-import attr
 import pychromecast
-import zeroconf
-from music_assistant.constants import CONF_ENABLED, CONF_HOSTNAME, CONF_PORT
-from music_assistant.mass import MusicAssistant
-from music_assistant.models.config_entry import ConfigEntry, ConfigEntryType
-from music_assistant.models.player import DeviceInfo, Player, PlayerState
+from music_assistant.models.config_entry import ConfigEntry
 from music_assistant.models.player_queue import QueueItem
 from music_assistant.models.playerprovider import PlayerProvider
-from music_assistant.utils import try_parse_int
-from pychromecast.controllers.multizone import MultizoneController, MultizoneManager
-from pychromecast.socket_client import (
-    CONNECTION_STATUS_CONNECTED,
-    CONNECTION_STATUS_DISCONNECTED,
-)
+from pychromecast.controllers.multizone import MultizoneManager
 
 from .const import PROV_ID, PROV_NAME, PROVIDER_CONFIG_ENTRIES
 from .models import ChromecastInfo
@@ -42,6 +27,8 @@ async def async_setup(mass):
 class ChromecastProvider(PlayerProvider):
     """Support for ChromeCast Audio PlayerProvider."""
 
+    # pylint: disable=abstract-method
+
     def __init__(self, *args, **kwargs):
         """Initialize."""
         self.mz_mgr = MultizoneManager()
@@ -66,17 +53,19 @@ class ChromecastProvider(PlayerProvider):
         return PROVIDER_CONFIG_ENTRIES
 
     async def async_on_start(self) -> bool:
-        """Called on startup. Handle initialization of the provider based on config."""
+        """Handle initialization of the provider based on config."""
         self._listener = pychromecast.CastListener(
             self.__chromecast_add_update_callback,
             self.__chromecast_remove_callback,
             self.__chromecast_add_update_callback,
         )
-        self._browser = pychromecast.discovery.start_discovery(self._listener, self.mass.zeroconf)
+        self._browser = pychromecast.discovery.start_discovery(
+            self._listener, self.mass.zeroconf
+        )
         return True
 
     async def async_on_stop(self):
-        """Called on shutdown. Handle correct close/cleanup of the provider on exit."""
+        """Handle correct close/cleanup of the provider on exit."""
         if not self._browser:
             return
         # stop discovery
@@ -88,6 +77,7 @@ class ChromecastProvider(PlayerProvider):
     async def async_cmd_play_uri(self, player_id: str, uri: str):
         """
         Play the specified uri/url on the goven player.
+
             :param player_id: player_id of the player to handle the command.
         """
         self.mass.add_job(self._players[player_id].play_uri, uri)
@@ -95,6 +85,7 @@ class ChromecastProvider(PlayerProvider):
     async def async_cmd_stop(self, player_id: str):
         """
         Send STOP command to given player.
+
             :param player_id: player_id of the player to handle the command.
         """
         self.mass.add_job(self._players[player_id].stop)
@@ -102,6 +93,7 @@ class ChromecastProvider(PlayerProvider):
     async def async_cmd_play(self, player_id: str):
         """
         Send STOP command to given player.
+
             :param player_id: player_id of the player to handle the command.
         """
         self.mass.add_job(self._players[player_id].play)
@@ -109,6 +101,7 @@ class ChromecastProvider(PlayerProvider):
     async def async_cmd_pause(self, player_id: str):
         """
         Send PAUSE command to given player.
+
             :param player_id: player_id of the player to handle the command.
         """
         self.mass.add_job(self._players[player_id].pause)
@@ -116,6 +109,7 @@ class ChromecastProvider(PlayerProvider):
     async def async_cmd_next(self, player_id: str):
         """
         Send NEXT TRACK command to given player.
+
             :param player_id: player_id of the player to handle the command.
         """
         self.mass.add_job(self._players[player_id].next)
@@ -123,6 +117,7 @@ class ChromecastProvider(PlayerProvider):
     async def async_cmd_previous(self, player_id: str):
         """
         Send PREVIOUS TRACK command to given player.
+
             :param player_id: player_id of the player to handle the command.
         """
         self.mass.add_job(self._players[player_id].previous)
@@ -130,6 +125,7 @@ class ChromecastProvider(PlayerProvider):
     async def async_cmd_power_on(self, player_id: str):
         """
         Send POWER ON command to given player.
+
             :param player_id: player_id of the player to handle the command.
         """
         self.mass.add_job(self._players[player_id].power_on)
@@ -137,6 +133,7 @@ class ChromecastProvider(PlayerProvider):
     async def async_cmd_power_off(self, player_id: str):
         """
         Send POWER OFF command to given player.
+
             :param player_id: player_id of the player to handle the command.
         """
         self.mass.add_job(self._players[player_id].power_off)
@@ -144,6 +141,7 @@ class ChromecastProvider(PlayerProvider):
     async def async_cmd_volume_set(self, player_id: str, volume_level: int):
         """
         Send volume level command to given player.
+
             :param player_id: player_id of the player to handle the command.
             :param volume_level: volume level to set (0..100).
         """
@@ -152,6 +150,7 @@ class ChromecastProvider(PlayerProvider):
     async def async_cmd_volume_mute(self, player_id: str, is_muted=False):
         """
         Send volume MUTE command to given player.
+
             :param player_id: player_id of the player to handle the command.
             :param is_muted: bool with new mute state.
         """
@@ -159,15 +158,19 @@ class ChromecastProvider(PlayerProvider):
 
     async def async_cmd_queue_load(self, player_id: str, queue_items: List[QueueItem]):
         """
-        Load/overwrite given items in the player's queue implementation
+        Load/overwrite given items in the player's queue implementation.
+
             :param player_id: player_id of the player to handle the command.
             :param queue_items: a list of QueueItems
         """
         self.mass.add_job(self._players[player_id].queue_load, queue_items)
 
-    async def async_cmd_queue_append(self, player_id: str, queue_items: List[QueueItem]):
+    async def async_cmd_queue_append(
+        self, player_id: str, queue_items: List[QueueItem]
+    ):
         """
         Append new items at the end of the queue.
+
             :param player_id: player_id of the player to handle the command.
             :param queue_items: a list of QueueItems
         """
@@ -188,10 +191,14 @@ class ChromecastProvider(PlayerProvider):
             port=service[5],
         )
         player_id = cast_info.uuid
-        LOGGER.debug("Chromecast discovered: %s (%s)", cast_info.friendly_name, player_id)
+        LOGGER.debug(
+            "Chromecast discovered: %s (%s)", cast_info.friendly_name, player_id
+        )
         if player_id in self._players:
             # player already added, the player will take care of reconnects itself.
-            LOGGER.debug("Player is already added:  %s (%s)", cast_info.friendly_name, player_id)
+            LOGGER.debug(
+                "Player is already added:  %s (%s)", cast_info.friendly_name, player_id
+            )
         else:
             player = ChromecastPlayer(self.mass, cast_info)
             self._players[player_id] = player
@@ -199,153 +206,9 @@ class ChromecastProvider(PlayerProvider):
         self.mass.add_job(self._players[player_id].set_cast_info, cast_info)
 
     def __chromecast_remove_callback(self, cast_uuid, cast_service_name, cast_service):
+        """Handle a Chromecast removed event."""
         # pylint: disable=unused-argument
         player_id = str(cast_service[1])
         friendly_name = cast_service[3]
         LOGGER.debug("Chromecast removed: %s - %s", friendly_name, player_id)
         self.mass.add_job(self.mass.player_manager.async_remove_player(player_id))
-
-
-# class StatusListener:
-#     def __init__(self, player_id, status_callback, mass):
-#         self.__handle_callback = status_callback
-#         self.mass = mass
-#         self.player_id = player_id
-
-#     def new_cast_status(self, status):
-#         """chromecast status changed (like volume etc.)"""
-#         self.mass.run_task(self.__handle_callback(caststatus=status))
-
-#     def new_media_status(self, status):
-#         """mediacontroller has new state"""
-#         self.mass.run_task(self.__handle_callback(mediastatus=status))
-
-#     def new_connection_status(self, status):
-#         """will be called when the connection changes"""
-#         if status.status == CONNECTION_STATUS_DISCONNECTED:
-#             # schedule a new scan which will handle reconnects and group parent changes
-#             self.mass.loop.run_in_executor(
-#                 None, self.mass.player_manager.providers[PROV_ID].run_chromecast_discovery
-#             )
-
-
-# class MZListener:
-#     def __init__(self, mz, callback, loop):
-#         self._mz = mz
-#         self._loop = loop
-#         self.__handle_group_members_update = callback
-
-#     def multizone_member_added(self, uuid):
-#         asyncio.run_coroutine_threadsafe(
-#             self.__handle_group_members_update(self._mz, added_player=str(uuid)),
-#             self._loop,
-#         )
-
-#     def multizone_member_removed(self, uuid):
-#         asyncio.run_coroutine_threadsafe(
-#             self.__handle_group_members_update(self._mz, removed_player=str(uuid)),
-#             self._loop,
-#         )
-
-#     def multizone_status_received(self):
-#         asyncio.run_coroutine_threadsafe(
-#             self.__handle_group_members_update(self._mz), self._loop
-#         )
-
-
-# async def async_ __report_progress(self):
-#     """report current progress while playing"""
-#     # chromecast does not send updates of the player's progress (cur_time)
-#     # so we need to send it in periodically
-#     while self._state == PlayerState.Playing:
-#         self.cur_time = self.media_status.adjusted_current_time
-#         await asyncio.sleep(1)
-#     self.__cc_report_progress_task = None
-
-# async def async_ handle_player_state(self, caststatus=None, mediastatus=None):
-#     """handle a player state message from the socket"""
-#     # handle generic cast status
-#     if caststatus:
-#         self.muted = caststatus.volume_muted
-#         self.volume_level = caststatus.volume_level * 100
-#     self.name = self._chromecast.name
-#     # handle media status
-#     if mediastatus:
-#         if mediastatus.player_state in ["PLAYING", "BUFFERING"]:
-#             self.state = PlayerState.Playing
-#             self.powered = True
-#         elif mediastatus.player_state == "PAUSED":
-#             self.state = PlayerState.Paused
-#         else:
-#             self.state = PlayerState.Stopped
-#         self.current_uri = mediastatus.content_id
-#         self.cur_time = mediastatus.adjusted_current_time
-#     if (
-#         self._state == PlayerState.Playing
-#         and self.__cc_report_progress_task is None
-#     ):
-#         self.__cc_report_progress_task = self.mass.add_job(
-#             self.__report_progress()
-#         )
-
-# def __chromecast_discovered(self, cast_info):
-#     """callback when a (new) chromecast device is discovered"""
-#     player_id = cast_info.uuid
-#     player = self.mass.player_manager.get_player_sync(player_id)
-#     if self.mass.player_manager.get_player_sync(player_id):
-#         # player already added, the player will take care of reconnects itself.
-#         LOGGER.warning("Player is already added: %s", player_id)
-#         self.mass.add_job(player.async_set_cast_info(cast_info))
-#     else:
-#         player = ChromecastPlayer(self.mass, cast_info)
-#         # player.cc = chromecast
-#         # player.mz = None
-
-#         # # register status listeners
-#         # status_listener = StatusListener(
-#         #     player_id, player.handle_player_state, self.mass
-#         # )
-#         # if chromecast.cast_type == "group":
-#         #     mz = MultizoneController(chromecast.uuid)
-#         #     mz.register_listener(
-#         #         MZListener(mz, self.__handle_group_members_update, self.mass.loop)
-#         #     )
-#         #     chromecast.register_handler(mz)
-#         #     player.mz = mz
-#         # chromecast.register_connection_listener(status_listener)
-#         # chromecast.register_status_listener(status_listener)
-#         # chromecast.media_controller.register_status_listener(status_listener)
-#         # player.cc.wait()
-#         self.mass.run_task(self.add_player(player))
-
-# def __update_group_players(self):
-#     """update childs of all group players"""
-#     for player in self.players:
-#         if player.cc.cast_type == "group":
-#             player.mz.update_members()
-
-# async def async_ __handle_group_members_update(
-#     self, mz, added_player=None, removed_player=None
-# ):
-#     """handle callback from multizone manager"""
-#     group_player_id = str(uuid.UUID(mz._uuid))
-#     group_player = await self.get_player(group_player_id)
-#     if added_player:
-#         player_id = str(uuid.UUID(added_player))
-#         child_player = await self.get_player(player_id)
-#         if child_player and player_id != group_player_id:
-#             group_player.add_group_child(player_id)
-#             LOGGER.debug("%s added to %s", child_player.name, group_player.name)
-#     elif removed_player:
-#         player_id = str(uuid.UUID(removed_player))
-#         group_player.remove_group_child(player_id)
-#         LOGGER.debug("%s removed from %s", player_id, group_player.name)
-#     else:
-#         for member in mz.members:
-#             player_id = str(uuid.UUID(member))
-#             child_player = await self.get_player(player_id)
-#             if not child_player or player_id == group_player_id:
-#                 continue
-#             if not player_id in group_player.group_childs:
-#                 group_player.add_group_child(player_id)
-#                 LOGGER.debug("%s added to %s", child_player.name, group_player.name)
index 19eefd3d1825c42ea7fafd848310cd969bd947d8..fe355031c2c2b1932ed3aa0d36d62b5f08716262 100644 (file)
@@ -1,6 +1,4 @@
 """Constants for the implementation."""
-from music_assistant.constants import CONF_ENABLED
-from music_assistant.models.config_entry import ConfigEntry, ConfigEntryType
 
 PROV_ID = "chromecast"
 PROV_NAME = "Chromecast"
index cb654a9a0878c23fdeeea54c47ed1073806a2460..1664757eff76d6aa9c9fb2b1d15cc14c03407ea8 100644 (file)
@@ -1,14 +1,13 @@
+"""
+Class to hold all data about a chromecast for creating connections.
 
-"""Class to hold all data about a chromecast for creating connections.
-    This also has the same attributes as the mDNS fields by zeroconf.
+This also has the same attributes as the mDNS fields by zeroconf.
 """
 import logging
 from dataclasses import dataclass, field
 from typing import Optional, Tuple
 
 from pychromecast.const import CAST_MANUFACTURERS
-from pychromecast.controllers.multizone import MultizoneController
-
 
 from .const import PROV_ID
 
@@ -19,6 +18,7 @@ DEFAULT_PORT = 8009
 @dataclass()
 class ChromecastInfo:
     """Class to hold all data about a chromecast for creating connections.
+
     This also has the same attributes as the mDNS fields by zeroconf.
     """
 
@@ -30,7 +30,7 @@ class ChromecastInfo:
     friendly_name: Optional[str] = ""
 
     def __post_init__(self):
-        """Always convert UUID to string."""
+        """Convert UUID to string."""
         self.uuid = str(self.uuid)
 
     @property
@@ -53,6 +53,7 @@ class ChromecastInfo:
 
 class CastStatusListener:
     """Helper class to handle pychromecast status callbacks.
+
     Necessary because a CastDevice entity can create a new socket client
     and therefore callbacks from multiple chromecast connections can
     potentially arrive. This class allows invalidating past chromecast objects.
@@ -90,7 +91,9 @@ class CastStatusListener:
 
     def added_to_multizone(self, group_uuid):
         """Handle the cast added to a group."""
-        LOGGER.debug("Player %s is added to group %s", self._cast_device.name, group_uuid)
+        LOGGER.debug(
+            "Player %s is added to group %s", self._cast_device.name, group_uuid
+        )
 
     def removed_from_multizone(self, group_uuid):
         """Handle the cast removed from a group."""
@@ -107,6 +110,7 @@ class CastStatusListener:
 
     def invalidate(self):
         """Invalidate this status listener.
+
         All following callbacks won't be forwarded.
         """
         # pylint: disable=protected-access
index 5481866eaa149570b8a5ba8b91b16e0311c6f509..4849a042fcfb0664b8ac4c6604dfdec6f4846c7c 100644 (file)
@@ -1,10 +1,11 @@
 """Representation of a Cast device on the network."""
 import logging
-from typing import List, Optional
+import uuid
 from datetime import datetime
+from typing import List, Optional
+
 import pychromecast
-import uuid
-from music_assistant.models.player import DeviceInfo, Player, PlayerFeature, PlayerState
+from music_assistant.models.player import DeviceInfo, PlayerFeature, PlayerState
 from music_assistant.models.player_queue import QueueItem
 from music_assistant.utils import compare_strings
 from pychromecast.controllers.multizone import MultizoneController
@@ -13,7 +14,7 @@ from pychromecast.socket_client import (
     CONNECTION_STATUS_DISCONNECTED,
 )
 
-from .const import PLAYER_CONFIG_ENTRIES, PROV_ID, PROVIDER_CONFIG_ENTRIES
+from .const import PLAYER_CONFIG_ENTRIES, PROV_ID
 from .models import CastStatusListener, ChromecastInfo
 
 LOGGER = logging.getLogger(PROV_ID)
@@ -22,6 +23,7 @@ PLAYER_FEATURES = [PlayerFeature.QUEUE]
 
 class ChromecastPlayer:
     """Representation of a Cast device on the network.
+
     This class is the holder of the pychromecast.Chromecast object and
     handles all reconnects and audio group changing
     "elected leader" itself.
@@ -40,6 +42,7 @@ class ChromecastPlayer:
         self.media_status = None
         self.media_status_received = None
         self.mz_mgr = None
+        self.mz_manager = None
         self._available = False
         self._powered = False
         self._status_listener: Optional[CastStatusListener] = None
@@ -54,7 +57,9 @@ class ChromecastPlayer:
     @property
     def name(self):
         """Return name of this player."""
-        return self._chromecast.name if self._chromecast else self._cast_info.friendly_name
+        return (
+            self._chromecast.name if self._chromecast else self._cast_info.friendly_name
+        )
 
     @property
     def powered(self):
@@ -126,8 +131,14 @@ class ChromecastPlayer:
     @property
     def group_childs(self):
         """Return group_childs."""
-        if self._cast_info.is_audio_group and self._chromecast and not self._is_speaker_group:
-            return [str(uuid.UUID(item)) for item in self._chromecast.mz_controller.members]
+        if (
+            self._cast_info.is_audio_group
+            and self._chromecast
+            and not self._is_speaker_group
+        ):
+            return [
+                str(uuid.UUID(item)) for item in self._chromecast.mz_controller.members
+            ]
         return []
 
     @property
@@ -179,7 +190,9 @@ class ChromecastPlayer:
         """Disconnect Chromecast object if it is set."""
         if self._chromecast is None:
             return
-        LOGGER.warning("[%s] Disconnecting from chromecast socket", self._cast_info.friendly_name)
+        LOGGER.warning(
+            "[%s] Disconnecting from chromecast socket", self._cast_info.friendly_name
+        )
         self._available = False
         self.mass.add_job(self._chromecast.disconnect)
         self._invalidate()
@@ -191,7 +204,6 @@ class ChromecastPlayer:
         self.media_status = None
         self.media_status_received = None
         self.mz_mgr = None
-        self.mz_controller = None
         if self._status_listener is not None:
             self._status_listener.invalidate()
             self._status_listener = None
@@ -206,7 +218,9 @@ class ChromecastPlayer:
             self._cast_info.is_audio_group
             and self._chromecast.mz_controller
             and self._chromecast.mz_controller.members
-            and compare_strings(self._chromecast.mz_controller.members[0], self.player_id)
+            and compare_strings(
+                self._chromecast.mz_controller.members[0], self.player_id
+            )
         )
         self.mass.add_job(self.mass.player_manager.async_update_player(self))
 
@@ -332,7 +346,7 @@ class ChromecastPlayer:
             self._chromecast.play_media(uri, "audio/flac")
 
     def queue_load(self, queue_items: List[QueueItem]):
-        """load (overwrite) queue with new items"""
+        """Load (overwrite) queue with new items."""
         if not self._chromecast.socket_client.is_connected:
             LOGGER.warning("Ignore player command: Socket client is not connected.")
             return
@@ -352,9 +366,7 @@ class ChromecastPlayer:
             self.queue_append(queue_items[51:])
 
     def queue_append(self, queue_items: List[QueueItem]):
-        """
-        append new items at the end of the queue
-        """
+        """Append new items at the end of the queue."""
         if not self._chromecast.socket_client.is_connected:
             LOGGER.warning("Ignore player command: Socket client is not connected.")
             return
@@ -364,7 +376,7 @@ class ChromecastPlayer:
             self.__send_player_queue(queuedata)
 
     def __create_queue_items(self, tracks):
-        """create list of CC queue items from tracks"""
+        """Create list of CC queue items from tracks."""
         queue_items = []
         for track in tracks:
             queue_item = self.__create_queue_item(track)
@@ -372,7 +384,7 @@ class ChromecastPlayer:
         return queue_items
 
     def __create_queue_item(self, track):
-        """create CC queue item from track info"""
+        """Create CC queue item from track info."""
         player_queue = self.mass.player_manager.get_player_queue(self.player_id)
         return {
             "opt_itemId": track.queue_item_id,
@@ -399,8 +411,9 @@ class ChromecastPlayer:
         }
 
     def __send_player_queue(self, queuedata):
-        """Send new data to the CC queue"""
+        """Send new data to the CC queue."""
         media_controller = self._chromecast.media_controller
+        # pylint: disable=protected-access
         receiver_ctrl = media_controller._socket_client.receiver_controller
 
         def send_queue():
@@ -409,12 +422,14 @@ class ChromecastPlayer:
             media_controller.send_message(queuedata, inc_session_id=False)
 
         if not media_controller.status.media_session_id:
-            receiver_ctrl.launch_app(media_controller.app_id, callback_function=send_queue)
+            receiver_ctrl.launch_app(
+                media_controller.app_id, callback_function=send_queue
+            )
         else:
             send_queue()
 
 
-def chunks(l, n):
-    """Yield successive n-sized chunks from l."""
-    for i in range(0, len(l), n):
-        yield l[i : i + n]
+def chunks(_list, chunk_size):
+    """Yield successive n-sized chunks from list."""
+    for i in range(0, len(_list), chunk_size):
+        yield _list[i : i + chunk_size]
index 2a2bf9ea53e03546d3e2c049829c63777ab63aca..09c851c7f7905a0fbd6ed2f1c3a67ead43ef98df 100644 (file)
@@ -2,7 +2,8 @@
 
 from .demo_playerprovider import DemoPlayerProvider
 
+
 async def async_setup(mass):
     """Perform async setup of this Plugin/Provider."""
     prov = DemoPlayerProvider()
-    await mass.async_register_provider(prov)
\ No newline at end of file
+    await mass.async_register_provider(prov)
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..dfee494952a1ea6555bdb578e608a97d5b7ea90c 100644 (file)
@@ -0,0 +1 @@
+"""Demo music provider."""
index 5a61e7a4124b47eab80b21ddc0ba3239f30b9d04..45d91f0a439cc5d13d992141a5b968d9fbce74e4 100644 (file)
@@ -1,24 +1,20 @@
 """Demo/test providers."""
 
-from abc import abstractmethod
-from dataclasses import dataclass
-from typing import List
 import functools
+from typing import List
 
+import vlc
 from music_assistant.models.config_entry import ConfigEntry
 from music_assistant.models.player import DeviceInfo, Player, PlayerFeature, PlayerState
 from music_assistant.models.player_queue import QueueItem
 from music_assistant.models.playerprovider import PlayerProvider
-import vlc
 
 PROV_ID = "demo_player"
 PROV_NAME = "Demo/Test players"
 
 
 class DemoPlayerProvider(PlayerProvider):
-    """
-    Demo PlayerProvider which provides fake players.
-    """
+    """Demo PlayerProvider which provides fake players."""
 
     def __init__(self, *args, **kwargs):
         """Initialize."""
@@ -41,7 +37,7 @@ class DemoPlayerProvider(PlayerProvider):
         return []
 
     async def async_on_start(self) -> bool:
-        """Called on startup. Handle initialization of the provider based on config."""
+        """Handle initialization of the provider based on config."""
         # create some fake players
         for count in range(5)[1:]:
             player_id = f"demo_{count}"
@@ -94,7 +90,7 @@ class DemoPlayerProvider(PlayerProvider):
         return True
 
     async def async_on_stop(self):
-        """Called on shutdown. Handle correct close/cleanup of the provider on exit."""
+        """Handle correct close/cleanup of the provider on exit."""
         for player_id, player in self._players.items():
             player.vlc_player.release()
             player.vlc_instance.release()
@@ -103,7 +99,8 @@ class DemoPlayerProvider(PlayerProvider):
         self._players = {}
 
     def player_event(self, player_id, event):
-        """Called on vlc player events."""
+        """Call on vlc player events."""
+        # pylint: disable = unused-argument
         vlc_player: vlc.MediaPlayer = self._players[player_id].vlc_player
         self._players[player_id].muted = vlc_player.audio_get_mute()
         self._players[player_id].volume_level = vlc_player.audio_get_volume()
@@ -115,13 +112,16 @@ class DemoPlayerProvider(PlayerProvider):
         else:
             self._players[player_id].state = PlayerState.Stopped
         self._players[player_id].elapsed_time = int(vlc_player.get_time() / 1000)
-        self.mass.add_job(self.mass.player_manager.async_update_player(self._players[player_id]))
+        self.mass.add_job(
+            self.mass.player_manager.async_update_player(self._players[player_id])
+        )
 
     # SERVICE CALLS / PLAYER COMMANDS
 
     async def async_cmd_play_uri(self, player_id: str, uri: str):
         """
         Play the specified uri/url on the given player.
+
             :param player_id: player_id of the player to handle the command.
         """
         # self._players[player_id].current_uri = uri
@@ -132,13 +132,15 @@ class DemoPlayerProvider(PlayerProvider):
     async def async_cmd_stop(self, player_id: str):
         """
         Send STOP command to given player.
+
             :param player_id: player_id of the player to handle the command.
         """
         self.mass.add_job(self._players[player_id].vlc_player.stop)
 
     async def async_cmd_play(self, player_id: str):
         """
-        Send STOP command to given player.
+        Send PLAY command to given player.
+
             :param player_id: player_id of the player to handle the command.
         """
         if self._players[player_id].vlc_player.get_media():
@@ -147,6 +149,7 @@ class DemoPlayerProvider(PlayerProvider):
     async def async_cmd_pause(self, player_id: str):
         """
         Send PAUSE command to given player.
+
             :param player_id: player_id of the player to handle the command.
         """
         self.mass.add_job(self._players[player_id].vlc_player.pause)
@@ -154,6 +157,7 @@ class DemoPlayerProvider(PlayerProvider):
     async def async_cmd_next(self, player_id: str):
         """
         Send NEXT TRACK command to given player.
+
             :param player_id: player_id of the player to handle the command.
         """
         self.mass.add_job(self._players[player_id].vlc_player.next_chapter)
@@ -161,6 +165,7 @@ class DemoPlayerProvider(PlayerProvider):
     async def async_cmd_previous(self, player_id: str):
         """
         Send PREVIOUS TRACK command to given player.
+
             :param player_id: player_id of the player to handle the command.
         """
         self.mass.add_job(self._players[player_id].vlc_player.previous_chapter)
@@ -168,31 +173,41 @@ class DemoPlayerProvider(PlayerProvider):
     async def async_cmd_power_on(self, player_id: str):
         """
         Send POWER ON command to given player.
+
             :param player_id: player_id of the player to handle the command.
         """
         self._players[player_id].powered = True
-        self.mass.add_job(self.mass.player_manager.async_update_player(self._players[player_id]))
+        self.mass.add_job(
+            self.mass.player_manager.async_update_player(self._players[player_id])
+        )
 
     async def async_cmd_power_off(self, player_id: str):
         """
         Send POWER OFF command to given player.
+
             :param player_id: player_id of the player to handle the command.
         """
         self.mass.add_job(self._players[player_id].vlc_player.stop)
         self._players[player_id].powered = False
-        self.mass.add_job(self.mass.player_manager.async_update_player(self._players[player_id]))
+        self.mass.add_job(
+            self.mass.player_manager.async_update_player(self._players[player_id])
+        )
 
     async def async_cmd_volume_set(self, player_id: str, volume_level: int):
         """
         Send volume level command to given player.
+
             :param player_id: player_id of the player to handle the command.
             :param volume_level: volume level to set (0..100).
         """
-        self.mass.add_job(self._players[player_id].vlc_player.audio_set_volume, volume_level)
+        self.mass.add_job(
+            self._players[player_id].vlc_player.audio_set_volume, volume_level
+        )
 
     async def async_cmd_volume_mute(self, player_id: str, is_muted=False):
         """
         Send volume MUTE command to given player.
+
             :param player_id: player_id of the player to handle the command.
             :param is_muted: bool with new mute state.
         """
@@ -203,7 +218,8 @@ class DemoPlayerProvider(PlayerProvider):
 
     async def async_cmd_queue_play_index(self, player_id: str, index: int):
         """
-        Play item at index X on player's queue
+        Play item at index X on player's queue.
+
             :param player_id: player_id of the player to handle the command.
             :param index: (int) index of the queue item that should start playing
         """
@@ -211,7 +227,8 @@ class DemoPlayerProvider(PlayerProvider):
 
     async def async_cmd_queue_load(self, player_id: str, queue_items: List[QueueItem]):
         """
-        Load/overwrite given items in the player's queue implementation
+        Load/overwrite given items in the player's queue implementation.
+
             :param player_id: player_id of the player to handle the command.
             :param queue_items: a list of QueueItems
         """
@@ -222,6 +239,7 @@ class DemoPlayerProvider(PlayerProvider):
     ):
         """
         Insert new items at position X into existing queue.
+
         If insert_at_index 0 or None, will start playing newly added item(s)
             :param player_id: player_id of the player to handle the command.
             :param queue_items: a list of QueueItems
@@ -229,17 +247,23 @@ class DemoPlayerProvider(PlayerProvider):
         """
         raise NotImplementedError
 
-    async def async_cmd_queue_append(self, player_id: str, queue_items: List[QueueItem]):
+    async def async_cmd_queue_append(
+        self, player_id: str, queue_items: List[QueueItem]
+    ):
         """
         Append new items at the end of the queue.
+
             :param player_id: player_id of the player to handle the command.
             :param queue_items: a list of QueueItems
         """
         raise NotImplementedError
 
-    async def async_cmd_queue_update(self, player_id: str, queue_items: List[QueueItem]):
+    async def async_cmd_queue_update(
+        self, player_id: str, queue_items: List[QueueItem]
+    ):
         """
         Overwrite the existing items in the queue, used for reordering.
+
             :param player_id: player_id of the player to handle the command.
             :param queue_items: a list of QueueItems
         """
@@ -248,6 +272,7 @@ class DemoPlayerProvider(PlayerProvider):
     async def async_cmd_queue_clear(self, player_id: str):
         """
         Clear the player's queue.
+
             :param player_id: player_id of the player to handle the command.
         """
         raise NotImplementedError
index 096fecc164817db4be25a1900b65a21f0df62885..7aa7f227c05860f2305af25697010c0aad0cf148 100644 (file)
@@ -1,8 +1,11 @@
 """Filesystem musicprovider support for MusicAssistant."""
+# pylint: skip-file
+# flake8: noqa
 import base64
 import os
 from typing import List
 
+import taglib
 from music_assistant.constants import CONF_ENABLED
 from music_assistant.models.media_types import (
     Album,
@@ -14,7 +17,6 @@ from music_assistant.models.media_types import (
 )
 from music_assistant.models.musicprovider import MusicProvider
 from music_assistant.utils import LOGGER, parse_title_and_version
-import taglib
 
 PROV_NAME = "Local files and playlists"
 PROV_CLASS = "FileProvider"
@@ -111,9 +113,7 @@ class FileProvider(MusicProvider):
         artist.item_id = prov_item_id
         artist.provider = self.prov_id
         artist.name = name
-        artist.ids.append(
-            {"provider": self.prov_id, "item_id": artist.item_id}
-        )
+        artist.ids.append({"provider": self.prov_id, "item_id": artist.item_id})
         return artist
 
     async def async_get_album(self, prov_item_id) -> Album:
@@ -164,9 +164,7 @@ class FileProvider(MusicProvider):
         playlist.provider = self.prov_id
         playlist.name = itempath.split(os.sep)[-1].replace(".m3u", "")
         playlist.is_editable = True
-        playlist.ids.append(
-            {"provider": self.prov_id, "item_id": prov_item_id}
-        )
+        playlist.ids.append({"provider": self.prov_id, "item_id": prov_item_id})
         playlist.owner = "disk"
         playlist.checksum = os.path.getmtime(itempath)
         return playlist
index a2a90f2d622490e57d483766997027d2b79df7f9..7e10da234c867701f79c31fa0eac2941b7845b0c 100644 (file)
@@ -1,9 +1,6 @@
 """Plugin that enables integration with Home Assistant."""
 
-import asyncio
-import functools
 import logging
-import os
 from typing import List
 
 import slugify as slug
@@ -39,7 +36,9 @@ CONFIG_ENTRY_URL = ConfigEntry(
     entry_key=CONF_URL, entry_type=ConfigEntryType.STRING, description_key="hass_url"
 )
 CONFIG_ENTRY_TOKEN = ConfigEntry(
-    entry_key=CONF_TOKEN, entry_type=ConfigEntryType.PASSWORD, description_key="hass_token"
+    entry_key=CONF_TOKEN,
+    entry_type=ConfigEntryType.PASSWORD,
+    description_key="hass_token",
 )
 CONFIG_ENTRY_PUBLISH_PLAYERS = ConfigEntry(
     entry_key=CONF_PUBLISH_PLAYERS,
@@ -58,8 +57,8 @@ async def async_setup(mass):
 
 
 class HomeAssistantPlugin(Provider):
-    """
-    Homeassistant plugin
+    """Homeassistant plugin.
+
     allows publishing of our players to hass
     allows using hass entities (like switches, media_players or gui inputs) to be triggered
     """
@@ -112,12 +111,14 @@ class HomeAssistantPlugin(Provider):
         return entries
 
     async def async_on_start(self) -> bool:
-        """Called on startup. Handle initialization of the provider based on config."""
+        """Handle initialization of the provider based on config."""
         config = self.mass.config.get_provider_config(PROV_ID)
         if IS_SUPERVISOR:
             self._hass = HomeAssistant(loop=self.mass.loop)
         else:
-            self._hass = HomeAssistant(config[CONF_URL], config[CONF_TOKEN], loop=self.mass.loop)
+            self._hass = HomeAssistant(
+                config[CONF_URL], config[CONF_TOKEN], loop=self.mass.loop
+            )
         # register callbacks
         self._hass.register_event_callback(self.__async_hass_event)
         self.mass.add_event_listener(
@@ -129,20 +130,20 @@ class HomeAssistantPlugin(Provider):
         return True
 
     async def async_on_stop(self):
-        """Called on shutdown. Handle correct close/cleanup of the provider on exit."""
+        """Handle correct close/cleanup of the provider on exit."""
         for task in self._tasks:
             task.cancel()
         if self._hass:
             await self._hass.async_close()
 
     async def __async_mass_event(self, event, event_data):
-        """Received event from Music Assistant"""
+        """Receive event from Music Assistant."""
         if event in [EVENT_PLAYER_CHANGED, EVENT_PLAYER_ADDED]:
             await self.__async_publish_player(event_data)
         # TODO: player removals
 
     async def __async_hass_event(self, event_type, event_data):
-        """Received event from Home Assistant"""
+        """Receive event from Home Assistant."""
         if event_type == EVENT_STATE_CHANGED:
             if event_data["entity_id"] in self._tracked_entities:
                 new_state = event_data["new_state"]
@@ -184,7 +185,9 @@ class HomeAssistantPlugin(Provider):
                     await self.mass.player_manager.async_cmd_volume_down(player_id)
                 elif service == "volume_set":
                     volume_level = service_data["volume_level"] * 100
-                    await self.mass.player_manager.async_cmd_volume_set(player_id, volume_level)
+                    await self.mass.player_manager.async_cmd_volume_set(
+                        player_id, volume_level
+                    )
                 elif service == "media_play":
                     await self.mass.player_manager.async_cmd_play(player_id)
                 elif service == "media_pause":
@@ -198,7 +201,11 @@ class HomeAssistantPlugin(Provider):
                 elif service in ["play_media", "select_source"]:
                     return await self.__async_handle_play_media(player_id, service_data)
                 else:
-                    LOGGER.error("%s service is unhandled. Service data: %s", service, service_data)
+                    LOGGER.error(
+                        "%s service is unhandled. Service data: %s",
+                        service,
+                        service_data,
+                    )
 
     async def __async_handle_play_media(self, player_id, service_data):
         """Handle play media request from homeassistant."""
@@ -206,7 +213,7 @@ class HomeAssistantPlugin(Provider):
         if not media_content_id:
             media_content_id = service_data.get("source")
         queue_opt = "add" if service_data.get("enqueue") else "play"
-        if not "://" in media_content_id:
+        if "://" not in media_content_id:
             media_items = []
             for playlist_str in media_content_id.split(","):
                 playlist_str = playlist_str.strip()
@@ -216,7 +223,9 @@ class HomeAssistantPlugin(Provider):
                 if playlist:
                     media_items.append(playlist)
                 else:
-                    radio = await self.mass.music_manager.async_get_radio_by_name(playlist_str)
+                    radio = await self.mass.music_manager.async_get_radio_by_name(
+                        playlist_str
+                    )
                     if radio:
                         media_items.append(radio)
                         queue_opt = "play"
@@ -228,7 +237,9 @@ class HomeAssistantPlugin(Provider):
             playlist = await self.mass.music_manager.async_getplaylist(
                 "spotify", media_content_id.split(":")[-1]
             )
-            return await self.mass.player_manager.async_play_media(player_id, playlist, queue_opt)
+            return await self.mass.player_manager.async_play_media(
+                player_id, playlist, queue_opt
+            )
 
     async def __async_publish_player(self, player: Player):
         """Publish player details to Home Assistant."""
@@ -237,11 +248,13 @@ class HomeAssistantPlugin(Provider):
         if not player.available:
             return
         player_id = player.player_id
-        entity_id = "media_player.mass_" + slug.slugify(player.name, separator="_").lower()
+        entity_id = (
+            "media_player.mass_" + slug.slugify(player.name, separator="_").lower()
+        )
         player_queue = self.mass.player_manager.get_player_queue(player_id)
         cur_item = player_queue.cur_item if player_queue else None
         state_attributes = {
-            "supported_features": 196541, # https://github.com/home-assistant/core/blob/dev/homeassistant/components/media_player/const.py#L59
+            "supported_features": 196541,
             "friendly_name": player.name,
             "source_list": self._sources,
             "source": "unknown",
@@ -251,14 +264,19 @@ class HomeAssistantPlugin(Provider):
             "media_duration": cur_item.duration if cur_item else None,
             "media_position": player_queue.cur_item_time if player_queue else None,
             "media_title": cur_item.name if cur_item else None,
-            "media_artist": cur_item.artists[0].name if cur_item and cur_item.artists else None,
-            "media_album_name": cur_item.album.name if cur_item and cur_item.album else None,
+            "media_artist": cur_item.artists[0].name
+            if cur_item and cur_item.artists
+            else None,
+            "media_album_name": cur_item.album.name
+            if cur_item and cur_item.album
+            else None,
             "entity_picture": "",
             "mass_player_id": player_id,
         }
         if cur_item:
             host = self.mass.web.internal_url
             item_type = "radio" if cur_item.media_type == MediaType.Radio else "track"
+            # pylint: disable=line-too-long
             img_url = f"{host}/api/{item_type}/{cur_item.item_id}/thumb?provider={cur_item.provider}"
             state_attributes["entity_picture"] = img_url
         self._published_players[entity_id] = player.player_id
@@ -273,7 +291,8 @@ class HomeAssistantPlugin(Provider):
             async for playlist in self.mass.music_manager.async_get_library_playlists()
         ]
         self._sources += [
-            playlist.name async for playlist in self.mass.music_manager.async_get_library_radios()
+            playlist.name
+            async for playlist in self.mass.music_manager.async_get_library_radios()
         ]
 
     @callback
@@ -315,7 +334,11 @@ class HomeAssistantPlugin(Provider):
             if entity_id.startswith("media_player.mass_"):
                 continue
             result.append(
-                {"value": f"volume_{entity_id}", "text": entity_name, "entity_id": entity_id}
+                {
+                    "value": f"volume_{entity_id}",
+                    "text": entity_name,
+                    "entity_id": entity_id,
+                }
             )
         return result
 
@@ -326,14 +349,18 @@ class HomeAssistantPlugin(Provider):
                 continue
             cur_state = entity_obj["state"] not in ["off", "unavailable"]
             if control_entity.get("source"):
-                cur_state = entity_obj["attributes"].get("source") == control_entity["source"]
+                cur_state = (
+                    entity_obj["attributes"].get("source") == control_entity["source"]
+                )
             await self.mass.player_manager.async_update_player_control(
                 control_entity["value"], cur_state
             )
         for control_entity in self.__get_volume_control_entities():
             if control_entity["entity_id"] != entity_obj["entity_id"]:
                 continue
-            cur_state = int(try_parse_float(entity_obj["attributes"].get("volume_level")) * 100)
+            cur_state = int(
+                try_parse_float(entity_obj["attributes"].get("volume_level")) * 100
+            )
             await self.mass.player_manager.async_update_player_control(
                 control_entity["value"], cur_state
             )
@@ -352,14 +379,16 @@ class HomeAssistantPlugin(Provider):
             if not control_entity["value"] in enabled_controls:
                 continue
             entity_id = control_entity["entity_id"]
-            if not entity_id in self._hass.states:
+            if entity_id not in self._hass.states:
                 LOGGER.warning("entity not found: %s", entity_id)
                 continue
             state_obj = self._hass.states[entity_id]
             cur_state = state_obj["state"] not in ["off", "unavailable"]
             source = control_entity.get("source")
             if source:
-                cur_state = state_obj["attributes"].get("source") == control_entity["source"]
+                cur_state = (
+                    state_obj["attributes"].get("source") == control_entity["source"]
+                )
 
             control = PlayerControl(
                 type=PlayerControlType.POWER,
@@ -372,7 +401,7 @@ class HomeAssistantPlugin(Provider):
             control.entity_id = entity_id
             control.source = source
             await self.mass.player_manager.async_register_player_control(control)
-            if not entity_id in self._tracked_entities:
+            if entity_id not in self._tracked_entities:
                 self._tracked_entities.append(entity_id)
 
     async def __async_register_volume_controls(self):
@@ -383,10 +412,12 @@ class HomeAssistantPlugin(Provider):
             if not control_entity["value"] in enabled_controls:
                 continue
             entity_id = control_entity["entity_id"]
-            if not entity_id in self._hass.states:
+            if entity_id not in self._hass.states:
                 LOGGER.warning("entity not found: %s", entity_id)
                 continue
-            cur_volume = try_parse_float(self._hass.get_state(entity_id, "volume_level")) * 100
+            cur_volume = (
+                try_parse_float(self._hass.get_state(entity_id, "volume_level")) * 100
+            )
             control = PlayerControl(
                 type=PlayerControlType.VOLUME,
                 id=control_entity["value"],
@@ -397,7 +428,7 @@ class HomeAssistantPlugin(Provider):
             # store some vars on the control object for convenience
             control.entity_id = entity_id
             await self.mass.player_manager.async_register_player_control(control)
-            if not entity_id in self._tracked_entities:
+            if entity_id not in self._tracked_entities:
                 self._tracked_entities.append(entity_id)
 
     async def async_power_control_set_state(self, control_id: str, new_state: bool):
index d0dbd680a0bb7ab70cca3a354b04735d18f69bd9..ab157f8b41a46c8928dacc37fed6d4d4df947f77 100644 (file)
@@ -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
+from music_assistant.app_vars import get_app_var  # noqa
 from music_assistant.constants import (
     CONF_PASSWORD,
     CONF_USERNAME,
@@ -37,10 +37,14 @@ LOGGER = logging.getLogger(PROV_ID)
 
 CONFIG_ENTRIES = [
     ConfigEntry(
-        entry_key=CONF_USERNAME, entry_type=ConfigEntryType.STRING, description_key=CONF_USERNAME
+        entry_key=CONF_USERNAME,
+        entry_type=ConfigEntryType.STRING,
+        description_key=CONF_USERNAME,
     ),
     ConfigEntry(
-        entry_key=CONF_PASSWORD, entry_type=ConfigEntryType.PASSWORD, description_key=CONF_PASSWORD
+        entry_key=CONF_PASSWORD,
+        entry_type=ConfigEntryType.PASSWORD,
+        description_key=CONF_PASSWORD,
     ),
 ]
 
@@ -54,7 +58,10 @@ async def async_setup(mass):
 class QobuzProvider(MusicProvider):
     """Provider for the Qobux music service."""
 
+    # pylint: disable=abstract-method
+
     _http_session = None
+    __user_auth_info = None
 
     @property
     def id(self) -> str:
@@ -74,15 +81,10 @@ class QobuzProvider(MusicProvider):
     @property
     def supported_mediatypes(self) -> List[MediaType]:
         """Return MediaTypes the provider supports."""
-        return [
-            MediaType.Album,
-            MediaType.Artist,
-            MediaType.Playlist,
-            MediaType.Track,
-        ]
+        return [MediaType.Album, MediaType.Artist, MediaType.Playlist, MediaType.Track]
 
     async def async_on_start(self) -> bool:
-        """Called on startup. Handle initialization of the provider based on config."""
+        """Handle initialization of the provider based on config."""
         # pylint: disable=attribute-defined-outside-init
         self._http_session = aiohttp.ClientSession(
             loop=self.mass.loop, connector=aiohttp.TCPConnector()
@@ -93,7 +95,7 @@ class QobuzProvider(MusicProvider):
             return False
         self.__username = config[CONF_USERNAME]
         self.__password = config[CONF_PASSWORD]
-        
+
         self.__user_auth_info = None
         self.__logged_in = False
         self._throttler = Throttler(rate_limit=4, period=1)
@@ -102,7 +104,7 @@ class QobuzProvider(MusicProvider):
         return True
 
     async def async_on_stop(self):
-        """Called on shutdown. Handle correct close/cleanup of the provider on exit."""
+        """Handle correct close/cleanup of the provider on exit."""
         if self._http_session:
             await self._http_session.close()
 
@@ -111,6 +113,7 @@ class QobuzProvider(MusicProvider):
     ) -> SearchResult:
         """
         Perform search on musicprovider.
+
             :param search_query: Search query.
             :param media_types: A list of media_types to include. All types if None.
             :param limit: Number of items to return in the search (per type).
@@ -197,25 +200,25 @@ class QobuzProvider(MusicProvider):
         return await self.__async_parse_artist(artist_obj)
 
     async def async_get_album(self, prov_album_id) -> Album:
-        """get full album details by id"""
+        """Get full album details by id."""
         params = {"album_id": prov_album_id}
         album_obj = await self.__async_get_data("album/get", params)
         return await self.__async_parse_album(album_obj)
 
     async def async_get_track(self, prov_track_id) -> Track:
-        """get full track details by id"""
+        """Get full track details by id."""
         params = {"track_id": prov_track_id}
         track_obj = await self.__async_get_data("track/get", params)
         return await self.__async_parse_track(track_obj)
 
     async def async_get_playlist(self, prov_playlist_id) -> Playlist:
-        """get full playlist details by id"""
+        """Get full playlist details by id."""
         params = {"playlist_id": prov_playlist_id}
         playlist_obj = await self.__async_get_data("playlist/get", params)
         return await self.__async_parse_playlist(playlist_obj)
 
     async def async_get_album_tracks(self, prov_album_id) -> List[Track]:
-        """get all album tracks for given album id"""
+        """Get all album tracks for given album id."""
         params = {"album_id": prov_album_id}
         async for item in self.__async_get_all_items("album/get", params, key="tracks"):
             track = await self.__async_parse_track(item)
@@ -229,7 +232,7 @@ class QobuzProvider(MusicProvider):
                 )
 
     async def async_get_playlist_tracks(self, prov_playlist_id) -> List[Track]:
-        """get all playlist tracks for given playlist id"""
+        """Get all playlist tracks for given playlist id."""
         params = {"playlist_id": prov_playlist_id, "extra": "tracks"}
         endpoint = "playlist/get"
         async for item in self.__async_get_all_items(endpoint, params, key="tracks"):
@@ -246,7 +249,7 @@ class QobuzProvider(MusicProvider):
                 # track version if the original is marked unavailable ?
 
     async def async_get_artist_albums(self, prov_artist_id) -> List[Album]:
-        """get a list of albums for the given artist"""
+        """Get a list of albums for the given artist."""
         params = {"artist_id": prov_artist_id, "extra": "albums"}
         endpoint = "artist/get"
         async for item in self.__async_get_all_items(endpoint, params, key="albums"):
@@ -256,27 +259,35 @@ class QobuzProvider(MusicProvider):
                     yield album
 
     async def async_get_artist_toptracks(self, prov_artist_id) -> List[Track]:
-        """get a list of most popular tracks for the given artist"""
+        """Get a list of most popular tracks for the given artist."""
         # artist toptracks not supported on Qobuz, so use search instead
         # assuming qobuz returns results sorted by popularity
         artist = await self.async_get_artist(prov_artist_id)
         params = {"query": artist.name, "limit": 25, "type": "tracks"}
         searchresult = await self.__async_get_data("catalog/search", params)
         for item in searchresult["tracks"]["items"]:
-            if "performer" in item and str(item["performer"]["id"]) == str(prov_artist_id):
+            if "performer" in item and str(item["performer"]["id"]) == str(
+                prov_artist_id
+            ):
                 track = await self.__async_parse_track(item)
                 if track:
                     yield track
 
     async def async_library_add(self, prov_item_id, media_type: MediaType):
-        """add item to library"""
+        """Add item to library."""
         result = None
         if media_type == MediaType.Artist:
-            result = await self.__async_get_data("favorite/create", {"artist_ids": prov_item_id})
+            result = await self.__async_get_data(
+                "favorite/create", {"artist_ids": prov_item_id}
+            )
         elif media_type == MediaType.Album:
-            result = await self.__async_get_data("favorite/create", {"album_ids": prov_item_id})
+            result = await self.__async_get_data(
+                "favorite/create", {"album_ids": prov_item_id}
+            )
         elif media_type == MediaType.Track:
-            result = await self.__async_get_data("favorite/create", {"track_ids": prov_item_id})
+            result = await self.__async_get_data(
+                "favorite/create", {"track_ids": prov_item_id}
+            )
         elif media_type == MediaType.Playlist:
             result = await self.__async_get_data(
                 "playlist/subscribe", {"playlist_id": prov_item_id}
@@ -284,14 +295,20 @@ class QobuzProvider(MusicProvider):
         return result
 
     async def async_library_remove(self, prov_item_id, media_type: MediaType):
-        """remove item from library"""
+        """Remove item from library."""
         result = None
         if media_type == MediaType.Artist:
-            result = await self.__async_get_data("favorite/delete", {"artist_ids": prov_item_id})
+            result = await self.__async_get_data(
+                "favorite/delete", {"artist_ids": prov_item_id}
+            )
         elif media_type == MediaType.Album:
-            result = await self.__async_get_data("favorite/delete", {"album_ids": prov_item_id})
+            result = await self.__async_get_data(
+                "favorite/delete", {"album_ids": prov_item_id}
+            )
         elif media_type == MediaType.Track:
-            result = await self.__async_get_data("favorite/delete", {"track_ids": prov_item_id})
+            result = await self.__async_get_data(
+                "favorite/delete", {"track_ids": prov_item_id}
+            )
         elif media_type == MediaType.Playlist:
             playlist = await self.async_get_playlist(prov_item_id)
             if playlist.is_editable:
@@ -305,7 +322,7 @@ class QobuzProvider(MusicProvider):
         return result
 
     async def async_add_playlist_tracks(self, prov_playlist_id, prov_track_ids):
-        """add track(s) to playlist"""
+        """Add track(s) to playlist."""
         params = {
             "playlist_id": prov_playlist_id,
             "track_ids": ",".join(prov_track_ids),
@@ -313,10 +330,12 @@ class QobuzProvider(MusicProvider):
         return await self.__async_get_data("playlist/addTracks", params)
 
     async def async_remove_playlist_tracks(self, prov_playlist_id, prov_track_ids):
-        """remove track(s) from playlist"""
+        """Remove track(s) from playlist."""
         playlist_track_ids = []
         params = {"playlist_id": prov_playlist_id, "extra": "tracks"}
-        for track in await self.__async_get_all_items("playlist/get", params, key="tracks"):
+        for track in await self.__async_get_all_items(
+            "playlist/get", params, key="tracks"
+        ):
             if track["id"] in prov_track_ids:
                 playlist_track_ids.append(track["playlist_track_id"])
         params = {
@@ -325,24 +344,24 @@ class QobuzProvider(MusicProvider):
         }
         return await self.__async_get_data("playlist/deleteTracks", params)
 
-    async def async_get_stream_details(self, track_id: str) -> StreamDetails:
+    async def async_get_stream_details(self, item_id: str) -> StreamDetails:
         """Return the content details for the given track when it will be streamed."""
         streamdetails = None
         for format_id in [27, 7, 6, 5]:
             # it seems that simply requesting for highest available quality does not work
             # from time to time the api response is empty for this request ?!
-            params = {"format_id": format_id, "track_id": track_id, "intent": "stream"}
+            params = {"format_id": format_id, "track_id": item_id, "intent": "stream"}
             streamdetails = await self.__async_get_data(
                 "track/getFileUrl", params, sign_request=True
             )
             if streamdetails and streamdetails.get("url"):
                 break
         if not streamdetails or not streamdetails.get("url"):
-            LOGGER.error("Unable to retrieve stream url for track %s", track_id)
+            LOGGER.error("Unable to retrieve stream url for track %s", item_id)
             return None
         return StreamDetails(
             type=StreamType.URL,
-            item_id=str(track_id),
+            item_id=str(item_id),
             provider=PROV_ID,
             path=streamdetails["url"],
             content_type=ContentType(streamdetails["mime_type"].split("/")[1]),
@@ -353,8 +372,9 @@ class QobuzProvider(MusicProvider):
 
     async def async_mass_event(self, msg, msg_details):
         """
-        received event from mass
-        we use this to report playback start/stop to qobuz
+        Received event from mass.
+
+        We use this to report playback start/stop to qobuz.
         """
         if not self.__user_auth_info:
             return
@@ -395,7 +415,7 @@ class QobuzProvider(MusicProvider):
             await self.__async_get_data("/track/reportStreamingEnd", params)
 
     async def __async_parse_artist(self, artist_obj):
-        """parse qobuz artist object to generic layout"""
+        """Parse qobuz artist object to generic layout."""
         artist = Artist()
         if not artist_obj or not artist_obj.get("id"):
             return None
@@ -408,7 +428,10 @@ class QobuzProvider(MusicProvider):
         if artist_obj.get("image"):
             for key in ["extralarge", "large", "medium", "small"]:
                 if artist_obj["image"].get(key):
-                    if not "2a96cbd8b46e442fc41c2b86b821562f" in artist_obj["image"][key]:
+                    if (
+                        "2a96cbd8b46e442fc41c2b86b821562f"
+                        not in artist_obj["image"][key]
+                    ):
                         artist.metadata["image"] = artist_obj["image"][key]
                         break
         if artist_obj.get("biography"):
@@ -418,7 +441,7 @@ class QobuzProvider(MusicProvider):
         return artist
 
     async def __async_parse_album(self, album_obj):
-        """parse qobuz album object to generic layout"""
+        """Parse qobuz album object to generic layout."""
         album = Album()
         if (
             not album_obj
@@ -492,7 +515,7 @@ class QobuzProvider(MusicProvider):
         return album
 
     async def __async_parse_track(self, track_obj):
-        """parse qobuz track object to generic layout"""
+        """Parse qobuz track object to generic layout."""
         track = Track()
         if (
             not track_obj
@@ -504,7 +527,7 @@ class QobuzProvider(MusicProvider):
             return None
         track.item_id = str(track_obj["id"])
         track.provider = PROV_ID
-        if track_obj.get("performer") and not "Various " in track_obj["performer"]:
+        if track_obj.get("performer") and "Various " not in track_obj["performer"]:
             artist = await self.__async_parse_artist(track_obj["performer"])
             if artist:
                 track.artists.append(artist)
@@ -513,7 +536,7 @@ class QobuzProvider(MusicProvider):
             if (
                 track_obj.get("album")
                 and track_obj["album"].get("artist")
-                and not "Various " in track_obj["album"]["artist"]
+                and "Various " not in track_obj["album"]["artist"]
             ):
                 artist = await self.__async_parse_artist(track_obj["album"]["artist"])
                 if artist:
@@ -574,7 +597,7 @@ class QobuzProvider(MusicProvider):
         return track
 
     async def __async_parse_playlist(self, playlist_obj):
-        """parse qobuz playlist object to generic layout"""
+        """Parse qobuz playlist object to generic layout."""
         playlist = Playlist()
         if not playlist_obj or not playlist_obj.get("id"):
             return None
@@ -597,7 +620,7 @@ class QobuzProvider(MusicProvider):
         return playlist
 
     async def __async_auth_token(self):
-        """login to qobuz and store the token"""
+        """Login to qobuz and store the token."""
         if self.__user_auth_info:
             return self.__user_auth_info["user_auth_token"]
         params = {
@@ -608,11 +631,13 @@ class QobuzProvider(MusicProvider):
         details = await self.__async_get_data("user/login", params)
         if details and "user" in details:
             self.__user_auth_info = details
-            LOGGER.info("Succesfully logged in to Qobuz as %s", details["user"]["display_name"])
+            LOGGER.info(
+                "Succesfully logged in to Qobuz as %s", details["user"]["display_name"]
+            )
             return details["user_auth_token"]
 
     async def __async_get_all_items(self, endpoint, params=None, key="tracks"):
-        """get all items from a paged list"""
+        """Get all items from a paged list."""
         if not params:
             params = {}
         limit = 50
@@ -622,7 +647,7 @@ class QobuzProvider(MusicProvider):
             params["offset"] = offset
             result = await self.__async_get_data(endpoint, params=params)
             offset += limit
-            if not result or not key in result or not "items" in result[key]:
+            if not result or key not in result or "items" not in result[key]:
                 break
             for item in result[key]["items"]:
                 yield item
@@ -630,7 +655,7 @@ class QobuzProvider(MusicProvider):
                 break
 
     async def __async_get_data(self, endpoint, params=None, sign_request=False):
-        """get data from api"""
+        """Get data from api."""
         if not params:
             params = {}
         url = "http://www.qobuz.com/api.json/0.2/%s" % endpoint
@@ -659,13 +684,15 @@ class QobuzProvider(MusicProvider):
                 url, headers=headers, params=params, verify_ssl=False
             ) as response:
                 result = await response.json()
-                if "error" in result or ("status" in result and "error" in result["status"]):
+                if "error" in result or (
+                    "status" in result and "error" in result["status"]
+                ):
                     LOGGER.error("%s - %s", endpoint, result)
                     return None
                 return result
 
     async def __async_post_data(self, endpoint, params=None, data=None):
-        """post data to api"""
+        """Post data to api."""
         if not params:
             params = {}
         if not data:
@@ -677,7 +704,9 @@ class QobuzProvider(MusicProvider):
             url, params=params, json=data, verify_ssl=False
         ) as response:
             result = await response.json()
-            if "error" in result or ("status" in result and "error" in result["status"]):
+            if "error" in result or (
+                "status" in result and "error" in result["status"]
+            ):
                 LOGGER.error("%s - %s", endpoint, result)
                 return None
             return result
index f06b3f0a45b768e42413fd4e35e8c06a92113dfa..71f6909663f9855d03d8439263c71dbc49d3c1c5 100644 (file)
@@ -6,4 +6,4 @@ from .sonos import SonosProvider
 async def async_setup(mass):
     """Perform async setup of this Plugin/Provider."""
     prov = SonosProvider()
-    await mass.async_register_provider(prov)
\ No newline at end of file
+    await mass.async_register_provider(prov)
index 15e936e7168a1f02126fe68df2471b1bdd6b00ef..f6921849e14fbe0fac5df6f0c4c0bed16f54c0b0 100644 (file)
@@ -22,7 +22,9 @@ PLAYER_CONFIG_ENTRIES = []  # we don't have any player config entries (for now)
 
 
 class SonosProvider(PlayerProvider):
-    """Support for Sonos speakers"""
+    """Support for Sonos speakers."""
+
+    # pylint: disable=abstract-method
 
     _discovery_running = False
     _tasks = []
@@ -45,17 +47,18 @@ class SonosProvider(PlayerProvider):
         return CONFIG_ENTRIES
 
     async def async_on_start(self) -> bool:
-        """Called on startup. Handle initialization of the provider."""
+        """Handle initialization of the provider."""
         self._tasks.append(self.mass.add_job(self.__async_periodic_discovery()))
 
     async def async_on_stop(self):
-        """Called on shutdown. Handle correct close/cleanup of the provider on exit."""
+        """Handle correct close/cleanup of the provider on exit."""
         for task in self._tasks:
             task.cancel()
 
     async def async_cmd_play_uri(self, player_id: str, uri: str):
         """
         Play the specified uri/url on the goven player.
+
             :param player_id: player_id of the player to handle the command.
         """
         player = self._players.get(player_id)
@@ -67,6 +70,7 @@ class SonosProvider(PlayerProvider):
     async def async_cmd_stop(self, player_id: str):
         """
         Send STOP command to given player.
+
             :param player_id: player_id of the player to handle the command.
         """
         player = self._players.get(player_id)
@@ -78,6 +82,7 @@ class SonosProvider(PlayerProvider):
     async def async_cmd_play(self, player_id: str):
         """
         Send STOP command to given player.
+
             :param player_id: player_id of the player to handle the command.
         """
         player = self._players.get(player_id)
@@ -89,6 +94,7 @@ class SonosProvider(PlayerProvider):
     async def async_cmd_pause(self, player_id: str):
         """
         Send PAUSE command to given player.
+
             :param player_id: player_id of the player to handle the command.
         """
         player = self._players.get(player_id)
@@ -100,6 +106,7 @@ class SonosProvider(PlayerProvider):
     async def async_cmd_next(self, player_id: str):
         """
         Send NEXT TRACK command to given player.
+
             :param player_id: player_id of the player to handle the command.
         """
         player = self._players.get(player_id)
@@ -111,6 +118,7 @@ class SonosProvider(PlayerProvider):
     async def async_cmd_previous(self, player_id: str):
         """
         Send PREVIOUS TRACK command to given player.
+
             :param player_id: player_id of the player to handle the command.
         """
         player = self._players.get(player_id)
@@ -122,6 +130,7 @@ class SonosProvider(PlayerProvider):
     async def async_cmd_power_on(self, player_id: str):
         """
         Send POWER ON command to given player.
+
             :param player_id: player_id of the player to handle the command.
         """
         player = self._players.get(player_id)
@@ -135,6 +144,7 @@ class SonosProvider(PlayerProvider):
     async def async_cmd_power_off(self, player_id: str):
         """
         Send POWER OFF command to given player.
+
             :param player_id: player_id of the player to handle the command.
         """
         player = self._players.get(player_id)
@@ -149,6 +159,7 @@ class SonosProvider(PlayerProvider):
     async def async_cmd_volume_set(self, player_id: str, volume_level: int):
         """
         Send volume level command to given player.
+
             :param player_id: player_id of the player to handle the command.
             :param volume_level: volume level to set (0..100).
         """
@@ -161,6 +172,7 @@ class SonosProvider(PlayerProvider):
     async def async_cmd_volume_mute(self, player_id: str, is_muted=False):
         """
         Send volume MUTE command to given player.
+
             :param player_id: player_id of the player to handle the command.
             :param is_muted: bool with new mute state.
         """
@@ -172,7 +184,8 @@ class SonosProvider(PlayerProvider):
 
     async def async_cmd_queue_play_index(self, player_id: str, index: int):
         """
-        Play item at index X on player's queue
+        Play item at index X on player's queue.
+
             :param player_id: player_id of the player to handle the command.
             :param index: (int) index of the queue item that should start playing
         """
@@ -184,7 +197,8 @@ class SonosProvider(PlayerProvider):
 
     async def async_cmd_queue_load(self, player_id: str, queue_items: List[QueueItem]):
         """
-        Load/overwrite given items in the player's queue implementation
+        Load/overwrite given items in the player's queue implementation.
+
             :param player_id: player_id of the player to handle the command.
             :param queue_items: a list of QueueItems
         """
@@ -201,6 +215,7 @@ class SonosProvider(PlayerProvider):
     ):
         """
         Insert new items at position X into existing queue.
+
         If insert_at_index 0 or None, will start playing newly added item(s)
             :param player_id: player_id of the player to handle the command.
             :param queue_items: a list of QueueItems
@@ -209,13 +224,18 @@ class SonosProvider(PlayerProvider):
         player = self._players.get(player_id)
         if player:
             for pos, item in enumerate(queue_items):
-                self.mass.add_job(player.soco.add_uri_to_queue, item.uri, insert_at_index + pos)
+                self.mass.add_job(
+                    player.soco.add_uri_to_queue, item.uri, insert_at_index + pos
+                )
         else:
             LOGGER.warning("Received command for unavailable player: %s", player_id)
 
-    async def async_cmd_queue_append(self, player_id: str, queue_items: List[QueueItem]):
+    async def async_cmd_queue_append(
+        self, player_id: str, queue_items: List[QueueItem]
+    ):
         """
         Append new items at the end of the queue.
+
             :param player_id: player_id of the player to handle the command.
             :param queue_items: a list of QueueItems
         """
@@ -230,6 +250,7 @@ class SonosProvider(PlayerProvider):
     async def async_cmd_queue_clear(self, player_id: str):
         """
         Clear the player's queue.
+
             :param player_id: player_id of the player to handle the command.
         """
         player = self._players.get(player_id)
@@ -256,8 +277,10 @@ class SonosProvider(PlayerProvider):
         cur_player_ids = [item.player_id for item in self._players.values()]
         # remove any disconnected players...
         for player in list(self._players.values()):
-            if not player.is_group and not player.soco.uid in new_device_ids:
-                self.mass.add_job(self.mass.player_manager.async_remove_player(player.player_id))
+            if not player.is_group and player.soco.uid not in new_device_ids:
+                self.mass.add_job(
+                    self.mass.player_manager.async_remove_player(player.player_id)
+                )
                 for sub in player.subscriptions:
                     sub.unsubscribe()
                 self._players.pop(player, None)
@@ -349,14 +372,14 @@ class SonosProvider(PlayerProvider):
             group_player.is_group_player = True
             group_player.name = group.label
             group_player.group_childs = [item.uid for item in group.members]
-            self.mass.run_task(self.mass.player_manager.async_update_player(group_player))
+            self.mass.run_task(
+                self.mass.player_manager.async_update_player(group_player)
+            )
 
     async def __topology_changed(self, player_id, event=None):
-        """
-        Received topology changed event
-        from one of the sonos players.
-        Schedule discovery to work out the changes.
-        """
+        """Received topology changed event from one of the sonos players."""
+        # pylint: disable=unused-argument
+        # Schedule discovery to work out the changes.
         self.mass.add_job(self.__run_discovery)
 
     async def __async_report_progress(self, player_id: str):
@@ -404,4 +427,5 @@ class ProcessSonosEventQueue:
 
     def put(self, item, block=True, timeout=None):
         """Process event."""
+        # pylint: disable=unused-argument
         self._callback_handler(self._player_id, item)
index 0fdb80dd8be05ca1c535ecc71603229b1c567e7f..8d1c9d58f21a50114f7fa1e81d41cdfbb7024934 100644 (file)
@@ -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
+from music_assistant.app_vars import get_app_var  # noqa
 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 (
@@ -35,10 +35,14 @@ LOGGER = logging.getLogger(PROV_ID)
 
 CONFIG_ENTRIES = [
     ConfigEntry(
-        entry_key=CONF_USERNAME, entry_type=ConfigEntryType.STRING, description_key=CONF_USERNAME
+        entry_key=CONF_USERNAME,
+        entry_type=ConfigEntryType.STRING,
+        description_key=CONF_USERNAME,
     ),
     ConfigEntry(
-        entry_key=CONF_PASSWORD, entry_type=ConfigEntryType.PASSWORD, description_key=CONF_PASSWORD
+        entry_key=CONF_PASSWORD,
+        entry_type=ConfigEntryType.PASSWORD,
+        description_key=CONF_PASSWORD,
     ),
 ]
 
@@ -52,6 +56,8 @@ async def async_setup(mass):
 class SpotifyProvider(MusicProvider):
     """Implementation for the Spotify MusicProvider."""
 
+    # pylint: disable=abstract-method
+
     _http_session = None
     __auth_token = None
     sp_user = None
@@ -83,7 +89,7 @@ class SpotifyProvider(MusicProvider):
         ]
 
     async def async_on_start(self) -> bool:
-        """Called on startup. Handle initialization of the provider based on config."""
+        """Handle initialization of the provider based on config."""
         config = self.mass.config.get_provider_config(self.id)
         # pylint: disable=attribute-defined-outside-init
         self._cur_user = None
@@ -103,7 +109,7 @@ class SpotifyProvider(MusicProvider):
         return token is not None
 
     async def async_on_stop(self):
-        """Called on shutdown. Handle correct close/cleanup of the provider on exit."""
+        """Handle correct close/cleanup of the provider on exit."""
         if self._http_session:
             await self._http_session.close()
 
@@ -112,6 +118,7 @@ class SpotifyProvider(MusicProvider):
     ) -> SearchResult:
         """
         Perform search on musicprovider.
+
             :param search_query: Search query.
             :param media_types: A list of media_types to include. All types if None.
             :param limit: Number of items to return in the search (per type).
@@ -153,8 +160,10 @@ class SpotifyProvider(MusicProvider):
         return result
 
     async def async_get_library_artists(self) -> List[Artist]:
-        """retrieve library artists from spotify"""
-        spotify_artists = await self.__async_get_data("me/following?type=artist&limit=50")
+        """Retrieve library artists from spotify."""
+        spotify_artists = await self.__async_get_data(
+            "me/following?type=artist&limit=50"
+        )
         if spotify_artists:
             # TODO: use cursor method to retrieve more than 50 artists
             for artist_obj in spotify_artists["artists"]["items"]:
@@ -162,21 +171,21 @@ class SpotifyProvider(MusicProvider):
                 yield prov_artist
 
     async def async_get_library_albums(self) -> List[Album]:
-        """retrieve library albums from the provider"""
+        """Retrieve library albums from the provider."""
         async for item in self.__async_get_all_items("me/albums"):
             album = await self.__async_parse_album(item)
             if album:
                 yield album
 
     async def async_get_library_tracks(self) -> List[Track]:
-        """retrieve library tracks from the provider"""
+        """Retrieve library tracks from the provider."""
         async for item in self.__async_get_all_items("me/tracks"):
             track = await self.__async_parse_track(item)
             if track:
                 yield track
 
     async def async_get_library_playlists(self) -> List[Playlist]:
-        """retrieve playlists from the provider"""
+        """Retrieve playlists from the provider."""
         async for item in self.__async_get_all_items("me/playlists"):
             playlist = await self.__async_parse_playlist(item)
             if playlist:
@@ -187,27 +196,27 @@ class SpotifyProvider(MusicProvider):
         yield None  # TODO: Return spotify radio
 
     async def async_get_artist(self, prov_artist_id) -> Artist:
-        """get full artist details by id"""
+        """Get full artist details by id."""
         artist_obj = await self.__async_get_data("artists/%s" % prov_artist_id)
         return await self.__async_parse_artist(artist_obj)
 
     async def async_get_album(self, prov_album_id) -> Album:
-        """get full album details by id"""
+        """Get full album details by id."""
         album_obj = await self.__async_get_data("albums/%s" % prov_album_id)
         return await self.__async_parse_album(album_obj)
 
     async def async_get_track(self, prov_track_id) -> Track:
-        """get full track details by id"""
+        """Get full track details by id."""
         track_obj = await self.__async_get_data("tracks/%s" % prov_track_id)
         return await self.__async_parse_track(track_obj)
 
     async def async_get_playlist(self, prov_playlist_id) -> Playlist:
-        """get full playlist details by id"""
+        """Get full playlist details by id."""
         playlist_obj = await self.__async_get_data(f"playlists/{prov_playlist_id}")
         return await self.__async_parse_playlist(playlist_obj)
 
     async def async_get_album_tracks(self, prov_album_id) -> List[Track]:
-        """get all album tracks for given album id"""
+        """Get all album tracks for given album id."""
         endpoint = f"albums/{prov_album_id}/tracks"
         async for track_obj in self.__async_get_all_items(endpoint):
             track = await self.__async_parse_track(track_obj)
@@ -215,7 +224,7 @@ class SpotifyProvider(MusicProvider):
                 yield track
 
     async def async_get_playlist_tracks(self, prov_playlist_id) -> List[Track]:
-        """get all playlist tracks for given playlist id"""
+        """Get all playlist tracks for given playlist id."""
         endpoint = f"playlists/{prov_playlist_id}/tracks"
         async for track_obj in self.__async_get_all_items(endpoint):
             playlist_track = await self.__async_parse_track(track_obj)
@@ -229,7 +238,7 @@ class SpotifyProvider(MusicProvider):
                 )
 
     async def async_get_artist_albums(self, prov_artist_id) -> List[Album]:
-        """get a list of all albums for the given artist"""
+        """Get a list of all albums for the given artist."""
         params = {"include_groups": "album,single,compilation"}
         endpoint = f"artists/{prov_artist_id}/albums"
         async for item in self.__async_get_all_items(endpoint, params):
@@ -238,7 +247,7 @@ class SpotifyProvider(MusicProvider):
                 yield album
 
     async def async_get_artist_toptracks(self, prov_artist_id) -> List[Track]:
-        """get a list of 10 most popular tracks for the given artist"""
+        """Get a list of 10 most popular tracks for the given artist."""
         artist = await self.async_get_artist(prov_artist_id)
         endpoint = f"artists/{prov_artist_id}/top-tracks"
         items = await self.__async_get_data(endpoint)
@@ -249,7 +258,7 @@ class SpotifyProvider(MusicProvider):
                 yield track
 
     async def async_library_add(self, prov_item_id, media_type: MediaType):
-        """add item to library"""
+        """Add item to library."""
         result = False
         if media_type == MediaType.Artist:
             result = await self.__async_put_data(
@@ -266,7 +275,7 @@ class SpotifyProvider(MusicProvider):
         return result
 
     async def async_library_remove(self, prov_item_id, media_type: MediaType):
-        """remove item from library"""
+        """Remove item from library."""
         result = False
         if media_type == MediaType.Artist:
             result = await self.__async_delete_data(
@@ -277,29 +286,35 @@ class SpotifyProvider(MusicProvider):
         elif media_type == MediaType.Track:
             result = await self.__async_delete_data("me/tracks", {"ids": prov_item_id})
         elif media_type == MediaType.Playlist:
-            result = await self.__async_delete_data(f"playlists/{prov_item_id}/followers")
+            result = await self.__async_delete_data(
+                f"playlists/{prov_item_id}/followers"
+            )
         return result
 
     async def async_add_playlist_tracks(self, prov_playlist_id, prov_track_ids):
-        """add track(s) to playlist"""
+        """Add track(s) to playlist."""
         track_uris = []
         for track_id in prov_track_ids:
             track_uris.append("spotify:track:%s" % track_id)
         data = {"uris": track_uris}
-        return await self.__async_post_data(f"playlists/{prov_playlist_id}/tracks", data=data)
+        return await self.__async_post_data(
+            f"playlists/{prov_playlist_id}/tracks", data=data
+        )
 
     async def async_remove_playlist_tracks(self, prov_playlist_id, prov_track_ids):
-        """remove track(s) from playlist"""
+        """Remove track(s) from playlist."""
         track_uris = []
         for track_id in prov_track_ids:
             track_uris.append({"uri": "spotify:track:%s" % track_id})
         data = {"tracks": track_uris}
-        return await self.__async_delete_data(f"playlists/{prov_playlist_id}/tracks", data=data)
+        return await self.__async_delete_data(
+            f"playlists/{prov_playlist_id}/tracks", data=data
+        )
 
-    async def async_get_stream_details(self, track_id: str) -> StreamDetails:
+    async def async_get_stream_details(self, item_id: str) -> StreamDetails:
         """Return the content details for the given track when it will be streamed."""
         # make sure a valid track is requested.
-        track = await self.async_get_track(track_id)
+        track = await self.async_get_track(item_id)
         if not track:
             return None
         # make sure that the token is still valid by just requesting it
@@ -321,20 +336,22 @@ class SpotifyProvider(MusicProvider):
         )
 
     async def __async_parse_artist(self, artist_obj):
-        """parse spotify artist object to generic layout"""
+        """Parse spotify artist object to generic layout."""
         if not artist_obj:
             return None
         artist = Artist()
         artist.item_id = artist_obj["id"]
         artist.provider = self.id
-        artist.provider_ids.append(MediaItemProviderId(provider=PROV_ID, item_id=artist_obj["id"]))
+        artist.provider_ids.append(
+            MediaItemProviderId(provider=PROV_ID, item_id=artist_obj["id"])
+        )
         artist.name = artist_obj["name"]
         if "genres" in artist_obj:
             artist.tags = artist_obj["genres"]
         if artist_obj.get("images"):
             for img in artist_obj["images"]:
                 img_url = img["url"]
-                if not "2a96cbd8b46e442fc41c2b86b821562f" in img_url:
+                if "2a96cbd8b46e442fc41c2b86b821562f" not in img_url:
                     artist.metadata["image"] = img_url
                     break
         if artist_obj.get("external_urls"):
@@ -342,7 +359,7 @@ class SpotifyProvider(MusicProvider):
         return artist
 
     async def __async_parse_album(self, album_obj):
-        """parse spotify album object to generic layout"""
+        """Parse spotify album object to generic layout."""
         if not album_obj:
             return None
         if "album" in album_obj:
@@ -381,13 +398,15 @@ class SpotifyProvider(MusicProvider):
             album.metadata["explicit"] = str(album_obj["explicit"]).lower()
         album.provider_ids.append(
             MediaItemProviderId(
-                provider=PROV_ID, item_id=album_obj["id"], quality=TrackQuality.LOSSY_OGG
+                provider=PROV_ID,
+                item_id=album_obj["id"],
+                quality=TrackQuality.LOSSY_OGG,
             )
         )
         return album
 
     async def __async_parse_track(self, track_obj):
-        """parse spotify track object to generic layout"""
+        """Parse spotify track object to generic layout."""
         if not track_obj:
             return None
         if "track" in track_obj:
@@ -419,13 +438,15 @@ class SpotifyProvider(MusicProvider):
             track.metadata["spotify_url"] = track_obj["external_urls"]["spotify"]
         track.provider_ids.append(
             MediaItemProviderId(
-                provider=PROV_ID, item_id=track_obj["id"], quality=TrackQuality.LOSSY_OGG
+                provider=PROV_ID,
+                item_id=track_obj["id"],
+                quality=TrackQuality.LOSSY_OGG,
             )
         )
         return track
 
     async def __async_parse_playlist(self, playlist_obj):
-        """parse spotify playlist object to generic layout"""
+        """Parse spotify playlist object to generic layout."""
 
         if not playlist_obj.get("id"):
             return None
@@ -438,7 +459,8 @@ class SpotifyProvider(MusicProvider):
         playlist.name = playlist_obj["name"]
         playlist.owner = playlist_obj["owner"]["display_name"]
         playlist.is_editable = (
-            playlist_obj["owner"]["id"] == self.sp_user["id"] or playlist_obj["collaborative"]
+            playlist_obj["owner"]["id"] == self.sp_user["id"]
+            or playlist_obj["collaborative"]
         )
         if playlist_obj.get("images"):
             playlist.metadata["image"] = playlist_obj["images"][0]["url"]
@@ -448,9 +470,11 @@ class SpotifyProvider(MusicProvider):
         return playlist
 
     async def async_get_token(self):
-        """get auth token on spotify"""
+        """Get auth token on spotify."""
         # return existing token if we have one in memory
-        if self.__auth_token and (self.__auth_token["expiresAt"] > int(time.time()) + 20):
+        if self.__auth_token and (
+            self.__auth_token["expiresAt"] > int(time.time()) + 20
+        ):
             return self.__auth_token
         tokeninfo = {}
         if not self._username or not self._password:
@@ -468,7 +492,7 @@ class SpotifyProvider(MusicProvider):
         return tokeninfo
 
     def __get_token(self):
-        """get spotify auth token with spotty bin"""
+        """Get spotify auth token with spotty bin."""
         # get token with spotty
         scopes = [
             "user-read-playback-state",
@@ -517,7 +541,7 @@ class SpotifyProvider(MusicProvider):
         return tokeninfo
 
     async def __async_get_all_items(self, endpoint, params=None, key="items"):
-        """get all items from a paged list"""
+        """Get all items from a paged list."""
         if not params:
             params = {}
         limit = 50
@@ -527,7 +551,7 @@ class SpotifyProvider(MusicProvider):
             params["offset"] = offset
             result = await self.__async_get_data(endpoint, params=params)
             offset += limit
-            if not result or not key in result or not result[key]:
+            if not result or key not in result or not result[key]:
                 break
             for item in result[key]:
                 yield item
@@ -535,7 +559,7 @@ class SpotifyProvider(MusicProvider):
                 break
 
     async def __async_get_data(self, endpoint, params=None):
-        """get data from api"""
+        """Get data from api."""
         if not params:
             params = {}
         url = "https://api.spotify.com/v1/%s" % endpoint
@@ -554,7 +578,7 @@ class SpotifyProvider(MusicProvider):
                 return result
 
     async def __async_delete_data(self, endpoint, params=None, data=None):
-        """delete data from api"""
+        """Delete data from api."""
         if not params:
             params = {}
         url = "https://api.spotify.com/v1/%s" % endpoint
@@ -566,7 +590,7 @@ class SpotifyProvider(MusicProvider):
             return await response.text()
 
     async def __async_put_data(self, endpoint, params=None, data=None):
-        """put data on api"""
+        """Put data on api."""
         if not params:
             params = {}
         url = "https://api.spotify.com/v1/%s" % endpoint
@@ -578,7 +602,7 @@ class SpotifyProvider(MusicProvider):
             return await response.text()
 
     async def __async_post_data(self, endpoint, params=None, data=None):
-        """post data on api"""
+        """Post data on api."""
         if not params:
             params = {}
         url = "https://api.spotify.com/v1/%s" % endpoint
@@ -591,13 +615,17 @@ class SpotifyProvider(MusicProvider):
 
     @staticmethod
     def get_spotty_binary():
-        """find the correct spotty binary belonging to the platform"""
+        """Find the correct spotty binary belonging to the platform."""
         sp_binary = None
         if platform.system() == "Windows":
-            sp_binary = os.path.join(os.path.dirname(__file__), "spotty", "windows", "spotty.exe")
+            sp_binary = os.path.join(
+                os.path.dirname(__file__), "spotty", "windows", "spotty.exe"
+            )
         elif platform.system() == "Darwin":
             # macos binary is x86_64 intel
-            sp_binary = os.path.join(os.path.dirname(__file__), "spotty", "darwin", "spotty")
+            sp_binary = os.path.join(
+                os.path.dirname(__file__), "spotty", "darwin", "spotty"
+            )
         elif platform.system() == "Linux":
             # try to find out the correct architecture by trial and error
             architecture = platform.machine()
index ec646b2faed740b0f6116badc7ac0cb62db039fa..3e1d1c3591f7cc7112d0f7802f50043d1de13f00 100644 (file)
@@ -1,23 +1,14 @@
 """Squeezebox emulated player provider."""
 
 import asyncio
-import decimal
 import logging
-import os
-import random
-import socket
-import struct
-import sys
-import time
-from collections import OrderedDict
 from typing import List
 
 from music_assistant.constants import CONF_CROSSFADE_DURATION
-from music_assistant.models.config_entry import ConfigEntry, ConfigEntryType
-from music_assistant.models.player import DeviceInfo, Player, PlayerFeature, PlayerState
+from music_assistant.models.config_entry import ConfigEntry
+from music_assistant.models.player import DeviceInfo, PlayerFeature
 from music_assistant.models.player_queue import QueueItem
 from music_assistant.models.playerprovider import PlayerProvider
-from music_assistant.utils import get_hostname, get_ip, run_periodic, try_parse_int
 
 from .constants import PROV_ID, PROV_NAME
 from .discovery import DiscoveryProtocol
@@ -40,7 +31,7 @@ async def async_setup(mass):
 
 
 class PySqueezeProvider(PlayerProvider):
-    """Python implementation of SlimProto server"""
+    """Python implementation of SlimProto server."""
 
     _socket_clients = {}
     _tasks = []
@@ -61,16 +52,18 @@ class PySqueezeProvider(PlayerProvider):
         return CONFIG_ENTRIES
 
     async def async_on_start(self) -> bool:
-        """Called on startup. Handle initialization of the provider."""
+        """Handle initialization of the provider. Called on startup."""
         # start slimproto server
         self._tasks.append(
-            self.mass.add_job(asyncio.start_server(self.__async_client_connected, "0.0.0.0", 3483))
+            self.mass.add_job(
+                asyncio.start_server(self.__async_client_connected, "0.0.0.0", 3483)
+            )
         )
         # setup discovery
         self._tasks.append(self.mass.add_job(self.async_start_discovery()))
 
     async def async_on_stop(self):
-        """Called on shutdown. Handle correct close/cleanup of the provider on exit."""
+        """Handle correct close/cleanup of the provider on exit."""
         for task in self._tasks:
             task.cancel()
         for client in self._socket_clients.values():
@@ -78,12 +71,15 @@ class PySqueezeProvider(PlayerProvider):
 
     async def async_cmd_play_uri(self, player_id: str, uri: str):
         """
-        Play the specified uri/url on the goven player.
+        Play the specified uri/url on the given player.
+
             :param player_id: player_id of the player to handle the command.
         """
         socket_client = self._socket_clients.get(player_id)
         if socket_client:
-            crossfade = self.mass.config.player_settings[player_id][CONF_CROSSFADE_DURATION]
+            crossfade = self.mass.config.player_settings[player_id][
+                CONF_CROSSFADE_DURATION
+            ]
             await socket_client.async_cmd_play_uri(
                 uri, send_flush=True, crossfade_duration=crossfade
             )
@@ -93,6 +89,7 @@ class PySqueezeProvider(PlayerProvider):
     async def async_cmd_stop(self, player_id: str):
         """
         Send STOP command to given player.
+
             :param player_id: player_id of the player to handle the command.
         """
         socket_client = self._socket_clients.get(player_id)
@@ -103,7 +100,8 @@ class PySqueezeProvider(PlayerProvider):
 
     async def async_cmd_play(self, player_id: str):
         """
-        Send STOP command to given player.
+        Send PLAY command to given player.
+
             :param player_id: player_id of the player to handle the command.
         """
         socket_client = self._socket_clients.get(player_id)
@@ -115,6 +113,7 @@ class PySqueezeProvider(PlayerProvider):
     async def async_cmd_pause(self, player_id: str):
         """
         Send PAUSE command to given player.
+
             :param player_id: player_id of the player to handle the command.
         """
         socket_client = self._socket_clients.get(player_id)
@@ -126,6 +125,7 @@ class PySqueezeProvider(PlayerProvider):
     async def async_cmd_next(self, player_id: str):
         """
         Send NEXT TRACK command to given player.
+
             :param player_id: player_id of the player to handle the command.
         """
         queue = self.mass.player_manager.get_player_queue(player_id)
@@ -137,6 +137,7 @@ class PySqueezeProvider(PlayerProvider):
     async def async_cmd_previous(self, player_id: str):
         """
         Send PREVIOUS TRACK command to given player.
+
             :param player_id: player_id of the player to handle the command.
         """
         queue = self.mass.player_manager.get_player_queue(player_id)
@@ -148,6 +149,7 @@ class PySqueezeProvider(PlayerProvider):
     async def async_cmd_power_on(self, player_id: str):
         """
         Send POWER ON command to given player.
+
             :param player_id: player_id of the player to handle the command.
         """
         socket_client = self._socket_clients.get(player_id)
@@ -155,13 +157,16 @@ class PySqueezeProvider(PlayerProvider):
             await socket_client.async_cmd_power(True)
             # save power and volume state in cache
             cache_str = f"squeezebox_player_state_{player_id}"
-            await self.mass.cache.async_set(cache_str, (True, socket_client.volume_level))
+            await self.mass.cache.async_set(
+                cache_str, (True, socket_client.volume_level)
+            )
         else:
             LOGGER.warning("Received command for unavailable player: %s", player_id)
 
     async def async_cmd_power_off(self, player_id: str):
         """
         Send POWER OFF command to given player.
+
             :param player_id: player_id of the player to handle the command.
         """
         socket_client = self._socket_clients.get(player_id)
@@ -170,13 +175,16 @@ class PySqueezeProvider(PlayerProvider):
             # store last power state as we need it when the player (re)connects
             # save power and volume state in cache
             cache_str = f"squeezebox_player_state_{player_id}"
-            await self.mass.cache.async_set(cache_str, (False, socket_client.volume_level))
+            await self.mass.cache.async_set(
+                cache_str, (False, socket_client.volume_level)
+            )
         else:
             LOGGER.warning("Received command for unavailable player: %s", player_id)
 
     async def async_cmd_volume_set(self, player_id: str, volume_level: int):
         """
         Send volume level command to given player.
+
             :param player_id: player_id of the player to handle the command.
             :param volume_level: volume level to set (0..100).
         """
@@ -185,13 +193,16 @@ class PySqueezeProvider(PlayerProvider):
             await socket_client.async_cmd_volume_set(volume_level)
             # save power and volume state in cache
             cache_str = f"squeezebox_player_state_{player_id}"
-            await self.mass.cache.async_set(cache_str, (socket_client.powered, volume_level))
+            await self.mass.cache.async_set(
+                cache_str, (socket_client.powered, volume_level)
+            )
         else:
             LOGGER.warning("Received command for unavailable player: %s", player_id)
 
     async def async_cmd_volume_mute(self, player_id: str, is_muted=False):
         """
         Send volume MUTE command to given player.
+
             :param player_id: player_id of the player to handle the command.
             :param is_muted: bool with new mute state.
         """
@@ -203,7 +214,8 @@ class PySqueezeProvider(PlayerProvider):
 
     async def async_cmd_queue_play_index(self, player_id: str, index: int):
         """
-        Play item at index X on player's queue
+        Play item at index X on player's queue.
+
             :param player_id: player_id of the player to handle the command.
             :param index: (int) index of the queue item that should start playing
         """
@@ -215,7 +227,8 @@ class PySqueezeProvider(PlayerProvider):
 
     async def async_cmd_queue_load(self, player_id: str, queue_items: List[QueueItem]):
         """
-        Load/overwrite given items in the player's queue implementation
+        Load/overwrite given items in the player's queue implementation.
+
             :param player_id: player_id of the player to handle the command.
             :param queue_items: a list of QueueItems
         """
@@ -227,6 +240,7 @@ class PySqueezeProvider(PlayerProvider):
     ):
         """
         Insert new items at position X into existing queue.
+
         If insert_at_index 0 or None, will start playing newly added item(s)
             :param player_id: player_id of the player to handle the command.
             :param queue_items: a list of QueueItems
@@ -238,17 +252,23 @@ class PySqueezeProvider(PlayerProvider):
         if queue and insert_at_index == queue.cur_index:
             return await self.async_cmd_queue_play_index(player_id, insert_at_index)
 
-    async def async_cmd_queue_append(self, player_id: str, queue_items: List[QueueItem]):
+    async def async_cmd_queue_append(
+        self, player_id: str, queue_items: List[QueueItem]
+    ):
         """
         Append new items at the end of the queue.
+
             :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
 
-    async def async_cmd_queue_update(self, player_id: str, queue_items: List[QueueItem]):
+    async def async_cmd_queue_update(
+        self, player_id: str, queue_items: List[QueueItem]
+    ):
         """
         Overwrite the existing items in the queue, used for reordering.
+
             :param player_id: player_id of the player to handle the command.
             :param queue_items: a list of QueueItems
         """
@@ -257,12 +277,14 @@ class PySqueezeProvider(PlayerProvider):
     async def async_cmd_queue_clear(self, player_id: str):
         """
         Clear the player's queue.
+
             :param player_id: player_id of the player to handle the command.
         """
         # queue is handled by built-in queue controller but send stop
         return await self.async_cmd_stop(player_id)
 
     async def async_start_discovery(self):
+        """Start discovery for players."""
         transport, protocol = await self.mass.loop.create_datagram_endpoint(
             lambda: DiscoveryProtocol(self.mass.web.http_port),
             local_addr=("0.0.0.0", 3483),
@@ -314,7 +336,9 @@ class PySqueezeProvider(PlayerProvider):
             if queue:
                 next_item = queue.next_item
                 if next_item:
-                    crossfade = self.mass.config.player_settings[player_id][CONF_CROSSFADE_DURATION]
+                    crossfade = self.mass.config.player_settings[player_id][
+                        CONF_CROSSFADE_DURATION
+                    ]
                     await self._socket_clients[player_id].async_cmd_play_uri(
                         next_item.uri, send_flush=False, crossfade_duration=crossfade
                     )
index f710bfb8845cee43da36eb0f6cacbc01f6d18618..ecd683c15651e3cb008ab3492494c41e57aaa6a8 100644 (file)
@@ -1,4 +1,4 @@
 """Constants for Squeezebox emulation."""
 
 PROV_ID = "squeezebox"
-PROV_NAME = "Squeezebox emulation"
\ No newline at end of file
+PROV_NAME = "Squeezebox emulation"
index 06afbcdf854b20f09e5e00aaea56aac7ac741f20..d5ae0b803040a93d6d0b1696528c3cab23f434fe 100644 (file)
@@ -1,21 +1,21 @@
 """Squeezebox emulation discovery implementation."""
 
-from collections import OrderedDict
-import socket
 import logging
+import socket
 import struct
+from collections import OrderedDict
 
-from music_assistant.utils import (
-    get_hostname,
-    get_ip
-)
+from music_assistant.utils import get_hostname, get_ip
 
 LOGGER = logging.getLogger("squeezebox")
 
-class Datagram():
+
+class Datagram:
     """Description of a discovery datagram."""
+
     @classmethod
-    def decode(self, data):
+    def decode(cls, data):
+        """Decode a datagram message."""
         if data[0] == "e":
             return TLVDiscoveryRequestDatagram(data)
         elif data[0] == "E":
@@ -40,13 +40,15 @@ class ClientDiscoveryDatagram(Datagram):
     client = None
 
     def __init__(self, data):
-        s = struct.unpack("!cxBB8x6B", data.encode())
-        assert s[0] == "d"
-        self.device = s[1]
-        self.firmware = hex(s[2])
-        self.client = ":".join(["%02x" % (x,) for x in s[3:]])
+        """Initialize class."""
+        msg = struct.unpack("!cxBB8x6B", data.encode())
+        assert msg[0] == "d"
+        self.device = msg[1]
+        self.firmware = hex(msg[2])
+        self.client = ":".join(["%02x" % (x,) for x in msg[3:]])
 
     def __repr__(self):
+        """Print the class contents."""
         return "<%s device=%r firmware=%r client=%r>" % (
             self.__class__.__name__,
             self.device,
@@ -57,7 +59,10 @@ class ClientDiscoveryDatagram(Datagram):
 
 class DiscoveryResponseDatagram(Datagram):
     """Description of a discovery response datagram."""
+
     def __init__(self, hostname, port):
+        """Initialize class."""
+        # pylint: disable=unused-argument
         hostname = hostname[:16].encode("UTF-8")
         hostname += (16 - len(hostname)) * "\x00"
         self.packet = struct.pack("!c16s", "D", hostname).decode()
@@ -65,16 +70,18 @@ class DiscoveryResponseDatagram(Datagram):
 
 class TLVDiscoveryRequestDatagram(Datagram):
     """Description of a discovery request datagram."""
+
     def __init__(self, data):
+        """Initialize class."""
         requestdata = OrderedDict()
         assert data[0] == "e"
         idx = 1
         length = len(data) - 5
         while idx <= length:
-            typ, l = struct.unpack_from("4sB", data.encode(), idx)
-            if l:
-                val = data[idx + 5 : idx + 5 + l]
-                idx += 5 + l
+            typ, _len = struct.unpack_from("4sB", data.encode(), idx)
+            if _len:
+                val = data[idx + 5 : idx + 5 + _len]
+                idx += 5 + _len
             else:
                 val = None
                 idx += 5
@@ -83,12 +90,15 @@ class TLVDiscoveryRequestDatagram(Datagram):
         self.data = requestdata
 
     def __repr__(self):
+        """Pretty print class."""
         return "<%s data=%r>" % (self.__class__.__name__, self.data.items())
 
 
 class TLVDiscoveryResponseDatagram(Datagram):
     """Description of a TLV discovery response datagram."""
+
     def __init__(self, responsedata):
+        """Initialize class."""
         parts = ["E"]  # new discovery format
         for typ, value in responsedata.items():
             if value is None:
@@ -102,10 +112,14 @@ class TLVDiscoveryResponseDatagram(Datagram):
 
 class DiscoveryProtocol:
     """Description of a discovery protocol."""
+
     def __init__(self, web_port):
+        """Initialze class."""
         self.web_port = web_port
+        self.transport = None
 
     def connection_made(self, transport):
+        """Call on connection."""
         self.transport = transport
         # Allow receiving multicast broadcasts
         sock = self.transport.get_extra_info("socket")
@@ -114,12 +128,16 @@ class DiscoveryProtocol:
         sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
 
     def error_received(self, exc):
+        """Call on Error."""
         LOGGER.error(exc)
 
     def connection_lost(self, *args, **kwargs):
+        """Call on Connection lost."""
+        # pylint: disable=unused-argument
         LOGGER.debug("Connection lost to discovery")
 
-    def build_TLV_response(self, requestdata):
+    def build_tlv_response(self, requestdata):
+        """Build TLV Response message."""
         responsedata = OrderedDict()
         for typ, value in requestdata.items():
             if typ == "NAME":
@@ -150,21 +168,25 @@ class DiscoveryProtocol:
         return responsedata
 
     def datagram_received(self, data, addr):
+        """Datagram received callback."""
+        # pylint: disable=broad-except
         try:
             data = data.decode()
             dgram = Datagram.decode(data)
             if isinstance(dgram, ClientDiscoveryDatagram):
-                self.sendDiscoveryResponse(addr)
+                self.send_discovery_response(addr)
             elif isinstance(dgram, TLVDiscoveryRequestDatagram):
-                resonsedata = self.build_TLV_response(dgram.data)
-                self.sendTLVDiscoveryResponse(resonsedata, addr)
+                resonsedata = self.build_tlv_response(dgram.data)
+                self.send_tlv_discovery_response(resonsedata, addr)
         except Exception as exc:
             LOGGER.exception(exc)
 
-    def sendDiscoveryResponse(self, addr):
+    def send_discovery_response(self, addr):
+        """Send discovery response message."""
         dgram = DiscoveryResponseDatagram(get_hostname(), 3483)
         self.transport.sendto(dgram.packet.encode(), addr)
 
-    def sendTLVDiscoveryResponse(self, resonsedata, addr):
+    def send_tlv_discovery_response(self, resonsedata, addr):
+        """Send TLV discovery response message."""
         dgram = TLVDiscoveryResponseDatagram(resonsedata)
         self.transport.sendto(dgram.packet.encode(), addr)
index a136c6fe4429d6e488b134bca668e8f3196e88e4..9c1930ab30e7df5ebfa2d31920970905013ed729 100644 (file)
@@ -48,7 +48,7 @@ class Event(Enum):
 
 
 class SqueezeSocketClient:
-    """Squeezebox socket client"""
+    """Squeezebox socket client."""
 
     def __init__(
         self,
@@ -87,7 +87,7 @@ class SqueezeSocketClient:
 
     @property
     def player_id(self) -> str:
-        """Return player_id (=mac address) of the player."""
+        """Return player id (=mac address) of the player."""
         return self._player_id
 
     @property
@@ -97,7 +97,7 @@ class SqueezeSocketClient:
 
     @property
     def device_address(self) -> str:
-        """Return device IP address of the player"""
+        """Return device IP address of the player."""
         dev_address = self._writer.get_extra_info("peername")
         return dev_address[0] if dev_address else ""
 
@@ -174,7 +174,8 @@ class SqueezeSocketClient:
         old_gain = self._volume_control.old_gain()
         new_gain = self._volume_control.new_gain()
         await self.__async_send_frame(
-            b"audg", struct.pack("!LLBBLL", old_gain, old_gain, 1, 255, new_gain, new_gain)
+            b"audg",
+            struct.pack("!LLBBLL", old_gain, old_gain, 1, 255, new_gain, new_gain),
         )
         self._volume_level = volume_level
 
@@ -195,9 +196,8 @@ class SqueezeSocketClient:
         self._powered = True
         enable_crossfade = crossfade_duration > 0
         command = b"s"
-        autostart = (
-            b"3"  # we use direct stream for now so let the player do the messy work with buffers
-        )
+        # we use direct stream for now so let the player do the messy work with buffers
+        autostart = b"3"
         trans_type = b"1" if enable_crossfade else b"0"
         formatbyte = b"f"  # fixed to flac
         uri = "/stream" + uri.split("/stream")[1]
@@ -206,8 +206,8 @@ class SqueezeSocketClient:
             autostart=autostart,
             flags=0x00,
             formatbyte=formatbyte,
-            transType=trans_type,
-            transDuration=crossfade_duration,
+            trans_type=trans_type,
+            trans_duration=crossfade_duration,
         )
         # extract host and port from uri
         regex = "(?:http.*://)?(?P<host>[^:/ ]+).?(?P<port>[0-9]*).*"
@@ -227,7 +227,7 @@ class SqueezeSocketClient:
     async def __async_send_heartbeat(self):
         """Send periodic heartbeat message to player."""
         timestamp = int(time.time())
-        data = self.__pack_stream(b"t", replayGain=timestamp, flags=0)
+        data = self.__pack_stream(b"t", replay_gain=timestamp, flags=0)
         await self.__async_send_frame(b"strm", data)
 
     async def __async_send_frame(self, command, data):
@@ -273,13 +273,13 @@ class SqueezeSocketClient:
         pcmargs=(b"?", b"?", b"?", b"?"),
         threshold=200,
         spdif=b"0",
-        transDuration=0,
-        transType=b"0",
+        trans_duration=0,
+        trans_type=b"0",
         flags=0x40,
-        outputThreshold=0,
-        replayGain=0,
-        serverPort=8095,
-        serverIp=0,
+        output_threshold=0,
+        replay_gain=0,
+        server_port=8095,
+        server_ip=0,
     ):
         """Create stream request message based on given arguments."""
         return struct.pack(
@@ -290,14 +290,14 @@ class SqueezeSocketClient:
             *pcmargs,
             threshold,
             spdif,
-            transDuration,
-            transType,
+            trans_duration,
+            trans_type,
             flags,
-            outputThreshold,
+            output_threshold,
             0,
-            replayGain,
-            serverPort,
-            serverIp,
+            replay_gain,
+            server_port,
+            server_ip,
         )
 
     @callback
@@ -347,30 +347,33 @@ class SqueezeSocketClient:
     @callback
     def _process_stat_stmd(self, data):
         """Process incoming stat STMd message (decoder ready)."""
-        #pylint: disable=unused-argument
+        # pylint: disable=unused-argument
         LOGGER.debug("STMu received - Decoder Ready for next track.")
         asyncio.create_task(self._event_callback(Event.EVENT_DECODER_READY, self))
 
     @callback
     def _process_stat_stmf(self, data):
         """Process incoming stat STMf message (connection closed)."""
-        #pylint: disable=unused-argument
+        # pylint: disable=unused-argument
         LOGGER.debug("STMf received - connection closed.")
         self._state = State.Stopped
         asyncio.create_task(self._event_callback(Event.EVENT_UPDATED, self))
 
     @callback
     def _process_stat_stmo(self, data):
-        """Process incoming stat STMo message:
-        No more decoded (uncompressed) data to play; triggers rebuffering."""
-        #pylint: disable=unused-argument
+        """
+        Process incoming stat STMo message.
+
+        No more decoded (uncompressed) data to play; triggers rebuffering.
+        """
+        # pylint: disable=unused-argument
         LOGGER.debug("STMo received - output underrun.")
         LOGGER.debug("Output Underrun")
 
     @callback
     def _process_stat_stmp(self, data):
         """Process incoming stat STMp message: Pause confirmed."""
-        #pylint: disable=unused-argument
+        # pylint: disable=unused-argument
         LOGGER.debug("STMp received - pause confirmed.")
         self._state = State.Paused
         asyncio.create_task(self._event_callback(Event.EVENT_UPDATED, self))
@@ -378,22 +381,22 @@ class SqueezeSocketClient:
     @callback
     def _process_stat_stmr(self, data):
         """Process incoming stat STMr message: Resume confirmed."""
-        #pylint: disable=unused-argument
+        # pylint: disable=unused-argument
         LOGGER.debug("STMr received - resume confirmed.")
         self._state = State.Playing
         asyncio.create_task(self._event_callback(Event.EVENT_UPDATED, self))
 
     @callback
     def _process_stat_stms(self, data):
+        # pylint: disable=unused-argument
         """Process incoming stat STMs message: Playback of new track has started."""
         LOGGER.debug("STMs received - playback of new track has started.")
-        #pylint: disable=unused-argument
         self._state = State.Playing
         asyncio.create_task(self._event_callback(Event.EVENT_UPDATED, self))
 
     @callback
     def _process_stat_stmt(self, data):
-        """Process incoming stat STMt message: heartbeat from client"""
+        """Process incoming stat STMt message: heartbeat from client."""
         # pylint: disable=unused-variable
         timestamp = time.time()
         self._last_heartbeat = timestamp
@@ -422,7 +425,7 @@ class SqueezeSocketClient:
     @callback
     def _process_stat_stmu(self, data):
         """Process incoming stat STMu message: Buffer underrun: Normal end of playback."""
-        #pylint: disable=unused-argument
+        # pylint: disable=unused-argument
         LOGGER.debug("STMu received - end of playback.")
         self.state = State.Stopped
         asyncio.create_task(self._event_callback(Event.EVENT_UPDATED, self))
@@ -430,7 +433,7 @@ class SqueezeSocketClient:
     @callback
     def _process_resp(self, data):
         """Process incoming RESP message: Response received at player."""
-        #pylint: disable=unused-argument
+        # pylint: disable=unused-argument
         # send continue
         asyncio.create_task(self.__async_send_frame(b"cont", b"0"))
 
@@ -446,9 +449,7 @@ class SqueezeSocketClient:
 
 
 class PySqueezeVolume(object):
-
-    """Represents a sound volume. This is an awful lot more complex than it
-    sounds."""
+    """Represents a sound volume. This is an awful lot more complex than it sounds."""
 
     minimum = 0
     maximum = 100
@@ -562,30 +563,36 @@ class PySqueezeVolume(object):
 
     # new gain parameters, from the same place
     total_volume_range = -50  # dB
-    step_point = -1  # Number of steps, up from the bottom, where a 2nd volume ramp kicks in.
-    step_fraction = 1  # fraction of totalVolumeRange where alternate volume ramp kicks in.
+    step_point = (
+        -1
+    )  # Number of steps, up from the bottom, where a 2nd volume ramp kicks in.
+    step_fraction = (
+        1  # fraction of totalVolumeRange where alternate volume ramp kicks in.
+    )
 
     def __init__(self):
+        """Initialize class."""
         self.volume = 50
 
     def increment(self):
-        """Increment the volume"""
+        """Increment the volume."""
         self.volume += self.step
         if self.volume > self.maximum:
             self.volume = self.maximum
 
     def decrement(self):
-        """Decrement the volume"""
+        """Decrement the volume."""
         self.volume -= self.step
         if self.volume < self.minimum:
             self.volume = self.minimum
 
     def old_gain(self):
-        """Return the "Old" gain value as required by the squeezebox"""
+        """Return the "Old" gain value as required by the squeezebox."""
         return self.old_map[self.volume]
 
     def decibels(self):
         """Return the "new" gain value."""
+        # pylint: disable=invalid-name
 
         step_db = self.total_volume_range * self.step_fraction
         max_volume_db = 0  # different on the boom?
@@ -609,10 +616,10 @@ class PySqueezeVolume(object):
         return m * (x2 - x1) + y1
 
     def new_gain(self):
-        db = self.decibels()
-        floatmult = 10 ** (db / 20.0)
+        """Return new gainvalue of the volume control."""
+        decibel = self.decibels()
+        floatmult = 10 ** (decibel / 20.0)
         # avoid rounding errors somehow
-        if -30 <= db <= 0:
+        if -30 <= decibel <= 0:
             return int(floatmult * (1 << 8) + 0.5) * (1 << 8)
-        else:
-            return int((floatmult * (1 << 16)) + 0.5)
+        return int((floatmult * (1 << 16)) + 0.5)
index fb730b85fb1594a87a573081dcbcc4ccd78973a8..821f7a739c64d7f20fec0e83675a2d97693032b9 100644 (file)
@@ -1,34 +1,20 @@
-"""Tunein musicprovider support for MusicAssistant."""
-import datetime
-import hashlib
+"""Tune-In musicprovider support for MusicAssistant."""
 import logging
-import time
 from typing import List, Optional
 
 import aiohttp
 from asyncio_throttle import Throttler
-from music_assistant.constants import (
-    CONF_PASSWORD,
-    CONF_USERNAME,
-    EVENT_PLAYBACK_STOPPED,
-    EVENT_STREAM_STARTED,
-)
+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 (
-    Album,
-    AlbumType,
-    Artist,
     MediaItemProviderId,
     MediaType,
-    Playlist,
     Radio,
     SearchResult,
-    Track,
     TrackQuality,
 )
 from music_assistant.models.musicprovider import MusicProvider
 from music_assistant.models.streamdetails import ContentType, StreamDetails, StreamType
-from music_assistant.utils import parse_title_and_version, try_parse_int
 
 PROV_ID = "tunein"
 PROV_NAME = "TuneIn Radio"
@@ -36,10 +22,14 @@ LOGGER = logging.getLogger(PROV_ID)
 
 CONFIG_ENTRIES = [
     ConfigEntry(
-        entry_key=CONF_USERNAME, entry_type=ConfigEntryType.STRING, description_key=CONF_USERNAME
+        entry_key=CONF_USERNAME,
+        entry_type=ConfigEntryType.STRING,
+        description_key=CONF_USERNAME,
     ),
     ConfigEntry(
-        entry_key=CONF_PASSWORD, entry_type=ConfigEntryType.PASSWORD, description_key=CONF_PASSWORD
+        entry_key=CONF_PASSWORD,
+        entry_type=ConfigEntryType.PASSWORD,
+        description_key=CONF_PASSWORD,
     ),
 ]
 
@@ -51,6 +41,9 @@ async def async_setup(mass):
 
 
 class TuneInProvider(MusicProvider):
+    """Provider implementation for Tune In."""
+
+    # pylint: disable=abstract-method
 
     _username = None
     _password = None
@@ -78,7 +71,7 @@ class TuneInProvider(MusicProvider):
         return [MediaType.Radio]
 
     async def async_on_start(self) -> bool:
-        """Called on startup. Handle initialization of the provider based on config."""
+        """Handle initialization of the provider based on config."""
         # pylint: disable=attribute-defined-outside-init
         self._http_session = aiohttp.ClientSession(
             loop=self.mass.loop, connector=aiohttp.TCPConnector()
@@ -92,7 +85,7 @@ class TuneInProvider(MusicProvider):
         self._throttler = Throttler(rate_limit=1, period=1)
 
     async def async_on_stop(self):
-        """Called on shutdown. Handle correct close/cleanup of the provider on exit."""
+        """Handle correct close/cleanup of the provider on exit."""
         if self._http_session:
             await self._http_session.close()
 
@@ -101,6 +94,7 @@ class TuneInProvider(MusicProvider):
     ) -> SearchResult:
         """
         Perform search on musicprovider.
+
             :param search_query: Search query.
             :param media_types: A list of media_types to include. All types if None.
             :param limit: Number of items to return in the search (per type).
@@ -120,10 +114,10 @@ class TuneInProvider(MusicProvider):
                     radio = await self.__async_parse_radio(item)
                     yield radio
 
-    async def async_get_radio(self, radio_id: str) -> Radio:
+    async def async_get_radio(self, prov_radio_id: str) -> Radio:
         """Get radio station details."""
         radio = None
-        params = {"c": "composite", "detail": "listing", "id": radio_id}
+        params = {"c": "composite", "detail": "listing", "id": prov_radio_id}
         result = await self.__async_get_data("Describe.ashx", params)
         if result and result.get("body") and result["body"][0].get("children"):
             item = result["body"][0]["children"][0]
@@ -131,7 +125,7 @@ class TuneInProvider(MusicProvider):
         return radio
 
     async def __async_parse_radio(self, details: dict) -> Radio:
-        """parse Radio object from json obj returned from api"""
+        """Parse Radio object from json obj returned from api."""
         radio = Radio(item_id=details["preset_id"], provider=PROV_ID)
         if "name" in details:
             radio.name = details["name"]
@@ -187,22 +181,26 @@ class TuneInProvider(MusicProvider):
                     item_id=item_id,
                     provider=PROV_ID,
                     path=stream["url"],
-                    content_type=stream["media_type"],
+                    content_type=ContentType(stream["media_type"]),
                     sample_rate=44100,
                     bit_depth=16,
                     details=stream,
                 )
         return None
 
-    async def __async_get_data(self, endpoint, params={}):
-        """get data from api"""
+    async def __async_get_data(self, endpoint, params=None):
+        """Get data from api."""
+        if not params:
+            params = {}
         url = "https://opml.radiotime.com/%s" % endpoint
         params["render"] = "json"
         params["formats"] = "ogg,aac,wma,mp3"
         params["username"] = self._username
         params["partnerId"] = "1"
         async with self._throttler:
-            async with self._http_session.get(url, params=params, verify_ssl=False) as response:
+            async with self._http_session.get(
+                url, params=params, verify_ssl=False
+            ) as response:
                 result = await response.json()
                 if not result or "error" in result:
                     LOGGER.error(url)
index 40c095e297233128aa6f79b9dc10a1abd88e6814..fcacca2828520b2147e8e70d5d49fec76c707c8a 100644 (file)
@@ -1,8 +1,6 @@
-#!/usr/bin/env python3
-# -*- coding:utf-8 -*-
-
+# pylint: skip-file
+# flake8: noqa
 import asyncio
-from collections import OrderedDict
 import decimal
 import os
 import random
@@ -10,6 +8,7 @@ import socket
 import struct
 import sys
 import time
+from collections import OrderedDict
 from typing import List
 
 from music_assistant.constants import CONF_ENABLED
index 49df66168965496be402a7aeb0e308a87f01410b..0117a161137f4a77642e2045f9d22575d585b45a 100755 (executable)
@@ -11,7 +11,6 @@ import tempfile
 import urllib.request
 from datetime import datetime
 from enum import Enum
-from types import FunctionType, MethodType
 from typing import Any, Callable, TypeVar
 
 import memory_tempfile
@@ -43,6 +42,8 @@ def is_callback(func: Callable[..., Any]) -> bool:
 
 
 def run_periodic(period):
+    """Run a coroutine at interval."""
+
     def scheduler(fcn):
         async def async_wrapper(*args, **kwargs):
             while True:
@@ -56,25 +57,26 @@ def run_periodic(period):
 
 def get_external_ip():
     """Try to get the external (WAN) IP address."""
+    # pylint: disable=broad-except
     try:
         return urllib.request.urlopen("https://ident.me").read().decode("utf8")
-    except:
+    except Exception:
         return None
 
 
 def filename_from_string(string):
-    """create filename from unsafe string"""
+    """Create filename from unsafe string."""
     keepcharacters = (" ", ".", "_")
     return "".join(c for c in string if c.isalnum() or c in keepcharacters).rstrip()
 
 
 def run_background_task(corofn, *args, executor=None):
-    """run non-async task in background"""
+    """Run non-async task in background."""
     return asyncio.get_event_loop().run_in_executor(executor, corofn, *args)
 
 
 def run_async_background_task(executor, corofn, *args):
-    """run async task in background"""
+    """Run async task in background."""
 
     def run_task(corofn, *args):
         new_loop = asyncio.new_event_loop()
@@ -88,7 +90,7 @@ def run_async_background_task(executor, corofn, *args):
 
 
 def get_sort_name(name):
-    """create a sort name for an artist/title"""
+    """Create a sort name for an artist/title."""
     sort_name = name
     for item in ["The ", "De ", "de ", "Les "]:
         if name.startswith(item):
@@ -97,6 +99,7 @@ def get_sort_name(name):
 
 
 def try_parse_int(possible_int):
+    """Try to parse an int."""
     try:
         return int(possible_int)
     except (TypeError, ValueError):
@@ -104,7 +107,7 @@ def try_parse_int(possible_int):
 
 
 async def async_iter_items(items):
-    """fake async iterator for compatability reasons."""
+    """Fake async iterator for compatability reasons."""
     if not isinstance(items, list):
         yield items
     else:
@@ -113,6 +116,7 @@ async def async_iter_items(items):
 
 
 def try_parse_float(possible_float):
+    """Try to parse a float."""
     try:
         return float(possible_float)
     except (TypeError, ValueError):
@@ -120,6 +124,7 @@ def try_parse_float(possible_float):
 
 
 def try_parse_bool(possible_bool):
+    """Try to parse a bool."""
     if isinstance(possible_bool, bool):
         return possible_bool
     else:
@@ -127,7 +132,7 @@ def try_parse_bool(possible_bool):
 
 
 def parse_title_and_version(track_title, track_version=None):
-    """try to parse clean track title and version from the title"""
+    """Try to parse clean track title and version from the title."""
     title = track_title.lower()
     version = ""
     for splitter in [" (", " [", " - ", " (", " [", "-"]:
@@ -174,7 +179,7 @@ def parse_title_and_version(track_title, track_version=None):
 
 
 def get_version_substitute(version_str):
-    """transform provider version str to universal version type"""
+    """Transform provider version str to universal version type."""
     version_str = version_str.lower()
     # substitute edit and edition with version
     if "edition" in version_str or "edit" in version_str:
@@ -193,22 +198,23 @@ def get_version_substitute(version_str):
     return version_str.strip()
 
 
-# pylint: disable=broad-except
 def get_ip():
-    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+    """Get primary IP-address for this host."""
+    # pylint: disable=broad-except
+    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
     try:
         # doesn't even have to be reachable
-        s.connect(("10.255.255.255", 1))
-        IP = s.getsockname()[0]
+        sock.connect(("10.255.255.255", 1))
+        _ip = sock.getsockname()[0]
     except Exception:
-        IP = "127.0.0.1"
+        _ip = "127.0.0.1"
     finally:
-        s.close()
-    return IP
+        sock.close()
+    return _ip
 
 
 def get_ip_pton():
-    """Return socket pton for local ip"""
+    """Return socket pton for local ip."""
     try:
         return socket.inet_pton(socket.AF_INET, get_ip())
     except OSError:
@@ -224,20 +230,24 @@ def get_hostname():
 
 
 def get_folder_size(folderpath):
-    """get folder size in gb"""
+    """Return folder size in gb."""
     total_size = 0
     # pylint: disable=unused-variable
     for dirpath, dirnames, filenames in os.walk(folderpath):
-        for f in filenames:
-            fp = os.path.join(dirpath, f)
-            total_size += os.path.getsize(fp)
+        for _file in filenames:
+            _fp = os.path.join(dirpath, _file)
+            total_size += os.path.getsize(_fp)
     # pylint: enable=unused-variable
     total_size_gb = total_size / float(1 << 30)
     return total_size_gb
 
 
 class EnhancedJSONEncoder(json.JSONEncoder):
+    """Custom JSON decoder."""
+
     def default(self, obj):
+        """Return default handler."""
+        # pylint: disable=method-hidden
         if dataclasses.is_dataclass(obj):
             return dataclasses.asdict(obj)
         if isinstance(obj, Enum):
@@ -253,66 +263,41 @@ class EnhancedJSONEncoder(json.JSONEncoder):
         return super().default(obj)
 
 
+# pylint: disable=invalid-name
 json_serializer = functools.partial(json.dumps, cls=EnhancedJSONEncoder)
-
-# def json_serializer(obj):
-#     """Recursively create serializable values for (custom) data types."""
-
-#     def get_val(val):
-#         if isinstance(val, (int, str, bool, float, tuple)):
-#             return val
-#         elif isinstance(val, list):
-#             new_list = []
-#             for item in val:
-#                 new_list.append(get_val(item))
-#             return new_list
-#         elif hasattr(val, "to_dict"):
-#             return get_val(val.to_dict())
-#         elif isinstance(val, dict):
-#             new_dict = {}
-#             for key, value in val.items():
-#                 new_dict[key] = get_val(value)
-#             return new_dict
-#         elif hasattr(val, "__dict__"):
-#             new_dict = {}
-#             for key, value in val.__dict__.items():
-#                 new_dict[key] = get_val(value)
-#             return new_dict
-
-#     return get_val(obj)
+# pylint: enable=invalid-name
 
 
 def get_compare_string(input_str):
-    """get clean lowered string for compare actions"""
+    """Return clean lowered string for compare actions."""
     unaccented_string = unidecode.unidecode(input_str)
     return re.sub(r"[^a-zA-Z0-9]", "", unaccented_string).lower()
 
 
 def compare_strings(str1, str2, strict=False):
-    """compare strings and return True if we have an (almost) perfect match"""
+    """Compare strings and return True if we have an (almost) perfect match."""
     match = str1.lower() == str2.lower()
     if not match and not strict:
         match = get_compare_string(str1) == get_compare_string(str2)
     return match
 
 
-# def json_serializer(obj):
-#     """json serializer to recursively create serializable values for custom data types"""
-#     return json.dumps(json_serializer(obj), skipkeys=True)
-
-
 def try_load_json_file(jsonfile):
-    """try to load json from file"""
+    """Try to load json from file."""
     try:
-        with open(jsonfile) as f:
-            return json.loads(f.read())
+        with open(jsonfile) as _file:
+            return json.loads(_file.read())
     except (FileNotFoundError, json.JSONDecodeError) as exc:
-        logging.getLogger().debug("Could not load json from file %s", jsonfile, exc_info=exc)
+        logging.getLogger().debug(
+            "Could not load json from file %s", jsonfile, exc_info=exc
+        )
         return None
 
 
 def create_tempfile():
     """Return a (named) temporary file."""
     if platform.system() == "Linux":
-        return memory_tempfile.MemoryTempfile(fallback=True).NamedTemporaryFile(buffering=0)
+        return memory_tempfile.MemoryTempfile(fallback=True).NamedTemporaryFile(
+            buffering=0
+        )
     return tempfile.NamedTemporaryFile(buffering=0)
index a7237eda95263dcb53f0710fee4fe6eed2071a52..d7706fec18ee8c6e17e0676b8474efa920ed35e7 100755 (executable)
@@ -1,17 +1,19 @@
-"""The web module handles serving the frontend and the rest/websocket api's"""
+"""The web module handles serving the frontend and the rest/websocket api's."""
 import asyncio
-import base64
 import datetime
+import functools
 import inspect
 import ipaddress
-import functools
 import json
 import logging
 import os
 import ssl
 
 import aiohttp
+import aiohttp_cors
+import jwt
 from aiohttp import web
+from aiohttp_jwt import JWTMiddleware, login_required
 from music_assistant.constants import (
     CONF_KEY_BASE,
     CONF_KEY_PLAYERSETTINGS,
@@ -20,17 +22,12 @@ from music_assistant.constants import (
 from music_assistant.models.media_types import MediaType, media_type_from_string
 from music_assistant.utils import (
     EnhancedJSONEncoder,
-    get_ip,
-    json_serializer,
     get_external_ip,
     get_hostname,
+    get_ip,
+    json_serializer,
 )
 
-import aiohttp_cors
-import jwt
-from aiohttp_jwt import JWTMiddleware, login_required, check_permissions, match_any
-
-
 LOGGER = logging.getLogger("mass")
 
 
@@ -38,9 +35,12 @@ class ClassRouteTableDef(web.RouteTableDef):
     """Helper class to add class based routing tables."""
 
     def __repr__(self) -> str:
+        """Print the class contents."""
         return "<ClassRouteTableDef count={}>".format(len(self._items))
 
     def route(self, method: str, path: str, **kwargs):
+        """Return the route."""
+        # pylint: disable=missing-function-docstring
         def inner(handler):
             handler.route_info = (method, path, kwargs)
             return handler
@@ -48,19 +48,25 @@ class ClassRouteTableDef(web.RouteTableDef):
         return inner
 
     def add_class_routes(self, instance) -> None:
+        """Add class routes."""
+        # pylint: disable=missing-function-docstring
         def predicate(member) -> bool:
-            return all((inspect.iscoroutinefunction(member), hasattr(member, "route_info")))
+            return all(
+                (inspect.iscoroutinefunction(member), hasattr(member, "route_info"))
+            )
 
         for _, handler in inspect.getmembers(instance, predicate):
             method, path, kwargs = handler.route_info
             super().route(method, path, **kwargs)(handler)
 
 
+# pylint: disable=invalid-name
 routes = ClassRouteTableDef()
+# pylint: enable=invalid-name
 
 
 def require_local_subnet(func):
-    """Decorator to specify web method as available locally only."""
+    """Return decorator to specify web method as available locally only."""
 
     @functools.wraps(func)
     async def wrapped(*args, **kwargs):
@@ -83,9 +89,10 @@ def require_local_subnet(func):
 
 
 class Web:
-    """webserver and json/websocket api"""
+    """Webserver and json/websocket api."""
 
     def __init__(self, mass):
+        """Initialize class."""
         self.mass = mass
         # load/create/update config
         self._local_ip = get_ip()
@@ -93,19 +100,25 @@ class Web:
         self.runner = None
 
         enable_ssl = self.config["ssl_certificate"] and self.config["ssl_key"]
-        if self.config["ssl_certificate"] and not os.path.isfile(self.config["ssl_certificate"]):
+        if self.config["ssl_certificate"] and not os.path.isfile(
+            self.config["ssl_certificate"]
+        ):
             enable_ssl = False
-            LOGGER.warning("SSL certificate file not found: %s", self.config["ssl_certificate"])
+            LOGGER.warning(
+                "SSL certificate file not found: %s", self.config["ssl_certificate"]
+            )
         if self.config["ssl_key"] and not os.path.isfile(self.config["ssl_key"]):
             enable_ssl = False
-            LOGGER.warning("SSL certificate key file not found: %s", self.config["ssl_key"])
+            LOGGER.warning(
+                "SSL certificate key file not found: %s", self.config["ssl_key"]
+            )
         if not self.config.get("external_url"):
             enable_ssl = False
         self._enable_ssl = enable_ssl
         self._jwt_shared_secret = f"mass_{self._local_ip}_{self.http_port}"
 
     async def async_setup(self):
-        """perform async setup"""
+        """Perform async setup."""
         routes.add_class_routes(self)
         jwt_middleware = JWTMiddleware(
             self._jwt_shared_secret, request_property="user", credentials_required=False
@@ -156,12 +169,11 @@ class Web:
         LOGGER.info("Started HTTP webserver on port %s", self.http_port)
         if self._enable_ssl:
             ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
-            ssl_context.load_cert_chain(self.config["ssl_certificate"], self.config["ssl_key"])
+            ssl_context.load_cert_chain(
+                self.config["ssl_certificate"], self.config["ssl_key"]
+            )
             https_site = web.TCPSite(
-                self.runner,
-                "0.0.0.0",
-                self.https_port,
-                ssl_context=ssl_context,
+                self.runner, "0.0.0.0", self.https_port, ssl_context=ssl_context
             )
             await https_site.start()
             LOGGER.info(
@@ -209,7 +221,7 @@ class Web:
 
     @routes.post("/login")
     async def async_login(self, request):
-        """Handler to retrieve a JWT token."""
+        """Handle the retrieval of a JWT token."""
         form = await request.json()
         username = form.get("username")
         password = form.get("password")
@@ -220,14 +232,16 @@ class Web:
 
     @routes.get("/info")
     async def async_info(self, request):
+        # pylint: disable=unused-argument
         """Return (discovery) info about this instance."""
         return web.json_response(self.discovery_info, dumps=json_serializer)
 
     async def async_index(self, request):
+        """Get the index page, redirect if we do not have a web directory."""
         # pylint: disable=unused-argument
         webdir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "web/")
         if not os.path.isdir(webdir):
-            raise web.HTTPFound('https://music-assistant.github.io/app')
+            raise web.HTTPFound("https://music-assistant.github.io/app")
         return web.FileResponse(os.path.join(webdir, "index.html"))
 
     @login_required
@@ -288,7 +302,7 @@ class Web:
     @login_required
     @routes.put("/api/library")
     async def async_library_add(self, request):
-        """Add item(s) to the library"""
+        """Add item(s) to the library."""
         body = await request.json()
         media_items = await self.__async_media_items_from_body(body)
         result = await self.mass.music_manager.async_library_add(media_items)
@@ -297,7 +311,7 @@ class Web:
     @login_required
     @routes.delete("/api/library")
     async def async_library_remove(self, request):
-        """R remove item(s) from the library"""
+        """Remove item(s) from the library."""
         body = await request.json()
         media_items = await self.__async_media_items_from_body(body)
         result = await self.mass.music_manager.async_library_remove(media_items)
@@ -306,31 +320,35 @@ class Web:
     @login_required
     @routes.get("/api/artists/{item_id}")
     async def async_artist(self, request):
-        """get full artist details"""
+        """Get full artist details."""
         item_id = request.match_info.get("item_id")
         provider = request.rel_url.query.get("provider")
         lazy = request.rel_url.query.get("lazy", "false") != "false"
         if item_id is None or provider is None:
             return web.Response(text="invalid item or provider", status=501)
-        result = await self.mass.music_manager.async_get_artist(item_id, provider, lazy=lazy)
+        result = await self.mass.music_manager.async_get_artist(
+            item_id, provider, lazy=lazy
+        )
         return web.json_response(result, dumps=json_serializer)
 
     @login_required
     @routes.get("/api/albums/{item_id}")
     async def async_album(self, request):
-        """get full album details"""
+        """Get full album details."""
         item_id = request.match_info.get("item_id")
         provider = request.rel_url.query.get("provider")
         lazy = request.rel_url.query.get("lazy", "false") != "false"
         if item_id is None or provider is None:
             return web.Response(text="invalid item or provider", status=501)
-        result = await self.mass.music_manager.async_get_album(item_id, provider, lazy=lazy)
+        result = await self.mass.music_manager.async_get_album(
+            item_id, provider, lazy=lazy
+        )
         return web.json_response(result, dumps=json_serializer)
 
     @login_required
     @routes.get("/api/tracks/{item_id}")
     async def async_track(self, request):
-        """get full track details"""
+        """Get full track details."""
         item_id = request.match_info.get("item_id")
         provider = request.rel_url.query.get("provider")
         lazy = request.rel_url.query.get("lazy", "false") != "false"
@@ -344,7 +362,7 @@ class Web:
     @login_required
     @routes.get("/api/playlists/{item_id}")
     async def async_playlist(self, request):
-        """get full playlist details"""
+        """Get full playlist details."""
         item_id = request.match_info.get("item_id")
         provider = request.rel_url.query.get("provider")
         if item_id is None or provider is None:
@@ -355,7 +373,7 @@ class Web:
     @login_required
     @routes.get("/api/radios/{item_id}")
     async def async_radio(self, request):
-        """get full radio details"""
+        """Get full radio details."""
         item_id = request.match_info.get("item_id")
         provider = request.rel_url.query.get("provider")
         if item_id is None or provider is None:
@@ -365,7 +383,7 @@ class Web:
 
     @routes.get("/api/{media_type}/{media_id}/thumb")
     async def async_get_image(self, request):
-        """get (resized) thumb image"""
+        """Get (resized) thumb image."""
         media_type_str = request.match_info.get("media_type")
         media_type = media_type_from_string(media_type_str)
         media_id = request.match_info.get("media_id")
@@ -384,7 +402,7 @@ class Web:
     @login_required
     @routes.get("/api/artists/{item_id}/toptracks")
     async def async_artist_toptracks(self, request):
-        """get top tracks for given artist"""
+        """Get top tracks for given artist."""
         item_id = request.match_info.get("item_id")
         provider = request.rel_url.query.get("provider")
         if item_id is None or provider is None:
@@ -395,7 +413,7 @@ class Web:
     @login_required
     @routes.get("/api/artists/{item_id}/albums")
     async def async_artist_albums(self, request):
-        """get (all) albums for given artist"""
+        """Get (all) albums for given artist."""
         item_id = request.match_info.get("item_id")
         provider = request.rel_url.query.get("provider")
         if item_id is None or provider is None:
@@ -406,7 +424,7 @@ class Web:
     @login_required
     @routes.get("/api/playlists/{item_id}/tracks")
     async def async_playlist_tracks(self, request):
-        """get playlist tracks from provider"""
+        """Get playlist tracks from provider."""
         item_id = request.match_info.get("item_id")
         provider = request.rel_url.query.get("provider")
         if item_id is None or provider is None:
@@ -421,7 +439,9 @@ class Web:
         item_id = request.match_info.get("item_id")
         body = await request.json()
         tracks = await self.__async_media_items_from_body(body)
-        result = await self.mass.music_manager.async_add_playlist_tracks(item_id, tracks)
+        result = await self.mass.music_manager.async_add_playlist_tracks(
+            item_id, tracks
+        )
         return web.json_response(result, dumps=json_serializer)
 
     @login_required
@@ -431,7 +451,9 @@ class Web:
         item_id = request.match_info.get("item_id")
         body = await request.json()
         tracks = await self.__async_media_items_from_body(body)
-        result = await self.mass.music_manager.async_remove_playlist_tracks(item_id, tracks)
+        result = await self.mass.music_manager.async_remove_playlist_tracks(
+            item_id, tracks
+        )
         return web.json_response(result, dumps=json_serializer)
 
     @login_required
@@ -501,7 +523,7 @@ class Web:
     @login_required
     @routes.post("/api/players/{player_id}/play_media/{queue_opt}")
     async def async_player_play_media(self, request):
-        """issue player play_media command"""
+        """Issue player play media command."""
         player_id = request.match_info.get("player_id")
         player = self.mass.player_manager.get_player(player_id)
         if not player:
@@ -509,7 +531,9 @@ class Web:
         queue_opt = request.match_info.get("queue_opt", "play")
         body = await request.json()
         media_items = await self.__async_media_items_from_body(body)
-        result = await self.mass.player_manager.async_play_media(player_id, media_items, queue_opt)
+        result = await self.mass.player_manager.async_play_media(
+            player_id, media_items, queue_opt
+        )
         return web.json_response(result, dumps=json_serializer)
 
     @login_required
@@ -542,7 +566,7 @@ class Web:
     @login_required
     @routes.get("/api/players/{player_id}/queue")
     async def async_player_queue(self, request):
-        """return the player queue details"""
+        """Return the player queue details."""
         player_id = request.match_info.get("player_id")
         player_queue = self.mass.player_manager.get_player_queue(player_id)
         return web.json_response(player_queue, dumps=json_serializer)
@@ -550,7 +574,7 @@ class Web:
     @login_required
     @routes.put("/api/players/{player_id}/queue/{cmd}")
     async def async_player_queue_cmd(self, request):
-        """change the player queue details"""
+        """Change the player queue details."""
         player_id = request.match_info.get("player_id")
         player_queue = self.mass.player_manager.get_player_queue(player_id)
         cmd = request.match_info.get("cmd")
@@ -574,7 +598,7 @@ class Web:
     @login_required
     @routes.get("/api/players/{player_id}")
     async def async_player(self, request):
-        """get single player."""
+        """Get single player."""
         player_id = request.match_info.get("player_id")
         player = self.mass.player_manager.get_player(player_id)
         if not player:
@@ -585,7 +609,7 @@ class Web:
     @routes.get("/api/config")
     async def async_get_config(self, request):
         # pylint: disable=unused-argument
-        """get the config"""
+        """Get the full config."""
         conf = {
             CONF_KEY_BASE: self.mass.config.base,
             CONF_KEY_PROVIDERS: self.mass.config.providers,
@@ -596,7 +620,7 @@ class Web:
     @login_required
     @routes.get("/api/config/{base}")
     async def async_get_config_item(self, request):
-        """Get the config."""
+        """Get the config by base type."""
         conf_base = request.match_info.get("base")
         conf = self.mass.config[conf_base]
         return web.json_response(conf, dumps=json_serializer)
@@ -604,19 +628,21 @@ class Web:
     @login_required
     @routes.put("/api/config/{base}/{key}/{entry_key}")
     async def async_put_config(self, request):
-        """save (partial) config"""
+        """Save the given config item."""
         conf_key = request.match_info.get("key")
         conf_base = request.match_info.get("base")
         entry_key = request.match_info.get("entry_key")
         try:
             new_value = await request.json()
         except json.decoder.JSONDecodeError:
-            new_value = self.mass.config[conf_base][conf_key].get_entry(entry_key).default_value
+            new_value = (
+                self.mass.config[conf_base][conf_key].get_entry(entry_key).default_value
+            )
         self.mass.config[conf_base][conf_key][entry_key] = new_value
         return web.json_response(True)
 
     async def async_websocket_handler(self, request):
-        """websockets handler"""
+        """Handle websockets connection."""
         ws_response = None
         authenticated = False
         remove_callbacks = []
@@ -671,7 +697,9 @@ class Web:
                     player_id = msg_details.get("player_id")
                     cmd = msg_details.get("cmd")
                     cmd_args = msg_details.get("cmd_args")
-                    player_cmd = getattr(self.mass.player_manager, f"async_cmd_{cmd}", None)
+                    player_cmd = getattr(
+                        self.mass.player_manager, f"async_cmd_{cmd}", None
+                    )
                     if player_cmd and cmd_args is not None:
                         result = await player_cmd(player_id, cmd_args)
                     elif player_cmd:
@@ -692,7 +720,8 @@ class Web:
     @require_local_subnet
     async def async_json_rpc(self, request):
         """
-        implement LMS jsonrpc interface
+        Implement LMS jsonrpc interface.
+
         for some compatability with tools that talk to lms
         only support for basic commands
         """
@@ -748,7 +777,7 @@ class Web:
         return web.Response(text="success")
 
     async def __async_media_items_from_body(self, data):
-        """Helper to turn posted body data into media items."""
+        """Convert posted body data into media items."""
         if not isinstance(data, list):
             data = [data]
         media_items = []
@@ -760,7 +789,7 @@ class Web:
         return media_items
 
     async def __async_stream_json(self, request, iterator):
-        """stream items from async iterator as json object"""
+        """Stream items from async iterator as json object."""
         resp = web.StreamResponse(
             status=200, reason="OK", headers={"Content-Type": "application/json"}
         )
@@ -799,4 +828,4 @@ class Web:
                 "expires": token_expires,
                 "scopes": scopes,
             }
-        return None
\ No newline at end of file
+        return None
index f7bc069ceb4606c03362a83efae0fadb346b5680..c8ed92433801aef61407660a4d5b9a54bea14145 100644 (file)
--- a/setup.cfg
+++ b/setup.cfg
@@ -12,7 +12,8 @@ ignore =
     W503,
     E203,
     D202,
-    W504
+    W504,
+    E266
 
 [isort]
 multi_line_output = 3
index 39c4353b639d6b2119e97fc925f9928a66c5aade..f24185b505fd6c089076b111d4cd1627eee34652 100644 (file)
--- a/setup.py
+++ b/setup.py
@@ -25,14 +25,14 @@ DOWNLOAD_URL = f"{GITHUB_URL}/archive/{mass_const.__version__}.zip"
 PROJECT_URLS = {
     "Bug Reports": f"{GITHUB_URL}/issues",
     "Website": "https://music-assistant.github.io/",
-    "Discord": "https://discord.gg/9xHYFY"
+    "Discord": "https://discord.gg/9xHYFY",
 }
 
 PACKAGES = find_packages(exclude=["tests", "tests.*"])
 PACKAGE_FILES = []
-for (path, directories, filenames) in os.walk('music_assistant/'):
+for (path, directories, filenames) in os.walk("music_assistant/"):
     for filename in filenames:
-        PACKAGE_FILES.append(os.path.join('..', path, filename))
+        PACKAGE_FILES.append(os.path.join("..", path, filename))
 
 with open("requirements.txt") as f:
     REQUIRES = f.read().splitlines()
@@ -53,8 +53,11 @@ setup(
     install_requires=REQUIRES,
     python_requires=f">={mass_const.REQUIRED_PYTHON_VER}",
     test_suite="tests",
-    entry_points={"console_scripts": ["mass = music_assistant.__main__:main", "musicassistant = music_assistant.__main__:main"]},
-    package_data={
-        'music_assistant': PACKAGE_FILES,
+    entry_points={
+        "console_scripts": [
+            "mass = music_assistant.__main__:main",
+            "musicassistant = music_assistant.__main__:main",
+        ]
     },
+    package_data={"music_assistant": PACKAGE_FILES},
 )