diff --git a/sort_playlist.py b/sort_playlist.py index 9364d91..bc8338b 100644 --- a/sort_playlist.py +++ b/sort_playlist.py @@ -11,7 +11,7 @@ logger = logging.getLogger('spotframework') log_format = '%(asctime)s %(levelname)s %(name)s - %(funcName)s - %(message)s' -file_handler = logging.FileHandler(".spot/resort_playlist.log") +file_handler = logging.FileHandler(".spot/sort_playlist.log") formatter = logging.Formatter(log_format) file_handler.setFormatter(formatter) diff --git a/spotframework/model/album.py b/spotframework/model/album.py index 3e3b208..6d10e92 100644 --- a/spotframework/model/album.py +++ b/spotframework/model/album.py @@ -1,6 +1,7 @@ from __future__ import annotations from typing import TYPE_CHECKING from typing import List +from spotframework.util.console import Color if TYPE_CHECKING: from spotframework.model.artist import Artist @@ -23,6 +24,10 @@ class Album: return f'{self.name} / {artists}' + def __repr__(self): + return Color.DARKCYAN + Color.BOLD + 'Album' + Color.END + \ + f': {self.name}, [{self.artists}]' + class SpotifyAlbum(Album): def __init__(self, @@ -56,3 +61,7 @@ class SpotifyAlbum(Album): self.label = label self.popularity = popularity + + def __repr__(self): + return Color.DARKCYAN + Color.BOLD + 'SpotifyAlbum' + Color.END + \ + f': {self.name}, {self.artists}, {self.uri}, {self.tracks}' diff --git a/spotframework/model/artist.py b/spotframework/model/artist.py index 1fa89d6..6ea1bb7 100644 --- a/spotframework/model/artist.py +++ b/spotframework/model/artist.py @@ -1,4 +1,5 @@ from typing import List +from spotframework.util.console import Color class Artist: @@ -8,6 +9,10 @@ class Artist: def __str__(self): return f'{self.name}' + def __repr__(self): + return Color.PURPLE + Color.BOLD + 'Artist' + Color.END + \ + f': {self.name}' + class SpotifyArtist(Artist): def __init__(self, @@ -30,3 +35,7 @@ class SpotifyArtist(Artist): self.genres = genres self.popularity = popularity + + def __repr__(self): + return Color.PURPLE + Color.BOLD + 'SpotifyArtist' + Color.END + \ + f': {self.name}, {self.uri}' diff --git a/spotframework/model/playlist.py b/spotframework/model/playlist.py index d3a11ff..34676e6 100644 --- a/spotframework/model/playlist.py +++ b/spotframework/model/playlist.py @@ -1,5 +1,6 @@ from spotframework.model.user import User from spotframework.model.track import Track, SpotifyTrack, PlaylistTrack +from spotframework.util.console import Color from tabulate import tabulate from typing import List import logging @@ -50,9 +51,46 @@ class Playlist: prefix = f'\n==={self.name}===\n\n' if self.name is not None else '' table = prefix + self.get_tracks_string() + '\n' + f'total: {len(self)}' - return table + def __repr__(self): + return Color.GREEN + Color.BOLD + 'Playlist' + Color.END + \ + f': {self.name}, ({len(self)})' + + def __add__(self, other): + if isinstance(other, Track): + self.tracks.append(other) + return self + + elif isinstance(other, list): + if all((isinstance(i, Track) for i in other)): + self.tracks += other + return self + else: + logger.error('list not full of tracks') + raise TypeError('list not full of tracks') + + else: + logger.error('list of tracks needed to add') + raise TypeError('list of tracks needed to add') + + def __sub__(self, other): + if isinstance(other, Track): + self.tracks.remove(other) + return self + + elif isinstance(other, list): + if all((isinstance(i, Track) for i in other)): + self.tracks -= other + return self + else: + logger.error('list not full of tracks') + raise TypeError('list not full of tracks') + + else: + logger.error('list of tracks needed to subtract') + raise TypeError('list of tracks needed to subtract') + def get_tracks_string(self): rows = [] @@ -109,3 +147,7 @@ class SpotifyPlaylist(Playlist): table = prefix + self.get_tracks_string() + '\n' + f'total: {len(self)}' return table + + def __repr__(self): + return Color.GREEN + Color.BOLD + 'SpotifyPlaylist' + Color.END + \ + f': {self.name} ({self.owner}), ({len(self)}), {self.uri}' diff --git a/spotframework/model/track.py b/spotframework/model/track.py index 12245e8..903a4f8 100644 --- a/spotframework/model/track.py +++ b/spotframework/model/track.py @@ -2,6 +2,7 @@ from __future__ import annotations from typing import TYPE_CHECKING from typing import List from datetime import datetime +from spotframework.util.console import Color if TYPE_CHECKING: from spotframework.model.album import Album from spotframework.model.artist import Artist @@ -44,6 +45,10 @@ class Track: return f'{self.name} / {album} / {artists}' + def __repr__(self): + return Color.YELLOW + Color.BOLD + 'Track' + Color.END + \ + f': {self.name}, ({self.album}), {self.artists}' + class SpotifyTrack(Track): def __init__(self, @@ -74,6 +79,10 @@ class SpotifyTrack(Track): self.popularity = popularity + def __repr__(self): + return Color.BOLD + Color.YELLOW + 'SpotifyTrack' + Color.END + \ + f': {self.name}, ({self.album}), {self.artists}, {self.uri}' + class PlaylistTrack(SpotifyTrack): def __init__(self, @@ -110,3 +119,7 @@ class PlaylistTrack(SpotifyTrack): self.added_at = datetime.fromisoformat(added_at.replace('T', ' ').replace('Z', '')) self.added_by = added_by self.is_local = is_local + + def __repr__(self): + return Color.BOLD + Color.YELLOW + 'PlaylistTrack' + Color.END + \ + f': {self.name}, ({self.album}), {self.artists}, {self.uri}, {self.added_at}' diff --git a/spotframework/model/user.py b/spotframework/model/user.py index cbefb73..e630a1d 100644 --- a/spotframework/model/user.py +++ b/spotframework/model/user.py @@ -1,3 +1,4 @@ +from spotframework.util.console import Color class User: @@ -19,3 +20,7 @@ class User: def __str__(self): return f'{self.username}' + + def __repr__(self): + return Color.RED + Color.BOLD + 'User' + Color.END + \ + f': {self.username}, {self.display_name}, {self.uri}' diff --git a/spotframework/net/network.py b/spotframework/net/network.py index fcf058e..0d2bb36 100644 --- a/spotframework/net/network.py +++ b/spotframework/net/network.py @@ -5,6 +5,7 @@ import time from typing import List, Optional from . import const from spotframework.net.parse import parse +from spotframework.net.user import NetworkUser from spotframework.model.playlist import SpotifyPlaylist from spotframework.model.track import Track, PlaylistTrack from requests.models import Response @@ -16,7 +17,7 @@ logger = logging.getLogger(__name__) class Network: - def __init__(self, user): + def __init__(self, user: NetworkUser): self.user = user def _make_get_request(self, method, url, params=None, headers={}) -> Optional[dict]: @@ -40,6 +41,11 @@ class Network: else: logger.error(f'{method} rate limit reached: cannot find Retry-After header') + elif req.status_code == 401: + logger.warning(f'{method} access token expired, refreshing') + self.user.refresh_token() + return self._make_get_request(method, url, params, headers) + else: error_text = req.json()['error']['message'] logger.error(f'{method} get {req.status_code} {error_text}') @@ -67,6 +73,11 @@ class Network: else: logger.error(f'{method} rate limit reached: cannot find Retry-After header') + elif req.status_code == 401: + logger.warning(f'{method} access token expired, refreshing') + self.user.refresh_token() + return self._make_post_request(method, url, params, json, headers) + else: error_text = str(req.text) logger.error(f'{method} post {req.status_code} {error_text}') @@ -94,6 +105,11 @@ class Network: else: logger.error(f'{method} rate limit reached: cannot find Retry-After header') + elif req.status_code == 401: + logger.warning(f'{method} access token expired, refreshing') + self.user.refresh_token() + return self._make_put_request(method, url, params, json, headers) + else: error_text = str(req.text) logger.error(f'{method} put {req.status_code} {error_text}') diff --git a/spotframework/net/user.py b/spotframework/net/user.py index 38b025e..df1b28d 100644 --- a/spotframework/net/user.py +++ b/spotframework/net/user.py @@ -1,5 +1,6 @@ import requests from spotframework.model.user import User +from spotframework.util.console import Color from base64 import b64encode import logging import time @@ -22,6 +23,10 @@ class NetworkUser(User): self.refresh_token() self.refresh_info() + def __repr__(self): + return Color.RED + Color.BOLD + 'NetworkUser' + Color.END + \ + f': {self.username}, {self.display_name}, {self.uri}' + def refresh_token(self) -> None: if self.refreshtoken is None: @@ -53,7 +58,7 @@ class NetworkUser(User): time.sleep(int(retry_after) + 1) return self.refresh_token() else: - logger.error(f'refresh_token rate limit reached: cannot find Retry-After header') + logger.error('refresh_token rate limit reached: cannot find Retry-After header') else: error_text = req.json()['error']['message'] @@ -97,7 +102,12 @@ class NetworkUser(User): time.sleep(int(retry_after) + 1) return self.get_info() else: - logger.error(f'get_info rate limit reached: cannot find Retry-After header') + logger.error('get_info rate limit reached: cannot find Retry-After header') + + elif req.status_code == 401: + logger.warning('access token expired, refreshing') + self.refresh_token() + return self.get_info() else: error_text = req.json()['error']['message'] diff --git a/spotframework/util/console.py b/spotframework/util/console.py new file mode 100644 index 0000000..d6f6766 --- /dev/null +++ b/spotframework/util/console.py @@ -0,0 +1,12 @@ + +class Color: + PURPLE = '\033[95m' + CYAN = '\033[96m' + DARKCYAN = '\033[36m' + BLUE = '\033[94m' + GREEN = '\033[92m' + YELLOW = '\033[93m' + RED = '\033[91m' + BOLD = '\033[1m' + UNDERLINE = '\033[4m' + END = '\033[0m'