Creating the DataManager class
We have implemented the base functionality to authenticate and consume data from the Spotify REST API, but now we need to create a class that will make use of this functionality so we get the information that we need to be displayed in the client.
Our Spotify terminal client will perform the following actions:
- Search an artist by name
- List the artist's albums
- List the album's tracks
- Request a track to be played
The first thing we are going to add is a custom exception that we can raise, and no result is returned from the Spotify REST API. Create a new file called empty_results_error.py in the musicterminal/client directory with the following contents:
class EmptyResultsError(Exception):
pass
To make it easier for us, let's create a DataManager class that will encapsulate all these functionalities for us. Create a file called data_manager.py in the musicterminal/client directory:
from .menu_item import MenuItem
from pytify.core import search_artist
from pytify.core import get_artist_albums
from pytify.core import get_album_tracks
from pytify.core import play
from .empty_results_error import EmptyResultsError
from pytify.auth import authenticate
from pytify.core import read_config
class DataManager():
def __init__(self):
self._conf = read_config()
self._auth = authenticate(self._conf)
First, we import the MenuItem, so we can return MenuItem objects with the request's results. After that, we import functions from the pytify module to search artists, get albums, list albums tracks, and play tracks. Also, in the pytify module, we import the read_config function and authenticate it.
Lastly, we import the custom exception that we just created, EmptyResultsError.
The initializer of the DataManager class starts reading the configuration and performs the authentication. The authentication information will be stored in the _auth property.
Next up, we are going to add a method to search for artists:
def search_artist(self, criteria):
results = search_artist(criteria, self._auth)
items = results['artists']['items']
if not items:
raise EmptyResultsError(f'Could not find the artist:
{criteria}')
return items[0]
The _search_artist method will get criteria as an argument and call the search_artist function from the python.core module. If no items are returned, it will raise an EmptyResultsError; otherwise, it will return the first match.
Before we continue creating the methods that will fetch the albums and the tracks, we need two utility methods to format the labels of the MenuItem objects.
The first one will format the artist label:
def _format_artist_label(self, item):
return f'{item["name"]} ({item["type"]})'
Here, the label will be the name of the item and the type, which can be an album, single, EP, and so on.
And the second one formats the name of the tracks:
def _format_track_label(self, item):
time = int(item['duration_ms'])
minutes = int((time / 60000) % 60)
seconds = int((time / 1000) % 60)
track_name = item['name']
return f'{track_name} - [{minutes}:{seconds}]'
Here, we extract the duration of the track in milliseconds, convert is to minutes: seconds, and format the label with the name of the track and its duration between square brackets.
After that, let's create a method to get the artist's albums:
def get_artist_albums(self, artist_id, max_items=20):
albums = get_artist_albums(artist_id, self._auth)['items']
if not albums:
raise EmptyResultsError(('Could not find any albums for'
f'the artist_id: {artist_id}'))
return [MenuItem(self._format_artist_label(album), album)
for album in albums[:max_items]]
The get_artist_albums method gets two arguments, the artist_id and the max_item, which is the maximum number of albums that will be returned by the method. By default, it is set to 20.
The first thing we do here is use the get_artist_albums method from the pytify.core module, passing the artist_id and the authentication objects, and we get the item's attribute from the results, assigning it to the variable albums. If the albums variable is empty, it will raise an EmptyResultsError; otherwise, it will create a list of MenuItem objects for every album.
And we can add another method for the tracks:
def get_album_tracklist(self, album_id):
results = get_album_tracks(album_id, self._auth)
if not results:
raise EmptyResultsError('Could not find the tracks for this
album')
tracks = results['items']
return [MenuItem(self._format_track_label(track), track)
for track in tracks]
The get_album_tracklist method gets album_id as an argument and the first thing we do is get the tracks for that album using the get_album_tracks function in the pytify.core module. If no result is returned, we raise an EmptyResultsError; otherwise, we build a list of MenuItem objects.
The last method is the one that will actually send a command to the Spotify REST API to play a track:
def play(self, track_uri):
play(track_uri, self._auth)
Very straightforward. Here, we just get track_uri as an argument and pass it down the play function in the pytify.core module, along with the authentication object. That will make the track start playing on the available device; it can be a mobile phone, Spotify's client on your computer, the Spotify web player, or even your games console.
Next up, let's put together everything we have built and run the Spotify player terminal.