single network request function, network error exception, using requests session

closes #3
This commit is contained in:
aj 2020-06-22 20:15:22 +01:00
parent dd802a0d8f
commit 3e679d37ac
12 changed files with 414 additions and 618 deletions

View File

@ -41,11 +41,9 @@ def check_phone():
if __name__ == '__main__': if __name__ == '__main__':
try: try:
network = Network(NetworkUser(client_id=os.environ['SPOT_CLIENT'],
network = Network(NetworkUser(os.environ['SPOT_CLIENT'], client_secret=os.environ['SPOT_SECRET'],
os.environ['SPOT_SECRET'], refresh_token=os.environ['SPOT_REFRESH'])).refresh_access_token()
os.environ['SPOT_REFRESH']))
network.user.refresh_access_token()
found = False found = False

View File

@ -29,10 +29,9 @@ if __name__ == '__main__':
# try: # try:
network = Network(NetworkUser(os.environ['SPOT_CLIENT'], network = Network(NetworkUser(client_id=os.environ['SPOT_CLIENT'],
os.environ['SPOT_SECRET'], client_secret=os.environ['SPOT_SECRET'],
os.environ['SPOT_REFRESH'])) refresh_token=os.environ['SPOT_REFRESH'])).refresh_access_token()
network.user.refresh_access_token()
playlists = network.get_user_playlists() playlists = network.get_user_playlists()
for playlist in playlists: for playlist in playlists:

View File

@ -122,10 +122,9 @@ def go():
logger.critical('none to execute, terminating') logger.critical('none to execute, terminating')
return return
net = Network(NetworkUser(os.environ['SPOT_CLIENT'], net = Network(NetworkUser(client_id=os.environ['SPOT_CLIENT'],
os.environ['SPOT_SECRET'], client_secret=os.environ['SPOT_SECRET'],
os.environ['SPOT_REFRESH'])) refresh_token=os.environ['SPOT_REFRESH'])).refresh_access_token()
net.user.refresh_access_token()
engine = PlaylistEngine(net) engine = PlaylistEngine(net)

View File

@ -14,10 +14,8 @@ stream_handler.setFormatter(stream_formatter)
logger.addHandler(stream_handler) logger.addHandler(stream_handler)
if __name__ == '__main__': if __name__ == '__main__':
network = Network(NetworkUser(client_id=os.environ['SPOT_CLIENT'],
network = Network(NetworkUser(os.environ['SPOT_CLIENT'], client_secret=os.environ['SPOT_SECRET'],
os.environ['SPOT_SECRET'], refresh_token=os.environ['SPOT_REFRESH'])).refresh_access_token()
os.environ['SPOT_REFRESH']))
network.user.refresh_access_token()
print(network.user.access_token) print(network.user.access_token)

View File

@ -39,7 +39,7 @@ def listen(verbose, client_id, client_secret, refresh_token):
net = Network(NetworkUser(client_id=client_id, net = Network(NetworkUser(client_id=client_id,
client_secret=client_secret, client_secret=client_secret,
refresh_token=refresh_token).refresh_access_token()) refresh_token=refresh_token)).refresh_access_token()
cmd = ListenCmd(net, stream_handler) cmd = ListenCmd(net, stream_handler)
cmd.cmdloop() cmd.cmdloop()

View File

@ -28,10 +28,9 @@ logger.addHandler(stream_handler)
def go(playlist_name): def go(playlist_name):
net = Network(NetworkUser(os.environ['SPOT_CLIENT'], net = Network(NetworkUser(client_id=os.environ['SPOT_CLIENT'],
os.environ['SPOT_SECRET'], client_secret=os.environ['SPOT_SECRET'],
os.environ['SPOT_REFRESH'])) refresh_token=os.environ['SPOT_REFRESH'])).refresh_access_token()
net.user.refresh_access_token()
engine = PlaylistEngine(net) engine = PlaylistEngine(net)
engine.reorder_playlist_by_added_date(playlist_name) engine.reorder_playlist_by_added_date(playlist_name)

