Source code for minim.api.spotify._web_api.playlists

from __future__ import annotations
from pathlib import Path
from typing import TYPE_CHECKING

from ..._shared import TTLCache, _copy_docstring
from ._shared import SpotifyResourceAPI
from .users import UsersAPI

if TYPE_CHECKING:
    from typing import Any

    from ...._types import Collection


[docs] class PlaylistsAPI(SpotifyResourceAPI): """ Playlists API endpoints for the Spotify Web API. .. important:: This class is managed by :class:`~minim.api.spotify.SpotifyWebAPIClient` and should not be instantiated directly. """ __slots__ = ()
[docs] @TTLCache.cached_method(ttl="user") def get_playlist( self, playlist_id: str, /, *, supported_item_types: str | Collection[str] | None = None, fields: str | Collection[str] | None = None, country_code: str | None = None, ) -> dict[str, Any]: """ `Playlists > Get Playlist <https://developer.spotify.com /documentation/web-api/reference/get-playlist>`_: Get Spotify catalog information for a playlist. .. admonition:: Authorization scopes and third-party application mode :class: entitlement .. tab-set:: .. tab-item:: Conditional :code:`playlist-read-private` scope Access your private playlists. `Learn more. <https://developer.spotify.com/documentation/web-api /concepts/scopes#playlist-read-private>`__ :code:`playlist-read-collaborative` scope Access your collaborative playlists. `Learn more. <https://developer.spotify.com/documentation/web-api /concepts/scopes#playlist-read-collaborative>`__ .. tab-item:: Optional Extended quota mode before November 27, 2024 Access 30-second preview URLs. `Learn more. <https://developer.spotify.com/blog /2024-11-27-changes-to-the-web-api>`__ Parameters ---------- playlist_id : str; positional-only Spotify ID of the playlist. **Example**: :code:`"3cEYpjA9oz9GiPac4AsH4n"`. supported_item_types : str or Collection[str]; keyword-only; optional Item types supported by the client. .. note:: This parameter was introduced to allow existing clients to maintain their current behavior and may be deprecated in the future. **Valid values**: :code:`"track"`, :code:`"episode"`. **API default**: :code:`"track"`. **Examples**: :code:`"track"`, :code:`"track,episode"`, :code:`["track", "episode"]`. fields : str or Collection[str]; keyword-only; optional Fields to return. Use a dot separator to specify non-recurring fields and parentheses to specify recurring fields within objects. Multiple levels of parentheses can be used to drill down into nested objects. Fields can be excluded by prefixing them with an exclamation mark. If not specified, all fields are returned. **Examples**: * :code:`"description,uri"` – Returns only the playlist description and URI. * :code:`"tracks.items(added_at,added_by.id)"` – Returns only the date added and the Spotify user ID of the user who added the track. * :code:`"tracks.items(track(name,href,album(name,href)))"` – Drills down into the album details. * :code:`"tracks.items(track(name,href,album(!name,href)))"` – Excludes the album name. country_code : str; keyword-only; optional ISO 3166-1 alpha-2 country code. If provided, only content available in that market is returned. When a user access token accompanies the request, the country associated with the user account takes priority over this parameter. .. note:: If neither a country code is provided nor a country can be determined from the user account, the content is considered unavailable for the client. **Example**: :code:`"ES"`. Returns ------- playlist : dict[str, Any] Spotify metadata for the playlist. .. admonition:: Sample response :class: response dropdown .. code-block:: { "collaborative": <bool>, "description": <str>, "external_urls": { "spotify": <str> }, "followers": { "href": <str>, "total": <int> }, "href": <str>, "id": <str>, "images": [ { "height": <int>, "url": <str>, "width": <int> } ], "name": <str>, "owner": { "display_name": <str>, "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "type": "user", "uri": <str> }, "primary_color": <int>, "public": <bool>, "snapshot_id": <str>, "tracks": { "href": <str>, "items": [ { "added_at": <str>, "added_by": { "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "type": "user", "uri": <str> }, "is_local": <bool>, "primary_color": <str>, "track": { "audio_preview_url": <str>, "description": <str>, "duration_ms": <int>, "episode": <bool>, "explicit": <bool>, "external_urls": { "spotify": <str> }, "href": <str>, "html_description": <str>, "id": <str>, "images": [ { "height": <int>, "url": <str>, "width": <int> } ], "is_externally_hosted": <bool>, "is_playable": <bool>, "language": <str>, "languages": <list[str]>, "name": <str>, "release_date": <str>, "release_date_precision": <str>, "show": { "copyrights": <list[str]>, "description": <str>, "explicit": <bool>, "external_urls": { "spotify": <str> }, "href": <str>, "html_description": <str>, "id": <str>, "images": [ { "height": <int>, "url": <str>, "width": <int> } ], "is_externally_hosted": <bool>, "languages": <list[str]>, "media_type": <str>, "name": <str>, "publisher": <str>, "total_episodes": <int>, "type": "show", "uri": <str> }, "track": <bool>, "type": "track", "uri": <str> }, "video_thumbnail": { "url": <str> } }, { "added_at": <str>, "added_by": { "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "type": "user", "uri": <str> }, "is_local": <bool>, "primary_color": <str>, "track": { "album": { "album_type": <str>, "artists": [ { "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "name": <str>, "type": "artist", "uri": <str> } ], "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "images": [ { "height": <int>, "url": <str>, "width": <int> } ], "is_playable": <bool>, "name": <str>, "release_date": <str>, "release_date_precision": <str>, "total_tracks": <int>, "type": "album", "uri": <str> }, "artists": [ { "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "name": <str>, "type": "artist", "uri": <str> } ], "disc_number": <int>, "duration_ms": <int>, "episode": <bool>, "explicit": <bool>, "external_ids": { "isrc": <str> }, "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "is_local": <bool>, "is_playable": <bool>, "name": <str>, "popularity": <int>, "preview_url": <str>, "track": <bool>, "track_number": <int>, "type": "track", "uri": <str> }, "video_thumbnail": { "url": <str> } } ], "limit": <int>, "next": <int>, "offset": <int>, "previous": <int>, "total": <int> }, "type": "playlist", "uri": <str> } """ self._validate_spotify_id(playlist_id) params = {} if supported_item_types is not None: params["additional_types"] = self._prepare_types( supported_item_types, allowed_types=self._AUDIO_TYPES, type_prefix="audio", ) if fields is not None: if isinstance(fields, str): if len(fields): params["fields"] = fields else: params["fields"] = ",".join(fields) if country_code is not None: self._client.markets._validate_market(country_code) params["market"] = country_code return self._client._request( "GET", f"playlists/{playlist_id}", params=params ).json()
[docs] def update_playlist_details( self, playlist_id: str, /, *, name: str | None = None, description: str | None = None, public: bool | None = None, collaborative: bool | None = None, ) -> None: """ `Playlists > Change Playlist Details <https://developer.spotify.com/documentation/web-api/reference /change-playlist-details>`_: Update the details of a playlist. .. admonition:: Authorization scopes :class: entitlement .. tab-set:: .. tab-item:: Conditional :code:`playlist-modify-public` scope Manage your public playlists. `Learn more. <https://developer.spotify.com/documentation/web-api /concepts/scopes#playlist-modify-public>`__ :code:`playlist-modify-private` scope Manage your private playlists. `Learn more. <https://developer.spotify.com/documentation/web-api /concepts/scopes#playlist-modify-private>`__ .. important:: At least one of :code:`name`, :code:`description`, :code:`public`, or :code:`collaborative` must be specified. Parameters ---------- playlist_id : str; positional-only Spotify ID of the playlist. **Example**: :code:`"3cEYpjA9oz9GiPac4AsH4n"`. name : str; keyword-only; optional New playlist name. description : str; keyword-only; optional New playlist description. public : bool; keyword-only; optional Whether the playlist is displayed on the current user's profile. collaborative : bool; keyword-only; optional Whether other users can modify the playlist. .. note:: :code:`collaborative=True` can only be set on private playlists. """ self._validate_spotify_id(playlist_id) payload = {} if name is not None: payload["name"] = self._prepare_string("name", name) if description is not None: payload["description"] = self._prepare_string( "description", description, allow_blank=True ) if public is not None: self._validate_type("public", public, bool) payload["public"] = public if collaborative is not None: self._validate_type("collaborative", collaborative, bool) if collaborative: if public is None: payload["public"] = False elif public: raise ValueError( "`public` must be False when `collaborative` is True." ) payload["collaborative"] = collaborative if not payload: raise ValueError("At least one change must be specified.") self._client._request("PUT", f"playlists/{playlist_id}", json=payload)
[docs] @TTLCache.cached_method(ttl="user") def get_playlist_items( self, playlist_id: str, /, *, supported_item_types: str | Collection[str] | None = None, fields: str | Collection[str] | None = None, country_code: str | None = None, limit: int | None = None, offset: int | None = None, ) -> dict[str, Any]: """ `Playlists > Get Playlist Items <https://developer.spotify.com /documentation/web-api/reference/get-playlists-tracks>`_: Get Spotify catalog information for items in a playlist. .. admonition:: Authorization scopes and third-party application mode :class: entitlement .. tab-set:: .. tab-item:: Conditional :code:`playlist-read-private` scope Access your private playlists. `Learn more. <https://developer.spotify.com/documentation/web-api /concepts/scopes#playlist-read-private>`__ :code:`playlist-read-collaborative` scope Access your collaborative playlists. `Learn more. <https://developer.spotify.com/documentation/web-api /concepts/scopes#playlist-read-collaborative>`__ .. tab-item:: Optional Extended quota mode before November 27, 2024 Access 30-second preview URLs. `Learn more. <https://developer.spotify.com/blog /2024-11-27-changes-to-the-web-api>`__ Parameters ---------- playlist_id : str; positional-only Spotify ID of the playlist. **Example**: :code:`"3cEYpjA9oz9GiPac4AsH4n"`. supported_item_types : str or Collection[str]; keyword-only; \ optional Item types supported by the client. .. note:: This parameter was introduced to allow existing clients to maintain their current behavior and may be deprecated in the future. **Valid values**: :code:`"track"`, :code:`"episode"`. **API default**: :code:`"track"`. **Examples**: :code:`"track"`, :code:`"track,episode"`, :code:`["track", "episode"]`. fields : str or Collection[str]; keyword-only; optional Fields to return. Use a dot separator to specify non-recurring fields and parentheses to specify recurring fields within objects. Multiple levels of parentheses can be used to drill down into nested objects. Fields can be excluded by prefixing them with an exclamation mark. If not specified, all fields are returned. **Examples**: * :code:`"description,uri"` – Returns only the playlist description and URI. * :code:`"tracks.items(added_at,added_by.id)"` – Returns only the date added and the Spotify user ID of the user who added the track. * :code:`"tracks.items(track(name,href,album(name,href)))"` – Drills down into the album details. * :code:`"tracks.items(track(name,href,album(!name,href)))"` – Excludes the album name. country_code : str; keyword-only; optional ISO 3166-1 alpha-2 country code. If provided, only content available in that market is returned. When a user access token accompanies the request, the country associated with the user account takes priority over this parameter. .. note:: If neither a country code is provided nor a country can be determined from the user account, the content is considered unavailable for the client. **Example**: :code:`"ES"`. limit : int; keyword-only; optional Maximum number of items to return. **Valid range**: :code:`1` to :code:`50`. **API default**: :code:`20`. offset : int; keyword-only; optional Index of the first item to return. Use with `limit` to get the next batch of items. **Minimum value**: :code:`0`. **API default**: :code:`0`. Returns ------- items : dict[str, Any] Page of Spotify metadata for the playlist items. .. admonition:: Sample response :class: response dropdown .. code-block:: { "href": <str>, "items": [ { "added_at": <str>, "added_by": { "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "type": "user", "uri": <str> }, "is_local": <bool>, "primary_color": <str>, "track": { "album": { "album_type": <str>, "artists": [ { "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "name": <str>, "type": "artist", "uri": <str> } ], "available_markets": <list[str]>, "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "images": [ { "height": <int>, "url": <str>, "width": <int> } ], "name": <str>, "release_date": <str>, "release_date_precision": <str>, "total_tracks": <int>, "type": "album", "uri": <str> }, "artists": [ { "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "name": <str>, "type": "artist", "uri": <str> } ], "available_markets": <list[str]>, "disc_number": <int>, "duration_ms": <int>, "episode": <bool>, "explicit": <bool>, "external_ids": { "spotify": <str> }, "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "is_local": <bool>, "name": <str>, "popularity": <int>, "preview_url": <str>, "track": <bool>, "track_number": <int>, "type": "track", "uri": <str> }, "video_thumbnail": { "url": <str> } }, { "added_at": <str>, "added_by": { "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "type": "user", "uri": <str> }, "is_local": <bool>, "primary_color": <str>, "track": { "album": { "album_type": <str>, "artists": [ { "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "name": <str>, "type": "artist", "uri": <str> } ], "available_markets": <list[str]>, "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "images": [ { "height": <int>, "url": <str>, "width": <int> } ], "name": <str>, "release_date": <str>, "release_date_precision": <str>, "total_tracks": <int>, "type": "album", "uri": <str> }, "artists": [ { "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "name": <str>, "type": "artist", "uri": <str> } ], "available_markets": <list[str]>, "disc_number": <int>, "duration_ms": <int>, "episode": <bool>, "explicit": <bool>, "external_ids": { "isrc": <str> }, "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "is_local": <bool>, "name": <str>, "popularity": <int>, "preview_url": <str>, "track": <bool>, "track_number": <int>, "type": "track", "uri": <str> }, "video_thumbnail": { "url": <str> } } ], "limit": <int>, "next": <int>, "offset": <int>, "previous": <int>, "total": <int> } """ params = {} if supported_item_types is not None: params["additional_types"] = self._prepare_types( supported_item_types, allowed_types=self._AUDIO_TYPES, type_prefix="audio", ) if fields is not None: if isinstance(fields, str): if len(fields): params["fields"] = fields else: params["fields"] = ",".join(fields) return self._get_resource_items( "playlists", playlist_id, "items", country_code=country_code, limit=limit, offset=offset, params=params, )
[docs] def add_playlist_items( self, playlist_id: str, /, uris: str | Collection[str], *, to_index: int | None = None, ) -> dict[str, str]: """ `Playlists > Add Items to Playlist <https://developer.spotify.com/documentation/web-api/reference /add-tracks-to-playlist>`_: Add items to a playlist. .. admonition:: Authorization scopes and user authentication :class: entitlement .. tab-set:: .. tab-item:: Required User authentication Access and manage your library. .. tab-item:: Conditional :code:`playlist-modify-public` scope Manage your public playlists. `Learn more. <https://developer.spotify.com/documentation/web-api /concepts/scopes#playlist-modify-public>`__ :code:`playlist-modify-private` scope Manage your private playlists. `Learn more. <https://developer.spotify.com/documentation/web-api /concepts/scopes#playlist-modify-private>`__ Parameters ---------- playlist_id : str; positional-only Spotify ID of the playlist. **Example**: :code:`"3cEYpjA9oz9GiPac4AsH4n"`. uris : str or Collection[str] Spotify URIs of tracks and/or show episodes. A maximum of 100 URIs can be sent in a request. **Examples**: * :code:`"spotify:track:4iV5W9uYEdYUVa79Axb7RhQ"`, * :code:`"spotify:track:4iV5W9uYEdYUVa79Axb7Rh,spotify:track:1301WleyT98MSxVHPZCA6M"`, * .. code-block:: [ "spotify:track:4iV5W9uYEdYUVa79Axb7Rh", "spotify:track:1301WleyT98MSxVHPZCA6M", "spotify:episode:512ojhOuo1ktJprKbVcKyQ", ] to_index : int; keyword-only; optional Zero-based index at which to insert the tracks and/or shows. If not specified, the items are appended to the end of the playlist. **Examples**: * :code:`0` – Insert items in the first position. * :code:`2` – Insert items in the third position. Returns ------- snapshot_id : dict[str, str] Version identifier for the playlist after the items have been added. **Sample response**: :code:`{"snapshot_id": "AAAAB8C+GjVHq8v4vzStbL6AUYzo1cDV"}`. """ self._client._require_authentication("playlists.add_playlist_items") self._validate_spotify_id(playlist_id) params = {} if to_index is not None: self._validate_number("to_index", to_index, int, 0) params["position"] = to_index return self._client._request( "POST", f"playlists/{playlist_id}/items", params=params, json={ "uris": self._prepare_spotify_uris( uris, limit=100, resource_types=self._AUDIO_TYPES ) }, ).json()
[docs] def reorder_playlist_items( self, playlist_id: str, /, *, from_index: int, to_index: int, from_count: int | None = None, snapshot_id: str | None = None, ) -> dict[str, str]: """ `Playlists > Update Playlist Items <https://developer.spotify.com/documentation/web-api/reference /reorder-or-replace-playlists-tracks>`__: Reorder items in a playlist. .. admonition:: Authorization scopes and user authentication :class: entitlement .. tab-set:: .. tab-item:: Required User authentication Access and manage your library. .. tab-item:: Conditional :code:`playlist-modify-public` scope Manage your public playlists. `Learn more. <https://developer.spotify.com/documentation/web-api /concepts/scopes#playlist-modify-public>`__ :code:`playlist-modify-private` scope Manage your private playlists. `Learn more. <https://developer.spotify.com/documentation/web-api /concepts/scopes#playlist-modify-private>`__ Parameters ---------- playlist_id : str; positional-only Spotify ID of the playlist. **Example**: :code:`"3cEYpjA9oz9GiPac4AsH4n"`. from_index : int; keyword-only Zero-based index of the first item to be reordered. to_index : int; keyword-only Zero-based index at which to insert the items. **Examples**: * :code:`0` – Move items selected by `range_start` and `range_length` before the current item in the first position. * :code:`10` – Move items selected by `range_start` and `range_length` before the current item in the eleventh position. from_count : int; keyword-only; optional Number of items, starting from `range_start`, to be reordered. **API default**: :code:`1`. snapshot_id : str; keyword-only; optional Version identifier for the playlist against which to make changes. Returns ------- snapshot_id : dict[str, str] Version identifier for the playlist after the items have been reordered. **Sample response**: :code:`{"snapshot_id": "AAAAB8C+GjVHq8v4vzStbL6AUYzo1cDV"}`. """ self._client._require_authentication( "playlists.reorder_playlist_items" ) self._validate_spotify_id(playlist_id) self._validate_number("from_index", from_index, int, 0) self._validate_number("to_index", to_index, int, 0) payload = {"insert_before": to_index, "range_start": from_index} if from_count is not None: self._validate_number("from_count", from_count, int, 1) payload["range_length"] = from_count if snapshot_id is not None: payload["snapshot_id"] = self._prepare_string( "snapshot_id", snapshot_id ) return self._client._request( "PUT", f"playlists/{playlist_id}/items", json=payload ).json()
[docs] def replace_playlist_items( self, playlist_id: str, /, uris: str | Collection[str] ) -> dict[str, str]: """ `Playlists > Update Playlist Items <https://developer.spotify.com/documentation/web-api/reference /reorder-or-replace-playlists-tracks>`__: Clear and replace items in a playlist. .. admonition:: Authorization scopes and user authentication :class: entitlement .. tab-set:: .. tab-item:: Required User authentication Access and manage your library. .. tab-item:: Conditional :code:`playlist-modify-public` scope Manage your public playlists. `Learn more. <https://developer.spotify.com/documentation/web-api /concepts/scopes#playlist-modify-public>`__ :code:`playlist-modify-private` scope Manage your private playlists. `Learn more. <https://developer.spotify.com/documentation/web-api /concepts/scopes#playlist-modify-private>`__ Parameters ---------- playlist_id : str; positional-only Spotify ID of the playlist. **Example**: :code:`"3cEYpjA9oz9GiPac4AsH4n"`. uris : str or Collection[str] Spotify URIs of tracks and/or show episodes. A maximum of 100 URIs can be sent in a request. **Examples**: * :code:`"spotify:track:4iV5W9uYEdYUVa79Axb7RhQ"`, * :code:`"spotify:track:4iV5W9uYEdYUVa79Axb7Rh,spotify:track:1301WleyT98MSxVHPZCA6M"`, * .. code-block:: [ "spotify:track:4iV5W9uYEdYUVa79Axb7Rh", "spotify:track:1301WleyT98MSxVHPZCA6M", "spotify:episode:512ojhOuo1ktJprKbVcKyQ", ] Returns ------- snapshot_id : dict[str, str] Version identifier for the playlist after the items have been replaced. **Sample response**: :code:`{"snapshot_id": "AAAAB8C+GjVHq8v4vzStbL6AUYzo1cDV"}`. """ self._client._require_authentication( "playlists.replace_playlist_items" ) self._validate_spotify_id(playlist_id) return self._client._request( "PUT", f"playlists/{playlist_id}/items", json={ "uris": self._prepare_spotify_uris( uris, limit=100, resource_types=self._AUDIO_TYPES ) if uris else [] }, ).json()
[docs] def remove_playlist_items( self, playlist_id: str, /, uris: str | Collection[str], *, snapshot_id: str | None = None, ) -> dict[str, str]: """ `Playlists > Remove Playlist Items <https://developer.spotify.com/documentation/web-api/reference /remove-tracks-playlist>`_: Remove items from a playlist. .. admonition:: Authorization scopes and user authentication :class: entitlement .. tab-set:: .. tab-item:: Required User authentication Access and manage your library. .. tab-item:: Conditional :code:`playlist-modify-public` scope Manage your public playlists. `Learn more. <https://developer.spotify.com/documentation/web-api /concepts/scopes#playlist-modify-public>`__ :code:`playlist-modify-private` scope Manage your private playlists. `Learn more. <https://developer.spotify.com/documentation/web-api /concepts/scopes#playlist-modify-private>`__ Parameters ---------- playlist_id : str; positional-only Spotify ID of the playlist. **Example**: :code:`"3cEYpjA9oz9GiPac4AsH4n"`. uris : str or Collection[str] Spotify URIs of tracks and/or show episodes. A maximum of 100 URIs can be sent in a request. **Examples**: * :code:`"spotify:track:4iV5W9uYEdYUVa79Axb7RhQ"`, * :code:`"spotify:track:4iV5W9uYEdYUVa79Axb7Rh,spotify:track:1301WleyT98MSxVHPZCA6M"`, * .. code-block:: [ "spotify:track:4iV5W9uYEdYUVa79Axb7Rh", "spotify:track:1301WleyT98MSxVHPZCA6M", "spotify:episode:512ojhOuo1ktJprKbVcKyQ", ] snapshot_id : str; keyword-only; optional Version identifier for the playlist against which to make changes. Returns ------- snapshot_id : dict[str, str] Version identifier for the playlist after the items have been removed. **Sample response**: :code:`{"snapshot_id": "AAAAB8C+GjVHq8v4vzStbL6AUYzo1cDV"}`. """ self._client._require_authentication("playlists.remove_playlist_items") self._validate_spotify_id(playlist_id) payload = { "items": self._prepare_spotify_uris( uris, limit=100, resource_types=self._AUDIO_TYPES ) } if snapshot_id is not None: payload["snapshot_id"] = self._prepare_string( "snapshot_id", snapshot_id ) return self._client._request( "DELETE", f"playlists/{playlist_id}/items", json=payload ).json()
[docs] @_copy_docstring(UsersAPI.get_my_playlists) def get_my_playlists( self, *, limit: int | None = None, offset: int | None = None ) -> dict[str, Any]: return self._client.users.get_my_playlists(limit=limit, offset=offset)
[docs] @_copy_docstring(UsersAPI.get_user_playlists) def get_user_playlists( self, user_id: str | None = None, /, *, limit: int | None = None, offset: int | None = None, ) -> dict[str, Any]: return self._client.users.get_user_playlists( user_id, limit=limit, offset=offset )
[docs] def create_playlist( self, name: str, *, description: str | None = None, public: bool | None = None, collaborative: bool | None = None, ) -> dict[str, Any]: """ `Playlists > Create Playlist <https://developer.spotify.com /documentation/web-api/reference/create-playlist>`_: Create a playlist. .. admonition:: Authorization scopes and user authentication :class: entitlement .. tab-set:: .. tab-item:: Required User authentication Access and manage your library. .. tab-item:: Conditional :code:`playlist-modify-public` scope Manage your public playlists. `Learn more. <https://developer.spotify.com/documentation/web-api /concepts/scopes#playlist-modify-public>`__ :code:`playlist-modify-private` scope Manage your private playlists. `Learn more. <https://developer.spotify.com/documentation/web-api /concepts/scopes#playlist-modify-private>`__ Parameters ---------- name : str Playlist name. **Example**: :code:`"My New Playlist Title"`. description : str; keyword-only; optional Playlist description. public : bool; keyword-only; optional Whether the playlist is displayed on the current user's profile. **API default**: :code:`True`. collaborative : bool; keyword-only; optional Whether other users can modify the playlist. .. note:: :code:`public=False` must accompany :code:`collaborative=True` to create a collaborative playlist. **API default**: :code:`False`. Returns ------- playlist : dict[str, Any] Spotify metadata for the newly created playlist. .. admonition:: Sample response :class: response dropdown .. code-block:: { "collaborative": <bool>, "description": <str>, "external_urls": { "spotify": <str> }, "followers": { "href": None, "total": 0 }, "href": <str>, "id": <str>, "images": [], "name": <str>, "owner": { "display_name": <str>, "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "type": "user", "uri": <str> }, "primary_color": None, "public": <bool>, "snapshot_id": <str>, "tracks": { "href": <str>, "items": [], "limit": 100, "next": None, "offset": 0, "previous": None, "total": 0 }, "type": "playlist", "uri": <str> } """ self._client._require_authentication("playlists.create_playlist") payload = {"name": self._prepare_string("name", name)} if description is not None: payload["description"] = self._prepare_string( "description", description, allow_blank=True ) if public is not None: self._validate_type("public", public, bool) self._client._require_scopes( "playlists.create_playlist", f"playlist-modify-{'public' if public else 'private'}", ) payload["public"] = public if collaborative is not None: self._validate_type("collaborative", collaborative, bool) if collaborative: if public is None: payload["public"] = False elif public: raise ValueError( "`public` must be False when `collaborative` is True." ) payload["collaborative"] = collaborative return self._client._request( "POST", f"users/{self._client.my_profile['id']}/playlists", json=payload, ).json()
[docs] @TTLCache.cached_method(ttl="daily") def get_categorized_playlists( self, category_id: str, /, *, limit: int | None = None, offset: int | None = None, ) -> dict[str, Any]: """ `Playlists > Get Category's Playlist <https://developer.spotify.com/documentation/web-api/reference /get-a-categories-playlists>`_: Get Spotify catalog information for playlists tagged with a particular category. .. admonition:: Third-party application mode :class: entitlement dropdown .. tab-set:: .. tab-item:: Optional Extended quota mode before November 27, 2024 Access 30-second preview URLs. `Learn more. <https://developer.spotify.com/blog /2024-11-27-changes-to-the-web-api>`__ Parameters ---------- category_id : str, positional-only Spotify category ID. .. seealso:: :meth:`~minim.api.spotify.CategoriesAPI.get_categories` – Get information on available categories. **Examples**: :code:`"dinner"`, :code:`"party"`. limit : int; keyword-only; optional Maximum number of playlists to return. **Valid range**: :code:`1` to :code:`50`. **API default**: :code:`20`. offset : int; keyword-only; optional Index of the first playlist to return. Use with `limit` to get the next batch of playlists. **Minimum value**: :code:`0`. **API default**: :code:`0`. Returns ------- playlists : dict[str, Any] Page of Spotify metadata for the playlists in the specified category. .. admonition:: Sample response :class: response dropdown .. code-block:: { "message": <str>, "playlists": { "href": <str>, "items": [ { "collaborative": <bool>, "description": <str>, "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "images": [ { "height": <int>, "url": <str>, "width": <int> } ], "name": <str>, "owner": { "display_name": <str>, "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "type": <str>, "uri": <str> }, "public": <bool>, "snapshot_id": <str>, "tracks": { "href": <str>, "total": <int> }, "type": <str>, "uri": <str> } ], "limit": <int>, "next": <str>, "offset": <int>, "previous": <str>, "total": <int> } } """ params = {} if limit is not None: self._validate_number("limit", limit, int, 1, 50) params["limit"] = limit if offset is not None: self._validate_number("offset", offset, int, 0) params["offset"] = offset return self._client._request( "GET", f"browse/categories/{category_id}/playlists", params=params ).json()
[docs] @TTLCache.cached_method(ttl="user") def get_playlist_cover_image( self, playlist_id: str, / ) -> list[dict[str, int | str]]: """ `Playlists > Get Playlist Cover Image <https://developer.spotify.com/documentation/web-api/reference /get-playlist-cover>`_: Get the cover image for a playlist. Parameters ---------- playlist_id : str; positional-only Spotify ID of the playlist. **Example**: :code:`"3cEYpjA9oz9GiPac4AsH4n"`. Returns ------- playlist_cover_image : list[dict[str, int | str]] Playlist cover image. **Sample response**: .. code-block:: [ { "height": <int>, "url": <str>, "width": <int> } ] """ self._validate_spotify_id(playlist_id) return self._client._request( "GET", f"playlists/{playlist_id}/images" ).json()
[docs] def add_playlist_cover_image( self, playlist_id: str, /, image: bytes | str | Path ) -> None: """ `Playlists > Add Custom Playlist Cover Image <https://developer.spotify.com/documentation/web-api/reference /upload-custom-playlist-cover>`_: Add a custom cover image to a playlist. .. admonition:: Authorization scopes :class: entitlement .. tab-set:: .. tab-item:: Required :code:`ugc-image-upload` scope Upload images to Spotify on your behalf. `Learn more. <https://developer.spotify.com/documentation /web-api/concepts/scopes#ugc-image-upload>`__ .. tab-item:: Conditional :code:`playlist-modify-public` scope Manage your public playlists. `Learn more. <https://developer.spotify.com/documentation/web-api /concepts/scopes#playlist-modify-public>`__ :code:`playlist-modify-private` scope Manage your private playlists. `Learn more. <https://developer.spotify.com/documentation/web-api /concepts/scopes#playlist-modify-private>`__ Parameters ---------- playlist_id : str; positional-only Spotify ID of the playlist. **Example**: :code:`"3cEYpjA9oz9GiPac4AsH4n"`. image : bytes, str, or pathlib.Path Base64-encoded JPEG image data, provided as a bytes object or a file path. **Example**: :code:`"/9j/2wCEABoZGSccJz4lJT5CLy8vQkc9Ozs9R0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0cBHCcnMyYzPSYmPUc9Mj1HR0dEREdHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR//dAAQAAf/uAA5BZG9iZQBkwAAAAAH/wAARCAABAAEDACIAAREBAhEB/8QASwABAQAAAAAAAAAAAAAAAAAAAAYBAQAAAAAAAAAAAAAAAAAAAAAQAQAAAAAAAAAAAAAAAAAAAAARAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwAAARECEQA/AJgAH//Z"`. """ self._client._require_scopes( "playlists.add_playlist_cover_image", "ugc-image-upload" ) self._validate_spotify_id(playlist_id) if isinstance(image, str | Path): image = Path(image).expanduser().resolve(True) with open(image, "rb") as f: image = f.read() if not isinstance(image, bytes) or not ( image.startswith(b"\xff\xd8") and image.endswith(b"\xff\xd9") ): raise ValueError( "The value or file provided via `image` does not " "contain binary data or a JPEG image." ) if len(image) < 262_144: raise ValueError( "The JPEG image provided via `image` exceeds 256 KB." ) self._client._request( "PUT", f"playlists/{playlist_id}/images", data=image, headers={"content-type": "image/jpeg"}, )
[docs] @_copy_docstring(UsersAPI.follow_playlist) def follow_playlist( self, playlist_id: str, /, *, public: bool | None = None ) -> None: self._client.users.follow_playlist(playlist_id, public=public)
[docs] @_copy_docstring(UsersAPI.unfollow_playlist) def unfollow_playlist(self, playlist_id: str, /) -> None: self._client.users.unfollow_playlist(playlist_id)
[docs] @_copy_docstring(UsersAPI.is_following_playlist) def is_following_playlist(self, playlist_id: str, /) -> bool: return self._client.users.is_following_playlist(playlist_id)