Esempio n. 1
0
    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)
Esempio n. 2
0
    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)
Esempio n. 3
0
    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)
Esempio n. 4
0
    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)
Esempio n. 5
0
    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
Esempio n. 6
0
    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)
Esempio n. 7
0
    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)
Esempio n. 8
0
    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)
Esempio n. 9
0
    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)
Esempio n. 10
0
    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)
Esempio n. 11
0
    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)
Esempio n. 12
0
    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
Esempio n. 13
0
    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}
Esempio n. 14
0
    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)
Esempio n. 15
0
    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)
Esempio n. 16
0
    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
Esempio n. 17
0
    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]
Esempio n. 18
0
    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)
Esempio n. 19
0
    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]
Esempio n. 20
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)
Esempio n. 21
0
    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)
Esempio n. 22
0
    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)
Esempio n. 23
0
    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)
Esempio n. 24
0
    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]
Esempio n. 25
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
Esempio n. 26
0
    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)
Esempio n. 27
0
    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}
Esempio n. 28
0
    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)
Esempio n. 29
0
    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', {})))
Esempio n. 30
0
    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])