from datetime import date from typing import Any, Dict, Iterable, List, Tuple from server.legacy.consumer_analytics.vendors.base import BaseVendor from server.legacy.core.constants import APPLE TRACKS_PER_COUNTRY_FIELDS = ("streams",) TRACKS_PER_COUNTRY_SOURCE_FIELDS = ( "other", "library", "search", "discovery", "music_kit", "external", "now_playing", "voice", ) TRACKS_PER_COUNTRY_CONTAINER_FIELDS = ("single_track", "radio", "playlist", "album") TRACKS_PER_COUNTRY_PACKED_FIELDS = { "source": TRACKS_PER_COUNTRY_SOURCE_FIELDS, "container_type": TRACKS_PER_COUNTRY_CONTAINER_FIELDS, } TRACKS_PER_COUNTRY_PACKED_FIELDS_MAPPING = {"source": "source", "container_type": "container"} class AppleMusicVendor(BaseVendor): """Apple Music vendor class for Consumer Analytics client.""" vendor = APPLE @staticmethod def streams_to_list(data: dict, fields: Iterable[str]) -> List[int]: return list(data.get(field, 0) for field in fields) @staticmethod def streams_to_dict(data: dict, fields: Iterable[str]) -> Dict[str, int]: return {str(i): data.get(field, 0) for i, field in enumerate(fields)} @staticmethod def sum_streams_dict(item1: dict, item2: dict) -> dict: for key, value in item2.items(): item1[key] += value return item1 @classmethod def parse_date_item( cls, _date: date, date_item: Dict[str, Any], keys: Tuple[tuple, dict], **kwargs ) -> Dict[str, Any]: """Parse streams and source types data map for particular date from compact to full format. Args: _date: date key. date_item: data map to parse. keys: iterable of keys to get data from passed data map by. Returns: { "date": _date, "streams": number of streams, "source": {"0": streams per source 0 number, ...}, "container"" {"0": streams per container 0 number, ...} } """ flat_keys, packed_keys = keys data = {"date": _date, **{key: date_item[key] for key in flat_keys}} for key, fields in packed_keys.items(): values = date_item[key] data[TRACKS_PER_COUNTRY_PACKED_FIELDS_MAPPING[key]] = cls.streams_to_dict(values, fields) return data @staticmethod def get_data_item_keys(streams_only: bool = False) -> Tuple[tuple, dict]: """Return iterable of keys for date streams item.""" if streams_only: return ("streams",), {} return TRACKS_PER_COUNTRY_FIELDS, TRACKS_PER_COUNTRY_PACKED_FIELDS @classmethod def convert_item_to_track_compat_v2(cls, streams_count: int, streams_info: dict) -> dict: container_info = streams_info.get("container_type") source_info = streams_info["source"] return { "str": streams_count, "src": cls.streams_to_list(source_info, TRACKS_PER_COUNTRY_SOURCE_FIELDS), "ctr": cls.streams_to_list(container_info, TRACKS_PER_COUNTRY_CONTAINER_FIELDS), } @classmethod def convert_item_to_track_compat_v1( cls, streams_count: int, streams_info: dict, item_date: date, item: dict ) -> dict: container_info = streams_info.get("container_type") source_info = streams_info["source"] converted_sources = cls.streams_to_dict(source_info, TRACKS_PER_COUNTRY_SOURCE_FIELDS) converted_containers = cls.streams_to_dict(container_info, TRACKS_PER_COUNTRY_CONTAINER_FIELDS) if item: item["streams"] += streams_count item["source"] = cls.sum_streams_dict(item["source"], converted_sources) item["container"] = cls.sum_streams_dict(item["container"], converted_containers) return item else: return { "date": item_date, "othersPlaylist": None, "streams": streams_count, "source": converted_sources, "container": converted_containers, } @staticmethod def generate_tracks_playlists_summary_result( data: Dict[str, Dict[str, int]], include_shuffle: bool, all_markets: bool = False, add_isrc: bool = False ) -> list: def get_item(_id, item): res = { "containerId": _id, "localStreams": item.get("l_st", 0), "globalStreams": item["g_st"], "localDaysStreamed": item.get("l_d", 0), "globalDaysStreamed": item["g_d"], } if add_isrc: res["isrc"] = item.get("isrc") if all_markets: res["markets"] = [ (code, market["st"], market["d"]) for code, market in sorted(item.get("markets", {}).items()) ] return res return [get_item(playlist_id, stats_item) for playlist_id, stats_item in sorted(data.items())] @staticmethod def generate_tracks_playlists_per_day_item( streams_date: date or None, streams_item: Dict[str, int], include_shuffle: bool, market_field: str = "local" ) -> dict: return { "date": streams_date, f"{market_field}Streams": streams_item.get("l_st", 0), "globalStreams": streams_item["g_st"], } @classmethod def parse_streams_from_release_to_date_item(cls, market: str, streams_count: int, *args) -> dict: return {"countryCode": market, "streams": streams_count}