def test_insert_user_stats_mult_ranges_recording(self):
        """ Test if multiple time range data is inserted correctly """
        with open(self.path_to_data_file('user_top_recordings_db.json')) as f:
            recordings_data = json.load(f)
        recordings_data_year = deepcopy(recordings_data)
        recordings_data_year['stats_range'] = 'year'

        db_stats.insert_user_jsonb_data(
            user_id=self.user['id'],
            stats_type='recordings',
            stats=StatRange[UserEntityRecord](**recordings_data))
        db_stats.insert_user_jsonb_data(
            user_id=self.user['id'],
            stats_type='recordings',
            stats=StatRange[UserEntityRecord](**recordings_data_year))

        result = db_stats.get_user_stats(user_id=self.user['id'],
                                         stats_range='all_time',
                                         stats_type='recordings')
        self.assertDictEqual(result.dict(exclude={'user_id', 'last_updated'}),
                             recordings_data)

        result = db_stats.get_user_stats(user_id=self.user['id'],
                                         stats_range='year',
                                         stats_type='recordings')
        self.assertDictEqual(result.dict(exclude={'user_id', 'last_updated'}),
                             recordings_data_year)
    def test_get_user_stats(self):
        data_inserted = self.insert_test_data()

        data = db_stats.get_user_stats(self.user['id'], 'artist')
        self.assertEqual(data['artist']['count'], 2)

        data = db_stats.get_user_stats(self.user['id'], 'recording')
        self.assertListEqual(data['recording']['all_time']['recordings'], data_inserted['user_recordings'])
    def test_get_user_stats(self):
        data_inserted = self.insert_test_data()

        data = db_stats.get_user_stats(self.user['id'], 'artist')
        self.assertEqual(data['artist']['count'], 2)

        data = db_stats.get_user_stats(self.user['id'], 'recording')
        self.assertListEqual(data['recording']['all_time'], data_inserted['user_recordings'])
