import logging
from urllib3.util import Retry
from requests import Session, Response
from requests.exceptions import HTTPError, Timeout
from requests.adapters import HTTPAdapter
from cachecontrol import CacheControlAdapter
from .creds import ClientCreds, _set_empty_user_creds_if_none
from .excs import ApiError, AuthError
from .utils import _safe_getitem
from .wrappers import (
_dispatch_request,
_set_and_get_me_attr_sync,
_default_to_locale,
_inject_user_id,
)
from .base_client import _BaseClient, TOKEN_EXPIRED_MSG
logger = logging.getLogger(__name__)
[docs]class Spotify(_BaseClient):
"""
Spotify's Synchronous Client
Arguments:
client_creds (pyfy.creds.ClientCreds): A client credentials model
user_creds (pyfy.creds.UserCreds): A user credentials model
ensure_user_auth (bool):
* Whether or not to fail upon instantiation if user_creds provided where invalid and not refresheable.
* Default: False
proxies:
* socks or http proxies
* http://docs.python-requests.org/en/master/user/advanced/#proxies & http://docs.python-requests.org/en/master/user/advanced/#socks
timeout (int):
* Seconds before request raises a timeout error
* Default: 7
max_retries (int):
* Max retries before a request fails
* Default: 10
backoff_factor (float):
* Factor by which requests delays the next request when encountring a 429 too-many-requests error
* Default: 0.1
default_to_locale (bool):
* Will pass methods decorated with @_default_to_locale the user's locale if available.
* Default: True
cache:
* Whether or not to cache HTTP requests for the user
* Default: True
populate_user_creds (bool):
* Sets user_creds info from Spotify to client's user_creds object. e.g. country.
* Default: True
"""
IS_ASYNC = False
def __init__(
self,
access_token=None,
client_creds=ClientCreds(),
user_creds=None,
ensure_user_auth=False,
proxies={},
timeout=7,
max_retries=10,
backoff_factor=0.1,
default_to_locale=True,
cache=True,
populate_user_creds=True,
):
super().__init__(
access_token,
client_creds,
user_creds,
ensure_user_auth,
proxies,
timeout,
max_retries,
backoff_factor,
default_to_locale,
cache,
populate_user_creds,
)
if populate_user_creds and self.user_creds:
self.populate_user_creds()
[docs] def populate_user_creds(self):
"""
Populates self.user_creds with Spotify's info on user.
Data is fetched from self.me() and set to user recursively
"""
me = self.me()
if me:
self._populate_user_creds(me)
def _create_session(self, max_retries, proxies, backoff_factor, cache):
sess = Session()
# Retry only on idempotent methods and only when too many requests
retries = Retry(
total=max_retries,
backoff_factor=backoff_factor,
status_forcelist=[429],
method_whitelist=["GET", "UPDATE", "DELETE"],
)
retries_adapter = HTTPAdapter(max_retries=retries)
if cache:
cache_adapter = CacheControlAdapter(cache_etags=True)
sess.mount("http://", retries_adapter)
sess.mount("http://", cache_adapter)
sess.proxies.update(proxies)
return sess
@_dispatch_request
def _check_authorization(self):
"""
Checks whether the credentials provided are valid or not by making and api call that requires no scope but still requires authorization
"""
return list(), dict()
def _send_authorized_request(self, r):
if (
getattr(self._caller, "access_is_expired", None) is True
): # True if expired and None if there's no expiry set
self._refresh_token()
r.headers.update(self._access_authorization_header)
return self._send_request(r)
def _send_request(self, r):
prepped = r.prepare()
logger.debug(r.url)
try:
res = self._session.send(prepped, timeout=self.timeout)
res.raise_for_status()
except Timeout as e:
raise ApiError(
"Request timed out.\nTry increasing the client's timeout period",
http_response=Response(),
http_request=r,
e=e,
)
except HTTPError as e:
if res.status_code == 401:
if (
res.json().get("error", None).get("message", None)
== TOKEN_EXPIRED_MSG
):
old_auth_header = r.headers["Authorization"]
self._refresh_token() # Should either raise an error or refresh the token
new_auth_header = self._access_authorization_header
if new_auth_header == old_auth_header:
msg = "refresh_token() was successfully called but token wasn't refreshed. Execution stopped to avoid infinite looping."
logger.critical(msg)
raise RuntimeError(msg)
r.headers.update(new_auth_header)
return self._send_request(r)
else:
msg = res.json().get("error_description") or res.json()
raise AuthError(msg=msg, http_response=res, http_request=r, e=e)
else:
msg = _safe_getitem(res.json(), "error", "message") or _safe_getitem(
res.json(), "error_description"
)
raise ApiError(msg=msg, http_response=res, http_request=r, e=e)
else:
return res
[docs] def authorize_client_creds(self, client_creds=None):
"""
Authorize with client credentials oauth flow i.e. Only with client secret and client id.
Call this to send request using client credentials.
https://developer.spotify.com/documentation/general/guides/authorization-guide/
Note:
This will give you limited access to most endpoints
Arguments:
client_creds (pyfy.creds.ClientCreds): Client Credentials object. Defaults to ``self.client_creds``.
Raises:
pyfy.excs.AuthErrror:
"""
r = self._prep_authorize_client_creds(client_creds)
try:
res = self._send_request(r)
except ApiError as e:
raise AuthError(
msg="Failed to authenticate with client credentials",
http_response=e.http_response,
http_request=r,
e=e,
)
else:
new_creds_json = res.json()
new_creds_model = self._client_json_to_object(new_creds_json)
self._update_client_creds_with(new_creds_model)
self._caller = self.client_creds
self._check_authorization()
@property
def is_active(self):
"""
Checks if user_creds or client_creds are valid (depending on who was last set)
"""
if self._caller is None:
return False
try:
self._check_authorization()
except AuthError:
return False
else:
return True
def _refresh_token(self):
if self._caller is self.user_creds:
return self._refresh_user_token()
elif self._caller is self.client_creds:
return self.authorize_client_creds()
else:
raise AuthError("No caller to refresh token for")
def _refresh_user_token(self):
r = self._prep_refresh_user_token()
res = self._send_request(r).json()
new_creds_obj = self._user_json_to_object(res)
self._update_user_creds_with(new_creds_obj)
[docs] @_set_empty_user_creds_if_none
def build_user_creds(self, grant, set_user_creds=True):
"""
Second part of OAuth2 authorization code flow, Raises an AuthError if unauthorized
Arguments:
grant (str): Code returned to user after authorizing your application
set_user_creds (bool): Whether or not to set the user created to the client as the current active user
Returns:
pyfy.creds.UserCreds: User Credentials Model
"""
# Get user creds
user_creds_json = self._request_user_creds(grant)
user_creds_model = self._user_json_to_object(user_creds_json)
# Set user creds
if set_user_creds:
self.user_creds = user_creds_model
return user_creds_model
@property
def is_premium(self, **kwargs):
"""
Checks whether user is premium or not
Returns:
bool:
"""
if _set_and_get_me_attr_sync(self, "product") == "premium":
return True
return False
@_dispatch_request(authorized_request=False)
def _request_user_creds(self, grant):
return [grant], dict()
####################################################################### RESOURCES ############################################################################
##### Playback
[docs] @_dispatch_request
def devices(self, *args, **kwargs):
"""
Lists user's devices
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
[docs] @_dispatch_request
def play(self, *args, **kwargs):
"""
Starts playback
Play a list of one or more tracks, or a specific artist, album or playlist.
Only one of track_ids, album_id, artist_id, playlist_id should be specified.
Start playback at offset_position OR offset_uri, only if artist_id is not being used.
Arguments:
track_ids (list, tuple, str):
* Optional
* List, string or tuple containing track ID(s).
album_id (str):
* Optional
artist_id (str):
* Optional
playlist_id (str):
* Optional
device_id (str):
* Optional
offset_position (int):
* Optional
offset_uri (str):
* Optional
poition_ms (int):
* Optional
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
[docs] @_dispatch_request
def pause(self, *args, **kwargs):
"""
Pauses playback
Arguments:
device_id (str):
* Optional
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
[docs] @_dispatch_request
@_default_to_locale("market")
def currently_playing(self, *args, **kwargs):
"""
Lists currenly playing
Arguments:
market (str):
* Optional
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
[docs] @_dispatch_request
@_default_to_locale("market")
def currently_playing_info(self, *args, **kwargs):
"""
Lists currently playing info
Arguments:
market (str):
* Optional
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
[docs] @_dispatch_request
def recently_played_tracks(self, *args, **kwargs):
"""
Lists recently played tracks
Arguments:
limit (int):
* Optional
after:
* Optional
before:
* Optional
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
[docs] @_dispatch_request
def next(self, *args, **kwargs):
"""
Next playback
Arguments:
device_id:
* Optional
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
[docs] @_dispatch_request
def previous(self, *args, **kwargs):
"""
Previous Playback
Arguments:
device_id:
* Optional
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
[docs] @_dispatch_request
def repeat(self, *args, **kwargs):
"""
Toggle repeat
Arguments:
state:
* Optional
* Default: 'context'
device_id:
* Optional
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
[docs] @_dispatch_request
def seek(self, *args, **kwargs):
"""
Seek Playback
Arguments:
posiotion_ms:
* Required
device_id:
* Optional
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
[docs] @_dispatch_request
def shuffle(self, *args, **kwargs):
"""
Shuffle Playback
Arguments:
state:
* Optional
* Default: True
device_id:
* Optional
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
[docs] @_dispatch_request
def playback_transfer(self, *args, **kwargs):
"""
Transfer playback to another device
Arguments:
device_ids (list, str):
* Required
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
[docs] @_dispatch_request
def volume(self, *args, **kwargs):
"""
Change volume
Arguments:
volume_percent (int):
* Required
device_id:
* Optional
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
[docs] @_dispatch_request
def queue(self, *args, **kwargs):
"""
Add an item to the end of the user’s current playback queue
Arguments:
track_id (str):
* Required
device_id:
* Optional
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
##### Playlists
[docs] @_dispatch_request
@_default_to_locale("market")
def playlist(self, *args, **kwargs):
"""
Lists playlist
Arguments:
playlist_id:
* Required
market:
* Optional
fields:
* Optional
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
[docs] @_dispatch_request
def playlist_cover(self, *args, **kwargs):
"""
Get a Playlist Cover Image
Arguments:
playlist_id:
* Required
Returns:
list:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
[docs] @_dispatch_request
def user_playlists(self, *args, **kwargs):
"""
Lists playlists owned by a user
Arguments:
user_id:
* Optional
* Defaults to user's
limit:
* Optional
offset:
* Optional
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
[docs] @_dispatch_request
@_inject_user_id
def follows_playlist(self, *args, **kwargs):
"""
Lists whether or not user follows a playlist
Arguments:
playlist_id:
* Required
user_ids (list, str):
* Optional
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
[docs] @_dispatch_request
@_inject_user_id
def create_playlist(self, *args, **kwargs):
"""
Creates a playlist
Arguments:
name:
* Required
description:
* Optional
public:
* Optional
* Default: False
collaborative:
* Optional
* default: False
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
[docs] @_dispatch_request
def follow_playlist(self, *args, **kwargs):
"""
Follows a playlist
Arguments:
playlist_id:
* Required
public:
* Optional
* Default: False
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
[docs] @_dispatch_request
def update_playlist(self, *args, **kwargs):
"""
Updates a playlist
Arguments:
playlist_id:
* Required
name:
* Optional
description:
* Optional
public:
* Optional
collaborative:
* Optional
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
[docs] @_dispatch_request
def unfollow_playlist(self, *args, **kwargs):
"""
Unfollow a playlist
Arguments:
playlist_id:
* Required
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
[docs] @_dispatch_request
def delete_playlist(self, *args, **kwargs):
"""
An alias to unfollow_playlist
Arguments:
playlist_id:
* Required
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
##### Playlist Contents
[docs] @_dispatch_request
@_default_to_locale("market")
def playlist_tracks(self, *args, **kwargs):
"""
List tracks in a playlist
Arguments:
playlist_id:
* Required
market:
* Optional
fields:
* Optional
limit:
* Optional
offset:
* Optional
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
[docs] @_dispatch_request
def add_playlist_tracks(self, *args, **kwargs):
"""
Add tracks to a playlist
Arguments:
playlist_id:
* Required
track_ids (str, list):
* Required
position:
* Optional
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
[docs] @_dispatch_request
def reorder_playlist_track(self, *args, **kwargs):
"""
Reorder tracks in a playlist
Arguments:
playlist_id:
* Required
range_start:
* Optional
range_length:
* Optional
insert_before:
* Optional
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
[docs] @_dispatch_request
def replace_playlist_tracks(self, *args, **kwargs):
"""
Replace all tracks of a playlist with tracks of your choice
Arguments:
playlist_id:
* Required
track_ids:
* track_ids not full URIs
* Optional
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
[docs] @_dispatch_request
def delete_playlist_tracks(self, *args, **kwargs):
"""
Delete tracks from a playlist
https://developer.spotify.com/console/delete-playlist-tracks/
Examples:
``track_ids`` types supported: ::
1) 'track_id'
2) ['track_id', 'track_id', 'track_id']
3) [
{
'id': track_id,
'positions': [
position1, position2
]
},
{
'id': track_id,
'positions': position1
},
track_id
]
Arguments:
playlist_id:
* Required
track_ids:
* Required
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
##### Tracks
[docs] @_dispatch_request
@_default_to_locale("market")
def user_tracks(self, *args, **kwargs):
"""
List user's tracks
Arguments:
market:
* Optional
limit:
* Optional
offset:
* Optional
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
[docs] @_dispatch_request
@_default_to_locale("market")
def tracks(self, *args, **kwargs):
"""
List tracks
Arguments:
track_ids (str, list):
* Required
market:
* Optional
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
[docs] @_dispatch_request
def owns_tracks(self, *args, **kwargs):
"""
Lists whether or not current user owns tracks
Arguments:
track_ids (str, list):
* Required
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
[docs] @_dispatch_request
def save_tracks(self, *args, **kwargs):
"""
Save tracks
Arguments:
track_ids (str, list):
* Required
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
[docs] @_dispatch_request
def delete_tracks(self, *args, **kwargs):
"""
Delete user's tracks
Arguments:
track_ids (str, list):
Returns:
dict:
* Required
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
##### Artists
[docs] @_dispatch_request
def artists(self, *args, **kwargs):
"""
List artists
Arguments:
artist_ids (str, list):
Returns:
dict:
* Required
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
[docs] @_dispatch_request
def followed_artists(self, *args, **kwargs):
"""
List artists followed by current user
Arguments:
after:
* Optional
limit:
* Optional
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
[docs] @_dispatch_request
def follows_artists(self, *args, **kwargs):
"""
Whether or not current user follows an artist(s)
Arguments:
artist_ids (str, list):
* Required
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
[docs] @_dispatch_request
def follow_artists(self, *args, **kwargs):
"""
Follow an artist(s)
Arguments:
artist_ids (str, list):
* Required
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
[docs] @_dispatch_request
def unfollow_artists(self, *args, **kwargs):
"""
Unfollow artist(s)
Arguments:
artist_ids (str, list):
* Required
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
[docs] @_dispatch_request
@_default_to_locale("country")
def artist_top_tracks(self, *args, **kwargs):
"""
List top tracks of an artist
Arguments:
artist_id (str):
* Required
country:
* Optional
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
##### Albums
[docs] @_dispatch_request
@_default_to_locale("market")
def albums(self, *args, **kwargs):
"""
List Albums
Arguments:
album_ids (str, list):
* Required
market:
* Optional
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
[docs] @_dispatch_request
def user_albums(self, *args, **kwargs):
"""
Albums owned by current user
Arguments:
limit:
* Optional
offset:
* Optional
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
[docs] @_dispatch_request
def owns_albums(self, *args, **kwargs):
"""
Whether or not current user owns an album(s)
Arguments:
album_ids (str, list):
* Required
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
[docs] @_dispatch_request
def save_albums(self, *args, **kwargs):
"""
Save Albums
Arguments:
album_ids (str, list):
* Required
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
[docs] @_dispatch_request
def delete_albums(self, *args, **kwargs):
"""
Delete Albums
Arguments:
album_ids (str, list):
* Required
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
##### Users
[docs] @_dispatch_request
def me(self, *args, **kwargs):
"""
List current user's profile
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
[docs] @_dispatch_request
def user_profile(self, *args, **kwargs):
"""
List a user's profile
Arguments:
user_id (str):
* Required
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
[docs] @_dispatch_request
def follows_users(self, *args, **kwargs):
"""
Whether or not current user follows a user(s)
Arguments:
user_ids (str, list):
* Required
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
[docs] @_dispatch_request
def follow_users(self, *args, **kwargs):
"""
Follow a user
Arguments:
user_ids (str, list):
* Required
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
[docs] @_dispatch_request
def unfollow_users(self, *args, **kwargs):
"""
Unfollow user(s)
Arguments:
user_ids (str, list):
* Required
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
##### Others
[docs] @_dispatch_request
@_default_to_locale("market")
def album_tracks(self, *args, **kwargs):
"""
List tracks of an album
Arguments:
album_id (str):
* Required
market:
* Optional
limit:
* Optional
offset:
* Optional
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
[docs] @_dispatch_request
@_default_to_locale("market")
def artist_albums(self, *args, **kwargs):
"""
List albums of an artist
Arguments:
artist_id (str):
* Required
include_groups:
* Optional
market:
* Optional
limit:
* Optional
offset:
* Optional
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
[docs] @_dispatch_request
def user_top_tracks(self, *args, **kwargs):
"""
List top tracks of a user
Arguments:
time_range:
* Optional
limit:
* Optional
offset:
* Optional
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
[docs] @_dispatch_request
def user_top_artists(self, *args, **kwargs):
"""
List top artists of a user
Arguments:
time_range:
* Optional
limit:
* Optional
offset:
* Optional
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
[docs] @_dispatch_request
def next_page(self, *args, **kwargs):
"""
Next Page
Note:
* You can either provide a response or a url
* Providing a URL will be slightly faster as Pyfy will not have to search for the key in the response dict
Arguments:
response (dict):
* Optional
url (str):
* Optional
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
[docs] @_dispatch_request
def previous_page(self, *args, **kwargs):
"""
Previous Page
Note:
* You can either provide a response or a url
* Providing a URL will be slightly faster as Pyfy will not have to search for the key in the response dict
Arguments:
response (dict):
* Optional
url (str):
* Optional
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
##### Personalization & Explore
[docs] @_dispatch_request
@_default_to_locale("country", support_from_token=False)
def category(self, *args, **kwargs):
"""
List Category
Arguments:
category_id:
* Required
country:
* Optional
locale:
* Optional
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
[docs] @_dispatch_request
@_default_to_locale("country", support_from_token=False)
def categories(self, *args, **kwargs):
"""
List Categories
Arguments:
country:
* Optional
locale:
* Optional
limit:
* Optional
offset:
* Optional
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
[docs] @_dispatch_request
@_default_to_locale("country", support_from_token=False)
def category_playlist(self, *args, **kwargs):
"""
List playlists from a category
Arguments:
category_id:
* Required
country:
* Optional
limit:
* Optional
offset:
* Optional
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
[docs] @_dispatch_request
def available_genre_seeds(self, *args, **kwargs):
"""
Available genre seeds
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
[docs] @_dispatch_request
@_default_to_locale("country", support_from_token=False)
def featured_playlists(self, *args, **kwargs):
"""
Featured Playlists
Arguments:
country:
* Optional
locale:
* Optional
timestamp:
* Optional
limit:
* Optional
offset:
* Optional
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
[docs] @_dispatch_request
@_default_to_locale("country", support_from_token=False)
def new_releases(self, *args, **kwargs):
"""
New Releases
Arguments:
country:
* Optional
limit:
* Optional
offset:
* Optional
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
[docs] @_dispatch_request
@_default_to_locale("market")
def search(self, *args, **kwargs):
"""
Search
Examples:
tracks parameter example: ::
'track' or ['track'] or 'artist' or ['track','artist']
Arguments:
q:
* Query
* Required
types:
* Optional
* Default: ``'track'``
market:
* Optional
limit:
* Optional
offset:
* Optional
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
[docs] @_dispatch_request
def track_audio_analysis(self, *args, **kwargs):
"""
List audio analysis of a track
Arguments:
track_id:
* Required
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
[docs] @_dispatch_request
def tracks_audio_features(self, *args, **kwargs):
"""
List audio features of tracks
Arguments:
track_ids (str, list):
* Required
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs
[docs] @_dispatch_request
@_default_to_locale("market", support_from_token=False)
def recommendations(self, *args, **kwargs):
"""
List Recommendations
https://developer.spotify.com/documentation/web-api/reference/browse/get-recommendations/
Arguments:
limit:
* Optional
market:
* Optional
seed_artists:
* Optional
seed_genres:
* Optional
seed_tracks:
* Optional
min_acousticness:
* Optional
max_acousticness:
* Optional
target_acousticness:
* Optional
min_danceability:
* Optional
max_danceability:
* Optional
target_danceability:
* Optional
min_duration_ms:
* Optional
max_duration_ms:
* Optional
target_duration_ms:
* Optional
min_energy:
* Optional
max_energy:
* Optional
target_energy:
* Optional
min_instrumentalness:
* Optional
max_instrumentalness:
* Optional
target_instrumentalness:
* Optional
min_key:
* Optional
max_key:
* Optional
target_key:
* Optional
min_liveness:
* Optional
max_liveness:
* Optional
target_liveness:
* Optional
min_loudness:
* Optional
max_loudness:
* Optional
target_loudness:
* Optional
min_mode:
* Optional
max_mode:
* Optional
target_mode:
* Optional
min_popularity:
* Optional
max_popularity:
* Optional
target_popularity:
* Optional
min_speechiness:
* Optional
max_speechiness:
* Optional
target_speechiness:
* Optional
min_tempo:
* Optional
max_tempo:
* Optional
target_tempo:
* Optional
min_time_signature:
* Optional
max_time_signature:
* Optional
target_time_signature:
* Optional
min_valence:
* Optional
max_valence:
* Optional
target_valence:
* Optional
Returns:
dict:
Raises:
pyfy.excs.ApiError:
"""
return args, kwargs