View File

@ -13,10 +13,7 @@ class AbstractProcessor(ABC):
self.playlist_uris = uris self.playlist_uris = uris
def has_targets(self) -> bool: def has_targets(self) -> bool:
if self.playlist_names or self.playlist_uris: return bool(self.playlist_names or self.playlist_uris)
return True
else:
return False
@abstractmethod @abstractmethod
def process(self, tracks: List[SimplifiedTrack]) -> List[SimplifiedTrack]: def process(self, tracks: List[SimplifiedTrack]) -> List[SimplifiedTrack]:
@ -69,5 +66,5 @@ class BatchSingleTypeAwareProcessor(BatchSingleProcessor, ABC):
return_tracks += malformed_tracks return_tracks += malformed_tracks
return return_tracks return return_tracks
else:
return tracks return tracks

View File

@ -1,5 +1,5 @@
from spotframework.model.track import CurrentlyPlaying from spotframework.model.track import CurrentlyPlaying
from spotframework.net.network import Network from spotframework.net.network import Network, SpotifyNetworkException
from typing import Optional from typing import Optional
import logging import logging
@ -20,31 +20,40 @@ class Listener:
self.recent_tracks = [] self.recent_tracks = []
self.prev_now_playing: Optional[CurrentlyPlaying] = None self.prev_now_playing: Optional[CurrentlyPlaying] = None
self.now_playing: Optional[CurrentlyPlaying] = net.get_player() 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}')
self.on_playback_change = [] self.on_playback_change = []
def update_now_playing(self): def update_now_playing(self):
"""update currently playing values""" """update currently playing values"""
logger.debug('updating now playing') logger.debug('updating now playing')
live_now_playing = self.net.get_player()
if self.now_playing is None and live_now_playing is None: try:
return live_now_playing = self.net.get_player()
if self.now_playing is None and live_now_playing is None:
return
if live_now_playing != self.now_playing: if live_now_playing != self.now_playing:
self.prev_now_playing = self.now_playing self.prev_now_playing = self.now_playing
self.now_playing = live_now_playing self.now_playing = live_now_playing
for func in self.on_playback_change: for func in self.on_playback_change:
func(live_now_playing) func(live_now_playing)
else: else:
self.now_playing = live_now_playing self.now_playing = live_now_playing
except SpotifyNetworkException as e:
logger.error(f'error occured retrieving currently playing - {e}')
def update_recent_tracks(self): def update_recent_tracks(self):
"""retrieve recently played tracks and merge with previously stored""" """retrieve recently played tracks and merge with previously stored"""
logger.debug('updating recent tracks') logger.debug('updating recent tracks')
tracks = self.net.get_recently_played_tracks(response_limit=self.request_size)
if tracks is not None: try:
tracks = self.net.get_recently_played_tracks(response_limit=self.request_size)
for track in tracks: for track in tracks:
if track.played_at not in [i.played_at for i in self.recent_tracks]: if track.played_at not in [i.played_at for i in self.recent_tracks]:
self.recent_tracks.append(track) self.recent_tracks.append(track)
@ -52,5 +61,6 @@ class Listener:
self.recent_tracks.sort(key=lambda x: x.played_at) self.recent_tracks.sort(key=lambda x: x.played_at)
if self.max_recent_tracks is not None: if self.max_recent_tracks is not None:
self.recent_tracks = self.recent_tracks[-self.max_recent_tracks:] self.recent_tracks = self.recent_tracks[-self.max_recent_tracks:]
else:
logger.error('no recent tracks returned') except SpotifyNetworkException as e:
logger.error(f'error occured retrieving recent tracks - {e}')

View File

@ -6,7 +6,3 @@ class Image:
height: int height: int
width: int width: int
url: str url: str
@staticmethod
def wrap(**kwargs):
return Image(**kwargs)

File diff suppressed because it is too large Load Diff

View File