def _get_entity_stats(user_name: str, entity: str, count_key: str):
    user, stats_range = _validate_stats_user_params(user_name)

    offset = get_non_negative_param("offset", default=0)
    count = get_non_negative_param("count", default=DEFAULT_ITEMS_PER_GET)

    stats = db_stats.get_user_stats(user["id"], stats_range, entity)
    if stats is None:
        raise APINoContent('')

    entity_list, total_entity_count = _process_user_entity(
        stats, offset, count)

    return jsonify({
        "payload": {
            "user_id": user_name,
            entity: entity_list,
            "count": len(entity_list),
            count_key: total_entity_count,
            "offset": offset,
            "range": stats_range,
            "from_ts": stats.from_ts,
            "to_ts": stats.to_ts,
            "last_updated": int(stats.last_updated.timestamp()),
        }
    })
    def test_insert_user_recordings(self):
        """ Test if recording stats are inserted correctly """
        with open(self.path_to_data_file('user_top_recordings_db.json')) as f:
            recordings_data = json.load(f)
        db_stats.insert_user_jsonb_data(user_id=self.user['id'], stats_type='recordings',
                                        stats=StatRange[EntityRecord](**recordings_data))

        result = db_stats.get_user_stats(user_id=self.user['id'], stats_range='all_time', stats_type='recordings')
        self.assertDictEqual(result.dict(exclude={'user_id', 'last_updated'}), recordings_data)
    def test_delete(self):
        user_id = db_user.create(10, 'frank')

        user = db_user.get(user_id)
        self.assertIsNotNone(user)

        with open(self.path_to_data_file('user_top_artists_db.json')) as f:
            artists_data = ujson.load(f)
        db_stats.insert_user_jsonb_data(
            user_id=user_id,
            stats_type='artists',
            stats=StatRange[EntityRecord](**artists_data),
        )
        user_stats = db_stats.get_user_stats(user_id, 'all_time', 'artists')
        self.assertIsNotNone(user_stats)

        db_user.delete(user_id)
        user = db_user.get(user_id)
        self.assertIsNone(user)
        user_stats = db_stats.get_user_stats(user_id, 'all_time', 'artists')
        self.assertIsNone(user_stats)
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
Exemple #8
0
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 profile(user_name):
    # Which database to use to showing user listens.
    db_conn = webserver.timescale_connection._ts
    # Which database to use to show playing_now stream.
    playing_now_conn = webserver.redis_connection._redis

    user = _get_user(user_name)
    # User name used to get user may not have the same case as original user name.
    user_name = user.musicbrainz_id

    # Getting data for current page
    max_ts = request.args.get("max_ts")
    if max_ts is not None:
        try:
            max_ts = int(max_ts)
        except ValueError:
            raise BadRequest("Incorrect timestamp argument max_ts: %s" %
                             request.args.get("max_ts"))

    min_ts = request.args.get("min_ts")
    if min_ts is not None:
        try:
            min_ts = int(min_ts)
        except ValueError:
            raise BadRequest("Incorrect timestamp argument min_ts: %s" %
                             request.args.get("min_ts"))

    args = {}
    if max_ts:
        args['to_ts'] = max_ts
    else:
        args['from_ts'] = min_ts
    data, min_ts_per_user, max_ts_per_user = db_conn.fetch_listens(
        user_name, limit=LISTENS_PER_PAGE, **args)

    listens = []
    for listen in data:
        listens.append(listen.to_api())

    # If there are no previous listens then display now_playing
    if not listens or listens[0]['listened_at'] >= max_ts_per_user:
        playing_now = playing_now_conn.get_playing_now(user.id)
        if playing_now:
            listen = {
                "track_metadata": playing_now.data,
                "playing_now": "true",
            }
            listens.insert(0, listen)

    user_stats = db_stats.get_user_stats(user.id, 'all_time', 'artists')

    logged_in_user_follows_user = None
    already_reported_user = False
    if current_user.is_authenticated:
        logged_in_user_follows_user = db_user_relationship.is_following_user(
            current_user.id, user.id)
        already_reported_user = db_user.is_user_reported(
            current_user.id, user.id)

    pin = get_current_pin_for_user(user_id=user.id)
    if pin:
        pin = dict(fetch_track_metadata_for_pins([pin])[0])

    props = {
        "user": {
            "id": user.id,
            "name": user.musicbrainz_id,
        },
        "listens": listens,
        "latest_listen_ts": max_ts_per_user,
        "oldest_listen_ts": min_ts_per_user,
        "latest_spotify_uri": _get_spotify_uri_for_listens(listens),
        "artist_count": format(user_stats.count, ",d") if user_stats else None,
        "profile_url": url_for('user.profile', user_name=user_name),
        "mode": "listens",
        "userPinnedRecording": pin,
        "web_sockets_server_url": current_app.config['WEBSOCKETS_SERVER_URL'],
        "logged_in_user_follows_user": logged_in_user_follows_user,
        "already_reported_user": already_reported_user,
    }

    return render_template("user/profile.html",
                           props=ujson.dumps(props),
                           mode='listens',
                           user=user,
                           active_section='listens')
 def test_get_user_recordings(self):
     data_inserted = self.insert_test_data()
     result = db_stats.get_user_stats(self.user['id'], 'all_time', 'recordings')
     self.assertDictEqual(result.dict(exclude={'user_id', 'last_updated'}), data_inserted['user_recordings'])
Exemple #11
0
    def test_handle_user_entity(self):
        data = {
            'type':
            'user_entity',
            'entity':
            'artists',
            'stats_range':
            'all_time',
            'from_ts':
            1,
            'to_ts':
            10,
            'data': [{
                'user_id':
                1,
                'data': [{
                    'artist_name': 'Kanye West',
                    'listen_count': 200,
                }],
                'count':
                1,
            }, {
                'user_id':
                2,
                'data': [{
                    'artist_name': 'Selena Gomez',
                    'listen_count': 100,
                }, {
                    'artist_name': 'Tom Ellis',
                    'listen_count': 50,
                }],
                'count':
                2,
            }]
        }

        handle_user_entity(data)

        received = db_stats.get_user_stats(1, 'all_time', 'artists')
        expected = StatApi[EntityRecord](
            user_id=1,
            to_ts=10,
            from_ts=1,
            count=1,
            stats_range='all_time',
            data=StatRecordList[EntityRecord](__root__=[
                ArtistRecord(
                    artist_mbids=[],
                    listen_count=200,
                    artist_name='Kanye West',
                )
            ]),
            last_updated=received.last_updated)
        self.assertEqual(received, expected)

        received = db_stats.get_user_stats(2, 'all_time', 'artists')
        expected = StatApi[EntityRecord](
            user_id=2,
            to_ts=10,
            from_ts=1,
            count=2,
            stats_range='all_time',
            data=StatRecordList[EntityRecord](__root__=[
                ArtistRecord(
                    artist_mbids=[],
                    listen_count=100,
                    artist_name='Selena Gomez',
                ),
                ArtistRecord(
                    artist_mbids=[],
                    listen_count=50,
                    artist_name='Tom Ellis',
                )
            ]),
            last_updated=received.last_updated)
        self.assertEqual(received, expected)