Source code for minim.api.tidal._api.playlists

from __future__ import annotations
from typing import TYPE_CHECKING

from ...._types import ORDERED_COLLECTION_TYPES
from ..._shared import TTLCache, _copy_docstring
from ._shared import TIDALResourceAPI
from .search import SearchAPI
from .users import UsersAPI

if TYPE_CHECKING:
    from typing import Any

    from ...._types import Collection


[docs] class PlaylistsAPI(TIDALResourceAPI): """ Playlists API endpoints for the TIDAL API. .. important:: This class is managed by :class:`~minim.api.tidal.TIDALAPIClient` and should not be instantiated directly. """ _ITEM_TYPES = {"tracks", "videos"} _RELATIONSHIPS = { "collaboratorProfiles", "collaborators", "coverArt", "items", "ownerProfiles", "owners", } _SORT_FIELDS = {"createdAt", "lastModifiedAt", "name"} __slots__ = () @classmethod def _process_playlist_items( cls, items: tuple[int | str, str] | tuple[int | str, str, str] | dict[str, Any] | Collection[ tuple[int | str, str] | tuple[int | str, str, str] | dict[str, Any] ], /, *, meta: bool = True, recursive: bool = True, ) -> list[dict[str, Any]]: """ Process user-specified items to add to, update in, or remove from a playlist. Parameters ---------- items : tuple[int | str, ...], dict[str, Any], or \ Collection[tuple[int | str, ...] | dict[str, Any]]; \ positional-only TIDAL IDs (and UUIDs) of tracks and videos, provided as tuples of the ID (and UUID) and the item type, or properly formatted dictionaries. meta : bool; keyword-only; default: :code:`True` Whether the items have UUID information. Returns ------- items : list[dict[str, Any]] List of properly formatted dictionaries containing metadata for the tracks and videos. """ if isinstance(items, dict): item_id = items.get("id") item_type = items.get("type") if meta: item_uuid = items.get("meta", {}).get("itemId") else: num_items = len(items) if not num_items: raise ValueError("At least one item must be specified.") if isinstance(items[0], dict | ORDERED_COLLECTION_TYPES): if num_items > 20: raise ValueError( "A maximum of 20 items can be sent in a request." ) return [ cls._process_playlist_items( item, meta=meta, recursive=False ) for item in items ] item_id, *item_uuid, item_type = items if meta: item_uuid = item_uuid[0] if item_type not in cls._ITEM_TYPES: item_types = "', '".join(sorted(cls._ITEM_TYPES)) raise ValueError( f"Invalid item type {item_type!r}. Valid values: '{item_types}'." ) item = {"id": str(item_id), "type": item_type} if meta: item["meta"] = {"itemId": item_uuid} if recursive: return [item] return item
[docs] @TTLCache.cached_method(ttl="user") def get_playlists( self, playlist_uuids: str | Collection[str] | None = None, /, *, owner_ids: int | str | Collection[int | str] | None = None, country_code: str | None = None, expand: str | Collection[str] | None = None, cursor: str | None = None, sort_by: str | None = None, descending: bool | None = None, ) -> dict[str, Any]: """ `Playlists > Get Single Playlist <https://tidal-music.github.io /tidal-api-reference/#/playlists/get_playlists__id_>`_: Get TIDAL catalog information for a playlist․ `Playlists > Get Multiple Playlists <https://tidal-music.github.io/tidal-api-reference/#/playlists /get_playlists>`_: Get TIDAL catalog information for multiple playlists. .. admonition:: User authentication and authorization scope :class: entitlement .. tab-set:: .. tab-item:: Conditional :code:`playlists.read` scope Read a user's playlists. .. tab-item:: Optional User authentication Access information for a resource's owners. .. important:: Exactly one of `playlist_uuids` or `owner_ids` must be provided. When `owner_ids` is specified, the request will always be sent to the endpoint for multiple playlists. Parameters ---------- playlist_uuids : str or Collection[str]; positional-only, optional UUIDs of the TIDAL playlists. **Examples**: :code:`"36ea71a8-445e-41a4-82ab-6628c581535d"`, :code:`["36ea71a8-445e-41a4-82ab-6628c581535d", "b0d95b5e-7c4f-4dae-b042-b8c6228c2ba4"]`. owner_ids : int, str, or Collection[int | str]; keyword-only; \ optional TIDAL IDs of the playlist resources' owners. If authenticated, :code:`"me"` can be used in lieu of a TIDAL ID for the current user. **Examples**: :code:`"me"`, :code:`123456`, :code:`"654321"`, :code:`[123456, "654321"]`. country_code : str; keyword-only; optional ISO 3166-1 alpha-2 country code. **Example**: :code:`"US"`. expand : str or Collection[str]; keyword-only; optional Related resources to include metadata for in the response. **Valid values**: :code:`"collaboratorProfiles"`, :code:`"collaborators"`, :code:`"coverArt"`, :code:`"items"`, :code:`"ownerProfiles"`, :code:`"owners"`. **Examples**: :code:`"coverArt"`, :code:`["items", "owners"]`. cursor : str; keyword-only; optional Cursor for fetching the next page of results when requesting multiple playlists. **Example**: :code:`"3nI1Esi"`. sort_by : str; keyword-only; optional Field to sort the playlists by. **Valid values**: :code:`"createdAt"`, :code:`"lastModifiedAt`, :code:`"name"`. descending : bool; keyword-only; optional Whether to sort in descending order. **API default**: :code:`False`. Returns ------- playlists : dict[str, Any] Page of TIDAL metadata for the playlists. .. admonition:: Sample responses :class: response dropdown .. tab-set:: .. tab-item:: Single playlist .. code-block:: { "data": { "attributes": { "accessType": <str>, "bounded": <bool>, "createdAt": <str>, "description": <str>, "duration": <str>, "externalLinks": [ { "href": <str>, "meta": { "type": <str> } } ], "lastModifiedAt": <str>, "name": <str>, "numberOfItems": <int>, "playlistType": <str> }, "id": <str>, "relationships": { "coverArt": { "data": [ { "id": <str>, "type": "artworks" } ], "links": { "self": <str> } }, "items": { "data": [ { "id": <str>, "meta": { "addedAt": <str>, "itemId": <str> }, "type": "tracks" }, { "id": <str>, "meta": { "addedAt": <str>, "itemId": <str> }, "type": "videos" } ], "links": { "self": <str> } }, "ownerProfiles": { "data": [], "links": { "self": <str> } }, "owners": { "data": [], "links": { "self": <str> } } }, "type": "playlists" }, "included": [ { "attributes": { "files": [ { "href": <str>, "meta": { "height": <int>, "width": <int> } } ], "mediaType": "IMAGE" }, "id": <str>, "relationships": { "owners": { "links": { "self": <str> } } }, "type": "artworks" }, { "attributes": { "accessType": <str>, "availability": <list[str]>, "bpm": <float>, "copyright": { "text": <str> }, "duration": <str>, "explicit": <bool>, "externalLinks": [ { "href": <str>, "meta": { "type": <str> } } ], "isrc": <str>, "key": <str>, "keyScale": <str>, "mediaTags": <list[str]>, "popularity": <float>, "spotlighted": <bool>, "title": <str>, "toneTags": <list[str]>, "version": <str> }, "id": <str>, "relationships": { "albums": { "links": { "self": <str> } }, "artists": { "links": { "self": <str> } }, "genres": { "links": { "self": <str> } }, "lyrics": { "links": { "self": <str> } }, "owners": { "links": { "self": <str> } }, "providers": { "links": { "self": <str> } }, "radio": { "links": { "self": <str> } }, "shares": { "links": { "self": <str> } }, "similarTracks": { "links": { "self": <str> } }, "sourceFile": { "links": { "self": <str> } }, "trackStatistics": { "links": { "self": <str> } } }, "type": "tracks" }, { "attributes": { "availability": <list[str]>, "copyright": { "text": <str> }, "duration": <str>, "explicit": <bool>, "externalLinks": [ { "href": <str>, "meta": { "type": <str> } } ], "isrc": <str>, "popularity": <float>, "releaseDate": <str>, "title": <str> }, "id": <str>, "relationships": { "albums": { "links": { "self": <str> } }, "artists": { "links": { "self": <str> } }, "providers": { "links": { "self": <str> } }, "thumbnailArt": { "links": { "self": <str> } } }, "type": "videos" } ], "links": { "self": <str> } } .. tab-item:: Multiple playlists .. code-block:: { "data": [ { "attributes": { "accessType": <str>, "bounded": <bool>, "createdAt": <str>, "description": <str>, "duration": <str>, "externalLinks": [ { "href": <str>, "meta": { "type": <str> } } ], "lastModifiedAt": <str>, "name": <str>, "numberOfItems": <int>, "playlistType": <str> }, "id": <str>, "relationships": { "coverArt": { "data": [ { "id": <str>, "type": "artworks" } ], "links": { "self": <str> } }, "items": { "data": [ { "id": <str>, "meta": { "addedAt": <str>, "itemId": <str> }, "type": "tracks" }, { "id": <str>, "meta": { "addedAt": <str>, "itemId": <str> }, "type": "videos" } ], "links": { "self": <str> } }, "ownerProfiles": { "data": [], "links": { "self": <str> } } "owners": { "data": [], "links": { "self": <str> } } }, "type": "playlists" } ], "included": [ { "attributes": { "files": [ { "href": <str>, "meta": { "height": <int>, "width": <int> } } ], "mediaType": "IMAGE" }, "id": <str>, "relationships": { "owners": { "links": { "self": <str> } } }, "type": "artworks" }, { "attributes": { "accessType": <str>, "availability": <list[str]>, "bpm": <float>, "copyright": { "text": <str> }, "duration": <str>, "explicit": <bool>, "externalLinks": [ { "href": <str>, "meta": { "type": <str> } } ], "isrc": <str>, "key": <str>, "keyScale": <str>, "mediaTags": <list[str]>, "popularity": <float>, "spotlighted": <bool>, "title": <str>, "toneTags": <list[str]>, "version": <str> }, "id": <str>, "relationships": { "albums": { "links": { "self": <str> } }, "artists": { "links": { "self": <str> } }, "genres": { "links": { "self": <str> } }, "lyrics": { "links": { "self": <str> } }, "owners": { "links": { "self": <str> } }, "providers": { "links": { "self": <str> } }, "radio": { "links": { "self": <str> } }, "shares": { "links": { "self": <str> } }, "similarTracks": { "links": { "self": <str> } }, "sourceFile": { "links": { "self": <str> } }, "trackStatistics": { "links": { "self": <str> } } }, "type": "tracks" }, { "attributes": { "availability": <list[str]>, "copyright": { "text": <str> }, "duration": <str>, "explicit": <bool>, "externalLinks": [ { "href": <str>, "meta": { "type": <str> } } ], "isrc": <str>, "popularity": <float>, "releaseDate": <str>, "title": <str> }, "id": <str>, "relationships": { "albums": { "links": { "self": <str> } }, "artists": { "links": { "self": <str> } }, "providers": { "links": { "self": <str> } }, "thumbnailArt": { "links": { "self": <str> } } }, "type": "videos" } ], "links": { "self": <str> } } """ if sum(arg is not None for arg in (playlist_uuids, owner_ids)) != 1: raise ValueError( "Exactly one of `playlist_uuids` or `owner_ids` must " "be provided." ) params = {} if owner_ids is not None: self._validate_tidal_ids(owner_ids) params["filter[owners.id]"] = ( owner_ids if isinstance(owner_ids, ORDERED_COLLECTION_TYPES) else sorted(owner_ids) ) if sort_by is not None: self._process_sort( sort_by, descending=descending, prefix="", sort_fields=self._SORT_FIELDS, params=params, ) return self._get_resources( "playlists", playlist_uuids, country_code=country_code, expand=expand, cursor=cursor, resource_identifier_type="uuid", params=params, )
[docs] def create_playlist( self, name: str, country_code: str | None = None, *, description: str | None = None, public: bool | None = None, ) -> dict[str, Any]: """ `Playlists > Create Playlist <https://tidal-music.github.io /tidal-api-reference/#/playlists/post_playlists>`_: Create a playlist. .. admonition:: Authorization scope :class: entitlement .. tab-set:: .. tab-item:: Required :code:`playlists.write` scope Write to a user's playlists. Parameters ---------- name : str Playlist name. country_code : str; optional ISO 3166-1 alpha-2 country code. description : str; keyword-only; optional Playlist description. public : bool; keyword-only; optional Whether the playlist is displayed on the user's profile. **API default**: :code:`False`. Returns ------- playlist : dict[str, Any] TIDAL metadata for the newly created playlist. .. admonition:: Sample response :class: response dropdown .. code-block:: { "data": { "attributes": { "accessType": <str>, "bounded": <bool>, "createdAt": <str>, "externalLinks": [ { "href": <str>, "meta": { "type": "TIDAL_SHARING" } } ], "lastModifiedAt": <str>, "name": <str>, "playlistType": "USER" }, "id": <str>, "relationships": { "coverArt": { "links": { "self": <str> } }, "items": { "links": { "self": <str> } }, "owners": { "links": { "self": <str> } } }, "type": "playlists" }, "links": { "self": <str> } } """ self._client._require_scopes( "playlists.create_playlist", "playlists.write" ) params = {} if country_code is not None: self._validate_country_code(country_code) params["countryCode"] = country_code payload = { "data": { "attributes": {"name": self._prepare_string("name", name)}, "type": "playlists", } } attrs = payload["data"]["attributes"] if description is not None: attrs["description"] = self._prepare_string( "description", description, allow_blank=True ) if public is not None: self._validate_type("public", public, bool) attrs["accessType"] = "PUBLIC" if public else "UNLISTED" return self._client._request( "POST", "playlists", params=params, json=payload ).json()
[docs] def update_playlist_details( self, playlist_uuid: str, /, country_code: str | None = None, *, name: str | None = None, description: str | None = None, public: bool | None = None, ) -> None: """ `Playlists > Update Playlist <https://tidal-music.github.io /tidal-api-reference/#/playlists/patch_playlists__id_>`_: Update the details of a playlist. .. admonition:: Authorization scope :class: entitlement .. tab-set:: .. tab-item:: Required :code:`playlists.write` scope Write to a user's playlists. .. important:: At least one of :code:`name`, :code:`description`, or :code:`public` must be specified. Parameters ---------- playlist_uuid : str; positional-only UUID of the TIDAL playlist. **Example**: :code:`"550e8400-e29b-41d4-a716-446655440000"`. country_code : str; optional ISO 3166-1 alpha-2 country code. name : str; keyword-only; optional Playlist name. description : str; keyword-only; optional Playlist description. public : bool; keyword-only; optional Whether the playlist is displayed on the user's profile. **API default**: :code:`False`. """ self._client._require_scopes( "playlists.update_playlist_details", "playlists.write" ) params = {} if country_code is not None: self._validate_country_code(country_code) params["countryCode"] = country_code self._validate_uuids(playlist_uuid) payload = { "data": { "attributes": {}, "id": playlist_uuid, "type": "playlists", } } attrs = payload["data"]["attributes"] if name is not None: attrs["name"] = self._prepare_string("name", name) if description is not None: attrs["description"] = self._prepare_string( "description", description, allow_blank=True ) if public is not None: self._validate_type("public", public, bool) attrs["accessType"] = "PUBLIC" if public else "UNLISTED" if not attrs: raise ValueError("At least one change must be specified.") self._client._request( "PATCH", f"playlists/{playlist_uuid}", params=params, json=payload )
[docs] def delete_playlist(self, playlist_uuid: str, /) -> None: """ `Playlists > Delete Playlist <https://tidal-music.github.io /tidal-api-reference/#/playlists/delete_playlists__id_>`_: Delete a playlist. .. admonition:: Authorization scope :class: entitlement .. tab-set:: .. tab-item:: Required :code:`playlists.write` scope Write to a user's playlists. Parameters ---------- playlist_uuid : str; positional-only UUID of the TIDAL playlist. **Example**: :code:`"550e8400-e29b-41d4-a716-446655440000"`. """ self._client._require_scopes( "playlists.delete_playlist", "playlists.write" ) self._validate_uuids(playlist_uuid) self._client._request("DELETE", f"playlist/{playlist_uuid}")
[docs] @TTLCache.cached_method(ttl="user") def get_playlist_cover_art( self, playlist_uuid: str, /, country_code: str | None = None, *, include_metadata: bool = False, cursor: str | None = None, ) -> dict[str, Any]: """ `Playlists > Get Cover Art Relationship <https://tidal-music.github.io/tidal-api-reference/#/playlists /get_playlists__id__relationships_coverArt>`_: Get TIDAL catalog information for the cover art of a playlist. Parameters ---------- playlist_uuid : str; positional-only UUID of the TIDAL playlist. **Example**: :code:`"36ea71a8-445e-41a4-82ab-6628c581535d"`. country_code : str; optional ISO 3166-1 alpha-2 country code. **Example**: :code:`"US"`. include_metadata : bool; keyword-only; default: :code:`False` Whether to include metadata for the playlist cover art. cursor : str; keyword-only; optional Cursor for fetching the next page of results. **Example**: :code:`"3nI1Esi"`. Returns ------- cover_art : dict[str, Any] Page of TIDAL metadata for the playlist cover art. .. admonition:: Sample response :class: response dropdown .. code-block:: { "data": [ { "id": <str>, "type": "artworks" } ], "included": [ { "attributes": { "files": [ { "href": <str>, "meta": { "height": <int>, "width": <int> } } ], "mediaType": "IMAGE" }, "id": <str>, "relationships": { "owners": { "links": { "self": <str> } } }, "type": "artworks" } ], "links": { "meta": { "nextCursor": <str> }, "next": <str>, "self": <str> } } """ return self._get_resource_relationship( "playlists", playlist_uuid, "coverArt", country_code=country_code, include_metadata=include_metadata, cursor=cursor, resource_identifier_type="uuid", )
[docs] @TTLCache.cached_method(ttl="user") def get_playlist_items( self, playlist_uuid: str, /, country_code: str | None = None, *, include_metadata: bool = False, cursor: str | None = None, ) -> dict[str, Any]: """ `Playlists > Get Items Relationship <https://tidal-music.github.io/tidal-api-reference/#/playlists /get_playlists__id__relationships_coverArt>`_: Get TIDAL catalog information for items in a playlist. Parameters ---------- playlist_uuid : str; positional-only UUID of the TIDAL playlist. **Example**: :code:`"36ea71a8-445e-41a4-82ab-6628c581535d"`. country_code : str; optional ISO 3166-1 alpha-2 country code. **Example**: :code:`"US"`. include_metadata : bool; keyword-only; default: :code:`False` Whether to include metadata for the playlist items. cursor : str; keyword-only; optional Cursor for fetching the next page of results. **Example**: :code:`"3nI1Esi"`. Returns ------- items : dict[str, Any] Page of TIDAL metadata for the playlist items. .. admonition:: Sample response :class: response dropdown .. code-block:: { "data": [ { "id": <str>, "meta": { "addedAt": <int>, "itemId": <int> }, "type": "tracks" }, { "id": <str>, "meta": { "addedAt": <int>, "itemId": <int> }, "type": "videos" } ], "included": [ { "attributes": { "accessType": <str>, "availability": <list[str]>, "bpm": <float>, "copyright": { "text": <str> }, "duration": <str>, "explicit": <bool>, "externalLinks": [ { "href": <str>, "meta": { "type": <str> } } ], "isrc": <str>, "key": <str>, "keyScale": <str>, "mediaTags": <list[str]>, "popularity": <float>, "spotlighted": <bool>, "title": <str>, "toneTags": <list[str]>, "version": <str> }, "id": <str>, "relationships": { "albums": { "links": { "self": <str> } }, "artists": { "links": { "self": <str> } }, "genres": { "links": { "self": <str> } }, "lyrics": { "links": { "self": <str> } }, "owners": { "links": { "self": <str> } }, "providers": { "links": { "self": <str> } }, "radio": { "links": { "self": <str> } }, "shares": { "links": { "self": <str> } }, "similarTracks": { "links": { "self": <str> } }, "sourceFile": { "links": { "self": <str> } }, "trackStatistics": { "links": { "self": <str> } } }, "type": "tracks" }, { "attributes": { "availability": <list[str]>, "copyright": { "text": <str> }, "duration": <str>, "explicit": <bool>, "externalLinks": [ { "href": <str>, "meta": { "type": <str> } } ], "isrc": <str>, "popularity": <float>, "releaseDate": <str>, "title": <str> }, "id": <str>, "relationships": { "albums": { "links": { "self": <str> } }, "artists": { "links": { "self": <str> } }, "providers": { "links": { "self": <str> } }, "thumbnailArt": { "links": { "self": <str> } } }, "type": "videos" } ], "links": { "meta": { "nextCursor": <str> }, "next": <str>, "self": <str> } } """ return self._get_resource_relationship( "playlists", playlist_uuid, "items", country_code=country_code, include_metadata=include_metadata, cursor=cursor, resource_identifier_type="uuid", )
[docs] def add_playlist_items( self, playlist_uuid: str, /, items: tuple[int | str, str] | dict[str, int | str] | Collection[tuple[int | str, str] | dict[str, int | str]], *, country_code: str | None = None, insert_before: str | None = None, ) -> None: """ `Playlists > Add to Items Relationship <https://tidal-music.github.io/tidal-api-reference/#/playlists /post_playlists__id__relationships_items>`_: Add items to a playlist. .. admonition:: Authorization scope :class: entitlement .. tab-set:: .. tab-item:: Required :code:`playlists.write` scope Write to a user's playlists. Parameters ---------- playlist_uuid : str; positional-only UUID of the TIDAL playlist. **Example**: :code:`"550e8400-e29b-41d4-a716-446655440000"`. items : tuple[int | str, str], dict[str, int | str], or \ Collection[tuple[int | str, str] | dict[str, int | str]] TIDAL IDs and types of the items to be added. **Examples**: * :code:`(458584456, "tracks")` * :code:`("29597422", "videos")` * :code:`{"id": "35633900", "types": "tracks"}` * .. code-block:: [ (458584456, "tracks"), ("29597422", "videos"), {"id": "35633900", "types": "tracks"}, ] country_code : str; keyword-only; optional ISO 3166-1 alpha-2 country code. **Example**: :code:`"US"`. insert_before : str; keyword-only; optional UUID of the item in the playlist before which to insert the items. If not specified, the items are appended to the end of the playlist. **Example**: :code:`"3794bdb3-1529-48d7-8a99-ef2cb0cf22c3"`. """ self._client._require_scopes( "playlists.add_playlist_items", "playlists.write" ) self._validate_uuids(playlist_uuid) params = {} if country_code is not None: self._validate_country_code(country_code) params["countryCode"] = country_code payload = {"data": self._process_playlist_items(items, meta=False)} if insert_before is not None: payload["meta"] = {"positionBefore": insert_before} self._client._request( "POST", f"playlists/{playlist_uuid}/relationships/items", params=params, json=payload, )
[docs] def reorder_playlist_items( self, playlist_uuid: str, /, items: tuple[int | str, str, str] | dict[str, Any] | Collection[tuple[int | str, str, str] | dict[str, Any]], insert_before: str, ) -> None: """ `Playlists > Update Items Relationship <https://tidal-music.github.io/tidal-api-reference/#/playlists /patch_playlists__id__relationships_items>`_: Reorder items in a playlist. .. admonition:: Authorization scope :class: entitlement .. tab-set:: .. tab-item:: Required :code:`playlists.write` scope Write to a user's playlists. Parameters ---------- playlist_uuid : str; positional-only UUID of the TIDAL playlist. **Example**: :code:`"550e8400-e29b-41d4-a716-446655440000"`. items : tuple[int | str, str, str], dict[str, Any], or \ Collection[tuple[int | str, str, str] | dict[str, Any]] TIDAL IDs, playlist item UUIDs, and types of the items to be reordered. **Examples**: * :code:`(458584456, "f0d6f5c4-081f-4348-9b65-ae677d92767b", "tracks")` * :code:`("29597422", "1e4c73df-b805-47cd-9e44-9a8721c5cb45", "videos")` * .. code-block:: { "id": "35633900", "meta": { "itemId": "fdd074f0-90c7-4cfb-bb6c-10060e1a3a58" }, "types": "tracks" } * .. code-block:: [ ( 458584456, "f0d6f5c4-081f-4348-9b65-ae677d92767b", "tracks", ), ( "29597422", "1e4c73df-b805-47cd-9e44-9a8721c5cb45", "videos", ), { "id": "35633900", "meta": { "itemId": "fdd074f0-90c7-4cfb-bb6c-10060e1a3a58" }, "types": "tracks", }, ] .. seealso:: :meth:`get_playlist_items` – Get playlist item UUIDs. insert_before : str UUID of the item in the playlist before which to insert the items. **Example**: :code:`"3794bdb3-1529-48d7-8a99-ef2cb0cf22c3"`. """ self._client._require_scopes( "playlists.reorder_playlist_items", "playlists.write" ) self._validate_uuids(playlist_uuid) payload = {"data": self._process_playlist_items(items)} if insert_before is not None: payload["meta"] = {"positionBefore": insert_before} self._client._request( "PATCH", f"playlists/{playlist_uuid}/relationships/items", json=payload, )
[docs] def remove_playlist_items( self, playlist_uuid: str, /, items: tuple[int | str, str, str] | dict[str, Any] | Collection[tuple[int | str, str, str] | dict[str, Any]], ) -> None: """ `Playlists > Delete from Items Relationship <https://tidal-music.github.io/tidal-api-reference/#/playlists /delete_playlists__id__relationships_items>`_: Remove items from a playlist. .. admonition:: Authorization scope :class: entitlement .. tab-set:: .. tab-item:: Required :code:`playlists.write` scope Write to a user's playlists. Parameters ---------- playlist_uuid : str; positional-only UUID of the TIDAL playlist. **Example**: :code:`"550e8400-e29b-41d4-a716-446655440000"`. items : tuple[int | str, str, str], dict[str, Any], or \ Collection[tuple[int | str, str, str] | dict[str, Any]] TIDAL IDs, playlist item UUIDs, and types of the items to be removed. **Examples**: * :code:`(458584456, "f0d6f5c4-081f-4348-9b65-ae677d92767b", "tracks")` * :code:`("29597422", "1e4c73df-b805-47cd-9e44-9a8721c5cb45", "videos")` * .. code-block:: { "id": "35633900", "meta": { "itemId": "fdd074f0-90c7-4cfb-bb6c-10060e1a3a58" }, "types": "tracks" } * .. code-block:: [ ( 458584456, "f0d6f5c4-081f-4348-9b65-ae677d92767b", "tracks", ), ( "29597422", "1e4c73df-b805-47cd-9e44-9a8721c5cb45", "videos", ), { "id": "35633900", "meta": { "itemId": "fdd074f0-90c7-4cfb-bb6c-10060e1a3a58" }, "types": "tracks", }, ] .. seealso:: :meth:`get_playlist_items` – Get playlist item UUIDs. """ self._client._require_scopes( "playlists.remove_playlist_items", "playlists.write" ) self._validate_uuids(playlist_uuid) self._client._request( "DELETE", f"playlists/{playlist_uuid}/relationships/items", json={"data": self._process_playlist_items(items)}, )
[docs] @TTLCache.cached_method(ttl="static") def get_playlist_owners( self, playlist_uuid: str, /, country_code: str | None = None, *, include_metadata: bool = False, cursor: str | None = None, ) -> dict[str, Any]: """ `Playlists > Get Owners Relationship <https://tidal-music.github.io/tidal-api-reference/#/playlists /get_playlists__id__relationships_owners>`_: Get TIDAL profile information for the owner of a playlist resource. .. admonition:: User authentication :class: entitlement dropdown .. tab-set:: .. tab-item:: Optional User authentication Access information for a resource's owners. Parameters ---------- playlist_uuid : str; positional-only UUID of the TIDAL playlist. **Example**: :code:`"550e8400-e29b-41d4-a716-446655440000"`. country_code : str; optional ISO 3166-1 alpha-2 country code. **Example**: :code:`"US"`. include_metadata : bool; keyword-only; default: :code:`False` Whether to include metadata for the owners. cursor : str; keyword-only; optional Cursor for fetching the next page of results. **Example**: :code:`"3nI1Esi"`. Returns ------- owners : dict[str, Any] Page of TIDAL profile information for the playlist resource's owners. .. admonition:: Sample response :class: response dropdown .. code-block:: { "data": [], "included": [], "links": { "meta": { "nextCursor": <str> }, "next": <str>, "self": <str> } } """ return self._get_resource_relationship( "playlists", playlist_uuid, "owners", country_code=country_code, include_metadata=include_metadata, cursor=cursor, resource_identifier_type="uuid", )
[docs] @TTLCache.cached_method(ttl="static") def get_playlist_owner_profiles( self, playlist_uuid: str, /, country_code: str | None = None, *, include_metadata: bool = False, cursor: str | None = None, ) -> dict[str, Any]: """ `Playlists > Get Owner Profiles Relationship <https://tidal-music.github.io/tidal-api-reference/#/playlists /get_playlists__id__relationships_owners>`_: Get TIDAL catalog information for the profiles of a playlist resource's owners. .. admonition:: User authentication :class: entitlement dropdown .. tab-set:: .. tab-item:: Optional User authentication Access information for a resource's owners. Parameters ---------- playlist_uuid : str; positional-only UUID of the TIDAL playlist. **Example**: :code:`"550e8400-e29b-41d4-a716-446655440000"`. country_code : str; optional ISO 3166-1 alpha-2 country code. **Example**: :code:`"US"`. include_metadata : bool; keyword-only; default: :code:`False` Whether to include metadata for the playlist's owners' profiles. cursor : str; keyword-only; optional Cursor for fetching the next page of results. **Example**: :code:`"3nI1Esi"`. Returns ------- owner_profiles : dict[str, Any] Page of TIDAL metadata for the playlist's owners' profiles. .. admonition:: Sample response :class: response dropdown .. code-block:: { "data": [], "included": [], "links": { "meta": { "nextCursor": <str> }, "next": <str>, "self": <str> } } """ return self._get_resource_relationship( "playlists", playlist_uuid, "ownerProfiles", country_code=country_code, include_metadata=include_metadata, cursor=cursor, resource_identifier_type="uuid", )
[docs] @_copy_docstring(SearchAPI.search_playlists) def search_playlists( self, query: str, /, country_code: str | None = None, *, include_explicit: bool | None = None, include_metadata: bool = False, cursor: str | None = None, ) -> dict[str, Any]: return self._client.search.search_playlists( query, country_code=country_code, include_explicit=include_explicit, include_metadata=include_metadata, cursor=cursor, )
[docs] @_copy_docstring(UsersAPI.get_playlist_collection) def get_playlist_collection( self, collection_id: str | None = None, /, *, country_code: str | None = None, locale: str | None = None, expand: str | Collection[str] | None = None, ) -> dict[str, Any]: return self._client.users.get_playlist_collection( collection_id=collection_id, country_code=country_code, locale=locale, expand=expand, )
[docs] @_copy_docstring(UsersAPI.get_user_followed_playlists) def get_user_followed_playlists( self, *, collection_id: str | None = None, user_id: int | str | None = None, include_folders: bool = False, include_metadata: bool = False, cursor: str | None = None, sort_by: str | None = None, descending: bool | None = None, ) -> dict[str, Any]: return self._client.users.get_user_followed_playlists( collection_id=collection_id, user_id=user_id, include_folders=include_folders, include_metadata=include_metadata, cursor=cursor, sort_by=sort_by, descending=descending, )
[docs] @_copy_docstring(UsersAPI.follow_playlists) def follow_playlists( self, playlist_uuids: str | dict[str, str] | Collection[str | dict[str, str]], /, *, collection_id: str | None = None, user_id: int | str | None = None, ) -> None: self._client.users.follow_playlists( playlist_uuids, collection_id=collection_id, user_id=user_id )
[docs] @_copy_docstring(UsersAPI.unfollow_playlists) def unfollow_playlists( self, playlist_uuids: str | dict[str, str] | Collection[str | dict[str, str]], /, *, collection_id: str | None = None, user_id: int | str | None = None, ) -> None: self._client.users.unfollow_playlists( playlist_uuids, collection_id=collection_id, user_id=user_id )