1616 ContentType ,
1717 ExternalID ,
1818 ImageType ,
19+ MediaType ,
1920 ProviderFeature ,
2021 StreamType ,
2122)
3132 AudioFormat ,
3233 MediaItemImage ,
3334 MediaItemType ,
34- MediaType ,
3535 Playlist ,
3636 ProviderMapping ,
3737 SearchResults ,
4646 VARIOUS_ARTISTS_NAME ,
4747)
4848from music_assistant .controllers .cache import use_cache
49- from music_assistant .helpers .app_vars import app_var
49+ from music_assistant .helpers .app_vars import app_var # type: ignore[attr-defined]
5050from music_assistant .helpers .json import json_loads
5151from music_assistant .helpers .throttle_retry import ThrottlerManager , throttle_with_retries
5252from music_assistant .helpers .util import (
7777 ProviderFeature .LIBRARY_PLAYLISTS_EDIT ,
7878 ProviderFeature .LIBRARY_TRACKS_EDIT ,
7979 ProviderFeature .PLAYLIST_TRACKS_EDIT ,
80+ ProviderFeature .PLAYLIST_CREATE ,
8081 ProviderFeature .BROWSE ,
8182 ProviderFeature .SEARCH ,
8283 ProviderFeature .ARTIST_ALBUMS ,
@@ -227,7 +228,7 @@ async def get_library_playlists(self) -> AsyncGenerator[Playlist, None]:
227228 yield self ._parse_playlist (item )
228229
229230 @use_cache (3600 * 24 * 30 ) # Cache for 30 days
230- async def get_artist (self , prov_artist_id ) -> Artist :
231+ async def get_artist (self , prov_artist_id : str ) -> Artist :
231232 """Get full artist details by id."""
232233 params = {"artist_id" : prov_artist_id }
233234 if (artist_obj := await self ._get_data ("artist/get" , ** params )) and artist_obj ["id" ]:
@@ -236,7 +237,7 @@ async def get_artist(self, prov_artist_id) -> Artist:
236237 raise MediaNotFoundError (msg )
237238
238239 @use_cache (3600 * 24 * 30 ) # Cache for 30 days
239- async def get_album (self , prov_album_id ) -> Album :
240+ async def get_album (self , prov_album_id : str ) -> Album :
240241 """Get full album details by id."""
241242 params = {"album_id" : prov_album_id }
242243 if (album_obj := await self ._get_data ("album/get" , ** params )) and album_obj ["id" ]:
@@ -245,7 +246,7 @@ async def get_album(self, prov_album_id) -> Album:
245246 raise MediaNotFoundError (msg )
246247
247248 @use_cache (3600 * 24 * 30 ) # Cache for 30 days
248- async def get_track (self , prov_track_id ) -> Track :
249+ async def get_track (self , prov_track_id : str ) -> Track :
249250 """Get full track details by id."""
250251 params = {"track_id" : prov_track_id }
251252 if (track_obj := await self ._get_data ("track/get" , ** params )) and track_obj ["id" ]:
@@ -254,16 +255,30 @@ async def get_track(self, prov_track_id) -> Track:
254255 raise MediaNotFoundError (msg )
255256
256257 @use_cache (3600 * 24 * 30 ) # Cache for 30 days
257- async def get_playlist (self , prov_playlist_id ) -> Playlist :
258+ async def get_playlist (self , prov_playlist_id : str ) -> Playlist :
258259 """Get full playlist details by id."""
259260 params = {"playlist_id" : prov_playlist_id }
260261 if (playlist_obj := await self ._get_data ("playlist/get" , ** params )) and playlist_obj ["id" ]:
261262 return self ._parse_playlist (playlist_obj )
262263 msg = f"Item { prov_playlist_id } not found"
263264 raise MediaNotFoundError (msg )
264265
266+ async def create_playlist (self , name : str ) -> Playlist :
267+ """Create a new playlist on Qobuz with the given name."""
268+ playlist_obj = await self ._get_data (
269+ "playlist/create" ,
270+ name = name ,
271+ description = "" ,
272+ is_public = 0 ,
273+ is_collaborative = 0 ,
274+ )
275+ if not playlist_obj or not playlist_obj .get ("id" ):
276+ msg = f"Failed to create playlist: { name } "
277+ raise InvalidDataError (msg )
278+ return self ._parse_playlist (playlist_obj )
279+
265280 @use_cache (3600 * 24 * 30 ) # Cache for 30 days
266- async def get_album_tracks (self , prov_album_id ) -> list [Track ]:
281+ async def get_album_tracks (self , prov_album_id : str ) -> list [Track ]:
267282 """Get all album tracks for given album id."""
268283 params = {"album_id" : prov_album_id }
269284 return [
@@ -295,7 +310,7 @@ async def get_playlist_tracks(self, prov_playlist_id: str, page: int = 0) -> lis
295310 return result
296311
297312 @use_cache (3600 * 24 * 14 ) # Cache for 14 days
298- async def get_artist_albums (self , prov_artist_id ) -> list [Album ]:
313+ async def get_artist_albums (self , prov_artist_id : str ) -> list [Album ]:
299314 """Get a list of albums for the given artist."""
300315 result = await self ._get_data (
301316 "artist/get" ,
@@ -311,7 +326,7 @@ async def get_artist_albums(self, prov_artist_id) -> list[Album]:
311326 ]
312327
313328 @use_cache (3600 * 24 * 14 ) # Cache for 14 days
314- async def get_artist_toptracks (self , prov_artist_id ) -> list [Track ]:
329+ async def get_artist_toptracks (self , prov_artist_id : str ) -> list [Track ]:
315330 """Get a list of most popular tracks for the given artist."""
316331 result = await self ._get_data (
317332 "artist/get" ,
@@ -387,7 +402,7 @@ async def add_playlist_tracks(self, prov_playlist_id: str, prov_track_ids: list[
387402
388403 async def remove_playlist_tracks (
389404 self , prov_playlist_id : str , positions_to_remove : tuple [int ]
390- ) -> None :
405+ ) -> Any :
391406 """Remove track(s) from playlist."""
392407 playlist_track_ids = set ()
393408 for pos in positions_to_remove :
@@ -497,7 +512,7 @@ async def on_streamed(
497512 duration = try_parse_int (streamdetails .seconds_streamed ),
498513 )
499514
500- def _parse_artist (self , artist_obj : dict ):
515+ def _parse_artist (self , artist_obj : dict ) -> Artist :
501516 """Parse qobuz artist object to generic layout."""
502517 artist = Artist (
503518 item_id = str (artist_obj ["id" ]),
@@ -686,7 +701,7 @@ async def _parse_track(self, track_obj: dict) -> Track:
686701
687702 return track
688703
689- def _parse_playlist (self , playlist_obj ) :
704+ def _parse_playlist (self , playlist_obj : str ) -> Playlist :
690705 """Parse qobuz playlist object to generic layout."""
691706 is_editable = (
692707 playlist_obj ["owner" ]["id" ] == self ._user_auth_info ["user" ]["id" ]
@@ -719,7 +734,7 @@ def _parse_playlist(self, playlist_obj):
719734 return playlist
720735
721736 @lock
722- async def _auth_token (self ):
737+ async def _auth_token (self ) -> None :
723738 """Login to qobuz and store the token."""
724739 if self ._user_auth_info :
725740 return self ._user_auth_info ["user_auth_token" ]
@@ -738,7 +753,7 @@ async def _auth_token(self):
738753 return details ["user_auth_token" ]
739754 return None
740755
741- async def _get_all_items (self , endpoint , key = "tracks" , ** kwargs ):
756+ async def _get_all_items (self , endpoint , key = "tracks" , ** kwargs ) -> list [ dict ] :
742757 """Get all items from a paged list."""
743758 limit = 50
744759 offset = 0
@@ -759,7 +774,9 @@ async def _get_all_items(self, endpoint, key="tracks", **kwargs):
759774 return all_items
760775
761776 @throttle_with_retries
762- async def _get_data (self , endpoint , sign_request = False , ** kwargs ):
777+ async def _get_data (
778+ self , endpoint : str , sign_request : bool = False , ** kwargs : Any
779+ ) -> dict | None :
763780 """Get data from api."""
764781 self .logger .debug ("Handling GET request to %s" , endpoint )
765782 url = f"http://www.qobuz.com/api.json/0.2/{ endpoint } "
@@ -808,7 +825,12 @@ async def _get_data(self, endpoint, sign_request=False, **kwargs):
808825 raise InvalidDataError (msg )
809826
810827 @throttle_with_retries
811- async def _post_data (self , endpoint , params = None , data = None ):
828+ async def _post_data (
829+ self ,
830+ endpoint : str ,
831+ params : dict [str , Any ] | None = None ,
832+ data : dict [str , Any ] | None = None ,
833+ ) -> dict [str , Any ]:
812834 """Post data to api."""
813835 self .logger .debug ("Handling POST request to %s" , endpoint )
814836 if not params :
0 commit comments