@ -1,26 +1,18 @@
from __future__ import annotations from __future__ import annotations
import requests
from spotframework.model.user import PublicUser from spotframework.model.user import PublicUser
from spotframework.util.console import Color
from dataclasses import dataclass, field from dataclasses import dataclass, field
from base64 import b64encode from typing import List
import logging from datetime import datetime
import time
from typing import Optional, List
from datetime import datetime, timezone
logger = logging.getLogger(__name__)
@dataclass @dataclass
class NetworkUser: class NetworkUser:
access_token: str
refresh_token: str
client_id: str client_id: str
client_secret: str client_secret: str
access_token: str = None
refresh_token: str = None
user: PublicUser = field(default=None, init=False) user: PublicUser = field(default=None, init=False)
last_refreshed: datetime = field(default=None, init=False) last_refreshed: datetime = field(default=None, init=False)
@ -29,93 +21,3 @@ class NetworkUser:
on_refresh: List = field(default_factory=list, init=False) on_refresh: List = field(default_factory=list, init=False)
refresh_counter: int = field(default=0, init=False) refresh_counter: int = field(default=0, init=False)
def refresh_access_token(self) -> NetworkUser:
if self.refresh_token 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}
data = {"grant_type": "refresh_token", "refresh_token": self.refresh_token}
now = datetime.utcnow()
req = requests.post('https://accounts.spotify.com/api/token', data=data, headers=headers)
if 200 <= req.status_code < 300:
logger.debug('token refreshed')
resp = req.json()
self.access_token = resp['access_token']
if resp.get('refresh_token', None):
self.refresh_token = resp['refresh_token']
self.token_expiry = resp['expires_in']
self.last_refreshed = now
for func in self.on_refresh:
func(self)
else:
if req.status_code == 429:
retry_after = req.headers.get('Retry-After', None)
if retry_after:
logger.warning(f'rate limit reached: retrying in {retry_after} seconds')
time.sleep(int(retry_after) + 1)
return self.refresh_access_token()
else:
logger.error('rate limit reached: cannot find Retry-After header')
else:
error_text = req.json().get('error', 'n/a')
error_description = req.json().get('error_description', 'n/a')
logger.error(f'get {req.status_code} {error_text} - {error_description}')
return self
def refresh_info(self) -> None:
self.user = PublicUser(**self.get_info())
def get_info(self) -> Optional[dict]:
headers = {'Authorization': 'Bearer %s' % self.access_token}
req = requests.get('https://api.spotify.com/v1/me', headers=headers)
if 200 <= req.status_code < 300:
logger.debug(f'retrieved {req.status_code}')
return req.json()
else:
if req.status_code == 429:
retry_after = req.headers.get('Retry-After', None)
if retry_after:
logger.warning(f'rate limit reached: retrying in {retry_after} seconds')
time.sleep(int(retry_after) + 1)
return self.get_info()
else:
logger.error('rate limit reached: cannot find Retry-After header')
elif req.status_code == 401:
logger.warning('access token expired, refreshing')
self.refresh_access_token()
if self.refresh_counter < 5:
self.refresh_counter += 1
return self.get_info()
else:
self.refresh_counter = 0
logger.critical('refresh token limit (5) reached')
else:
error = req.json().get('error', None)
if error:
message = error.get('message', 'n/a')
logger.error(f'{req.status_code} {message}')
else:
logger.error(f'{req.status_code} no error object found')

View File

