diff --git a/backup.py b/backup.py index 33bd203..d5c1690 100644 --- a/backup.py +++ b/backup.py @@ -1,5 +1,5 @@ -from spotframework.net.user import NetworkUser -from spotframework.net.network import Network +from spotframework.net.user import NetworkUser +from spotframework.net.network import Network, SpotifyNetworkException import spotframework.io.csv as csvwrite import sys @@ -32,22 +32,30 @@ if __name__ == '__main__': network = Network(NetworkUser(client_id=os.environ['SPOT_CLIENT'], client_secret=os.environ['SPOT_SECRET'], refresh_token=os.environ['SPOT_REFRESH'])).refresh_access_token() - playlists = network.get_user_playlists() - for playlist in playlists: - playlist.tracks = network.get_playlist_tracks(playlist.uri) + try: + playlists = network.get_user_playlists(response_limit=5) - path = sys.argv[1] + for playlist in playlists: + try: + playlist.tracks = network.get_playlist_tracks(playlist.uri) + except SpotifyNetworkException: + logger.exception(f'error occured during {playlist.name} track retrieval') - datepath = str(datetime.datetime.now()).split(' ')[0].replace('-', '/') + path = sys.argv[1] - totalpath = os.path.join(path, datepath) - pathdir = os.path.dirname(totalpath) - if not os.path.exists(totalpath): - os.makedirs(totalpath) + datepath = str(datetime.datetime.now()).split(' ')[0].replace('-', '/') - for play in playlists: - csvwrite.export_playlist(play, totalpath) + totalpath = os.path.join(path, datepath) + pathdir = os.path.dirname(totalpath) + if not os.path.exists(totalpath): + os.makedirs(totalpath) + + for play in playlists: + csvwrite.export_playlist(play, totalpath) + + except SpotifyNetworkException: + logger.exception('error occured during user playlist retrieval') # except Exception as e: # logger.exception(f'exception occured') diff --git a/requirements.txt b/requirements.txt index 4e5b19b..cce8fd7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ -certifi==2019.11.28 +certifi==2020.6.20 chardet==3.0.4 -Click==7.0 -idna==2.8 -requests==2.22.0 -tabulate==0.8.6 -urllib3==1.25.7 +click==7.1.2 +idna==2.10 +requests==2.24.0 +tabulate==0.8.7 +urllib3==1.25.9 diff --git a/spotframework/io/csv.py b/spotframework/io/csv.py index 9ed3435..a5dda25 100644 --- a/spotframework/io/csv.py +++ b/spotframework/io/csv.py @@ -4,7 +4,7 @@ import logging logger = logging.getLogger(__name__) -headers = ['name', 'artist', 'album', 'album artist', 'added', 'track id', 'album id', 'added by'] +headers = ['name', 'artist', 'album', 'album artist', 'added', 'track id', 'album id', 'artist id', 'added by'] def export_playlist(playlist, path, name=None): @@ -23,12 +23,13 @@ def export_playlist(playlist, path, name=None): for track in playlist.tracks: writer.writerow({ - 'name': track.name, - 'album': track.album.name, + 'name': track.track.name, + 'album': track.track.album.name, 'added': track.added_at, - 'track id': track.uri.object_id if track.uri is not None else 'none', - 'album id': track.album.uri.object_id if track.album.uri is not None else 'none', - 'added by': track.added_by.username, - 'album artist': ', '.join(x.name for x in track.album.artists), - 'artist': ', '.join(x.name for x in track.artists) + 'track id': track.track.uri.object_id if track.track.uri is not None else 'none', + 'album id': track.track.album.uri.object_id if track.track.album.uri is not None else 'none', + 'artist id': ', '.join(x.uri.object_id for x in track.track.artists), + 'added by': track.added_by.id, + 'album artist': ', '.join(x.name for x in track.track.album.artists), + 'artist': ', '.join(x.name for x in track.track.artists) }) diff --git a/spotframework/listener/listener.py b/spotframework/listener/listener.py index 9208ce9..52477d0 100644 --- a/spotframework/listener/listener.py +++ b/spotframework/listener/listener.py @@ -23,8 +23,8 @@ class Listener: self.now_playing = None try: self.now_playing: Optional[CurrentlyPlaying] = net.get_player() - except SpotifyNetworkException as e: - logger.error(f'error occured retrieving currently playing - {e}') + except SpotifyNetworkException: + logger.exception(f'error occured retrieving currently playing') self.on_playback_change = [] @@ -45,8 +45,8 @@ class Listener: else: self.now_playing = live_now_playing - except SpotifyNetworkException as e: - logger.error(f'error occured retrieving currently playing - {e}') + except SpotifyNetworkException: + logger.exception(f'error occured retrieving currently playing') def update_recent_tracks(self): """retrieve recently played tracks and merge with previously stored""" @@ -62,5 +62,5 @@ class Listener: if self.max_recent_tracks is not None: self.recent_tracks = self.recent_tracks[-self.max_recent_tracks:] - except SpotifyNetworkException as e: - logger.error(f'error occured retrieving recent tracks - {e}') + except SpotifyNetworkException: + logger.exception(f'error occured retrieving recent tracks') diff --git a/spotframework/model/playlist.py b/spotframework/model/playlist.py index d7b3b89..138bcbf 100644 --- a/spotframework/model/playlist.py +++ b/spotframework/model/playlist.py @@ -119,6 +119,23 @@ class SimplifiedPlaylist: class FullPlaylist(SimplifiedPlaylist): followers: dict = None + def __post_init__(self): + if isinstance(self.tracks, dict): + self.tracks = [] + + if isinstance(self.uri, str): + self.uri = Uri(self.uri) + + if self.uri: + if self.uri.object_type != Uri.ObjectType.playlist: + raise TypeError('provided uri not for a playlist') + + if all((isinstance(i, dict) for i in self.images)): + self.images = [Image(**i) for i in self.images] + + if isinstance(self.owner, dict): + self.owner = PublicUser(**self.owner) + def __str__(self): prefix = f'\n==={self.name}===\n\n' if self.name is not None else '' prefix += f'uri: {self.uri}\n' if self.uri is not None else '' diff --git a/spotframework/model/track.py b/spotframework/model/track.py index cb102a1..30fef89 100644 --- a/spotframework/model/track.py +++ b/spotframework/model/track.py @@ -1,5 +1,5 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Union, List +from typing import Union, List from datetime import datetime from dataclasses import dataclass, field @@ -9,8 +9,7 @@ from enum import Enum import spotframework.model.album import spotframework.model.artist import spotframework.model.service -if TYPE_CHECKING: - from spotframework.model.user import PublicUser +import spotframework.model.user @dataclass @@ -105,7 +104,7 @@ class LibraryTrack: @dataclass class PlaylistTrack: added_at: datetime - added_by: PublicUser + added_by: spotframework.model.user.PublicUser is_local: bool primary_color: str track: TrackFull @@ -115,6 +114,9 @@ class PlaylistTrack: if isinstance(self.track, dict): self.track = TrackFull(**self.track) + if isinstance(self.added_by, dict): + self.added_by = spotframework.model.user.PublicUser(**self.added_by) + if isinstance(self.added_at, str): self.added_at = datetime.strptime(self.added_at, '%Y-%m-%dT%H:%M:%S%z') diff --git a/spotframework/model/user.py b/spotframework/model/user.py index 20adb41..845fed1 100644 --- a/spotframework/model/user.py +++ b/spotframework/model/user.py @@ -9,10 +9,11 @@ class PublicUser: href: str id: str uri: Union[str, Uri] - display_name: str external_urls: dict type: str + display_name: str = None + email: str = None followers: dict = field(default_factory=dict) images: List[Image] = field(default_factory=list) @@ -28,7 +29,7 @@ class PublicUser: self.images = [Image(**i) for i in self.images] def __str__(self): - return f'{self.display_name}' + return f'{self.id}' @dataclass @@ -37,3 +38,14 @@ class PrivateUser(PublicUser): email: str = None product: str = None + def __post_init__(self): + if isinstance(self.uri, str): + self.uri = Uri(self.uri) + + if self.uri: + if self.uri.object_type != Uri.ObjectType.user: + raise TypeError('provided uri not for a user') + + if all((isinstance(i, dict) for i in self.images)): + self.images = [Image(**i) for i in self.images] + diff --git a/spotframework/net/network.py b/spotframework/net/network.py index c4d9704..9d42598 100644 --- a/spotframework/net/network.py +++ b/spotframework/net/network.py @@ -236,8 +236,8 @@ class Network: self.user.last_refreshed = datetime.datetime.utcnow() for func in self.user.on_refresh: func(self.user) - except SpotifyNetworkException as e: - logger.error(f'error refreshing user token - {e}') + except SpotifyNetworkException: + logger.exception(f'error refreshing user token') return self @@ -373,17 +373,18 @@ class Network: return return_items - def get_user_playlists(self) -> List[SimplifiedPlaylist]: + def get_user_playlists(self, response_limit: int = None) -> List[SimplifiedPlaylist]: """retrieve user owned playlists + :param response_limit: max playlists to return :return: List of user owned playlists if available """ logger.info('pulling all playlists') - playlists = self.get_playlists() + playlists = self.get_playlists(response_limit=response_limit) - if self.user.user.id is None: + if self.user.user is None: logger.debug('no user info, refreshing for filter') self.refresh_user_info() diff --git a/spotframework/player/player.py b/spotframework/player/player.py index ce9d9b2..bd307cf 100644 --- a/spotframework/player/player.py +++ b/spotframework/player/player.py @@ -15,7 +15,7 @@ class Player: self.last_status = None def __str__(self): - return f'{self.net.user.user.display_name} - {self.status}' + return f'{self.net.user.user.id} - {self.status}' def __repr__(self): return f'Player: {self.net.user} - {self.status}' @@ -24,8 +24,8 @@ class Player: def available_devices(self): try: return self.net.get_available_devices() - except SpotifyNetworkException as e: - logger.error(f'error retrieving current devices - {e}') + except SpotifyNetworkException: + logger.exception(f'error retrieving current devices') @property def status(self): @@ -34,8 +34,8 @@ class Player: if new_status: self.last_status = new_status return self.last_status - except SpotifyNetworkException as e: - logger.error(f'error retrieving current devices - {e}') + except SpotifyNetworkException: + logger.exception(f'error retrieving current devices') def play(self, context: Union[Context, AlbumFull, FullPlaylist] = None, @@ -70,20 +70,20 @@ class Player: self.net.play(uris=[i.uri for i in tracks] + uris) else: self.net.play() - except SpotifyNetworkException as e: - logger.error(f'error playing - {e}') + except SpotifyNetworkException: + logger.exception(f'error playing') def change_device(self, device: Device): try: self.net.change_playback_device(device.id) - except SpotifyNetworkException as e: - logger.error(f'error changing device to {device.name} - {e}') + except SpotifyNetworkException: + logger.exception(f'error changing device to {device.name}') def pause(self): try: self.net.pause() - except SpotifyNetworkException as e: - logger.error(f'error pausing - {e}') + except SpotifyNetworkException: + logger.exception(f'error pausing') def toggle_playback(self): status = self.status @@ -96,28 +96,28 @@ class Player: else: logger.warning('no current playback, playing') self.play() - except SpotifyNetworkException as e: - logger.error(f'error toggling playback - {e}') + except SpotifyNetworkException: + logger.exception(f'error toggling playback') def next(self): try: self.net.next() - except SpotifyNetworkException as e: - logger.error(f'error skipping track - {e}') + except SpotifyNetworkException: + logger.exception(f'error skipping track') def previous(self): try: self.net.previous() - except SpotifyNetworkException as e: - logger.error(f'error reversing track - {e}') + except SpotifyNetworkException: + logger.exception(f'error reversing track') def shuffle(self, state=None): if state is not None: if isinstance(state, bool): try: self.net.set_shuffle(state) - except SpotifyNetworkException as e: - logger.error(f'error setting shuffle - {e}') + except SpotifyNetworkException: + logger.exception(f'error setting shuffle') else: raise TypeError(f'{state} is not bool') else: @@ -135,7 +135,7 @@ class Player: self.net.set_volume(value, deviceid=device.id) else: self.net.set_volume(value) - except SpotifyNetworkException as e: - logger.error(f'error setting volume to {value} - {e}') + except SpotifyNetworkException: + logger.exception(f'error setting volume to {value}') else: logger.error(f'{value} not between 0 and 100')