def test_insert_user_stats_mult_ranges_artist_map(self): """ Test if multiple time range data is inserted correctly """ with open(self.path_to_data_file('user_artist_map_db.json')) as f: artist_map_data = json.load(f) artist_map_data_year = deepcopy(artist_map_data) artist_map_data_year['stats_range'] = 'year' db_stats.insert_user_jsonb_data( user_id=self.user['id'], stats_type='artist_map', stats=StatRange[UserArtistMapRecord](**artist_map_data)) db_stats.insert_user_jsonb_data( user_id=self.user['id'], stats_type='artist_map', stats=StatRange[UserArtistMapRecord](**artist_map_data_year)) result = db_stats.get_user_artist_map(1, 'all_time') self.assertDictEqual( result.dict(exclude={'user_id', 'last_updated', 'count'}), artist_map_data) result = db_stats.get_user_artist_map(1, 'year') self.assertDictEqual( result.dict(exclude={'user_id', 'last_updated', 'count'}), artist_map_data_year)
def test_insert_user_stats_mult_ranges_artist_map(self): """ Test if multiple time range data is inserted correctly """ with open(self.path_to_data_file('user_artist_map_db.json')) as f: artist_map_data = json.load(f) db_stats.insert_user_artist_map( user_id=self.user['id'], artist_map=UserArtistMapStatJson(**{'year': artist_map_data})) db_stats.insert_user_artist_map( self.user['id'], UserArtistMapStatJson(**{'all_time': artist_map_data})) result = db_stats.get_user_artist_map(1, 'all_time') self.assertDictEqual(result.all_time.dict(), artist_map_data) result = db_stats.get_user_artist_map(1, 'year') self.assertDictEqual(result.year.dict(), artist_map_data)
def test_insert_user_artist_map(self): """ Test if daily activity stats are inserted correctly """ with open(self.path_to_data_file('user_artist_map_db.json')) as f: artist_map_data = json.load(f) db_stats.insert_user_artist_map( user_id=self.user['id'], artist_map=UserArtistMapStatJson(**{'all_time': artist_map_data})) result = db_stats.get_user_artist_map(user_id=self.user['id'], stats_range='all_time') self.assertDictEqual(result.all_time.dict(), artist_map_data)
def test_insert_user_artist_map(self): """ Test if daily activity stats are inserted correctly """ with open(self.path_to_data_file('user_artist_map_db.json')) as f: artist_map_data = json.load(f) db_stats.insert_user_jsonb_data( user_id=self.user['id'], stats_type='artist_map', stats=StatRange[UserArtistMapRecord](**artist_map_data) ) result = db_stats.get_user_artist_map(user_id=self.user['id'], stats_range='all_time') self.assertDictEqual(result.dict(exclude={'user_id', 'last_updated', 'count'}), artist_map_data)
def get_artist_map(user_name: str): """ Get the artist map for user ``user_name``. The artist map shows the number of artists the user has listened to from different countries of the world. A sample response from the endpoint may look like:: { "payload": { "from_ts": 1587945600, "last_updated": 1592807084, "artist_map": [ { "country": "USA", "artist_count": 34 }, { "country": "GBR", "artist_count": 69 }, { "country": "IND", "artist_count": 32 } ], "stats_range": "all_time" "to_ts": 1589155200, "user_id": "ishaanshah" } } .. note:: - This endpoint is currently in beta - We cache the results for this query for a week to improve page load times, if you want to request fresh data you can use the ``force_recalculate`` flag. :param range: Optional, time interval for which statistics should be returned, possible values are ``week``, ``month``, ``year``, ``all_time``, defaults to ``all_time`` :type range: ``str`` :param force_recalculate: Optional, recalculate the data instead of returning the cached result. :type range: ``bool`` :statuscode 200: Successful query, you have data! :statuscode 204: Statistics for the user haven't been calculated, empty response will be returned :statuscode 400: Bad request, check ``response['error']`` for more details :statuscode 404: User not found :resheader Content-Type: *application/json* """ user = db_user.get_by_mb_id(user_name) if user is None: raise APINotFound("Cannot find user: {}".format(user_name)) stats_range = request.args.get('range', default='all_time') if not _is_valid_range(stats_range): raise APIBadRequest("Invalid range: {}".format(stats_range)) recalculate_param = request.args.get('force_recalculate', default='false') if recalculate_param.lower() not in ['true', 'false']: raise APIBadRequest( "Invalid value of force_recalculate: {}".format(recalculate_param)) force_recalculate = recalculate_param.lower() == 'true' # Check if stats are present in DB, if not calculate them calculated = not force_recalculate stats = db_stats.get_user_artist_map(user['id'], stats_range) if stats is None or getattr(stats, stats_range) is None: calculated = False # Check if the stats present in DB have been calculated in the past week, if not recalculate them stale = False if calculated: last_updated = getattr(stats, stats_range).last_updated if (datetime.now() - datetime.fromtimestamp(last_updated) ).days >= STATS_CALCULATION_INTERVAL: stale = True if stale or not calculated: artist_stats = db_stats.get_user_artists(user['id'], stats_range) # If top artists are missing, return the stale stats if present, otherwise return 204 if artist_stats is None or getattr(artist_stats, stats_range) is None: if stale: result = stats else: raise APINoContent('') else: # Calculate the data artist_msids = defaultdict(lambda: 0) artist_mbids = defaultdict(lambda: 0) top_artists = getattr(artist_stats, stats_range).artists for artist in top_artists: if artist.artist_msid is not None: artist_msids[artist.artist_msid] += artist.listen_count else: for artist_mbid in artist.artist_mbids: artist_mbids[artist_mbid] += artist.listen_count country_code_data = _get_country_codes(artist_msids, artist_mbids) result = UserArtistMapStatJson( **{ stats_range: { "artist_map": country_code_data, "from_ts": int( getattr(artist_stats, stats_range).from_ts), "to_ts": int(getattr(artist_stats, stats_range).to_ts), "last_updated": int(datetime.now().timestamp()) } }) # Store in DB for future use try: db_stats.insert_user_artist_map(user['id'], result) except Exception as err: current_app.logger.error( "Error while inserting artist map stats for {user}. Error: {err}. Data: {data}" .format(user=user_name, err=err, data=result), exc_info=True) else: result = stats return jsonify({ "payload": { "user_id": user_name, "range": stats_range, **(getattr(result, stats_range).dict()) } })
def _get_artist_map_stats(user_id, stats_range): recalculate_param = request.args.get('force_recalculate', default='false') if recalculate_param.lower() not in ['true', 'false']: raise APIBadRequest( "Invalid value of force_recalculate: {}".format(recalculate_param)) force_recalculate = recalculate_param.lower() == 'true' # Check if stats are present in DB, if not calculate them calculated = not force_recalculate stats = db_stats.get_user_artist_map(user_id, stats_range) if stats is None: calculated = False # Check if the stats present in DB have been calculated in the past week, if not recalculate them stale = False if calculated: if (datetime.now(timezone.utc) - stats.last_updated).days >= STATS_CALCULATION_INTERVAL: stale = True if stale or not calculated: artist_stats = db_stats.get_user_stats(user_id, stats_range, 'artists') # If top artists are missing, return the stale stats if present, otherwise return 204 if artist_stats is None: if stale: result = stats else: raise APINoContent('') else: # Calculate the data artist_mbid_counts = defaultdict(int) for artist in artist_stats.data.__root__: for artist_mbid in artist.artist_mbids: artist_mbid_counts[artist_mbid] += artist.listen_count country_code_data = _get_country_wise_counts(artist_mbid_counts) result = StatApi[UserArtistMapRecord]( **{ "data": country_code_data, "from_ts": artist_stats.from_ts, "to_ts": artist_stats.to_ts, "stats_range": stats_range, # this isn't stored in the database, but adding it here to avoid a subsequent db call to # just fetch last updated. could just store this value instead in db but that complicates # the implementation a bit "last_updated": datetime.now(), "user_id": user_id }) # Store in DB for future use try: db_stats.insert_user_jsonb_data(user_id, 'artist_map', result) except Exception as err: current_app.logger.error( "Error while inserting artist map stats for {user}. Error: {err}. Data: {data}" .format(user=user_id, err=err, data=result), exc_info=True) else: result = stats return result
def get_artist_map(user_name: str): """ Get the artist map for user ``user_name``. The artist map shows the number of artists the user has listened to from different countries of the world. A sample response from the endpoint may look like: .. code-block:: json { "payload": { "from_ts": 1587945600, "last_updated": 1592807084, "artist_map": [ { "country": "USA", "artist_count": 34 }, { "country": "GBR", "artist_count": 69 }, { "country": "IND", "artist_count": 32 } ], "stats_range": "all_time" "to_ts": 1589155200, "user_id": "ishaanshah" } } .. note:: - This endpoint is currently in beta - We cache the results for this query for a week to improve page load times, if you want to request fresh data you can use the ``force_recalculate`` flag. :param range: Optional, time interval for which statistics should be returned, possible values are ``week``, ``month``, ``year``, ``all_time``, defaults to ``all_time`` :type range: ``str`` :param force_recalculate: Optional, recalculate the data instead of returning the cached result. :type force_recalculate: ``bool`` :statuscode 200: Successful query, you have data! :statuscode 204: Statistics for the user haven't been calculated, empty response will be returned :statuscode 400: Bad request, check ``response['error']`` for more details :statuscode 404: User not found :resheader Content-Type: *application/json* """ user, stats_range = _validate_stats_user_params(user_name) recalculate_param = request.args.get('force_recalculate', default='false') if recalculate_param.lower() not in ['true', 'false']: raise APIBadRequest( "Invalid value of force_recalculate: {}".format(recalculate_param)) force_recalculate = recalculate_param.lower() == 'true' # Check if stats are present in DB, if not calculate them calculated = not force_recalculate stats = db_stats.get_user_artist_map(user['id'], stats_range) if stats is None: calculated = False # Check if the stats present in DB have been calculated in the past week, if not recalculate them stale = False if calculated: if (datetime.now(timezone.utc) - stats.last_updated).days >= STATS_CALCULATION_INTERVAL: stale = True if stale or not calculated: artist_stats = db_stats.get_user_stats(user['id'], stats_range, 'artists') # If top artists are missing, return the stale stats if present, otherwise return 204 if artist_stats is None: if stale: result = stats else: raise APINoContent('') else: # Calculate the data artist_mbid_counts = defaultdict(int) for artist in artist_stats.data.__root__: for artist_mbid in artist.artist_mbids: artist_mbid_counts[artist_mbid] += artist.listen_count country_code_data = _get_country_wise_counts(artist_mbid_counts) result = StatApi[UserArtistMapRecord]( **{ "data": country_code_data, "from_ts": artist_stats.from_ts, "to_ts": artist_stats.to_ts, "stats_range": stats_range, # this isn't stored in the database, but adding it here to avoid a subsequent db call to # just fetch last updated. could just store this value instead in db but that complicates # the implementation a bit "last_updated": datetime.now(), "user_id": user['id'] }) # Store in DB for future use try: db_stats.insert_user_jsonb_data(user['id'], 'artist_map', result) except Exception as err: current_app.logger.error( "Error while inserting artist map stats for {user}. Error: {err}. Data: {data}" .format(user=user_name, err=err, data=result), exc_info=True) else: result = stats return jsonify({ "payload": { "user_id": user_name, "range": stats_range, "from_ts": result.from_ts, "to_ts": result.to_ts, "last_updated": int(result.last_updated.timestamp()), "artist_map": [x.dict() for x in result.data.__root__] } })
def test_get_user_artist_map(self): data_inserted = self.insert_test_data() result = db_stats.get_user_artist_map(self.user['id'], 'all_time') self.assertDictEqual(result.all_time.dict(), data_inserted['user_artist_map'])
def test_get_user_artist_map(self): data_inserted = self.insert_test_data() result = db_stats.get_user_artist_map(self.user['id'], 'all_time') self.assertDictEqual(result.dict(exclude={'user_id', 'last_updated', 'count'}), data_inserted['user_artist_map'])