@ -1,8 +1,7 @@
from spotframework.net.network import Network from spotframework.net.network import Network, SpotifyNetworkException
from spotframework.model.track import SimplifiedTrack, Context, Device from spotframework.model.track import SimplifiedTrack, Context, Device
from spotframework.model.album import AlbumFull from spotframework.model.album import AlbumFull
from spotframework.model.playlist import FullPlaylist from spotframework.model.playlist import FullPlaylist
from spotframework.model.uri import Uri
from typing import List, Union from typing import List, Union
import logging import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -23,14 +22,20 @@ class Player:
@property @property
def available_devices(self): def available_devices(self):
return self.net.get_available_devices() try:
return self.net.get_available_devices()
except SpotifyNetworkException as e:
logger.error(f'error retrieving current devices - {e}')
@property @property
def status(self): def status(self):
new_status = self.net.get_player() try:
if new_status: new_status = self.net.get_player()
self.last_status = new_status if new_status:
return self.last_status self.last_status = new_status
return self.last_status
except SpotifyNetworkException as e:
logger.error(f'error retrieving current devices - {e}')
def play(self, def play(self,
context: Union[Context, AlbumFull, FullPlaylist] = None, context: Union[Context, AlbumFull, FullPlaylist] = None,
@ -43,55 +48,76 @@ class Player:
if searched_device: if searched_device:
device = searched_device device = searched_device
if context and (tracks or uris): try:
raise Exception('cant execute context and track list') if context and (tracks or uris):
if context: raise Exception('cant execute context and track list')
if device: if context:
self.net.play(uri=context.uri, deviceid=device.id) if device:
self.net.play(uri=context.uri, deviceid=device.id)
else:
self.net.play(uri=context.uri)
elif tracks or uris:
if tracks is None:
tracks = []
if uris is None:
uris = []
if device:
self.net.play(uris=[i.uri for i in tracks] + uris, deviceid=device.id)
else:
self.net.play(uris=[i.uri for i in tracks] + uris)
else: else:
self.net.play(uri=context.uri) self.net.play()
elif tracks or uris: except SpotifyNetworkException as e:
logger.error(f'error playing - {e}')
if tracks is None:
tracks = []
if uris is None:
uris = []
if device:
self.net.play(uris=[i.uri for i in tracks] + uris, deviceid=device.id)
else:
self.net.play(uris=[i.uri for i in tracks] + uris)
else:
self.net.play()
def change_device(self, device: Device): def change_device(self, device: Device):
self.net.change_playback_device(device.id) try:
self.net.change_playback_device(device.id)
except SpotifyNetworkException as e:
logger.error(f'error changing device to {device.name} - {e}')
def pause(self): def pause(self):
self.net.pause() try:
self.net.pause()
except SpotifyNetworkException as e:
logger.error(f'error pausing - {e}')
def toggle_playback(self): def toggle_playback(self):
status = self.status status = self.status
if status: try:
if status.is_playing: if status:
self.pause() if status.is_playing:
self.pause()
else:
self.play()
else: else:
logger.warning('no current playback, playing')
self.play() self.play()
else: except SpotifyNetworkException as e:
logger.warning('no current playback, playing') logger.error(f'error toggling playback - {e}')
self.play()
def next(self): def next(self):
self.net.next() try:
self.net.next()
except SpotifyNetworkException as e:
logger.error(f'error skipping track - {e}')
def previous(self): def previous(self):
self.net.previous() try:
self.net.previous()
except SpotifyNetworkException as e:
logger.error(f'error reversing track - {e}')
def shuffle(self, state=None): def shuffle(self, state=None):
if state is not None: if state is not None:
if isinstance(state, bool): if isinstance(state, bool):
self.net.set_shuffle(state) try:
self.net.set_shuffle(state)
except SpotifyNetworkException as e:
logger.error(f'error setting shuffle - {e}')
else: else:
raise TypeError(f'{state} is not bool') raise TypeError(f'{state} is not bool')
else: else:
@ -104,9 +130,12 @@ class Player:
def volume(self, value: int, device: Device = None): def volume(self, value: int, device: Device = None):
if 0 <= int(value) <= 100: if 0 <= int(value) <= 100:
if device: try:
self.net.set_volume(value, deviceid=device.id) if device:
else: self.net.set_volume(value, deviceid=device.id)
self.net.set_volume(value) else:
self.net.set_volume(value)
except SpotifyNetworkException as e:
logger.error(f'error setting volume to {value} - {e}')
else: else:
logger.error(f'{value} not between 0 and 100') logger.error(f'{value} not between 0 and 100')