added playlist printing, model props, added popularity sorting
This commit is contained in:
parent
0bd9fac8f4
commit
ad9c40ea27
38
spotframework/engine/filter/popularity.py
Normal file
38
spotframework/engine/filter/popularity.py
Normal file
@ -0,0 +1,38 @@
|
||||
from spotframework.engine.filter.abstract import AbstractProcessor
|
||||
from typing import List
|
||||
from spotframework.model.track import Track, SpotifyTrack
|
||||
|
||||
|
||||
class SortPopularity(AbstractProcessor):
|
||||
|
||||
def __init__(self,
|
||||
names: List[str] = None,
|
||||
keep_malformed_type: bool = True):
|
||||
super().__init__(names)
|
||||
self.keep_malformed_type = keep_malformed_type
|
||||
|
||||
def sort(self, tracks: List[SpotifyTrack]):
|
||||
tracks.sort(key=lambda x: x.popularity, reverse=True)
|
||||
|
||||
def process(self, tracks: List[Track]):
|
||||
return_tracks = []
|
||||
malformed_tracks = []
|
||||
|
||||
for track in tracks:
|
||||
if isinstance(track, SpotifyTrack):
|
||||
return_tracks.append(track)
|
||||
else:
|
||||
malformed_tracks.append(track)
|
||||
|
||||
self.sort(return_tracks)
|
||||
|
||||
if self.keep_malformed_type:
|
||||
return_tracks += malformed_tracks
|
||||
|
||||
return return_tracks
|
||||
|
||||
|
||||
class SortReversePopularity(SortPopularity):
|
||||
|
||||
def sort(self, tracks: List[SpotifyTrack]):
|
||||
tracks.sort(key=lambda x: x.popularity, reverse=False)
|
@ -1,31 +1,26 @@
|
||||
from abc import ABC
|
||||
from .abstract import AbstractProcessor
|
||||
from typing import List
|
||||
from spotframework.model.track import Track
|
||||
|
||||
|
||||
class SortReverseReleaseDate(AbstractProcessor):
|
||||
class BasicReversibleSort(AbstractProcessor, ABC):
|
||||
def __init__(self,
|
||||
names: List[str] = None,
|
||||
reverse: bool = False):
|
||||
super().__init__(names)
|
||||
self.reverse = reverse
|
||||
|
||||
|
||||
class SortReleaseDate(BasicReversibleSort):
|
||||
|
||||
def process(self, tracks: List[Track]):
|
||||
tracks.sort(key=lambda x: x.album.release_date, reverse=True)
|
||||
tracks.sort(key=lambda x: x.album.release_date, reverse=self.reverse)
|
||||
return tracks
|
||||
|
||||
|
||||
class SortReleaseDate(AbstractProcessor):
|
||||
class SortArtistName(BasicReversibleSort):
|
||||
|
||||
def process(self, tracks: List[Track]):
|
||||
tracks.sort(key=lambda x: x.album.release_date, reverse=False)
|
||||
return tracks
|
||||
|
||||
|
||||
class SortArtistName(AbstractProcessor):
|
||||
|
||||
def process(self, tracks: List[Track]):
|
||||
tracks.sort(key=lambda x: x.artists[0].name, reverse=False)
|
||||
return tracks
|
||||
|
||||
|
||||
class SortReverseArtistName(AbstractProcessor):
|
||||
|
||||
def process(self, tracks: List[Track]):
|
||||
tracks.sort(key=lambda x: x.artists[0].name, reverse=True)
|
||||
tracks.sort(key=lambda x: x.artists[0].name, reverse=self.reverse)
|
||||
return tracks
|
||||
|
@ -7,7 +7,7 @@ from spotframework.engine.filter.added import AddedSince
|
||||
|
||||
from typing import List
|
||||
from spotframework.model.track import SpotifyTrack
|
||||
from spotframework.model.playlist import Playlist
|
||||
from spotframework.model.playlist import SpotifyPlaylist
|
||||
from spotframework.net.network import Network
|
||||
from spotframework.engine.filter.abstract import AbstractProcessor
|
||||
from datetime import datetime
|
||||
@ -40,7 +40,7 @@ class PlaylistEngine:
|
||||
logger.error('error getting playlists')
|
||||
|
||||
def get_playlist_tracks(self,
|
||||
playlist: Playlist):
|
||||
playlist: SpotifyPlaylist):
|
||||
logger.info(f"pulling tracks for {playlist.name}")
|
||||
|
||||
tracks = self.net.get_playlist_tracks(playlist.playlist_id)
|
||||
|
@ -10,6 +10,14 @@ class Album:
|
||||
self.name = name
|
||||
self.artists = artists
|
||||
|
||||
@property
|
||||
def artists_names(self):
|
||||
return self._join_strings([i.name for i in self.artists])
|
||||
|
||||
@staticmethod
|
||||
def _join_strings(string_list: List[str]):
|
||||
return ' , '.join(string_list)
|
||||
|
||||
def __str__(self):
|
||||
artists = ' , '.join([i.name for i in self.artists]) if self.artists else 'n/a'
|
||||
|
||||
|
@ -1,8 +1,75 @@
|
||||
from spotframework.model.user import User
|
||||
from spotframework.model.track import Track, SpotifyTrack, PlaylistTrack
|
||||
from tabulate import tabulate
|
||||
from typing import List
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Playlist:
|
||||
|
||||
def __init__(self,
|
||||
name: str = None,
|
||||
description: str = None):
|
||||
self._tracks = []
|
||||
self.name = name
|
||||
self.description = description
|
||||
|
||||
def has_tracks(self):
|
||||
if len(self.tracks) > 0:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def __len__(self):
|
||||
return len(self.tracks)
|
||||
|
||||
@property
|
||||
def tracks(self):
|
||||
return self._tracks
|
||||
|
||||
@tracks.setter
|
||||
def tracks(self, value: List[Track]):
|
||||
tracks = []
|
||||
not_tracks = []
|
||||
|
||||
for track in value:
|
||||
if isinstance(track, Track):
|
||||
tracks.append(track)
|
||||
else:
|
||||
not_tracks.append(track)
|
||||
|
||||
if len(not_tracks) > 0:
|
||||
logger.error('playlist tracks must be off type Track')
|
||||
|
||||
self._tracks = tracks
|
||||
|
||||
def __str__(self):
|
||||
headers = ['name', 'album', 'artist', 'added at', 'popularity', 'uri']
|
||||
|
||||
rows = []
|
||||
for track in self.tracks:
|
||||
track_row = [track.name,
|
||||
track.album.name,
|
||||
track.artists_names,
|
||||
track.added_at if isinstance(track, PlaylistTrack) else '',
|
||||
track.popularity if isinstance(track, SpotifyTrack) else '',
|
||||
track.uri if isinstance(track, SpotifyTrack) else '']
|
||||
|
||||
rows.append(track_row)
|
||||
|
||||
table = tabulate(rows, headers=headers, showindex='always', tablefmt="fancy_grid")
|
||||
|
||||
prefix = f'\n==={self.name}===\n\n' if self.name is not None else ''
|
||||
|
||||
table = prefix + table + '\n' + f'total: {len(self)}'
|
||||
|
||||
return table
|
||||
|
||||
|
||||
class SpotifyPlaylist(Playlist):
|
||||
|
||||
def __init__(self,
|
||||
playlistid: str,
|
||||
|
||||
@ -16,12 +83,11 @@ class Playlist:
|
||||
collaborative: bool = None,
|
||||
public: bool = None,
|
||||
ext_spotify: str = None):
|
||||
self.tracks = []
|
||||
self.name = name
|
||||
|
||||
super().__init__(name=name, description=description)
|
||||
|
||||
self.playlist_id = playlistid
|
||||
self.owner = owner
|
||||
self.description = description
|
||||
|
||||
self.href = href
|
||||
self.uri = uri
|
||||
@ -29,9 +95,3 @@ class Playlist:
|
||||
self.collaborative = collaborative
|
||||
self.public = public
|
||||
self.ext_spotify = ext_spotify
|
||||
|
||||
def has_tracks(self):
|
||||
if len(self.tracks) > 0:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
@ -26,6 +26,18 @@ class Track:
|
||||
self.duration_ms = duration_ms
|
||||
self.explicit = excplicit
|
||||
|
||||
@property
|
||||
def artists_names(self):
|
||||
return self._join_strings([i.name for i in self.artists])
|
||||
|
||||
@property
|
||||
def album_artists_names(self):
|
||||
return self.album.artists_names
|
||||
|
||||
@staticmethod
|
||||
def _join_strings(string_list: List[str]):
|
||||
return ' , '.join(string_list)
|
||||
|
||||
def __str__(self):
|
||||
album = self.album.name if self.album else 'n/a'
|
||||
artists = ' , '.join([i.name for i in self.artists]) if self.artists else 'n/a'
|
||||
|
@ -5,7 +5,7 @@ import time
|
||||
from typing import List
|
||||
from . import const
|
||||
from spotframework.net.parse import parse
|
||||
from spotframework.model.playlist import Playlist
|
||||
from spotframework.model.playlist import SpotifyPlaylist
|
||||
|
||||
limit = 50
|
||||
|
||||
@ -33,7 +33,7 @@ class Network:
|
||||
|
||||
if retry_after:
|
||||
logger.warning(f'{method} rate limit reached: retrying in {retry_after} seconds')
|
||||
time.sleep(int(retry_after))
|
||||
time.sleep(int(retry_after) + 1)
|
||||
return self._make_get_request(method, url, params, headers)
|
||||
else:
|
||||
logger.error(f'{method} rate limit reached: cannot find Retry-After header')
|
||||
@ -60,7 +60,7 @@ class Network:
|
||||
|
||||
if retry_after:
|
||||
logger.warning(f'{method} rate limit reached: retrying in {retry_after} seconds')
|
||||
time.sleep(int(retry_after))
|
||||
time.sleep(int(retry_after) + 1)
|
||||
return self._make_post_request(method, url, params, json, headers)
|
||||
else:
|
||||
logger.error(f'{method} rate limit reached: cannot find Retry-After header')
|
||||
@ -87,7 +87,7 @@ class Network:
|
||||
|
||||
if retry_after:
|
||||
logger.warning(f'{method} rate limit reached: retrying in {retry_after} seconds')
|
||||
time.sleep(int(retry_after))
|
||||
time.sleep(int(retry_after) + 1)
|
||||
return self._make_put_request(method, url, params, json, headers)
|
||||
else:
|
||||
logger.error(f'{method} rate limit reached: cannot find Retry-After header')
|
||||
@ -106,7 +106,7 @@ class Network:
|
||||
|
||||
if tracks is not None:
|
||||
|
||||
playlist = Playlist(playlistid)
|
||||
playlist = SpotifyPlaylist(playlistid)
|
||||
playlist.tracks += tracks
|
||||
|
||||
return playlist
|
||||
@ -415,7 +415,7 @@ class Network:
|
||||
return None
|
||||
|
||||
def write_playlist_object(self,
|
||||
playlist: Playlist,
|
||||
playlist: SpotifyPlaylist,
|
||||
append_tracks: bool = False):
|
||||
|
||||
if playlist.playlist_id:
|
||||
|
@ -1,7 +1,7 @@
|
||||
from spotframework.model.artist import Artist, SpotifyArtist
|
||||
from spotframework.model.album import Album, SpotifyAlbum
|
||||
from spotframework.model.track import Track, SpotifyTrack, PlaylistTrack
|
||||
from spotframework.model.playlist import Playlist
|
||||
from spotframework.model.playlist import SpotifyPlaylist
|
||||
from spotframework.model.user import User
|
||||
|
||||
|
||||
@ -175,7 +175,7 @@ def parse_playlist(playlist_dict):
|
||||
public = playlist_dict.get('public', None)
|
||||
uri = playlist_dict.get('uri', None)
|
||||
|
||||
return Playlist(playlistid=playlist_id,
|
||||
return SpotifyPlaylist(playlistid=playlist_id,
|
||||
name=name,
|
||||
owner=owner,
|
||||
description=description,
|
||||
|
@ -2,6 +2,7 @@ import requests
|
||||
from spotframework.model.user import User
|
||||
from base64 import b64encode
|
||||
import logging
|
||||
import time
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -22,6 +23,15 @@ class NetworkUser(User):
|
||||
|
||||
def refresh_token(self):
|
||||
|
||||
if self.refreshtoken is None:
|
||||
raise NameError('no refresh token to query')
|
||||
|
||||
if self.client_id is None:
|
||||
raise NameError('no client id')
|
||||
|
||||
if self.client_secret is None:
|
||||
raise NameError('no client secret')
|
||||
|
||||
idsecret = b64encode(bytes(self.client_id + ':' + self.client_secret, "utf-8")).decode("ascii")
|
||||
headers = {'Authorization': 'Basic %s' % idsecret}
|
||||
|
||||
@ -33,7 +43,20 @@ class NetworkUser(User):
|
||||
logger.debug('token refreshed')
|
||||
self.accesstoken = req.json()['access_token']
|
||||
else:
|
||||
logger.error(f'http error {req.status_code}')
|
||||
|
||||
if req.status_code == 429:
|
||||
retry_after = req.headers.get('Retry-After', None)
|
||||
|
||||
if retry_after:
|
||||
logger.warning(f'refresh_token rate limit reached: retrying in {retry_after} seconds')
|
||||
time.sleep(int(retry_after) + 1)
|
||||
return self.refresh_token()
|
||||
else:
|
||||
logger.error(f'refresh_token rate limit reached: cannot find Retry-After header')
|
||||
|
||||
else:
|
||||
error_text = req.json()['error']['message']
|
||||
logger.error(f'refresh_token get {req.status_code} {error_text}')
|
||||
|
||||
def refresh_info(self):
|
||||
info = self.get_info()
|
||||
@ -64,4 +87,17 @@ class NetworkUser(User):
|
||||
logger.debug(f'retrieved {req.status_code}')
|
||||
return req.json()
|
||||
else:
|
||||
logger.error(f'http error {req.status_code}')
|
||||
|
||||
if req.status_code == 429:
|
||||
retry_after = req.headers.get('Retry-After', None)
|
||||
|
||||
if retry_after:
|
||||
logger.warning(f'get_info rate limit reached: retrying in {retry_after} seconds')
|
||||
time.sleep(int(retry_after) + 1)
|
||||
return self.get_info()
|
||||
else:
|
||||
logger.error(f'get_info rate limit reached: cannot find Retry-After header')
|
||||
|
||||
else:
|
||||
error_text = req.json()['error']['message']
|
||||
logger.error(f'get_info get {req.status_code} {error_text}')
|
||||
|
Loading…
Reference in New Issue
Block a user