from __future__ import annotations
from numbers import Number
from typing import TYPE_CHECKING
from ..._shared import TTLCache, _copy_docstring
from ._shared import SpotifyResourceAPI
from .users import UsersAPI
if TYPE_CHECKING:
from datetime import datetime
from typing import Any
from ...._types import Collection
IntAttributeSpec = (
int
| tuple[int | None, int | None]
| tuple[int | None, int | None, int | None]
)
FloatAttributeSpec = (
float
| tuple[float | None, float | None]
| tuple[float | None, float | None, float | None]
)
[docs]
class TracksAPI(SpotifyResourceAPI):
"""
Tracks 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__ = ()
def _parse_attribute(
self,
attribute: str,
value: int | float | tuple[int | float | None, ...],
data_type: type,
range_: tuple[int | float | None, int | float | None],
params: dict[str, Any],
) -> None:
"""
Parse and add a track attribute to a dictionary holding the
request parameters for :meth:`get_track_recommendations`.
Parameters
----------
attribute : str
Track attribute.
**Valid values**: :code:`"acousticness"`,
:code:`"danceability"`, :code:`"duration_ms"`,
:code:`"energy"`, :code:`"instrumentalness"`,
:code:`"key"`, :code:`"liveness"`, :code:`"loudness"`,
:code:`"mode"`, :code:`"popularity"`, :code:`"speechiness"`,
:code:`"tempo"`, :code:`"time_signature"`,
:code:`"valence"`.
value : int, float, or tuple[int | float, ...]
Track attribute value.
data_type : type
Allowed data type for the track attribute.
**Valid values**: :code:`int` or :code:`float`.
range_ : tuple[int | float | None, int | float | None]
Valid range for the track attribute.
params : dict[str, Any]
Query parameters to include in the request.
.. note::
This `dict` is mutated in-place.
"""
if value is None:
return
is_int = data_type is int
if isinstance(value, data_type):
self._validate_number(attribute, value, data_type, *range_)
params[f"target_{attribute}"] = value
elif isinstance(value, tuple | list | set):
if is_int and any(
not (isinstance(v, int) or v is None) for v in value
):
raise ValueError(
f"Values for track attribute {attribute!r} must "
"be integers (or None)."
)
elif any(
not (
isinstance(v, Number)
or isinstance(v, str)
and v.isdecimal()
or v is None
)
for v in value
):
raise ValueError(
f"Values for track attribute {attribute!r} must "
"be numbers (or None)."
)
length = len(value)
if length not in range(2, 4):
raise ValueError(
"The tuple provided for track attribute "
f"{attribute!r} must have length 2 or 3, not "
f"length {length}."
)
else:
for v in value:
self._validate_number(attribute, v, data_type, *range_)
if length == 2:
params[f"min_{attribute}"], params[f"max_{attribute}"] = (
value
)
elif length == 3:
(
params[f"min_{attribute}"],
params[f"max_{attribute}"],
params[f"target_{attribute}"],
) = value
else:
raise ValueError(
f"The value provided for track attribute {attribute!r} "
f"must be a {(dtype := data_type.__name__)} or a "
f"tuple of {dtype}s, not a {type(value).__name__}."
)
def _parse_seeds(
self,
seed_type: str,
seeds: str | Collection[str] | None,
n_seeds: int,
params: dict[str, Any],
) -> int:
"""
Parse and add seeds to a dictionary holding the request
parameters for :meth:`get_track_recommendations`.
Parameters
----------
seed_type : str
Seed type.
**Valid values**: :code:`"seed_artists"`,
:code:`"seed_genres"`, :code:`"seed_tracks"`.
seeds : str, Collection[str], or None
Seed values.
n_seeds : int
Starting number of seed values.
params : dict[str, Any]
Query parameters to include in the request.
.. note::
This `dict` is mutated in-place.
Returns
-------
n_seeds : int
Ending number of seed values.
"""
if seeds is None:
return n_seeds
if seed_type == "seed_genres":
params[seed_type], new_n_seeds = self._prepare_spotify_ids(
seeds, limit=5
)
else:
params[seed_type], new_n_seeds = self._prepare_seed_genres(
seeds, limit=5
)
return n_seeds + new_n_seeds
def _prepare_seed_genres(
self, seed_genres: str | Collection[str], /, limit: int
) -> tuple[str, int]:
"""
Validate, normalize, and serialize seed genres.
Parameters
----------
seed_genres : str or Collection[str]; positional-only
Seed genres.
limit : int; keyword-only
Maximum number of seed genres that can be sent in a
request.
Returns
-------
seed_genres : str
Comma-separated string of seed genres.
n_seed_genres : int
Number of seed genres.
"""
if isinstance(seed_genres, str):
return self._prepare_seed_genres(seed_genres.strip().split(","))
seed_genres = set(seed_genres)
n_genres = len(seed_genres)
if n_genres > limit:
raise ValueError(
f"A maximum of {limit} seed genres can be sent in a request."
)
for genre in seed_genres:
self._client.genres._validate_seed_genre(genre)
return ",".join(sorted(seed_genres)), n_genres
[docs]
@TTLCache.cached_method(ttl="popularity")
def get_tracks(
self,
track_ids: str | Collection[str],
/,
*,
country_code: str | None = None,
) -> dict[str, Any]:
"""
`Tracks > Get Track <https://developer.spotify.com/documentation
/web-api/reference/get-track>`_: Get Spotify catalog information
for a track․
`Tracks > Get Several Tracks <https://developer.spotify.com
/documentation/web-api/reference/get-several-tracks>`_: Get
Spotify catalog information for multiple tracks.
.. 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
----------
track_ids : str or Collection[str]; positional-only
Spotify IDs of the tracks. A maximum of 50 IDs can be sent
in a request.
**Examples**:
* :code:`"7ouMYWpwJ422jRcDASZB7P"`
* :code:`"7ouMYWpwJ422jRcDASZB7P,4VqPOruhp5EdPBeR92t6lQ"`
* :code:`["7ouMYWpwJ422jRcDASZB7P",
"4VqPOruhp5EdPBeR92t6lQ"]`
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
-------
tracks : dict[str, Any]
Spotify metadata for the tracks.
.. admonition:: Sample responses
:class: response dropdown
.. tab-set::
.. tab-item:: Single track
.. code-block::
{
"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>,
"restrictions": {
"reason": <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>,
"explicit": <bool>,
"external_ids": {
"ean": <str>,
"isrc": <str>,
"upc": <str>
},
"external_urls": {
"spotify": <str>
},
"href": <str>,
"id": <str>,
"is_local": <bool>,
"is_playable": <bool>,
"linked_from": <dict[str, Any]>,
"name": <str>,
"popularity": <int>,
"preview_url": <str>,
"restrictions": {
"reason": <str>
},
"track_number": <int>,
"type": "track",
"uri": <str>
}
.. tab-item:: Multiple tracks
.. code-block::
{
"tracks": [
{
"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>,
"restrictions": {
"reason": <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>,
"explicit": <bool>,
"external_ids": {
"ean": <str>,
"isrc": <str>,
"upc": <str>
},
"external_urls": {
"spotify": <str>
},
"href": <str>,
"id": <str>,
"is_local": <bool>,
"is_playable": <bool>,
"linked_from": <dict[str, Any]>,
"name": <str>,
"popularity": <int>,
"preview_url": <str>,
"restrictions": {
"reason": <str>
},
"track_number": <int>,
"type": "track",
"uri": <str>
}
]
}
"""
return self._get_resources(
"tracks", track_ids, country_code=country_code
)
[docs]
@_copy_docstring(UsersAPI.get_my_saved_tracks)
def get_my_saved_tracks(
self,
*,
country_code: str | None = None,
limit: int | None = None,
offset: int | None = None,
) -> dict[str, Any]:
return self._client.users.get_my_saved_tracks(
country_code=country_code, limit=limit, offset=offset
)
[docs]
@_copy_docstring(UsersAPI.save_tracks)
def save_tracks(
self,
track_ids: str
| tuple[str, str | datetime]
| dict[str, str | datetime]
| list[str | tuple[str, str | datetime] | dict[str, str | datetime]],
/,
) -> None:
self._client.users.save_tracks(track_ids)
[docs]
@_copy_docstring(UsersAPI.remove_saved_tracks)
def remove_saved_tracks(self, track_ids: str | Collection[str], /) -> None:
self._client.users.remove_saved_tracks(track_ids)
[docs]
@_copy_docstring(UsersAPI.are_tracks_saved)
def are_tracks_saved(
self, track_ids: str | Collection[str], /
) -> list[bool]:
return self._client.users.are_tracks_saved(track_ids)
[docs]
@TTLCache.cached_method(ttl="static")
def get_track_audio_features(
self, track_ids: str | Collection[str], /
) -> dict[str, Any]:
"""
`Tracks > Get Track's Audio Features
<https://developer.spotify.com/documentation/web-api/reference
/get-audio-features>`_: Get the audio features for a track․
`Tracks > Get Several Tracks' Audio Features
<https://developer.spotify.com/documentation/web-api/reference
/get-several-audio-features>`_: Get the audio features for
multiple tracks.
.. admonition:: Third-party application mode
:class: entitlement
.. tab-set::
.. tab-item:: Required
Extended quota mode before November 27, 2024
Access the :code:`GET /audio-features/{id}`
endpoint. `Learn more.
<https://developer.spotify.com/blog
/2024-11-27-changes-to-the-web-api>`__
Parameters
----------
track_ids : str or Collection[str]; positional-only
Spotify IDs of the tracks. A maximum of 50 IDs can be sent
in a request.
**Examples**:
* :code:`"7ouMYWpwJ422jRcDASZB7P"`
* :code:`"7ouMYWpwJ422jRcDASZB7P,4VqPOruhp5EdPBeR92t6lQ"`
* :code:`["7ouMYWpwJ422jRcDASZB7P",
"4VqPOruhp5EdPBeR92t6lQ"]`
Returns
-------
audio_features : dict[str, Any]
Audio features for the tracks.
.. admonition:: Sample responses
:class: response dropdown
.. tab-set::
.. tab-item:: Single track
.. code-block::
{
"acousticness": <float>,
"analysis_url": <str>,
"danceability": <float>,
"duration_ms": <int>,
"energy": <float>,
"id": <str>,
"instrumentalness": <float>,
"key": <int>,
"liveness": <float>,
"loudness": <float>,
"mode": <int>,
"speechiness": <float>,
"tempo": <float>,
"time_signature": <int>,
"track_href": <str>,
"type": "audio_features",
"uri": <str>,
"valence": <float>
}
.. tab-item:: Multiple tracks
.. code-block::
{
"audio_features": [
{
"acousticness": <float>,
"analysis_url": <str>,
"danceability": <float>,
"duration_ms": <int>,
"energy": <float>,
"id": <str>,
"instrumentalness": <float>,
"key": <int>,
"liveness": <float>,
"loudness": <float>,
"mode": <int>,
"speechiness": <float>,
"tempo": <float>,
"time_signature": <int>,
"track_href": <str>,
"type": "audio_features",
"uri": <str>,
"valence": <float>
}
]
}
"""
return self._get_resources("audio-features", track_ids, limit=100)
[docs]
@TTLCache.cached_method(ttl="static")
def get_track_audio_analysis(self, track_id: str, /) -> dict[str, Any]:
"""
`Tracks > Get Track's Audio Analysis
<https://developer.spotify.com/documentation/web-api/reference
/get-audio-analysis>`_: Get a low-level audio analysis
(track structure and musical content, including rhythm, pitch,
and timbre) of a track.
.. admonition:: Third-party application mode
:class: entitlement
.. tab-set::
.. tab-item:: Required
Extended quota mode before November 27, 2024
Access the :code:`GET/audio-analysis/{track_id}`
endpoint. `Learn more.
<https://developer.spotify.com/blog
/2024-11-27-changes-to-the-web-api>`__
Parameters
----------
track_id : str; positional-only
Spotify ID of the track.
**Example**: :code:`"11dFghVXANMlKmJXsNCbNl"`.
Returns
-------
audio_analysis : dict[str, Any]
Audio analysis of the track.
.. admonition:: Sample response
:class: response dropdown
.. code-block::
{
"bars": [
{
"confidence": <float>,
"duration": <float>,
"start": <float>
}
],
"beats": [
{
"confidence": <float>,
"duration": <float>,
"start": <float>
}
],
"meta": {
"analysis_time": <float>,
"analyzer_version": <str>,
"detailed_status": <str>,
"input_process": <str>,
"platform": <str>,
"status_code": <int>,
"timestamp": <int>
},
"sections": [
{
"confidence": <float>,
"duration": <float>,
"key": <int>,
"key_confidence": <float>,
"loudness": <float>,
"mode": <int>,
"mode_confidence": <float>,
"start": <float>,
"tempo": <float>,
"tempo_confidence": <float>,
"time_signature": <int>,
"time_signature_confidence": <float>
}
],
"segments": [
{
"confidence": <float>,
"duration": <float>,
"loudness_end": <float>,
"loudness_max": <float>,
"loudness_max_time": <float>,
"loudness_start": <float>,
"pitches": <list[float]>,
"start": <float>,
"timbre": <list[float]>
}
],
"tatums": [
{
"confidence": <float>,
"duration": <float>,
"start": <float>
}
],
"track": {
"analysis_channels": <int>,
"analysis_sample_rate": <int>,
"code_version": <float>,
"codestring": <str>,
"duration": <float>,
"echoprint_version": <float>,
"echoprintstring": <str>,
"end_of_fade_in": <float>,
"key": <int>,
"key_confidence": <float>,
"loudness": <float>,
"mode": <int>,
"mode_confidence": <float>,
"num_samples": <int>,
"offset_seconds": <float>,
"rhythm_version": <int>,
"rhythmstring": <str>,
"sample_md5": <str>,
"start_of_fade_out": <float>,
"synch_version": <int>,
"synchstring": <str>,
"tempo": <float>,
"tempo_confidence": <float>,
"time_signature": <int>,
"time_signature_confidence": <float>,
"window_seconds": <float>
}
}
"""
self._validate_spotify_id(track_id)
return self._client._request(
"GET", f"audio-analysis/{track_id}"
).json()
[docs]
@TTLCache.cached_method(ttl="recommendation")
def get_track_recommendations(
self,
seed_artist_ids: str | Collection[str] | None = None,
seed_genres: str | Collection[str] | None = None,
seed_track_ids: str | Collection[str] | None = None,
*,
country_code: str | None = None,
limit: int | None = None,
acousticness: FloatAttributeSpec | None = None,
danceability: FloatAttributeSpec | None = None,
duration_ms: IntAttributeSpec | None = None,
energy: FloatAttributeSpec | None = None,
instrumentalness: FloatAttributeSpec | None = None,
key: IntAttributeSpec | None = None,
liveness: FloatAttributeSpec | None = None,
loudness: FloatAttributeSpec | None = None,
mode: IntAttributeSpec | None = None,
popularity: IntAttributeSpec | None = None,
speechiness: FloatAttributeSpec | None = None,
tempo: FloatAttributeSpec | None = None,
time_signature: IntAttributeSpec | None = None,
valence: FloatAttributeSpec | None = None,
) -> dict[str, Any]:
"""
`Tracks > Get Recommendations <https://developer.spotify.com
/documentation/web-api/reference/get-recommendations>`_: Get
track recommendations based on seed artists, genres, and/or
tracks, with optional tuning parameters.
.. admonition:: Third-party application mode
:class: entitlement
.. tab-set::
.. tab-item:: Required
Extended quota mode before November 27, 2024
Access the :code:`GET/recommendations` endpoint.
`Learn more. <https://developer.spotify.com/blog
/2024-11-27-changes-to-the-web-api>`__
.. note::
For very new or obscure artists and tracks, there might not
be enough data to generate recommendations.
.. important::
At least one and up to five seed values may be provided in
any combination of :code:`seed_artist_ids`,
:code:`seed_genres`, and :code:`seed_track_ids`.
.. hint::
Track attribute parameters (`acousticness`, `danceability`,
`duration_ms`, etc.) can be provided in one of the following
ways:
.. table:: Track attribute specifications
============================= ===================================
Data type Specification
============================= ===================================
Number Target value
tuple[Number, Number] Minimum and maximum values
tuple[Number, Number, Number] Minimum, maximum, and target values
============================= ===================================
Parameters
----------
seed_artist_ids : str or Collection[str]; optional
Spotify IDs of seed artists.
**Examples**:
* :code:`"0TnOYISbd1XYRBk9myaseg"`
* :code:`"0TnOYISbd1XYRBk9myaseg,57dN52uHvrHOxijzpIgu3E"`
* :code:`["0TnOYISbd1XYRBk9myaseg",
"57dN52uHvrHOxijzpIgu3E"]`
seed_genres : str or Collection[str]; optional
Spotify IDs of seed genres.
.. seealso::
:meth:`~minim.api.spotify.GenresAPI.get_seed_genres`
– Get available seed genres.
seed_track_ids : str or Collection[str]; optional
Spotify IDs of seed tracks.
**Examples**:
* :code:`"7ouMYWpwJ422jRcDASZB7P"`
* :code:`"7ouMYWpwJ422jRcDASZB7P,4VqPOruhp5EdPBeR92t6lQ"`
* :code:`["7ouMYWpwJ422jRcDASZB7P",
"4VqPOruhp5EdPBeR92t6lQ"]`
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 tracks to return.
**Valid range**: :code:`1` to :code:`50`.
**API default**: :code:`20`.
acousticness : float or tuple[float, ...]; keyword-only; optional
Confidence measure of whether a track is acoustic.
**Valid range**: :code:`0.0` (electronic) to :code:`1.0`
(acoustic).
**Example**: :code:`0.00242`.
danceability : float or tuple[float, ...]; keyword-only; optional
Suitability of a track for dancing based on a combination of
musical elements, including tempo, rhythim stability, beat
strength, and overall regularity.
**Valid range**: :code:`0.0` (least danceable) to
:code:`1.0` (most danceable).
**Example**: :code:`0.585`.
duration_ms : int or tuple[int, ...]; keyword-only; optional
Track duration in milliseconds.
**Minimum value**: :code:`0`.
**Example**: :code:`237_040`.
energy : float or tuple[float, ...]; keyword-only; optional
Perceptual measure of a track's intensity and activity based
on its dynamic range, perceived loudness, timbre, onset rate,
and general entropy.
**Valid range**: :code:`0.0` (e.g., Bach prelude) to
:code:`1.0` (e.g., death metal).
**Example**: :code:`0.842`.
instrumentalness : float or tuple[float, ...]; keyword-only; optional
Confidence measure of whether a track contains no vocals.
**Valid range**: :code:`0.0` (vocal) to :code:`1.0`
(instrumental).
**Example**: :code:`0.00686`.
key : int or tuple[int, ...]; keyword-only; optional
Key a track is in using standard pitch class notation.
**Valid values**:
* :code:`-1` – No key detected.
* :code:`0` – C.
* :code:`1` – C♯ or D♭.
* :code:`2` – D
* :code:`3` – D♯ or E♭.
* :code:`4` – E.
* :code:`5` – F.
* :code:`6` – F♯ or G♭.
* :code:`7` – G.
* :code:`8` – G♯ or A♭.
* :code:`9` – A.
* :code:`10` – A♯ or B♭.
* :code:`11` – B.
liveness : float or tuple[float, ...]; keyword-only; optional
Confidence measure of whether a track was performed live
based on the presence of an audience in the recording.
**Valid range**: :code:`0.0` (studio) to :code:`1.0` (live).
**Example**: :code:`0.0866`.
loudness : float or tuple[float, ...]; keyword-only; optional
Overall loudness of a track in decibels.
**Maximum value**: :code:`0.0`.
**Example**: :code:`-5.883`.
mode : int or tuple[int, ...]; keyword-only; optional
Musical mode of a track.
**Valid values**: :code:`0` for minor scale, :code:`1` for
major scale.
popularity : int or tuple[int, ...]; keyword-only; optional
Popularity of a track based on the total number of plays it
has had and how recent those plays are.
.. note::
The popularity value is not updated in real time and may
lag actual value by a few days.
**Valid range**: :code:`0` (least popular) to :code:`100`
(most popular).
speechiness : float or tuple[float, ...]; keyword-only; optional
Confidence measure of whether a track contains spoken words.
**Valid range**: :code:`0.0` (music) to :code:`1.0`
(speech-like).
**Example**: :code:`0.0556`.
tempo : float or tuple[float, ...]; keyword-only; optional
Overall estimated tempo of a track in beats per minute.
**Minimum value**: :code:`0.0`.
**Example**: :code:`118.211`.
time_signature : int or tuple[int, ...]; keyword-only; optional
Estimated time signature of the track.
**Valid values**:
* :code:`3` – 3/4.
* :code:`4` – 4/4.
* :code:`5` – 5/4.
* :code:`6` – 6/4.
* :code:`7` – 7/4.
valence : float or tuple[float, ...]; keyword-only; optional
Confidence measure of the musical positiveness conveyed by a
track.
**Valid range**: :code:`0.0` (e.g., happy, cheerful,
euphoric) to :code:`1.0` (e.g., sad, depressed, angry).
**Example**: :code:`0.428`.
Returns
-------
recommendations : dict[str, Any]
Spotify metadata for the tracks recommended based on the
provided seeds and tuning parameters.
.. admonition:: Sample response
:class: response dropdown
.. code-block::
{
"seeds": [
{
"afterFilteringSize": <int>,
"afterRelinkingSize": <int>,
"href": <str>,
"id": <str>,
"initialPoolSize": <int>,
"type": <str>
}
],
"tracks": [
{
"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>,
"restrictions": {
"reason": <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>,
"explicit": <bool>,
"external_ids": {
"ean": <str>,
"isrc": <str>,
"upc": <str>
},
"external_urls": {
"spotify": <str>
},
"href": <str>,
"id": <str>,
"is_local": <bool>,
"is_playable": <bool>,
"linked_from": {},
"name": <str>,
"popularity": <int>,
"preview_url": <str>,
"restrictions": {
"reason": <str>
},
"track_number": <int>,
"type": "track",
"uri": <str>
}
]
}
"""
params = {}
if country_code is not None:
self._client.markets._validate_market(country_code)
params["market"] = country_code
if limit is not None:
self._validate_number("limit", limit, int, 1, 50)
params["limit"] = limit
n_seeds = self._parse_seeds(
"seed_tracks",
seed_track_ids,
self._parse_seeds(
"seed_genres",
seed_genres,
self._parse_seeds("seed_artists", seed_artist_ids, 0, params),
params,
),
params,
)
if n_seeds == 0:
raise ValueError("At least one seed must be provided.")
if n_seeds > 5:
raise ValueError("A maximum of 5 seeds is allowed.")
_locals = locals()
for attr, dtype, range_ in [
("acousticness", float, (0.0, 1.0)),
("danceability", float, (0.0, 1.0)),
("duration_ms", int, (0,)),
("energy", float, (0.0, 1.0)),
("instrumentalness", float, (0.0, 1.0)),
("key", int, (-1, 11)),
("liveness", float, (0.0, 1.0)),
("loudness", float, (None, 0.0)),
("mode", int, (0, 1)),
("popularity", int, (0, 100)),
("speechiness", float, (0.0, 1.0)),
("tempo", float, (0.0,)),
("time_signature", int, (3, 7)),
("valence", float, (0.0, 1.0)),
]:
self._parse_attribute(
attr, _locals.get(attr), dtype, range_, params
)
return self._client._request(
"GET", "recommendations", params=params
).json()
[docs]
@TTLCache.cached_method(ttl="hourly")
def get_my_top_tracks(
self,
*,
time_range: str | None = None,
limit: int | None = None,
offset: int | None = None,
) -> dict[str, Any]:
"""
`Users > Get Current User's Top Tracks
<https://developer.spotify.com/documentation/web-api/reference
/get-users-top-artists-and-tracks>`_: Get Spotify catalog
information for the current user's top tracks.
.. admonition:: Authorization scope and third-party application
mode
:class: entitlement
.. tab-set::
.. tab-item:: Required
:code:`user-top-read` scope
Read your top artists and contents. `Learn more.
<https://developer.spotify.com/documentation/web-api
/concepts/scopes#user-top-read>`__
.. 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
----------
time_range : str; keyword-only; optional
Time frame over which the current user's listening history
is analyzed to determine top tracks.
**Valid values**:
* :code:`"long_term"` – Approximately one year of data,
including all new data as it becomes available.
* :code:`"medium_term"` – Approximately the last six months
of data.
* :code:`"short_term"` – Approximately the last four weeks
of data.
**API default**: :code:`"medium_term"`.
limit : int; keyword-only; optional
Maximum number of tracks to return.
**Valid range**: :code:`1` to :code:`50`.
**API default**: :code:`20`.
offset : int; keyword-only; optional
Index of the first track to return. Use with `limit` to get
the next batch of tracks.
**Minimum value**: :code:`0`.
**API default**: :code:`0`.
Returns
-------
items : dict[str, Any]
Page of Spotify metadata for the current user's top tracks.
.. admonition:: Sample response
:class: response dropdown
.. code-block::
{
"href": <str>,
"items": [
{
"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>
}
],
"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></str>
}
],
"available_markets": <list[str]>,
"disc_number": <int>,
"duration_ms": <int>,
"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_number": <int>,
"type": "track",
"uri": <str>
}
],
"limit": <int>,
"next": <str>,
"offset": <int>,
"previous": <str>,
"total": <int>
}
"""
self._client._require_scopes(
"users.get_my_top_tracks", "user-top-read"
)
return self._client.users.get_my_top_items(
"tracks", time_range=time_range, limit=limit, offset=offset
)