# TOP Python Library Visit the code on [GitHub](https://github.com/JonasunderscoreJones/turbo-octo-potato/blob/main/top_lib.py) This is the library that holds the following code: - [Spotify and Last.fm Authentication](#spotify-and-lastfm-authentication) - [Spotify API manager and helper](#spotify-manager) - Progressbar code e.g. for the [likedsongsync2.py](/turbo-octo-potato/spotify-scripts/likedsongsync2) script ## Credentials and Setup The library makes use of the following environment variables defined in the `.env` file: ```properties LASTFM_API_KEY = "lastfm_api_key" LASTFM_API_SECRET = "lastfm_api_secret" LASTFM_USERNAME = "lastfm_username" LASTFM_PASSWORD_HASH = "lastfm_password_hash" SPOTIFY_CLIENT_ID = "spotify_client_id" SPOTIFY_CLIENT_SECRET = "spotify_client_secret" SPOTIFY_REDIRECT_URI = "http://localhost:6969" SPOTIFY_USER_ID = "spotify_user_id" LIKEDSONGPLAYLIST_ID = "spotify_playlist_id" SOMEPLAYLIST_ID = "spotify_playlist_id" INTROOUTROPLAYLIST_ID = "spotify_playlist_id" RANDOMTESTPLAYLIST_ID = "spotify_playlist_id" GITHUB_API_TOKEN = "github_api_token" REDDIT_CLIENT_ID = "reddit_client_id" REDDIT_CLIENT_SECRET = "reddit_client_secret" REDDIT_USER_AGENT = "reddit_user_agent" DISCORD_WEBHOOK_URL = 'discord_webhook_url' ``` :::info[Note] Depending on what you want to use the library for, you might not need all of these environment variables. ::: ## Spotify and Last.fm Authentication The library has a class called `Auth` that handles the authentication for both Spotify and Last.fm. ### Creating an Authentication Instance A new authentication instance can be created by calling the class with the following arguments: ```python my_auth = Auth( verbose:bool, lastfm_network:pylast.LastFMNetwork, spotify:spotipy.Spotify, ) ``` - `verbose` is an _optional_ boolean that determines if the authentication process should print out more information. - `lastfm_network` and `spotify` are _optional_ arguments that can be provided if the authentication for those services has already been done. :::tip[Example] Therefore if we don't have the authentication for Last.fm and Spotify yet and we want verbose logging enabled, we can create a new instance like this: ```python my_auth = Auth( verbose=True ) ``` ::: ### Authenticate Spotify Our newly created authentication instance can be used to authenticate Spotify by calling the `newSpotifyauth()` method: ```python my_auth.newSpotifyauth( scope:str ) ``` - `scope` is a _required_ string that defines the permissions the Spotify API should have. For more information on the scopes visit the [Spotify API Docs](https://developer.spotify.com/documentation/general/guides/scopes/) This automates the authentication process as much as possible. On first launch, the user is still required to authorize the script to access the spotify API. ### Authenticate Last.fm Much like the spotify instance, we can authenticate Last.fm by calling the `newLastfmauth()` method: ```python my_auth.newLastfmauth() ``` This will open a browser window where the user can authorize the script to access their Last.fm account on first launch. ### Verbose Logging If verbose logging was enabled when creating the instance, the authentication process will print out more information about what it's doing. Verbose logging can be toggled at any time using the `verbose()` method: ```python my_auth.verbose( verbose:bool ) ``` - `verbose` is a _required_ boolean that determines if the authentication process should print out more information. ### Getting the Spotify and Last.fm Instances After the authentication process has been completed, the Spotify and Last.fm instances can be accessed using the `getSpotify()` and `getLastfm()` methods although there are a bunch of helper and automatioon functions in the library that dont require you to get the instances manually e.g. using the [Spotify Manager](#spotify-manager) class.: ```python spotify_instance = my_auth.getSpotify() lastfm_instance = my_auth.getLastfm() ``` ### Get the used Credentials The credentials used for the authentication can be accessed using the `getCredentials()` method: ```python credentials = my_auth.getCredentials() ``` the credentials are stored in a dictionary with the following keys: - `LASTFM_API_KEY` - `LASTFM_API_SECRET` - `LASTFM_USERNAME` - `LASTFM_PASSWORD_HASH` - `SPOTIFY_CLIENT_ID` - `SPOTIFY_CLIENT_SECRET` - `SPOTIFY_REDIRECT_URI` - `SPOTIFY_USER_ID` The keys are self-explanatory and contain the credentials used for the authentication. ## Spotify Manager The library has a class called `SpotifyManager` that helps with managing more complex interactions with the Spotify API. ### Creating a Spotify Manager Instance A new Spotify Manager instance can be created by calling the class with the following arguments: ```python my_spotify_manager = SpotifyManager( spotify:spotipy.Spotify ) ``` :::tip[Example] We can plug in the newly created authenticated Spotify instance from above like this: ```python my_spotify_manager = SpotifyManager( auth.getSpotify() ) ``` ::: ### Get an artist's Albums The `fetchArtistAlbums()` method can be used to get all the albums of a specific artist: ```python albums = my_spotify_manager.fetchArtistAlbums( artist:str, raise_error:bool ) ``` - `artist` is the artist_id of the artist we want to get the albums from. - `raise_error` is an _optional_ boolean that determines if the method should raise an error if the artist has more than 50 albums/EP's/Singles. :::info The Spotify API has a bug where it only fetches the first 50 albums/EP's/Singles of an artist if they have more than 50. This is a bug on Spotify's end and has existed for quite a while. ::: ### Get an Album's Tracks The `getTrackUrisFromAlbum()` method can be used to get all the tracks of a specific album: ```python tracks = my_spotify_manager.getTrackUrisFromAlbum( album:str ) ``` - `album` is the album_id of the album we want to get the tracks from. The method returns a list of track uris that can be used to add the tracks to a playlist. :::tip[Example] You can also lookup the name or other info about a track. In this example, let's get the tracks of the album "The Story of Light" by SHINee and print the name of the first track: ```python # Get the album's tracks tracks = my_spotify_manager.getTrackUrisFromAlbum( "spotify:album:1zK5C9xg5Fz3J0bG6VwQFv" ) # Get the Spotify instance spotify = auth.getSpotify() # Get the first track track = spotify.track(tracks[0]) # Print the track's name print(track["name"]) ``` ::: ### Get a user's followed Artists The `fetchUserFollowedArtists()` method can be used to get all the artists a user follows: ```python artists = my_spotify_manager.fetchUserFollowedArtists() ``` The method returns the artist ids and names of all followed artists as tuples in a list: ```python [ ("spotify:artist:1dfeR4HaWDbWqFHLkxsg1d", "SHINee"), ("spotify:artist:0C8C8YiEiJqfI5fSG5Z6Y2", "ATEEZ"), ... ] ``` ## Progressbar The library has a class called `Progressbar` that helps with creating progress bars for scripts that take a long time to run. ### Creating a Progressbar Instance A new Progressbar instance can be created by calling the class with the following arguments: ```python my_progressbar = Progressbar( total:int, etaCalc: : Callable[[int, int], int] ) ``` - `total` is the total number of steps the progress bar should have. - `etaCalc` is an _optional_ function that calculates the estimated time of completion for the progress bar. :::tip[Example] Let's create a new progress bar with 100 steps and a simple ETA calculation function: ```python # the eta_calculator function takes a current step and the total number of steps and returns an estimated time of completion in seconds def eta_calculator(current:int, total:int) -> int: return (total - current) * 0.1 # simple calculator that estimates 0.1 seconds per step my_progressbar = Progressbar( 100, eta_calculator ) ``` ::: The total number of steps can be updated at any time using the `setTotal()` method: ```python my_progressbar.setTotal( total:int ) ``` - `total` is the new total number of steps the progress bar should have. ### Manual ETA Calculation If no `etaCalc` function is provided when creating the instance, the estimated time of completion can be set manually using the `setEta()` method: ```python my_progressbar.setEta( eta:int ) ``` ### Display the Progressbar The progress bar can be displayed using the `print()` method: ```python my_progressbar.print( current:int, eta:int ) ``` - `current` is the current step the progress bar is at. - `eta` is an optional overwrite of the estimated time of completion. If no `eta` is provided, the progress bar will use the `etaCalc` function to calculate the estimated time of completion. If no `etaCalc` function was provided when creating the instance, the progress bar will not display an estimated time of completion. ### Build a Progressbar state In case the progressbar should be built already but not displayed yet, the `buildSnapshot()` method can be used: ```python snapshot = my_progressbar.buildSnapshot( current:int, eta:int ) ``` - `current` is the current step the progress bar is at. - `eta` is an optional overwrite of the estimated time of completion. The method returns a string that can be printed to display the progress bar. :::tip[Example] Let's build a snapshot of a progress bar with 100 steps and a current step of 50: ```python snapshot = my_progressbar.buildSnapshot( 50 ) print(snapshot) ``` Now we can update the progress bar by printing the snapshot again with a new current step: ```python snapshot = my_progressbar.buildSnapshot( 75 ) print(snapshot, end="\r") ``` We give the extra argument `end="\r"` to overwrite the current line in the console with the new progress bar. Otherwhise we end up with a new line for each progress bar update. This way we end up with a smooth progress bar that updates in place. ::: ### Progressbar Eta Manager The library also has a class called `ProgressbarEtaManager` that automatically estimates the time of completion for a progress bar. ### Creating a Progressbar Eta Manager Instance A new Progressbar Eta Manager instance can be created by calling the class with the following arguments: ```python my_eta_manager = ProgressbarEtaManager() ``` The Progress Bar Eta Manager works by saving the time taken by each step and calculating the average. This way, at each step we need to run the `now()` function to log another step. ### Update the Progressbar Eta Manager The `now()` method can be used to log another step and update the estimated time of completion: ```python my_eta_manager.now() ``` ### Get the Average Step Time The average time taken per step can be accessed using the `getAvgEta()` method: ```python avg_eta = my_eta_manager.getAvgEta() ``` To get the estimated time of completion for a specific number of steps, we can multiply the average step time by the number of steps: ```python eta = avg_eta * steps ``` But the `Progressbar` class already does this for us, so we don't need to worry about it. ### Get the logged Step times The logged step times can be accessed using the `getDurations()` method: ```python durations = my_eta_manager.getDurations() ``` This returns a list of all the step times that have been logged in seconds. :::tip[Example] ## Combine Progressbar and Progressbar Eta Manager To plug the `Progressbar` and `ProgressbarEtaManager` together, we can create new `Progressbar` and `ProgressbarEtaManager` instances: ```python # Create a new Progressbar instance my_new_progressbar = Progressbar() # Set a total number of steps my_progressbar.setTotal(100) # Create a new Progressbar Eta Manager instance my_new_eta_manager = ProgressbarEtaManager() ``` Let's say we have a loop that runs 100 times and we want to display a progress bar with an estimated time of completion: ```python # code from above ... # Loop 100 times for i in range(100): # Log a new step my_new_eta_manager.now() # simulate some work time.sleep(math.random(0, 1, 0.1)) # Get the estimated time of completion eta = my_new_eta_manager.getAvgEta() * (100 - i) # Print the progress bar my_new_progressbar.print(i + 1, eta) ``` This way we can display a progress bar with an estimated time of completion that updates in place. The randomly generated delay between `0` and `1` seconds allows us to test the ETA calculation. :::