Glossario

Seleziona una delle parole chiave a sinistra ...

Programming in PythonProject 1: Spotify

Momento della lettura: ~25 min

One of the most challenging aspects of learning to program is the difficulty of synthesizing individual skills in the service of a larger project. This section provides a stepping stone on that path by progressively solving a real-world problem.

You'll want to follow along either on your own computer or in Binder. You can't use code blocks in this page, because there's an authentication step which requires a feature which isn't supported here.

Spotify

As an avid Spotify listener, you find that you'd prefer more flexibility in the way your playlists are built. For example, you find it tedious when two particularly long songs play back-to-back, and you want to eliminate those instances without having to read through and do it manually. Also, you want to have at least three separate genres represented in every block of eight consecutive songs. You want the flexibility to modify these requirements or add new ones at any time.

This is not the sort of capability that Spotify is ever going to provide through its app, but Spotify does support interaction through a programming language. Such an interface is called an API (application programming interface).

You decide to google Spotify API to see what the deal is. That takes you to the main Spotify API page, where you read about how the API uses standard HTTPS requests (these are the requests that your browser is using in the background load webpages, enter information into forms on the internet, etc.). Rather than proceeding along this route, you think to yourself "surely someone in the vast Python world has made a Python package to handle these HTTPS requests". So you google "Spotify Python API".

Turns out, you were right. The first few hits pertain to a package called spotipy. You check out the docs and find that you can install the package by running pip install spotipy. Since pip is a command line tool, this is something you should run from the terminal.

Note: if you're working in a Jupyter notebook, you can send code from a cell to the command line by prepending an exclamation point:

!pip install spotipy

Looking around in the documentation a bit more, we discover the functions user_playlist_tracks and user_playlist_add_tracks, which retrieve the tracks on a playlist and add new ones to it, respectively. So you decide to get the tracks from one of your playlists, manipulate them however you want inside the Python program, and put the new list in place of the old one. All we need from Spotify to make this work, in addition to the previous two functions, is a function to .

Looking around a bit more, you find user_playlist_remove_all_occurrences_of_tracks, which isn't exactly what you were looking for, but it will work since we can .

Your plan is beginning to take shape. You decide to make sure everything works before getting into the details of how you're going to modify the playlist. You follow the instructions in the documentation for getting the appropriate authorization credentials for your Python program to access your Spotify account. That step is a bit tedious, but it's going to be worth it. Working from the example in the documentation, you eventually arrive at some code that looks like the following (note that the values of the CLIENT variables and the playlist_id below are fake, so yours will necessarily be different).

import spotipy
import spotipy.util as util

username = 'sswatson'
scope = 'user-library-read'
scope = 'playlist-modify-public'

CLIENT_ID = 'bcc57908a2e54cee94f9e2307db67c2e'
CLIENT_SECRET = '6831b3ceaf0a40a6a1fdeb67105ef19b'

playlist_id = '57hQnYeBC4u0IUhaaHmM0k'

token = util.prompt_for_user_token(username,
                                   scope,
                                   client_id=CLIENT_ID,
                                   client_secret=CLIENT_SECRET,
                                   redirect_uri='http://localhost/')

sp = spotipy.Spotify(auth=token)

Next, you implement your plan sans-track-modification, to make sure the functions work as expected.

original_tracks = sp.user_playlist_tracks(username, playlist_id)
# shorten the name of the remove tracks function
remove_tracks = sp.user_playlist_remove_all_occurrences_of_tracks
remove_tracks(username, playlist_id, original_tracks)
sp.user_playlist_add_tracks(username, playlist_id, original_tracks)

That second line is there because you decided that function's name was so long it was getting unwieldy.

Hmm. Error. Specifically, a SpotifyException, which suggests that you didn't use the API in the intended way. You'll have to dig into this to figure out what went wrong. But first, it's a bit untidy to have those four lines of code loose in our program. Let's wrap them in a function. The playlist id should be an argument, and we should also take as an argument a track-modifying function that we'll start using once we get to that part.

def modify_playlist_tracks(playlist_id, track_modifier):
    original_tracks = sp.user_playlist_tracks(username, playlist_id)
    new_tracks = track_modifier(original_tracks)
    remove_tracks = sp.user_playlist_remove_all_occurrences_of_tracks
    remove_tracks(username, playlist_id, original_tracks)
    sp.user_playlist_add_tracks(username, playlist_id, new_tracks)

Now let's figure out the error. If we examine the traceback supplied as a part of the error message, we can see that the error is being thrown from the line where we call remove_tracks. So we look at the documentation for that function.

help(remove_tracks)

We see that the tracks argument is supposed to be a list of playlist ids. Is that what user_playlist_tracks returns? You investigate.

original_tracks = sp.user_playlist_tracks(username, playlist_id)
original_tracks

The output from that expression prints all over the screen, and it looks like it has a lot more data than just a list of id's. That's actually pretty helpful, because we'll need that data to modify the list appropriately. But in the meantime, we need to extract the actual playlist ids.

You begin by checking type(original_tracks). It's a . So you have a look at its keys:

original_tracks.keys()

This returns

dict_keys(['href', 'items', 'limit', 'next', 'offset', 'previous', 'total'])

Without looking to carefully at the other items, it's a good guess that 'items' is the one you want. You check type(original_tracks['items']) and find that it's a . To have a look at the first one, you do original_tracks['items'][0]. Repeating this step-by-step inspection, you find finally that original_tracks['items'][0]['track']['id'] is an actual playlist id.

Exercise
Write a list comprehension to calculate the list of all of the tracks' playlist ids.

Solution. [item for item in original_tracks['items']] would return the 'items' list. To map each item to its playlist id, we index it with 'track' and then with 'id' as above. So we get [item['track']['id'] for item in original_tracks['items']]

You insert this list comprehension into our function to fix it. You decide to reverse the list of tracks just to confirm that running the code has an effect on the Spotify side.

def modify_playlist_tracks(playlist_id, track_modifier):
    original_tracks = sp.user_playlist_tracks(username, playlist_id)
    new_tracks = track_modifier(original_tracks)
    remove_tracks = sp.user_playlist_remove_all_occurrences_of_tracks
    original_ids = [item['track']['id'] for item in
                                        original_tracks['items']]
    remove_tracks(username, playlist_id, original_ids)
    sp.user_playlist_add_tracks(username, playlist_id, new_tracks)


def track_modifier(tracks):
    return reversed([item['track']['id'] for item in tracks['items']])


modify_playlist_tracks(playlist_id, track_modifier)

This works! You can check that the order of the playlist was reversed.

Exercise
Add more features to the function track_modifier to modify playlists in ways that you find interesting or desirable. In the answer box below, describe what you did and add code snippets as you see fit.

Bruno
Bruno Bruno