Subsonic: Protect all list iteration from possible NoneType (#2180)
authorEric Munson <eric@munsonfam.org>
Thu, 8 May 2025 15:17:27 +0000 (11:17 -0400)
committerGitHub <noreply@github.com>
Thu, 8 May 2025 15:17:27 +0000 (17:17 +0200)
Fix: Subsonic: Protect all list iteration from possible NoneType

The specification allows for these fields to be None, so we need to check
that they exist before iterating them.

Signed-off-by: Eric B Munson <eric@munsonfam.org>
music_assistant/providers/opensubsonic/parsers.py
music_assistant/providers/opensubsonic/sonic_provider.py

index faef6d8626ce8a4e3bfb4243a3c93f00b398a985..24a7f9240f240f7c1feaffe966a2ce9746f53c2b 100644 (file)
@@ -7,6 +7,7 @@ from datetime import datetime
 from typing import TYPE_CHECKING
 
 from music_assistant_models.enums import ImageType, MediaType
+from music_assistant_models.errors import MediaNotFoundError
 from music_assistant_models.media_items import (
     Album,
     Artist,
@@ -234,6 +235,9 @@ def parse_epsiode(
     """Parse an Open Subsonic Podcast Episode into an MA PodcastEpisode."""
     eid = f"{sonic_episode.channel_id}{EP_CHAN_SEP}{sonic_episode.id}"
     pos = 1
+    if not sonic_channel.episode:
+        raise MediaNotFoundError(f"Podcast Channel '{sonic_channel.id}' missing episode list")
+
     for ep in sonic_channel.episode:
         if ep.id == sonic_episode.id:
             break
index 535f11dad9d7859a8a545491dcf63294aacef9d9..1abe5bffad9945bb936dcaf446c85f088917ef08 100644 (file)
@@ -272,6 +272,9 @@ class OpenSonicProvider(MusicProvider):
         chan_id, ep_id = eid.split(EP_CHAN_SEP)
         chan = await self._run_async(self._conn.get_podcasts, inc_episodes=True, pid=chan_id)
 
+        if not chan[0].episode:
+            raise MediaNotFoundError(f"Missing episode list for podcast channel '{chan[0].id}'")
+
         for episode in chan[0].episode:
             if episode.id == ep_id:
                 return episode
@@ -330,7 +333,14 @@ class OpenSonicProvider(MusicProvider):
     async def get_library_artists(self) -> AsyncGenerator[Artist, None]:
         """Provide a generator for reading all artists."""
         artists = await self._run_async(self._conn.get_artists)
+
+        if not artists.index:
+            return
+
         for index in artists.index:
+            if not index.artist:
+                continue
+
             for artist in index.artist:
                 yield parse_artist(self.instance_id, artist)
 
@@ -431,8 +441,9 @@ class OpenSonicProvider(MusicProvider):
             msg = f"Album {prov_album_id} not found"
             raise MediaNotFoundError(msg) from e
         tracks = []
-        for sonic_song in sonic_album.song:
-            tracks.append(self._parse_track(sonic_song))
+        if sonic_album.song:
+            for sonic_song in sonic_album.song:
+                tracks.append(self._parse_track(sonic_song))
         return tracks
 
     async def get_artist(self, prov_artist_id: str) -> Artist:
@@ -501,8 +512,9 @@ class OpenSonicProvider(MusicProvider):
             msg = f"Album {prov_artist_id} not found"
             raise MediaNotFoundError(msg) from e
         albums = []
-        for entry in sonic_artist.album:
-            albums.append(parse_album(self.logger, self.instance_id, entry))
+        if sonic_artist.album:
+            for entry in sonic_artist.album:
+                albums.append(parse_album(self.logger, self.instance_id, entry))
         return albums
 
     async def get_playlist(self, prov_playlist_id: str) -> Playlist: