From: Marcel van der Veldt Date: Mon, 13 Mar 2023 21:28:25 +0000 (+0100) Subject: fix supported features get mangled X-Git-Url: https://git.kitaultman.com/?a=commitdiff_plain;h=c4da7f72802d971e5b70c8779fab122b04e95486;p=music-assistant-server.git fix supported features get mangled --- diff --git a/music_assistant/server/models/metadata_provider.py b/music_assistant/server/models/metadata_provider.py index 9dfeb281..afa8b2c1 100644 --- a/music_assistant/server/models/metadata_provider.py +++ b/music_assistant/server/models/metadata_provider.py @@ -13,6 +13,13 @@ if TYPE_CHECKING: # ruff: noqa: ARG001, ARG002 +DEFAULT_SUPPORTED_FEATURES = ( + ProviderFeature.ARTIST_METADATA, + ProviderFeature.ALBUM_METADATA, + ProviderFeature.TRACK_METADATA, + ProviderFeature.GET_ARTIST_MBID, +) + class MetadataProvider(Provider): """Base representation of a Metadata Provider (controller). @@ -20,12 +27,10 @@ class MetadataProvider(Provider): Metadata Provider implementations should inherit from this base model. """ - _attr_supported_features: tuple[ProviderFeature, ...] = ( - ProviderFeature.ARTIST_METADATA, - ProviderFeature.ALBUM_METADATA, - ProviderFeature.TRACK_METADATA, - ProviderFeature.GET_ARTIST_MBID, - ) + @property + def supported_features(self) -> tuple[ProviderFeature, ...]: + """Return the features supported by this Provider.""" + return DEFAULT_SUPPORTED_FEATURES async def get_artist_metadata(self, artist: Artist) -> MediaItemMetadata | None: """Retrieve metadata for an artist on this Metadata provider.""" diff --git a/music_assistant/server/models/provider.py b/music_assistant/server/models/provider.py index 5fe35b9d..c37ac91d 100644 --- a/music_assistant/server/models/provider.py +++ b/music_assistant/server/models/provider.py @@ -18,8 +18,6 @@ if TYPE_CHECKING: class Provider: """Base representation of a Provider implementation within Music Assistant.""" - _attr_supported_features: tuple[ProviderFeature, ...] = tuple() - def __init__( self, mass: MusicAssistant, manifest: ProviderManifest, config: ProviderConfig ) -> None: @@ -34,8 +32,8 @@ class Provider: @property def supported_features(self) -> tuple[ProviderFeature, ...]: - """Return the features supported by this MusicProvider.""" - return self._attr_supported_features + """Return the features supported by this Provider.""" + return tuple() async def setup(self) -> None: """Handle async initialization of the provider. diff --git a/music_assistant/server/providers/fanarttv/__init__.py b/music_assistant/server/providers/fanarttv/__init__.py index cb1fb043..cf499a22 100644 --- a/music_assistant/server/providers/fanarttv/__init__.py +++ b/music_assistant/server/providers/fanarttv/__init__.py @@ -16,6 +16,11 @@ from music_assistant.server.models.metadata_provider import MetadataProvider if TYPE_CHECKING: from music_assistant.common.models.media_items import Album, Artist +SUPPORTED_FEATURES = ( + ProviderFeature.ARTIST_METADATA, + ProviderFeature.ALBUM_METADATA, +) + # TODO: add support for personal api keys ? @@ -36,10 +41,11 @@ class FanartTvMetadataProvider(MetadataProvider): """Handle async initialization of the provider.""" self.cache = self.mass.cache self.throttler = Throttler(rate_limit=2, period=1) - self._attr_supported_features = ( - ProviderFeature.ARTIST_METADATA, - ProviderFeature.ALBUM_METADATA, - ) + + @property + def supported_features(self) -> tuple[ProviderFeature, ...]: + """Return the features supported by this Provider.""" + return SUPPORTED_FEATURES async def get_artist_metadata(self, artist: Artist) -> MediaItemMetadata | None: """Retrieve metadata for artist on fanart.tv.""" diff --git a/music_assistant/server/providers/filesystem_local/base.py b/music_assistant/server/providers/filesystem_local/base.py index 43b4456b..6c8f85e6 100644 --- a/music_assistant/server/providers/filesystem_local/base.py +++ b/music_assistant/server/providers/filesystem_local/base.py @@ -43,6 +43,17 @@ PLAYLIST_EXTENSIONS = ("m3u", "pls") SUPPORTED_EXTENSIONS = TRACK_EXTENSIONS + PLAYLIST_EXTENSIONS IMAGE_EXTENSIONS = ("jpg", "jpeg", "JPG", "JPEG", "png", "PNG", "gif", "GIF") +SUPPORTED_FEATURES = ( + ProviderFeature.LIBRARY_ARTISTS, + ProviderFeature.LIBRARY_ALBUMS, + ProviderFeature.LIBRARY_TRACKS, + ProviderFeature.LIBRARY_PLAYLISTS, + ProviderFeature.PLAYLIST_TRACKS_EDIT, + ProviderFeature.PLAYLIST_CREATE, + ProviderFeature.BROWSE, + ProviderFeature.SEARCH, +) + @dataclass class FileSystemItem: @@ -85,16 +96,10 @@ class FileSystemProviderBase(MusicProvider): Supports having URI's from streaming providers within m3u playlist. """ - _attr_supported_features = ( - ProviderFeature.LIBRARY_ARTISTS, - ProviderFeature.LIBRARY_ALBUMS, - ProviderFeature.LIBRARY_TRACKS, - ProviderFeature.LIBRARY_PLAYLISTS, - ProviderFeature.PLAYLIST_TRACKS_EDIT, - ProviderFeature.PLAYLIST_CREATE, - ProviderFeature.BROWSE, - ProviderFeature.SEARCH, - ) + @property + def supported_features(self) -> tuple[ProviderFeature, ...]: + """Return the features supported by this Provider.""" + return SUPPORTED_FEATURES @abstractmethod async def setup(self) -> None: diff --git a/music_assistant/server/providers/musicbrainz/__init__.py b/music_assistant/server/providers/musicbrainz/__init__.py index dced64cb..59231976 100644 --- a/music_assistant/server/providers/musicbrainz/__init__.py +++ b/music_assistant/server/providers/musicbrainz/__init__.py @@ -24,6 +24,8 @@ if TYPE_CHECKING: LUCENE_SPECIAL = r'([+\-&|!(){}\[\]\^"~*?:\\\/])' +SUPPORTED_FEATURES = (ProviderFeature.GET_ARTIST_MBID,) + class MusicbrainzProvider(MetadataProvider): """The Musicbrainz Metadata provider.""" @@ -34,7 +36,11 @@ class MusicbrainzProvider(MetadataProvider): """Handle async initialization of the provider.""" self.cache = self.mass.cache self.throttler = Throttler(rate_limit=1, period=1) - self._attr_supported_features = (ProviderFeature.GET_ARTIST_MBID,) + + @property + def supported_features(self) -> tuple[ProviderFeature, ...]: + """Return the features supported by this Provider.""" + return SUPPORTED_FEATURES async def get_musicbrainz_artist_id( self, artist: Artist, ref_albums: Iterable[Album], ref_tracks: Iterable[Track] diff --git a/music_assistant/server/providers/qobuz/__init__.py b/music_assistant/server/providers/qobuz/__init__.py index 17fbb0f5..aae4987a 100644 --- a/music_assistant/server/providers/qobuz/__init__.py +++ b/music_assistant/server/providers/qobuz/__init__.py @@ -31,6 +31,22 @@ from music_assistant.constants import CONF_PASSWORD, CONF_USERNAME from music_assistant.server.helpers.app_vars import app_var # pylint: disable=no-name-in-module from music_assistant.server.models.music_provider import MusicProvider +SUPPORTED_FEATURES = ( + ProviderFeature.LIBRARY_ARTISTS, + ProviderFeature.LIBRARY_ALBUMS, + ProviderFeature.LIBRARY_TRACKS, + ProviderFeature.LIBRARY_PLAYLISTS, + ProviderFeature.LIBRARY_ARTISTS_EDIT, + ProviderFeature.LIBRARY_ALBUMS_EDIT, + ProviderFeature.LIBRARY_PLAYLISTS_EDIT, + ProviderFeature.LIBRARY_TRACKS_EDIT, + ProviderFeature.PLAYLIST_TRACKS_EDIT, + ProviderFeature.BROWSE, + ProviderFeature.SEARCH, + ProviderFeature.ARTIST_ALBUMS, + ProviderFeature.ARTIST_TOPTRACKS, +) + class QobuzProvider(MusicProvider): """Provider for the Qobux music service.""" @@ -41,21 +57,7 @@ class QobuzProvider(MusicProvider): async def setup(self) -> None: """Handle async initialization of the provider.""" self._throttler = Throttler(rate_limit=4, period=1) - self._attr_supported_features = ( - ProviderFeature.LIBRARY_ARTISTS, - ProviderFeature.LIBRARY_ALBUMS, - ProviderFeature.LIBRARY_TRACKS, - ProviderFeature.LIBRARY_PLAYLISTS, - ProviderFeature.LIBRARY_ARTISTS_EDIT, - ProviderFeature.LIBRARY_ALBUMS_EDIT, - ProviderFeature.LIBRARY_PLAYLISTS_EDIT, - ProviderFeature.LIBRARY_TRACKS_EDIT, - ProviderFeature.PLAYLIST_TRACKS_EDIT, - ProviderFeature.BROWSE, - ProviderFeature.SEARCH, - ProviderFeature.ARTIST_ALBUMS, - ProviderFeature.ARTIST_TOPTRACKS, - ) + if not self.config.get_value(CONF_USERNAME) or not self.config.get_value(CONF_PASSWORD): raise LoginFailed("Invalid login credentials") # try to get a token, raise if that fails @@ -63,6 +65,11 @@ class QobuzProvider(MusicProvider): if not token: raise LoginFailed(f"Login failed for user {self.config.get_value(CONF_USERNAME)}") + @property + def supported_features(self) -> tuple[ProviderFeature, ...]: + """Return the features supported by this Provider.""" + return SUPPORTED_FEATURES + async def search( self, search_query: str, media_types=list[MediaType] | None, limit: int = 5 ) -> list[MediaItemType]: diff --git a/music_assistant/server/providers/spotify/__init__.py b/music_assistant/server/providers/spotify/__init__.py index c4752be3..9a918682 100644 --- a/music_assistant/server/providers/spotify/__init__.py +++ b/music_assistant/server/providers/spotify/__init__.py @@ -36,6 +36,22 @@ from music_assistant.server.helpers.process import AsyncProcess from music_assistant.server.models.music_provider import MusicProvider CACHE_DIR = gettempdir() +SUPPORTED_FEATURES = ( + ProviderFeature.LIBRARY_ARTISTS, + ProviderFeature.LIBRARY_ALBUMS, + ProviderFeature.LIBRARY_TRACKS, + ProviderFeature.LIBRARY_PLAYLISTS, + ProviderFeature.LIBRARY_ARTISTS_EDIT, + ProviderFeature.LIBRARY_ALBUMS_EDIT, + ProviderFeature.LIBRARY_PLAYLISTS_EDIT, + ProviderFeature.LIBRARY_TRACKS_EDIT, + ProviderFeature.PLAYLIST_TRACKS_EDIT, + ProviderFeature.BROWSE, + ProviderFeature.SEARCH, + ProviderFeature.ARTIST_ALBUMS, + ProviderFeature.ARTIST_TOPTRACKS, + ProviderFeature.SIMILAR_TRACKS, +) class SpotifyProvider(MusicProvider): @@ -50,27 +66,17 @@ class SpotifyProvider(MusicProvider): self._throttler = Throttler(rate_limit=1, period=0.1) self._cache_dir = CACHE_DIR self._ap_workaround = False - self._attr_supported_features = ( - ProviderFeature.LIBRARY_ARTISTS, - ProviderFeature.LIBRARY_ALBUMS, - ProviderFeature.LIBRARY_TRACKS, - ProviderFeature.LIBRARY_PLAYLISTS, - ProviderFeature.LIBRARY_ARTISTS_EDIT, - ProviderFeature.LIBRARY_ALBUMS_EDIT, - ProviderFeature.LIBRARY_PLAYLISTS_EDIT, - ProviderFeature.LIBRARY_TRACKS_EDIT, - ProviderFeature.PLAYLIST_TRACKS_EDIT, - ProviderFeature.BROWSE, - ProviderFeature.SEARCH, - ProviderFeature.ARTIST_ALBUMS, - ProviderFeature.ARTIST_TOPTRACKS, - ProviderFeature.SIMILAR_TRACKS, - ) + # try to get a token, raise if that fails self._cache_dir = os.path.join(CACHE_DIR, self.instance_id) # try login which will raise if it fails await self.login() + @property + def supported_features(self) -> tuple[ProviderFeature, ...]: + """Return the features supported by this Provider.""" + return SUPPORTED_FEATURES + async def search( self, search_query: str, media_types=list[MediaType] | None, limit: int = 5 ) -> list[MediaItemType]: diff --git a/music_assistant/server/providers/theaudiodb/__init__.py b/music_assistant/server/providers/theaudiodb/__init__.py index 00a5470c..c0d9acf8 100644 --- a/music_assistant/server/providers/theaudiodb/__init__.py +++ b/music_assistant/server/providers/theaudiodb/__init__.py @@ -27,6 +27,13 @@ from music_assistant.server.models.metadata_provider import MetadataProvider if TYPE_CHECKING: from collections.abc import Iterable +SUPPORTED_FEATURES = ( + ProviderFeature.ARTIST_METADATA, + ProviderFeature.ALBUM_METADATA, + ProviderFeature.TRACK_METADATA, + ProviderFeature.GET_ARTIST_MBID, +) + IMG_MAPPING = { "strArtistThumb": ImageType.THUMB, "strArtistLogo": ImageType.LOGO, @@ -68,14 +75,13 @@ class AudioDbMetadataProvider(MetadataProvider): async def setup(self) -> None: """Handle async initialization of the provider.""" self.cache = self.mass.cache - self._attr_supported_features = ( - ProviderFeature.ARTIST_METADATA, - ProviderFeature.ALBUM_METADATA, - ProviderFeature.TRACK_METADATA, - ProviderFeature.GET_ARTIST_MBID, - ) self.throttler = Throttler(rate_limit=2, period=1) + @property + def supported_features(self) -> tuple[ProviderFeature, ...]: + """Return the features supported by this Provider.""" + return SUPPORTED_FEATURES + async def get_artist_metadata(self, artist: Artist) -> MediaItemMetadata | None: """Retrieve metadata for artist on theaudiodb.""" if data := await self._get_data("artist-mb.php", i=artist.musicbrainz_id): # noqa: SIM102 diff --git a/music_assistant/server/providers/tunein/__init__.py b/music_assistant/server/providers/tunein/__init__.py index e6dc561a..1a380c73 100644 --- a/music_assistant/server/providers/tunein/__init__.py +++ b/music_assistant/server/providers/tunein/__init__.py @@ -24,19 +24,26 @@ from music_assistant.server.helpers.playlists import fetch_playlist from music_assistant.server.helpers.tags import parse_tags from music_assistant.server.models.music_provider import MusicProvider +SUPPORTED_FEATURES = ( + ProviderFeature.LIBRARY_RADIOS, + ProviderFeature.BROWSE, +) + class TuneInProvider(MusicProvider): """Provider implementation for Tune In.""" _throttler: Throttler + @property + def supported_features(self) -> tuple[ProviderFeature, ...]: + """Return the features supported by this Provider.""" + return SUPPORTED_FEATURES + async def setup(self) -> None: """Handle async initialization of the provider.""" self._throttler = Throttler(rate_limit=1, period=1) - self._attr_supported_features = ( - ProviderFeature.LIBRARY_RADIOS, - ProviderFeature.BROWSE, - ) + if not self.config.get_value(CONF_USERNAME): raise LoginFailed("Username is invalid") if "@" in self.config.get_value(CONF_USERNAME): diff --git a/music_assistant/server/providers/url/__init__.py b/music_assistant/server/providers/url/__init__.py index 7c9f18cc..aeb248af 100644 --- a/music_assistant/server/providers/url/__init__.py +++ b/music_assistant/server/providers/url/__init__.py @@ -28,7 +28,6 @@ class URLProvider(MusicProvider): Called when provider is registered. """ - self._attr_available = True self._full_url = {} async def get_track(self, prov_track_id: str) -> Track: diff --git a/music_assistant/server/providers/ytmusic/__init__.py b/music_assistant/server/providers/ytmusic/__init__.py index f80b36fa..4765cde5 100644 --- a/music_assistant/server/providers/ytmusic/__init__.py +++ b/music_assistant/server/providers/ytmusic/__init__.py @@ -45,15 +45,24 @@ from .helpers import ( search, ) -# if TYPE_CHECKING: -# from collections.abc import AsyncGenerator - CONF_COOKIE = "cookie" YT_DOMAIN = "https://www.youtube.com" YTM_DOMAIN = "https://music.youtube.com" YTM_BASE_URL = f"{YTM_DOMAIN}/youtubei/v1/" +SUPPORTED_FEATURES = ( + ProviderFeature.LIBRARY_ARTISTS, + ProviderFeature.LIBRARY_ALBUMS, + ProviderFeature.LIBRARY_TRACKS, + ProviderFeature.LIBRARY_PLAYLISTS, + ProviderFeature.BROWSE, + ProviderFeature.SEARCH, + ProviderFeature.ARTIST_ALBUMS, + ProviderFeature.ARTIST_TOPTRACKS, + ProviderFeature.SIMILAR_TRACKS, +) + # TODO: fix disabled tests # ruff: noqa: PLW2901, RET504 @@ -69,17 +78,6 @@ class YoutubeMusicProvider(MusicProvider): async def setup(self) -> None: """Set up the YTMusic provider.""" - self._attr_supported_features = ( - ProviderFeature.LIBRARY_ARTISTS, - ProviderFeature.LIBRARY_ALBUMS, - ProviderFeature.LIBRARY_TRACKS, - ProviderFeature.LIBRARY_PLAYLISTS, - ProviderFeature.BROWSE, - ProviderFeature.SEARCH, - ProviderFeature.ARTIST_ALBUMS, - ProviderFeature.ARTIST_TOPTRACKS, - ProviderFeature.SIMILAR_TRACKS, - ) if not self.config.get_value(CONF_USERNAME) or not self.config.get_value(CONF_COOKIE): raise LoginFailed("Invalid login credentials") await self._initialize_headers(cookie=self.config.get_value(CONF_COOKIE)) @@ -87,6 +85,11 @@ class YoutubeMusicProvider(MusicProvider): self._cookies = {"CONSENT": "YES+1"} self._signature_timestamp = await self._get_signature_timestamp() + @property + def supported_features(self) -> tuple[ProviderFeature, ...]: + """Return the features supported by this Provider.""" + return SUPPORTED_FEATURES + async def search( self, search_query: str, media_types=list[MediaType] | None, limit: int = 5 ) -> list[MediaItemType]: