Mypy fixes (#1851)
authorJc2k <john.carr@unrouted.co.uk>
Thu, 9 Jan 2025 22:30:49 +0000 (22:30 +0000)
committerGitHub <noreply@github.com>
Thu, 9 Jan 2025 22:30:49 +0000 (23:30 +0100)
18 files changed:
music_assistant/constants.py
music_assistant/controllers/media/albums.py
music_assistant/controllers/media/artists.py
music_assistant/controllers/media/audiobooks.py
music_assistant/controllers/media/base.py
music_assistant/controllers/media/playlists.py
music_assistant/controllers/media/podcasts.py
music_assistant/controllers/media/radio.py
music_assistant/controllers/media/tracks.py
music_assistant/providers/airplay/provider.py
music_assistant/providers/airplay/raop.py
music_assistant/providers/filesystem_local/__init__.py
music_assistant/providers/plex/__init__.py
music_assistant/providers/radiobrowser/__init__.py
music_assistant/providers/test/__init__.py
pyproject.toml
requirements_all.txt
tests/core/test_helpers.py

index 301bad85b10f26e074bc065145027697342474a7..ee45661d996835206939a485cdc2781daabddf65 100644 (file)
@@ -3,11 +3,8 @@
 import pathlib
 from typing import Final
 
-from music_assistant_models.config_entries import (
-    ConfigEntry,
-    ConfigEntryType,
-    ConfigValueOption,
-)
+from music_assistant_models.config_entries import ConfigEntry, ConfigValueOption
+from music_assistant_models.enums import ConfigEntryType
 
 API_SCHEMA_VERSION: Final[int] = 26
 MIN_SCHEMA_VERSION: Final[int] = 24
index 482fd2b57d892a9bd29832fa46973f6edfce2f3f..b81fa7d90a31e0f717d8a47aae575f4b9bf76f89 100644 (file)
@@ -39,7 +39,7 @@ if TYPE_CHECKING:
     from music_assistant.models.music_provider import MusicProvider
 
 
-class AlbumsController(MediaControllerBase[Album]):
+class AlbumsController(MediaControllerBase[Album, Album]):
     """Controller managing MediaItems of type Album."""
 
     db_table = DB_TABLE_ALBUMS
index 2386429c15bb6b62e5616c060201c109b4ae0035..4f558a3ff4db017063089cabcfe408c22aa0b9c1 100644 (file)
@@ -36,7 +36,7 @@ if TYPE_CHECKING:
     from music_assistant.models.music_provider import MusicProvider
 
 
-class ArtistsController(MediaControllerBase[Artist]):
+class ArtistsController(MediaControllerBase[Artist, Artist | ItemMapping]):
     """Controller managing MediaItems of type Artist."""
 
     db_table = DB_TABLE_ARTISTS
index acc7a18129dd7d298b9a556e5dd98ee77b9bbacd..6c58b45b7647bd2fe34d5a7136e10f7f313223ac 100644 (file)
@@ -23,7 +23,7 @@ if TYPE_CHECKING:
     from music_assistant.models.music_provider import MusicProvider
 
 
-class AudiobooksController(MediaControllerBase[Audiobook]):
+class AudiobooksController(MediaControllerBase[Audiobook, Audiobook]):
     """Controller managing MediaItems of type Audiobook."""
 
     db_table = DB_TABLE_AUDIOBOOKS
index 216b7f6ce429e5ec81a145376f1e6744e82bca7c..16481dcd5fb69f59bba598d29e9f3b6ebc68cf6d 100644 (file)
@@ -39,7 +39,9 @@ if TYPE_CHECKING:
 
     from music_assistant import MusicAssistant
 
+MediaItemTypeBound = MediaItemType | ItemMapping
 ItemCls = TypeVar("ItemCls", bound="MediaItemType")
+LibraryUpdate = TypeVar("LibraryUpdate", bound="MediaItemTypeBound")
 
 JSON_KEYS = (
     "artists",
@@ -75,7 +77,7 @@ SORT_KEYS = {
 }
 
 
-class MediaControllerBase(Generic[ItemCls], metaclass=ABCMeta):
+class MediaControllerBase(Generic[ItemCls, LibraryUpdate], metaclass=ABCMeta):
     """Base model for controller managing a MediaType."""
 
     media_type: MediaType
@@ -162,7 +164,7 @@ class MediaControllerBase(Generic[ItemCls], metaclass=ABCMeta):
         return None
 
     async def update_item_in_library(
-        self, item_id: str | int, update: ItemCls, overwrite: bool = False
+        self, item_id: str | int, update: LibraryUpdate, overwrite: bool = False
     ) -> ItemCls:
         """Update existing library record in the library database."""
         await self._update_library_item(item_id, update, overwrite=overwrite)
index 365a83d7d459a3226ee4e55ec4be8c2bcedb6348..9bd021ada54c16d9518c9ac4c4e8b541d9d6c3a9 100644 (file)
@@ -22,7 +22,7 @@ from music_assistant.models.music_provider import MusicProvider
 from .base import MediaControllerBase
 
 
-class PlaylistController(MediaControllerBase[Playlist]):
+class PlaylistController(MediaControllerBase[Playlist, Playlist]):
     """Controller managing MediaItems of type Playlist."""
 
     db_table = DB_TABLE_PLAYLISTS
index 3d6dca37787a9035923c9202a557f139f12c3d88..b8a981eb6fb56dc831f9199948225854e2839c8c 100644 (file)
@@ -29,7 +29,7 @@ if TYPE_CHECKING:
     from music_assistant.models.music_provider import MusicProvider
 
 
-class PodcastsController(MediaControllerBase[Podcast]):
+class PodcastsController(MediaControllerBase[Podcast, Podcast]):
     """Controller managing MediaItems of type Podcast."""
 
     db_table = DB_TABLE_PODCASTS
index dbb13df3d6755f4172d90b498d7643613f330bc0..28e0357c0c92818ba1bd43ce6eabd70a00853c49 100644 (file)
@@ -14,7 +14,7 @@ from music_assistant.helpers.json import serialize_to_json
 from .base import MediaControllerBase
 
 
-class RadioController(MediaControllerBase[Radio]):
+class RadioController(MediaControllerBase[Radio, Radio]):
     """Controller managing MediaItems of type Radio."""
 
     db_table = DB_TABLE_RADIOS
index 1eb04597f9b12408ca28ce85e6230a459775a6af..13cf64642edcce9b22c56fc0511890becd47cc39 100644 (file)
@@ -41,7 +41,7 @@ from music_assistant.models.music_provider import MusicProvider
 from .base import MediaControllerBase
 
 
-class TracksController(MediaControllerBase[Track]):
+class TracksController(MediaControllerBase[Track, Track]):
     """Controller managing MediaItems of type Track."""
 
     db_table = DB_TABLE_TRACKS
index 3549b4898dd7672dfe17c3d596aa8602f36c70a7..517f3ce00bd7938a8a77881cd832dde0c4352d3a 100644 (file)
@@ -160,7 +160,7 @@ class AirplayProvider(PlayerProvider):
         self._dacp_info = AsyncServiceInfo(
             zeroconf_type,
             name=server_id,
-            addresses=[await get_ip_pton(self.mass.streams.publish_ip)],
+            addresses=[await get_ip_pton(str(self.mass.streams.publish_ip))],
             port=dacp_port,
             properties={
                 "txtvers": "1",
@@ -295,6 +295,7 @@ class AirplayProvider(PlayerProvider):
         # select audio source
         if media.media_type == MediaType.ANNOUNCEMENT:
             # special case: stream announcement
+            assert media.custom_data
             input_format = AIRPLAY_PCM_FORMAT
             audio_source = self.mass.streams.get_announcement_stream(
                 media.custom_data["url"],
@@ -310,11 +311,13 @@ class AirplayProvider(PlayerProvider):
         elif media.queue_id and media.queue_item_id:
             # regular queue (flow) stream request
             input_format = AIRPLAY_FLOW_PCM_FORMAT
+            queue = self.mass.player_queues.get(media.queue_id)
+            assert queue
+            start_queue_item = self.mass.player_queues.get_item(media.queue_id, media.queue_item_id)
+            assert start_queue_item
             audio_source = self.mass.streams.get_flow_stream(
-                queue=self.mass.player_queues.get(media.queue_id),
-                start_queue_item=self.mass.player_queues.get_item(
-                    media.queue_id, media.queue_item_id
-                ),
+                queue=queue,
+                start_queue_item=start_queue_item,
                 pcm_format=input_format,
             )
         else:
@@ -621,6 +624,7 @@ class AirplayProvider(PlayerProvider):
                 # to prevent an endless pingpong of volume changes
                 raop_volume = float(path.split("dmcp.device-volume=", 1)[-1])
                 volume = convert_airplay_volume(raop_volume)
+                assert mass_player.volume_level
                 if (
                     abs(mass_player.volume_level - volume) > 5
                     or (time.time() - airplay_player.last_command_sent) < 2
@@ -640,6 +644,7 @@ class AirplayProvider(PlayerProvider):
                 # device switched to another source (or is powered off)
                 if raop_stream := airplay_player.raop_stream:
                     # ignore this if we just started playing to prevent false positives
+                    assert mass_player.elapsed_time
                     if mass_player.elapsed_time > 10 and mass_player.state == PlayerState.PLAYING:
                         raop_stream.prevent_playback = True
                         self.mass.create_task(self.monitor_prevent_playback(player_id))
index 27c6062aa0f678186c4b31ec91c0884ad2ecdbf1..beffcd6e3b3e73da07fd26b94d5e30a2d0ba12be 100644 (file)
@@ -177,13 +177,16 @@ class RaopStream:
 
     async def start(self, start_ntp: int, wait_start: int = 1000) -> None:
         """Initialize CLIRaop process for a player."""
-        extra_args = []
+        assert self.prov.cliraop_bin
+        extra_args: list[str] = []
         player_id = self.airplay_player.player_id
         mass_player = self.mass.players.get(player_id)
         if not mass_player:
             return
-        bind_ip = await self.mass.config.get_provider_config_value(
-            self.prov.instance_id, CONF_BIND_INTERFACE
+        bind_ip = str(
+            await self.mass.config.get_provider_config_value(
+                self.prov.instance_id, CONF_BIND_INTERFACE
+            )
         )
         extra_args += ["-if", bind_ip]
         if self.mass.config.get_raw_player_config_value(player_id, CONF_ENCRYPTION, False):
@@ -194,10 +197,11 @@ class RaopStream:
             if prop_value := self.airplay_player.discovery_info.decoded_properties.get(prop):
                 extra_args += [f"-{prop}", prop_value]
         sync_adjust = self.mass.config.get_raw_player_config_value(player_id, CONF_SYNC_ADJUST, 0)
+        assert isinstance(sync_adjust, int)
         if device_password := self.mass.config.get_raw_player_config_value(
             player_id, CONF_PASSWORD, None
         ):
-            extra_args += ["-password", device_password]
+            extra_args += ["-password", str(device_password)]
         if self.prov.logger.isEnabledFor(logging.DEBUG):
             extra_args += ["-debug", "5"]
         elif self.prov.logger.isEnabledFor(VERBOSE_LOG_LEVEL):
@@ -306,7 +310,7 @@ class RaopStream:
         """Monitor stderr for the running CLIRaop process."""
         airplay_player = self.airplay_player
         mass_player = self.mass.players.get(airplay_player.player_id)
-        if not mass_player:
+        if not mass_player or not mass_player.active_source:
             return
         queue = self.mass.player_queues.get_active_queue(mass_player.active_source)
         logger = airplay_player.logger
index 51691abeeea0841022d6a0c9155044f1ec9943e3..dd4a1d885c7e40309230bd64c5ddb891235cc71e 100644 (file)
@@ -21,6 +21,7 @@ from music_assistant_models.enums import (
     ContentType,
     ExternalID,
     ImageType,
+    MediaType,
     ProviderFeature,
     StreamType,
 )
@@ -33,7 +34,6 @@ from music_assistant_models.media_items import (
     ItemMapping,
     MediaItemImage,
     MediaItemType,
-    MediaType,
     Playlist,
     ProviderMapping,
     SearchResults,
index f5ccc32feac28b6eeb49731e8d86a2b2740cf280..e43208c0efa99688b99693a9ec4fa54fbf7ab8b4 100644 (file)
@@ -901,7 +901,7 @@ class PlexProvider(MusicProvider):
 
         media: PlexMedia = plex_track.media[0]
 
-        media_type = (
+        content_type = (
             ContentType.try_parse(media.container) if media.container else ContentType.UNKNOWN
         )
         media_part: PlexMediaPart = media.parts[0]
@@ -911,7 +911,7 @@ class PlexProvider(MusicProvider):
             item_id=plex_track.key,
             provider=self.instance_id,
             audio_format=AudioFormat(
-                content_type=media_type,
+                content_type=content_type,
                 channels=media.audioChannels,
             ),
             stream_type=StreamType.HTTP,
@@ -919,7 +919,7 @@ class PlexProvider(MusicProvider):
             data=plex_track,
         )
 
-        if media_type != ContentType.M4A:
+        if content_type != ContentType.M4A:
             stream_details.path = self._plex_server.url(media_part.key, True)
             if audio_stream.samplingRate:
                 stream_details.audio_format.sample_rate = audio_stream.samplingRate
index 03962792eb5d1eab87bca08fc6f3b120497e0ba8..84cd3d0b35fdde742c5795ed8528d7bcbcb8ba0a 100644 (file)
@@ -11,6 +11,7 @@ from music_assistant_models.enums import (
     ContentType,
     ImageType,
     LinkType,
+    MediaType,
     ProviderFeature,
     StreamType,
 )
@@ -21,7 +22,6 @@ from music_assistant_models.media_items import (
     MediaItemImage,
     MediaItemLink,
     MediaItemType,
-    MediaType,
     ProviderMapping,
     Radio,
     SearchResults,
index 92f5756055bfc085e026e656d813bc382942c2e0..c061769bfc995531b75c74ae38293e12ca60b173 100644 (file)
@@ -207,7 +207,7 @@ class TestProvider(MusicProvider):
             metadata=MediaItemMetadata(images=UniqueList([DEFAULT_THUMB])),
         )
 
-    async def get_podcast(self, prov_podcast_id: str) -> Album:
+    async def get_podcast(self, prov_podcast_id: str) -> Podcast:
         """Get full podcast details by id."""
         return Podcast(
             item_id=prov_podcast_id,
@@ -255,13 +255,16 @@ class TestProvider(MusicProvider):
     async def get_library_artists(self) -> AsyncGenerator[Artist, None]:
         """Retrieve library artists from the provider."""
         num_artists = self.config.get_value(CONF_KEY_NUM_ARTISTS)
+        assert isinstance(num_artists, int)
         for artist_idx in range(num_artists):
             yield await self.get_artist(str(artist_idx))
 
     async def get_library_albums(self) -> AsyncGenerator[Album, None]:
         """Retrieve library albums from the provider."""
         num_artists = self.config.get_value(CONF_KEY_NUM_ARTISTS) or 5
+        assert isinstance(num_artists, int)
         num_albums = self.config.get_value(CONF_KEY_NUM_ALBUMS)
+        assert isinstance(num_albums, int)
         for artist_idx in range(num_artists):
             for album_idx in range(num_albums):
                 album_item_id = f"{artist_idx}_{album_idx}"
@@ -270,8 +273,11 @@ class TestProvider(MusicProvider):
     async def get_library_tracks(self) -> AsyncGenerator[Track, None]:
         """Retrieve library tracks from the provider."""
         num_artists = self.config.get_value(CONF_KEY_NUM_ARTISTS) or 5
+        assert isinstance(num_artists, int)
         num_albums = self.config.get_value(CONF_KEY_NUM_ALBUMS) or 5
+        assert isinstance(num_albums, int)
         num_tracks = self.config.get_value(CONF_KEY_NUM_TRACKS)
+        assert isinstance(num_tracks, int)
         for artist_idx in range(num_artists):
             for album_idx in range(num_albums):
                 for track_idx in range(num_tracks):
@@ -281,12 +287,14 @@ class TestProvider(MusicProvider):
     async def get_library_podcasts(self) -> AsyncGenerator[Podcast, None]:
         """Retrieve library tracks from the provider."""
         num_podcasts = self.config.get_value(CONF_KEY_NUM_PODCASTS)
+        assert isinstance(num_podcasts, int)
         for podcast_idx in range(num_podcasts):
             yield await self.get_podcast(str(podcast_idx))
 
     async def get_library_audiobooks(self) -> AsyncGenerator[Audiobook, None]:
         """Retrieve library audiobooks from the provider."""
         num_audiobooks = self.config.get_value(CONF_KEY_NUM_AUDIOBOOKS)
+        assert isinstance(num_audiobooks, int)
         for audiobook_idx in range(num_audiobooks):
             yield await self.get_audiobook(str(audiobook_idx))
 
index 8f9e5ab61a499231e7ba25b9fc975252ccd5d397..50bd42b2906f2c5f3834848200d883086c227eff 100644 (file)
@@ -25,7 +25,7 @@ dependencies = [
   "mashumaro==3.14",
   "memory-tempfile==2.2.3",
   "music-assistant-frontend==2.10.4",
-  "music-assistant-models==1.1.9",
+  "music-assistant-models==1.1.10",
   "orjson==3.10.12",
   "pillow==11.0.0",
   "podcastparser==0.6.10",
index 1455e75e3f7fecbefd047a56990e998c55fd1131..fa30b2a95e4288a7a0d7cb93462a4de635a35678 100644 (file)
@@ -24,7 +24,7 @@ ifaddr==0.2.0
 mashumaro==3.14
 memory-tempfile==2.2.3
 music-assistant-frontend==2.10.4
-music-assistant-models==1.1.9
+music-assistant-models==1.1.10
 orjson==3.10.12
 pillow==11.0.0
 pkce==1.0.3
index b77fdd5ab528b36c432f344279df682b205e4e1b..ea40e65a4abd47b28c5a559fa0fd13c35c10a396 100644 (file)
@@ -1,7 +1,7 @@
 """Tests for utility/helper functions."""
 
 import pytest
-from music_assistant_models import media_items
+from music_assistant_models.enums import MediaType
 from music_assistant_models.errors import MusicAssistantError
 
 from music_assistant.helpers import uri, util
@@ -44,37 +44,37 @@ async def test_uri_parsing() -> None:
     # test regular uri
     test_uri = "spotify://track/123456789"
     media_type, provider, item_id = await uri.parse_uri(test_uri)
-    assert media_type == media_items.MediaType.TRACK
+    assert media_type == MediaType.TRACK
     assert provider == "spotify"
     assert item_id == "123456789"
     # test spotify uri
     test_uri = "spotify:track:123456789"
     media_type, provider, item_id = await uri.parse_uri(test_uri)
-    assert media_type == media_items.MediaType.TRACK
+    assert media_type == MediaType.TRACK
     assert provider == "spotify"
     assert item_id == "123456789"
     # test public play/open url
     test_uri = "https://open.spotify.com/playlist/5lH9NjOeJvctAO92ZrKQNB?si=04a63c8234ac413e"
     media_type, provider, item_id = await uri.parse_uri(test_uri)
-    assert media_type == media_items.MediaType.PLAYLIST
+    assert media_type == MediaType.PLAYLIST
     assert provider == "spotify"
     assert item_id == "5lH9NjOeJvctAO92ZrKQNB"
     # test filename with slashes as item_id
     test_uri = "filesystem://track/Artist/Album/Track.flac"
     media_type, provider, item_id = await uri.parse_uri(test_uri)
-    assert media_type == media_items.MediaType.TRACK
+    assert media_type == MediaType.TRACK
     assert provider == "filesystem"
     assert item_id == "Artist/Album/Track.flac"
     # test regular url to builtin provider
     test_uri = "http://radiostream.io/stream.mp3"
     media_type, provider, item_id = await uri.parse_uri(test_uri)
-    assert media_type == media_items.MediaType.UNKNOWN
+    assert media_type == MediaType.UNKNOWN
     assert provider == "builtin"
     assert item_id == "http://radiostream.io/stream.mp3"
     # test local file to builtin provider
     test_uri = __file__
     media_type, provider, item_id = await uri.parse_uri(test_uri)
-    assert media_type == media_items.MediaType.UNKNOWN
+    assert media_type == MediaType.UNKNOWN
     assert provider == "builtin"
     assert item_id == __file__
     # test invalid uri