def search(self, query_types: List[Uri.ObjectType], query: str = None, track: str = None, album: str = None, artist: str = None, response_limit: int = 20) -> SearchResponse: if query is None and track is None and album is None and artist is None: raise ValueError('no query parameters') queries = [] if query is not None: queries.append(query) if track is not None: queries.append(f'track:{track}') if album is not None: queries.append(f'album:{album}') if artist is not None: queries.append(f'artist:{artist}') logger.info(f'querying track: {track}, album: {album}, artist: {artist}') resp = self.get_request(url='search', q=' '.join(queries), type=','.join([i.name for i in query_types]), limit=response_limit) albums = [init_with_key_filter(SimplifiedAlbum, i) for i in resp.get('albums', {}).get('items', [])] artists = [init_with_key_filter(ArtistFull, i) for i in resp.get('artists', {}).get('items', [])] tracks = [init_with_key_filter(TrackFull, i) for i in resp.get('tracks', {}).get('items', [])] playlists = [init_with_key_filter(SimplifiedPlaylist, i) for i in resp.get('playlists', {}).get('items', [])] return SearchResponse(tracks=tracks, albums=albums, artists=artists, playlists=playlists)
def playlist(self, uri: Uri, tracks: bool = True) -> FullPlaylist: """get playlist object with tracks for uri :param uri: target request uri :param tracks: populate tracks of playlist during generation :return: playlist object """ logger.info(f"retrieving {uri}") resp = self.get_request(f'playlists/{uri.object_id}') playlist = init_with_key_filter(FullPlaylist, resp) if resp.get('tracks') and tracks: if 'next' in resp['tracks']: logger.debug(f'paging tracks for {uri}') track_pager = PageCollection(net=self, page=resp['tracks']) track_pager.continue_iteration() playlist.tracks = [init_with_key_filter(PlaylistTrack, i) for i in track_pager.items] else: logger.debug(f'parsing {len(resp.get("tracks"))} tracks for {uri}') playlist.tracks = [init_with_key_filter(PlaylistTrack, i) for i in resp.get('tracks', [])] return playlist
def __post_init__(self): if isinstance(self.track, dict): # below seems more intuitive, currently parsing episode to track/album/artist structure for # serialising over api, below could be implemented # obj_type = None # if self.track['type'] == 'track': # obj_type = TrackFull # # if self.track['type'] == 'episode': # obj_type = EpisodeFull # # if obj_type is None: # raise TypeError(f'unkown obj type found {self.track["type"]}') obj_type = TrackFull self.track = init_with_key_filter(obj_type, self.track) if isinstance(self.added_by, dict): self.added_by = init_with_key_filter( 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')
def __post_init__(self): if isinstance(self.uri, str): self.uri = Uri(self.uri) if self.uri: if self.uri.object_type != Uri.ObjectType.episode: raise TypeError('provided uri not for an episode') if isinstance(self.resume_point, ResumePoint): self.resume_point = init_with_key_filter(ResumePoint, self.resume_point) if all((isinstance(i, dict) for i in self.images)): self.images = [init_with_key_filter(Image, i) for i in self.images] if isinstance(self.release_date, str): if self.release_date_precision == 'year': self.release_date = datetime.strptime(self.release_date, '%Y') elif self.release_date_precision == 'month': self.release_date = datetime.strptime(self.release_date, '%Y-%m') elif self.release_date_precision == 'day': self.release_date = datetime.strptime(self.release_date, '%Y-%m-%d')
def __post_init__(self): if isinstance(self.context, dict): self.context = init_with_key_filter(Context, self.context) if isinstance(self.track, dict): self.track = init_with_key_filter(TrackFull, self.track) if isinstance(self.played_at, str): self.played_at = datetime.strptime(self.played_at, '%Y-%m-%dT%H:%M:%S%z')
def __post_init__(self): if isinstance(self.context, Context): self.context = init_with_key_filter(Context, self.context) if isinstance(self.item, spotframework.model.track.SimplifiedTrack): self.item = init_with_key_filter( spotframework.model.track.SimplifiedTrack, self.item) if isinstance(self.device, Device): self.device = init_with_key_filter(Device, self.device)
def __post_init__(self): if all((isinstance(i, dict) for i in self.seeds)): self.seeds = [ init_with_key_filter(RecommendationsSeed, i) for i in self.seeds ] if all((isinstance(i, dict) for i in self.tracks)): self.tracks = [ init_with_key_filter(spotframework.model.track.TrackFull, i) for i in self.tracks ]
def __post_init__(self): if isinstance(self.album_type, str): self.album_type = SimplifiedAlbum.Type[ self.album_type.strip().lower()] if isinstance(self.uri, str): self.uri = Uri(self.uri) if self.uri: if self.uri.object_type not in [ Uri.ObjectType.album, Uri.ObjectType.show ]: raise TypeError('provided uri not for an album') if all((isinstance(i, dict) for i in self.artists)): self.artists = [ init_with_key_filter( spotframework.model.artist.SimplifiedArtist, i) for i in self.artists ] if all((isinstance(i, dict) for i in self.images)): self.images = [ init_with_key_filter(spotframework.model.service.Image, i) for i in self.images ] if isinstance(self.release_date, str): try: if self.release_date_precision == 'year': self.release_date = datetime.strptime( self.release_date, '%Y') elif self.release_date_precision == 'month': self.release_date = datetime.strptime( self.release_date, '%Y-%m') elif self.release_date_precision == 'day': self.release_date = datetime.strptime( self.release_date, '%Y-%m-%d') else: logger.error( f'invalid release date type {self.release_date_precision} - {self.release_date}' ) except ValueError: self.release_date = datetime(year=1900, month=1, day=1) logger.error( f'failed to parse release date for album {self.uri} {self.name} {self.artists_names} {self.release_date_precision} - {self.release_date}' ) elif self.release_date is None and self.release_date_precision is None: # for podcasts self.release_date = datetime(year=1900, month=1, day=1)
def create_playlist(self, username: str, name: str = 'New Playlist', public: bool = True, collaborative: bool = False, description: str = None) -> FullPlaylist: """create playlist for user :param username: username for playlist creation :param name: new playlist name :param public: make playlist public :param collaborative: make playlist collaborative :param description: description for new playlist :return: newly created playlist object """ logger.info(f'creating {name} for {username}, ' f'public: {public}, collaborative: {collaborative}, description: {description}') if collaborative and public: public = False logger.warning(f'public collaborative playlist requested, defaulting to private {username} / {name}') req = self.post_request(f'users/{username}/playlists', name=name, public=public, collaborative=collaborative, description=description) return init_with_key_filter(FullPlaylist, req)
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 = [init_with_key_filter(Image, i) for i in self.images] if isinstance(self.owner, dict): self.owner = init_with_key_filter(PublicUser, self.owner)
def recently_played_tracks(self, response_limit: int = None, after: datetime.datetime = None, before: datetime.datetime = None) -> Optional[List[PlayedTrack]]: """get list of recently played tracks :param response_limit: max number of tracks to return :param after: datetime after which to return tracks :param before: datetime before which to return tracks :return: list of recently played tracks if available """ logger.info(f"paging {'all' if response_limit is None else response_limit} recent tracks ({after}/{before})") params = dict() if after and before: raise ValueError('cant have before and after') if after: params['after'] = int(after.timestamp() * 1000) if before: params['before'] = int(before.timestamp() * 1000) resp = self.get_request('me/player/recently-played', params=params) pager = PageCollection(self, page=resp) if response_limit: pager.total_limit = response_limit else: pager.total_limit = 20 pager.continue_iteration() return [init_with_key_filter(PlayedTrack, i) for i in pager.items]
def player(self) -> CurrentlyPlaying: """get currently playing snapshot (player)""" logger.info("polling player") resp = self.get_request('me/player') return init_with_key_filter(CurrentlyPlaying, resp)
def __post_init__(self): if isinstance(self.album, dict): self.album = init_with_key_filter(AlbumFull, self.album) if isinstance(self.added_at, str): self.added_at = datetime.strptime(self.added_at, '%Y-%m-%dT%H:%M:%S%z')
def playlist_tracks(self, uri: Uri, response_limit: int = None, reduced_mem: bool = False) -> List[PlaylistTrack]: """get list of playlists tracks for uri :param uri: target playlist uri :param response_limit: max tracks to return :return: list of playlist tracks if available """ logger.info(f"paging tracks for {uri}") pager = PageCollection(net=self, url=f'playlists/{uri.object_id}/tracks', name='getPlaylistTracks') if response_limit: pager.total_limit = response_limit pager.iterate() result = pager.items if reduced_mem: result = [filter_response(i, Network.unneeded_keys) for i in result] return_items = [init_with_key_filter(PlaylistTrack, i) for i in result] if len(return_items) == 0: logger.error('no tracks returned') return return_items
def __post_init__(self): super().__post_init__() if all((isinstance(i, dict) for i in self.tracks)): self.tracks = [ init_with_key_filter(spotframework.model.track.SimplifiedTrack, i) for i in self.tracks ]
def show(self, uri, episodes: bool = True) -> Optional[ShowFull]: logger.info(f"retrieving {uri}") resp = self.get_request(f'shows/{uri.object_id}') show = init_with_key_filter(ShowFull, resp) if resp.get('episodes') and episodes: if 'next' in resp['episodes']: logger.debug(f'paging episodes for {uri}') track_pager = PageCollection(net=self, page=resp['episodes']) track_pager.continue_iteration() show.episodes = [init_with_key_filter(SimplifiedEpisode, i) for i in track_pager.items] else: logger.debug(f'parsing {len(resp.get("episodes"))} tracks for {uri}') show.episodes = [init_with_key_filter(SimplifiedEpisode, i) for i in resp.get('episodes', [])] return show
def available_devices(self) -> List[Device]: """get users available devices""" logger.info("polling available devices") resp = self.get_request('me/player/devices') if len(resp['devices']) == 0: logger.error('no devices returned') return [init_with_key_filter(Device, i) for i in resp['devices']]
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 = [init_with_key_filter(Image, i) for i in self.images]
def tracks(self, uris: List[Uri]) -> List[TrackFull]: logger.info(f'getting {len(uris)} tracks') tracks = [] chunked_uris = list(self.chunk(uris, 50)) for chunk in chunked_uris: resp = self.get_request(url='tracks', ids=','.join([i.object_id for i in chunk])) if resp: tracks += [init_with_key_filter(TrackFull, i) for i in resp.get('tracks', [])] return tracks
def episodes(self, uris) -> List[EpisodeFull]: logger.info(f'getting {len(uris)} episodes') episodes = [] chunked_uris = list(self.chunk(uris, 50)) for chunk in chunked_uris: resp = self.get_request(url='episodes', ids=','.join([i.object_id for i in chunk])) if resp: episodes += [init_with_key_filter(EpisodeFull, i) for i in resp.get('episodes', [])] return episodes
def shows(self, uris) -> List[SimplifiedShow]: logger.info(f'getting {len(uris)} shows') shows = [] chunked_uris = list(self.chunk(uris, 50)) for chunk in chunked_uris: resp = self.get_request(url='shows', ids=','.join([i.object_id for i in chunk])) if resp: shows += [init_with_key_filter(SimplifiedShow, i) for i in resp.get('shows', [])] return shows
def artists(self, uris) -> List[ArtistFull]: logger.info(f'getting {len(uris)} artists') artists = [] chunked_uris = list(self.chunk(uris, 50)) for chunk in chunked_uris: resp = self.get_request(url='artists', ids=','.join([i.object_id for i in chunk])) if resp: artists += [init_with_key_filter(ArtistFull, i) for i in resp.get('artists', [])] return artists
def __post_init__(self): if isinstance(self.uri, str): self.uri = Uri(self.uri) if self.uri: if self.uri.object_type not in [ Uri.ObjectType.track, Uri.ObjectType.episode ]: raise TypeError('provided uri not for a track') if all((isinstance(i, dict) for i in self.artists)): self.artists = [ init_with_key_filter( spotframework.model.artist.SimplifiedArtist, i) for i in self.artists ]
def track_audio_features(self, uris: List[Uri]) -> Optional[List[AudioFeatures]]: logger.info(f'getting {len(uris)} features') audio_features = [] chunked_uris = list(self.chunk(uris, 100)) for chunk in chunked_uris: resp = self.get_request(url='audio-features', ids=','.join(i.object_id for i in chunk)) if resp.get('audio_features', None): return [init_with_key_filter(AudioFeatures, i) for i in resp['audio_features']] else: logger.error('no audio features included') if len(audio_features) == len(uris): return audio_features else: logger.error('mismatched length of input and response')
def saved_tracks(self, response_limit: int = None) -> Optional[List[LibraryTrack]]: """get user library tracks :param response_limit: max tracks to return :return: List of saved library trakcs if available """ logger.info(f"paging library tracks") pager = PageCollection(net=self, url='me/tracks', name='getLibraryTracks') if response_limit: pager.total_limit = response_limit pager.iterate() return_items = [init_with_key_filter(LibraryTrack, i) for i in pager.items] if len(return_items) == 0: logger.error('no tracks returned') return return_items
def playlists(self, response_limit: int = None) -> Optional[List[SimplifiedPlaylist]]: """get current users playlists :param response_limit: max playlists to return :return: List of user created and followed playlists if available """ logger.info(f"paging playlists") pager = PageCollection(net=self, url='me/playlists', name='getPlaylists') if response_limit: pager.total_limit = response_limit pager.iterate() return_items = [init_with_key_filter(SimplifiedPlaylist, i) for i in pager.items] if len(return_items) == 0: logger.error('no playlists returned') return return_items
def recommendations(self, tracks: List[str] = None, artists: List[str] = None, response_limit=10) -> Optional[Recommendations]: logger.info(f'getting {response_limit} recommendations, ' f'tracks: {len(tracks) if tracks is not None else 0}, ' f'artists: {len(artists) if artists is not None else 0}') params = {'limit': response_limit} if tracks: random.shuffle(tracks) params['seed_tracks'] = tracks[:5] if artists: random.shuffle(artists) params['seed_artists'] = artists[:5] if len(params) == 1: logger.warning('update dictionairy length 0') else: return init_with_key_filter(Recommendations, self.get_request('recommendations', params=params))
def show_episodes(self, uri: Uri, response_limit: int = None) -> List[SimplifiedEpisode]: """get list of shows episodes for uri :param uri: target show uri :param response_limit: max episodes to return :return: list of show episodes if available """ logger.info(f"paging episodes for {uri}") pager = PageCollection(net=self, url=f'shows/{uri.object_id}/episodes', name='getShowEpisodes') if response_limit: pager.total_limit = response_limit pager.iterate() return_items = [init_with_key_filter(SimplifiedEpisode, i) for i in pager.items] if len(return_items) == 0: logger.error('no episodes returned') return return_items
def episode(self, uri) -> EpisodeFull: logger.info(f"retrieving {uri}") resp = self.get_request(f'episodes/{uri.object_id}') return init_with_key_filter(EpisodeFull, resp)
def current_user(self) -> PublicUser: logger.info(f"getting current user") resp = self.get_request('me') return init_with_key_filter(PublicUser, resp)