def _follow_unfollow_help(self, other, request_type): """ follow and unfollow are identical, except for the request type. This function implements that functionality to remove duplicate code. """ # Validate input if not isinstance(other, list): other = [other] for elem in other: if type(elem) not in [Artist, User, Playlist]: raise TypeError(elem) # Split up input artists = utils.separate(other, Artist) users = utils.separate(other, User) playlists = utils.separate(other, Playlist) for batch in utils.create_batches(utils.map_ids(artists)): response_json, status_code = utils.request( self._session, request_type=request_type, endpoint=Endpoints.USER_FOLLOW_ARTIST_USER, body=None, uri_params={ 'type': 'artist', 'ids': batch }) if status_code != 204: raise utils.SpotifyError(status_code, response_json) for batch in utils.create_batches(utils.map_ids(users)): response_json, status_code = utils.request( self._session, request_type=request_type, endpoint=Endpoints.USER_FOLLOW_ARTIST_USER, body=None, uri_params={ 'type': 'user', 'ids': batch }) if status_code != 204: raise utils.SpotifyError(status_code, response_json) for playlist in playlists: response_json, status_code = utils.request( self._session, request_type=request_type, endpoint=Endpoints.USER_FOLLOW_PLAYLIST % playlist, body=None, uri_params=None) if status_code != 200: raise utils.SpotifyError(status_code, response_json)
def set_playback_position(self, position, device_id=None): """ Set the current position in the currently playing track in ms. Args: position (int): the position (in ms). Must be non-negative. If greater than the len of the track, will play the next song. Returns: None Calls endpoints: - PUT /v1/me/player/seek Required token scopes: - user-modify-playback-state """ if position < 0: raise ValueError(position) uri_params = {'position_ms': position} if device_id is not None: uri_params['device_id'] = device_id response_json, status_code = utils.request( self._session, request_type=const.REQUEST_PUT, endpoint=Endpoints.PLAYER_SEEK, body=None, uri_params=uri_params) if status_code != 204: raise utils.SpotifyError(status_code, response_json)
def set_shuffle(self, shuffle_state, device_id=None): """ Set the shuffle state of the active device. Args: shuffle_state (bool): True to set shuffle to on, False to set shuffle to off Returns: None Calls endpoints: - PUT /v1/me/player/shuffle Required token scopes: - user-modify-playback-state """ uri_params = {'state': shuffle_state} if device_id is not None: uri_params['device_id'] = device_id response_json, status_code = utils.request( self._session, request_type=const.REQUEST_PUT, endpoint=Endpoints.PLAYER_SHUFFLE, body=None, uri_params=uri_params) if status_code != 204: raise utils.SpotifyError(status_code, response_json)
def set_active_device(self, device_id, force_play=const.KEEP_PLAY_STATE): """ Transfer playback to a different available device. Args: force_play: one of: - sp.FORCE_PLAY: resume playback after transfering to new device - sp.KEEP_PLAY_STATE: keep the current playback state. Returns: None Calls endpoints: - PUT /v1/me/player Required token scopes: - user-modify-playback-state """ if force_play not in [const.FORCE_PLAY, const.KEEP_PLAY_STATE]: raise ValueError(force_play) body = { 'device_ids': [device_id], 'play': force_play == const.FORCE_PLAY } response_json, status_code = utils.request( self._session, request_type=const.REQUEST_PUT, endpoint=Endpoints.PLAYER_TRANSFER, body=body, uri_params=None) if status_code != 204: raise utils.SpotifyError(status_code, response_json)
def available_devices(self): """ Get all devices currently available. Returns: List[str]: All available device ids. Calls endpoints: - GET /v1/me/player/devices Required token scopes: - user-read-playback-state """ response_json, status_code = utils.request( self._session, request_type=const.REQUEST_GET, endpoint=Endpoints.PLAYER_AVAILABLE_DEVICES, body=None, uri_params=None) if status_code != 200: raise utils.SpotifyError(status_code, response_json) try: devices = response_json['devices'] result = [elem['id'] for elem in devices] except KeyError: raise utils.SpotifyError(KEYSTRING) return result
def set_volume(self, volume, device_id=None): """ Set the current volume for the playback. Args: volume (int): volume (in percent) from 0 to 100 inclusive Returns: None Calls endpoints: - PUT /v1/me/player/volume Required token scopes: - user-modify-playback-state """ if volume < 0 or volume > 100: raise ValueError(volume) uri_params = {'volume_percent': volume} if device_id is not None: uri_params['device_id'] = device_id response_json, status_code = utils.request( self._session, request_type=const.REQUEST_PUT, endpoint=Endpoints.PLAYER_VOLUME, body=None, uri_params=uri_params) if status_code != 204: raise utils.SpotifyError(status_code, response_json)
def current_user(self): """ Returns: User: The user associated with the current Spotify API token. Raises: ValueError: if the Spotify API key is not valid. ValueError: if the response is empty. HTTPError: if failure or partial failure. Calls endpoints: - GET /v1/me """ # Construct params for API call endpoint = Endpoints.SEARCH_CURRENT_USER # Execute requests response_json, status_code = utils.request( session=self, request_type=const.REQUEST_GET, endpoint=endpoint) if status_code != 200: raise utils.SpotifyError(status_code, response_json) return User(self, response_json)
def previous(self, device_id=None): """ Skip to the previous song in the playback. Note: Will skip to the previous song in the playback regardless of where in the current song playback is. Returns: None Calls endpoints: - POST /v1/me/player/previous Required token scopes: - user-modify-playback-state """ uri_params = None if device_id is None else {'device_id': device_id} response_json, status_code = utils.request( self._session, request_type=const.REQUEST_POST, endpoint=Endpoints.PLAYER_PREVIOUS, body=None, uri_params=uri_params) if status_code != 204: raise utils.SpotifyError(status_code, response_json)
def update_visibility(self, visibility): """ Updates whether the playlist is public/private/collaborative. Args: visibility: One of: - sp.PUBLIC - sp.PRIVATE - sp.PRIVATE_COLLAB Required token scopes: - playlist-modify-public: If the playlist is public. - playlist-modify-private: If the playlist is private or collaborative. Calls endpoints: - PUT /v1/playlists/{playlist_id} """ endpoint = Endpoints.PLAYLIST % self.spotify_id() body = {} if visibility not in [const.PUBLIC, const.PRIVATE, const.PRIVATE_COLLAB]: raise ValueError('Invalid visibility, must be one of [sp.PUBLIC,' + 'sp.PRIVATE, sp.PRIVATE_COLLAB]') body['public'] = (visibility == const.PUBLIC) body['collaborative'] = (visibility == const.PRIVATE_COLLAB) response_json, status_code = utils.request( self._session, request_type='PUT', endpoint=endpoint, body=body ) if status_code != 200: raise utils.SpotifyError(status_code, response_json)
def resume(self, device_id=None): """ Resume the current playback. Returns: None Calls endpoints: - PUT /v1/me/player/play Required token scopes: - user-modify-playback-state Raises: SpotifyError: if playback is already playing """ uri_params = None if device_id is None else {'device_id': device_id} response_json, status_code = utils.request( self._session, request_type=const.REQUEST_PUT, endpoint=Endpoints.PLAYER_PLAY, body=None, uri_params=uri_params) if status_code != 204: raise utils.SpotifyError(status_code, response_json)
def replace_all_tracks(self, tracks): """ Replace all of the tracks in the playlist. Args: tracks (List[Track]): The tracks to populate the playlist. Warning: All previously present tracks in the playlist will be removed. Required token scopes: - playlist-modify-public: If the playlist is public. - playlist-modify-private: If the playlist is private or collaborative. Calls endpoints: - PUT /v1/playlists/{playlist_id}/tracks """ endpoint = Endpoints.PLAYLIST_TRACKS % self.spotify_id() if not all([isinstance(track, Track) for track in tracks]): raise TypeError('All elements of tracks must be Track objects') body = {} body['uris'] = [track.uri() for track in tracks] response_json, status_code = utils.request( self._session, request_type='PUT', endpoint=endpoint, body=body ) if status_code != 201: raise utils.SpotifyError(status_code, response_json)
def audio_analysis(self): #pylint: disable=line-too-long """ Get the audio analysis for this track. The Audio Analysis describes the track’s structure and musical content, including rhythm, pitch, and timbre. All information is precise to the audio sample. For more information on track audio analysis, see Spotify's `documentation <https://developer.spotify.com/documentation/web-api/reference/tracks/get-audio-analysis/>`__ Returns: dict: a dictionary containing the audio analysis as defined at the above link. Calls endpoints: - GET /v1/audio-analysis/{id} """ response_json, status_code = utils.request( session=self._session, request_type=const.REQUEST_GET, endpoint=Endpoints.TRACK_ANALYSIS % self.spotify_id() ) if status_code != 200: raise utils.SpotifyError(status_code, response_json) return response_json
def _update_fields(self): """ If field is not present, update it using the object's artist id. Raises: ValueError if artist id not present in the raw object data. Calls endpoints: - GET /v1/artists/{id} """ endpoint = Endpoints.ARTIST_DATA % self.spotify_id() response_json, status_code = utils.request( session=self._session, request_type=const.REQUEST_GET, endpoint=endpoint, ) if status_code != 200: raise utils.SpotifyError(status_code, response_json) # Updates _raw with new values. One liner : for each key in union of # keys in self._raw and response_json, takes value for key from # response_json if present, else takes value for key from self._raw. # TODO: this is weird notation, make a utility function for it. # Especially useful since it is an action necessary for many classes. self._raw = {**self._raw, **response_json}
def _update_fields(self): """ Update self._raw using the User id. Calls endpoints: GET /v1/users/{id} GET /v1/me Note: This method only includes private fields if those are allowed by the token scopes and the token was created by this user. Required token scopes: user-read-email: to read the email user-read-private: to read the country and subscription """ other = self._session.current_user() if self == other: # We know other is a 'User' object, so this protected access is okay #pylint: disable=protected-access self._raw.update(other._raw) # other._raw has superset of data gotten below, skip extra api call return response_json, status_code = utils.request( session=self._session, request_type=const.REQUEST_GET, endpoint=Endpoints.USER_DATA % self.spotify_id()) if status_code != 200: raise utils.SpotifyError(status_code, response_json) self._raw.update(response_json)
def update_description(self, description): """ Updates the playlist description as it appears on Spotify. Args: description (str): the new new description of this playlist. Required token scopes: - playlist-modify-public: If the playlist is public. - playlist-modify-private: If the playlist is private or collaborative. Calls endpoints: - PUT /v1/playlists/{playlist_id} """ endpoint = Endpoints.PLAYLIST % self.spotify_id() if not isinstance(description, str): raise TypeError('The description must be a string') body = {} body['description'] = description response_json, status_code = utils.request( self._session, request_type='PUT', endpoint=endpoint, body=body ) if status_code != 200: raise utils.SpotifyError(status_code, response_json)
def related_artists(self, search_limit=20): """ Get artists similar to this artist, as defined by Spotify. Args: search_limit (int): the maximum number of results to return. Must be between 1 and 20, inclusive (this is Spotify's limit). Returns: List[Artist]: The artists related to this artist. Calls endpoints: - GET /v1/artists/{id}/related-artists """ # This search limit is not part of the API, Spotify always returns up to # 20. # TODO: limit can't be None... # Type validation if search_limit is not None and not isinstance(search_limit, int): raise TypeError('search_limit should be None or int') # Argument validation if search_limit < 0 or search_limit > 20: raise ValueError('search_limit should be >= 0 and <= 20') # Save params for lazy loading check search_query = (search_limit) # Construct params for API call endpoint = Endpoints.ARTIST_RELATED_ARTISTS % self.spotify_id() # Lazy loading check if search_query == self._related_artists_query_params: return self._related_artists # Update stored params for lazy loading response_json, status_code = utils.request(session=self._session, request_type=\ const.REQUEST_GET, endpoint=endpoint ) if status_code != 200: raise utils.SpotifyError(status_code, response_json) if 'artists' not in response_json: raise utils.SpotifyError('Malformed response, missing key artists') result = [ Artist(self._session, x) for x in response_json.get('artists') ] self._related_artists = result self._related_artists_query_params = search_query return self._related_artists
def _player_data(self, key, market=const.TOKEN_REGION, should_raise_error=True): """ Helper function for the getter methods. Wraps calling the player endpoint and handling a missing key. Args: key: the key to get from the currently playing context market: see the :class:`shared args documentation <Player>` should_raise_error: if False: returns None when no device available if True: raises SpotifyError when no device available Returns: None if there is no active device and should_raise_error is False The result of player_data[key] otherwise Raises: SpotifyError: if Spotify returns an error or the key isn't found NetworkError: for misc network failures Calls endpoints: - GET /v1/me/player Required token scopes: - user-read-playback-state """ response_json, status_code = utils.request( self._session, request_type=const.REQUEST_GET, endpoint=Endpoints.PLAYER_DATA, body=None, uri_params={'market': market}) # No active device # TODO: update when bug report is resolved if status_code == 204: if not should_raise_error: return None raise utils.SpotifyError('No active device', status_code, response_json) # Valid Player errors if status_code in [403, 404]: raise utils.SpotifyError(status_code, response_json) # Misc other failure if status_code != 200: raise utils.NetworkError(status_code, response_json) if key not in response_json: raise utils.SpotifyError(KEYSTRING + ': key <%s> not found' % key) return response_json[key]
def play(self, item, offset=0, device_id=None): """ Change the current track and context for the player. Args: item: an instance of: - sp.Track - sp.Album - sp.Playlist - or sp.Artist. offset (int): the position in item to start playback. Ignored if item is not an Album or Playlist. 0 <= offset < len(item). Note: Playback will start at the beginning of the track. Use in combination with Player.set_playback_position to start elsewhere in the track. Returns: None Calls endpoints: - PUT /v1/me/player/play Required token scopes: - user-modify-playback-state """ # Validate inputs if type(item) not in [Track, Album, Playlist, Artist]: raise TypeError(item) if type(item) in [Album, Playlist]: if offset < 0 or offset >= len(item): raise ValueError(offset) # Build up the request uri_params = None if device_id is None else {'device_id': device_id} if isinstance(item, Track): body = {'uris': [item.uri()]} else: body = {'context_uri': item.uri()} if type(item) in [Album, Playlist]: body['offset'] = {'position': offset} response_json, status_code = utils.request( self._session, request_type=const.REQUEST_PUT, endpoint=Endpoints.PLAYER_PLAY, body=body, uri_params=uri_params) if status_code != 204: raise utils.SpotifyError(status_code, response_json)
def get_artists(self, artist_ids): """ Gets the artists with the given Spotify ids. Args: artist_ids (str, List[str): The Spotify artist id(s) to get. Returns: Union[Album, List[Album]]: The requested artist(s). Raises: TypeError: for invalid types in any argument. HTTPError: if failure or partial failure. Calls endpoints: - GET /v1/artists Note: the following endpoint is not used. - GET /v1/artists/{id} """ # Type validation if not isinstance(artist_ids, str) and\ not all(isinstance(x, str) for x in artist_ids): raise TypeError('artist_ids should be str or list of str') if isinstance(artist_ids, str): artist_ids = list(artist_ids) # Construct params for API call endpoint = Endpoints.SEARCH_ALBUMS uri_params = dict() # A maximum of 50 artists can be returned per API call batches = utils.create_batches(artist_ids, 50) result = list() for batch in batches: uri_params['ids'] = batch # Execute requests response_json, status_code = utils.request( session=self, request_type=const.REQUEST_GET, endpoint=endpoint, uri_params=uri_params) if status_code != 200: raise utils.SpotifyError(status_code, response_json) items = response_json['artists'] for item in items: result.append(Artist(self, item)) return result if len(result) != 1 else result[0]
def _save_remove_help(self, other, request_type): """ save and remove are identical, except for the request type and return codes. This function implements that functionality to remove duplicate code. """ # Validate input if not isinstance(other, list): other = [other] for elem in other: if type(elem) not in [Album, Track]: raise TypeError(elem) # Split up input albums = utils.separate(other, Album) tracks = utils.separate(other, Track) for batch in utils.create_batches(utils.map_ids(albums)): response_json, status_code = utils.request( self._session, request_type=request_type, endpoint=Endpoints.USER_SAVE_ALBUMS, body=None, uri_params={'ids': batch}) # All success codes are 200, except saving an album success = 201 if request_type == const.REQUEST_PUT else 200 if status_code != success: raise utils.SpotifyError(status_code, response_json) for batch in utils.create_batches(utils.map_ids(tracks)): response_json, status_code = utils.request( self._session, request_type=request_type, endpoint=Endpoints.USER_SAVE_TRACKS, body=None, uri_params={'ids': batch}) if status_code != 200: raise utils.SpotifyError(status_code, response_json)
def add_tracks(self, tracks, position=None): """ Adds one or more tracks to the playlist. Args: tracks: A Track object or list of Track objects to be added. position: An integer specifying the 0-indexed position in the playlist to insert tracks. A negative integer will be evaluated from the end of the playlist as negative indices behave in lists. This must be a valid index into a list of length len(playlist). Position can be omitted to append to the playlist instead. Required token scopes: - playlist-modify-public: If the playlist is public. - playlist-modify-private: If the playlist is private or collaborative. Calls endpoints: - POST /v1/playlists/{playlist_id}/tracks """ endpoint = Endpoints.PLAYLIST_TRACKS % self.spotify_id() body = {} uris = [] if isinstance(tracks, list): for track in tracks: if not isinstance(track, Track): raise TypeError('The elements of tracks must be Track ' + 'objects') uris.append(track.uri()) else: if not isinstance(tracks, Track): raise TypeError('The type of tracks must either be a Track ' + 'object or a list of Track objects') uris.append(tracks.uri()) body['uris'] = uris if position: if not isinstance(position, int): raise TypeError('The position must be an integer') original_position = position if position < 0: position += len(self) if position < 0 or position >= len(self): raise ValueError(f'Invalid position: {original_position}') body['position'] = position response_json, status_code = utils.request( self._session, request_type='POST', endpoint=endpoint, body=body ) if status_code != 201: raise utils.SpotifyError(status_code, response_json)
def create_playlist(self, name, visibility=const.PUBLIC, description=None): """ Create a new playlist owned by the current user. Args: name (str): The name for the new playlist. Does not need to be unique; a user may have several playlists with the same name. visibility: How other users interact with this playlist. One of: - sp.PUBLIC: publicly viewable, not collaborative - sp.PRIVATE: not publicly viewable, not collaborative - sp.PRIVATE_COLLAB: not publicly viewable, collaborative description (str): The viewable description of the playlist. Returns: Playlist: The newly created Playlist object. Note that this function modifies the user's library. Required token scopes: - playlist-modify-public - playlist-modify-private Calls endpoints: - POST /v1/users/{user_id}/playlists """ # Validate inputs if visibility not in [ const.PUBLIC, const.PRIVATE, const.PRIVATE_COLLAB ]: raise TypeError(visibility) body = { 'name': name, 'public': visibility == const.PUBLIC, 'collaborative': visibility == const.PRIVATE_COLLAB } if description is not None: body['description'] = description response_json, status_code = utils.request( self._session, request_type=const.REQUEST_POST, endpoint=Endpoints.USER_CREATE_PLAYLIST % self.spotify_id(), body=body, uri_params=None) if status_code != 201: raise utils.SpotifyError(status_code, response_json) return Playlist(self._session, response_json)
def enqueue(self, item, device_id=None): """ Add an item to the end of the queue. Args: item: the item to add to the queue. One of: - Album - Track - Playlist Returns: None Note: If a playlist is added, the order of added songs may be inconsistent. Note: When adding an Album or Playlist, this method can fail partway through, resulting in only some Tracks being added to the queue. Calls endpoints: - POST /v1/me/player/queue Required token scopes: - user-modify-playback-state """ if type(item) not in [Album, Track, Playlist]: raise ValueError(item) # Make into an iterable if isinstance(item, Track): item = [item] uri_params = {} if device_id is None else {'device_id': device_id} # Can only enqueue one item at a time for track in item: uri_params['uri'] = track.uri() response_json, status_code = utils.request( self._session, request_type=const.REQUEST_POST, endpoint=Endpoints.PLAYER_QUEUE, body=None, uri_params=uri_params) if status_code != 204: raise utils.SpotifyError(status_code, response_json)
def get_users(self, user_ids): """ Gets the users with the given Spotify ids. Args: user_ids (str, List[str]): The Spotify user id(s) to get. Returns: Union[User, List[User]]: The requested user(s). Raises: TypeError: for invalid types in any argument. HTTPError: if failure or partial failure. Calls endpoints: - GET /v1/users/{user_id} """ # Type validation if not isinstance(user_ids, str) and\ not all(isinstance(x, str) for x in user_ids): raise TypeError('user_ids should be str or list of str') if isinstance(user_ids, str): user_ids = list('user_ids should be str') # Construct params for API call uri_params = dict() # Each API call can return at most 1 user. Therefore there is no need # to batch this query. result = list() for user_id in user_ids: # Execute requests # TODO: Partial failure - if user with user_id does not exist, # status_code is 404 response_json, status_code = utils.request( session=self, request_type=const.REQUEST_GET, endpoint=Endpoints.SEARCH_USER % user_id, uri_params=uri_params) if status_code != 200: raise utils.SpotifyError(status_code, response_json) result.append(User(self, response_json)) return result if len(result) != 1 else result[0]
def recently_played(self, limit=50): """ Args: limit (int): Max number of items to return. Must be between 1 and 50, inclusive. Returns: List[Tracks]: The user's recently played tracks. Could be empty. Required token scopes: - user-read-recently-played Calls endpoints: - GET /v1/me/player/recently-played Note: - The 'before' and 'after' functionalities are not supported. - Does not return the time the tracks were played - A track must be played for >30s to be included in the history. Tracks played while in a 'private session' are not recorded. """ # Validate arguments if limit <= 0 or limit > 50: raise ValueError(limit) # Execute requests response_json, status_code = utils.request( self._session, request_type=const.REQUEST_GET, endpoint=Endpoints.USER_RECENTLY_PLAYED, body=None, uri_params={'limit': limit}) if status_code != 200: raise utils.SpotifyError(status_code, response_json) results = [] for elem in response_json['items']: results.append(Track(self._session, elem)) return results
def set_repeat(self, mode, device_id=None): """ Set the repeat state for the current playback. Args: mode: one of: - sp.TRACKS: repeat the current track - sp.CONTEXT: repeat the current context (playlist, album, etc.) - sp.OFF: turn repeat off Returns: None Calls endpoints: - PUT /v1/me/player/repeat Required token scopes: - user-modify-playback-state """ if mode not in [const.TRACKS, const.CONTEXT, const.OFF]: raise ValueError(mode) states = { const.TRACKS: 'track', const.CONTEXT: 'context', const.OFF: 'off' } uri_params = {'state': states[mode]} if device_id is not None: uri_params['device_id'] = device_id response_json, status_code = utils.request( self._session, request_type=const.REQUEST_PUT, endpoint=Endpoints.PLAYER_REPEAT, body=None, uri_params=uri_params) if status_code != 204: raise utils.SpotifyError(status_code, response_json)
def _update_fields(self): """ Update self._raw using the track id. Calls endpoints: - GET /v1/tracks/{id} """ response_json, status_code = utils.request( session=self._session, request_type=const.REQUEST_GET, endpoint=Endpoints.TRACK_DATA % self.spotify_id() ) if status_code != 200: raise utils.SpotifyError(status_code, response_json) # Updates _raw with new values. One liner : for each key in union of # keys in self._raw and response_json, takes value for key from # response_json if present, else takes value for key from self._raw. # TODO: this is weird notation, make a utility function for it. # Especially useful since it is an action necessary for many classes. self._raw = {**self._raw, **response_json}
def replace_image(self, path): """ Replace the playlist cover image. Note: The image must be a JPEG and can be at most 256 KB in size. Args: path: A string containing the path to the image to use as the playlist cover image. The image must be a JPEG up to 256 KB. Required token scopes: - ugc-image-upload - playlist-modify-public: If the playlist is public. - playlist-modify-private: If the playlist is private or collaborative. Calls endpoints: - PUT /v1/playlists/{playlist_id}/images """ endpoint = Endpoints.BASE_URI endpoint += Endpoints.PLAYLIST_IMAGES % self.spotify_id() mime_type, _ = mimetypes.guess_type(path) if mime_type != 'image/jpeg': raise ValueError('The image must be an image/jpeg') body = [] with open(path, 'rb') as fp: body.append(base64.b64encode(fp.read())) response_json, status_code = utils.request( self._session, request_type='PUT', endpoint=endpoint, body=body ) if status_code != 202: raise utils.SpotifyError(status_code, response_json)
def refresh(self): """ Refreshes the internal playlist data. Calls: GET /v1/playlists/{playlist_id} """ endpoint = Endpoints.SEARCH_PLAYLIST % self.spotify_id() response_json, status_code = utils.request( self._session, request_type='GET', endpoint=endpoint, ) if status_code != 200: raise utils.SpotifyError(status_code, response_json) self._raw = response_json self._owner = User(self._session, response_json['owner']) self._tracks = [] tracks = response_json.get('tracks', {}) for item in tracks.get('items', []): if 'track' not in item: raise ValueError('Track information missing') self._tracks.append(Track(self._session, item.get('track', {})))
def image(self): """ Get the the playlist cover image. Returns: Image: an image object if the Playlist has a cover image. None: if the Playlist has no cover image. Calls endpoints: - PUT /v1/playlists/{playlist_id} """ endpoint = Endpoints.PLAYLIST_IMAGES % self.spotify_id() response_json, status_code = utils.request( self._session, request_type='GET', endpoint=endpoint ) if status_code != 200: raise utils.SpotifyError(status_code, response_json) if len(response_json) > 1: raise utils.SpotifyError('Playlist has more than one cover image!') return None if len(response_json) == 0 else Image(response_json[0])