from itertools import groupby from typing import Dict, List from server.constants import DSP from server.constants.tracks import DEFAULT_IMAGE_SIZE def get_apple_tracks_response(tracklist: List[Dict], image_size: int = DEFAULT_IMAGE_SIZE) -> List[Dict]: """ Creates a specific List[Dict] response for Apple list of track(s) Args: tracklist (List[Dict]): returned response from Vendor API image_size (int): desirable size of track image. For Spotify 64, 300, 640 are allowed only. Returns: List[dict]: List of track data items. """ result = [] for track in tracklist: attrs, relations = track.get("attributes", {}), track.get("relationships", {}) image_url = attrs.get("artwork", {}).get("url") if image_url and image_size: image_url = image_url.format(w=image_size, h=image_size) albums = relations.get("albums", {}).get("data", []) artists = relations.get("artists", {}).get("data", []) result.append( { "id": track.get("id"), "vendor": "apple", "isrc": attrs.get("isrc"), "name": attrs.get("name"), "release_date": attrs.get("releaseDate"), "image": image_url, "album_ids": [a["id"] for a in albums], "artists": [ {"id": artist.get("id"), "name": artist.get("attributes", {}).get("name")} for artist in artists ], } ) return result def get_spotify_tracks_response(tracklist: List[Dict], image_size: int = DEFAULT_IMAGE_SIZE) -> List[Dict]: """ Creates a specific List[Dict] response for Spotify list of track(s) Args: tracklist: returned response from Vendor API image_size: desirable size of track image. For Spotify 64, 300, 640 are allowed only. Returns: List of track data items. """ result = [] for track in tracklist: result.append( { "id": track.get("id"), "vendor": "spotify", "isrc": track.get("external_ids", {}).get("isrc"), "name": track.get("name"), "release_date": track.get("album", {}).get("release_date"), "image": next( (image for image in track.get("album", {}).get("images", []) if image["height"] == image_size), {}, ).get("url"), "album_ids": [track.get("album", {}).get("id")], "artists": [ {"id": artist.get("id"), "name": artist.get("name"), "uri": artist.get("uri")} for artist in track.get("artists", []) ], } ) return result def get_dsp_tracks_response(dsp: DSP, tracklist: List[Dict], image_size: int = DEFAULT_IMAGE_SIZE) -> List[Dict]: """ Handle response formation - a List[Dict], from Vendor API get_tracks response depending on dsp Args: dsp (DSP): DSP.APPLE or DSP.SPOTIFY tracklist (List[Dict]): returned response from Vendor API image_size (int): desirable size of track image. For Spotify 64, 300, 640 are allowed only. Returns: List[dict]: List of track data items. Raises: NotImplementedError: If method for passed DSP is not implemented. """ if dsp == DSP.APPLE: return get_apple_tracks_response(tracklist, image_size) if dsp == DSP.SPOTIFY: return get_spotify_tracks_response(tracklist, image_size) raise NotImplementedError() def get_track_id_data_map(data: Dict) -> Dict: """track_id to track data mapper""" result = {track["id"]: track for track in data} return result def get_track_id_artists_map(data: Dict) -> Dict: """track_id to artists information mapper""" result = {track_id: list(artists) for track_id, artists in groupby(data, lambda i: i["track_id"])} return result def get_track_ids_by_dsp(dsp: DSP, data: List[Dict]) -> List: """track_id to data mapper for specific dsp""" result = [x["song_id"] for x in data if x["vendor"] and x["vendor"] == dsp.value] return result async def get_spotify_visited_tracks_response(data, tracks, dsp_tracks, artists, is_sony): """ Get spotify visited tracks response by provided data Args: data (List[Dict]): data from database with tracks and visit count for most visited tracks tracks (Dict): tracks data from database dsp_tracks (Dict): tracks data from dsp artists (Dict): artists information data from database is_sony (List[str]): list of sony track_ids Return: List[Dict] """ for visit in data: if visit["vendor"] != DSP.SPOTIFY.value: continue song_id = visit["song_id"] visit.update( { "is_sony": song_id in is_sony, "artists": [{"id": a["id"], "name": a["name"]} for a in artists[song_id]] if song_id in artists else [], } ) if song_id in tracks: song = tracks[song_id] visit.update({"name": song["name"], "artist_name": song["artist_name"]}) # There are some tracks that does not exists in our database. Let's mix data from DSP-API. if song_id in dsp_tracks: song = dsp_tracks[song_id] visit["image_url"] = song["image"] if "name" not in visit: visit.update({"name": song["name"], "artists": song.get("artists", [])}) return data async def get_apple_visited_tracks_response(data: List[Dict], tracks: Dict, dsp_tracks: Dict, artists: Dict): """ Get apple visited tracks response by provided data Args: data (List[Dict]): data from database with tracks and visit count for most visited tracks tracks (Dict): tracks data from database dsp_tracks (Dict): tracks data from dsp artists (Dict): artists information data from database Return: List[Dict] """ for visit in data: if visit["vendor"] != DSP.APPLE.value: continue song_id = visit["song_id"] visit.update( { "is_sony": False, "artists": [{"id": a[1], "name": a[2]} for a in artists["song_id"]] if song_id in artists else [], } ) if song_id in tracks: song = tracks["song_id"] visit.update({"name": song["name"], "artist_name": song["artist_name"], "image_url": song["artwork_url"]}) # There are some tracks that does not exists in our database. Let's mix data from DSP-API. if song_id in dsp_tracks and "name" not in visit: song = dsp_tracks["song_id"] visit.update({"name": song["name"], "image_url": song["image"]}) return data async def get_visited_tracks_response( dsp: DSP, data: List[Dict], tracks: Dict, dsp_tracks: Dict, artists: Dict, is_sony: (List, None) = None ): """ Handle response formation - a List[Dict], for visited_tracks depending on dsp Args: dsp (DSP): DSP.APPLE or DSP.SPOTIFY data (List[Dict]): data from database with tracks and visit count for most visited tracks tracks (Dict): tracks data from database dsp_tracks (Dict): tracks data from dsp artists (Dict): artists information data from database is_sony (List[str]): checked tracks for is_sony logic (only for spotify) Returns: List[dict]: List of track data items. Raises: NotImplementedError: If method for passed DSP is not implemented. """ if dsp == DSP.SPOTIFY: return await get_spotify_visited_tracks_response(data, tracks, dsp_tracks, artists, is_sony) if dsp == DSP.APPLE: return await get_apple_visited_tracks_response(data, tracks, dsp_tracks, artists) raise NotImplementedError() def get_weekly_summary(last_week_map: dict, prev_week_map: dict, prefix: str, default=0) -> dict: key_to_summary = {} for key in last_week_map.keys() | prev_week_map.keys(): last_week_value, prev_week_value = last_week_map.get(key, default), prev_week_map.get(key, default) change = last_week_value - prev_week_value key_to_summary[key] = { prefix: last_week_value, f"{prefix}_7days_change": change, f"{prefix}_7days_change_rate": round(change / prev_week_value * 100) if prev_week_value else None, } return key_to_summary