def get_playlist_tracks(args): playlists = [] current_user_id = args.get("current_user_id") limit = args.get("limit") offset = args.get("offset") db = get_db_read_replica() with db.scoped_session() as session: try: playlist_id = args.get("playlist_id") playlist = ( session .query(Playlist) .filter( Playlist.is_current == True, Playlist.playlist_id == playlist_id ) .first() ) if playlist is None: return None playlist_track_ids = [track_id['track'] for track_id in playlist.playlist_contents['track_ids']] if limit and offset: playlist_track_ids = playlist_track_ids[offset:offset+limit] playlist_tracks = ( session .query(Track) .filter( Track.is_current == True, Track.track_id.in_(playlist_track_ids) ) .all() ) tracks = helpers.query_result_to_list(playlist_tracks) tracks = populate_track_metadata( session, playlist_track_ids, tracks, current_user_id) if args.get("with_users", False): add_users_to_tracks(session, tracks, current_user_id) tracks_dict = {track['track_id']: track for track in tracks} playlist_tracks = [] for track_id in playlist_track_ids: playlist_tracks.append(tracks_dict[track_id]) return playlist_tracks except sqlalchemy.orm.exc.NoResultFound: pass return playlists
def _get_user_listening_history(session: Session, args: GetUserListeningHistoryArgs): user_id = args["user_id"] current_user_id = args["current_user_id"] limit = args["limit"] offset = args["offset"] if user_id != current_user_id: return [] listening_history_results = (session.query( UserListeningHistory.listening_history).filter( UserListeningHistory.user_id == current_user_id)).scalar() if not listening_history_results: return [] # add query pagination listening_history_results = listening_history_results[offset:offset + limit] track_ids = [] listen_dates = [] for listen in listening_history_results: track_ids.append(listen["track_id"]) listen_dates.append(listen["timestamp"]) track_results = (session.query(Track).filter( Track.track_id.in_(track_ids))).all() track_results_dict = { track_result.track_id: track_result for track_result in track_results } # sort tracks in listening history order sorted_track_results = [] for track_id in track_ids: if track_id in track_results_dict: sorted_track_results.append(track_results_dict[track_id]) tracks = helpers.query_result_to_list(sorted_track_results) # bundle peripheral info into track results tracks = populate_track_metadata(session, track_ids, tracks, current_user_id) add_users_to_tracks(session, tracks, current_user_id) for idx, track in enumerate(tracks): track[response_name_constants.activity_timestamp] = listen_dates[idx] return tracks
def get_remix_track_parents(args): """Fetch remix parents for a given track. Args: args:dict args.track_id: track id args.limit: limit args.offset: offset args.with_users: with users args.current_user_id: current user ID """ track_id = args.get("track_id") current_user_id = args.get("current_user_id") limit = args.get("limit") offset = args.get("offset") db = get_db_read_replica() with db.scoped_session() as session: def get_unpopulated_remix_parents(): base_query = ( session.query(Track) .join( Remix, and_( Remix.parent_track_id == Track.track_id, Remix.child_track_id == track_id, ), ) .filter(Track.is_current == True, Track.is_unlisted == False) .order_by(desc(Track.created_at), desc(Track.track_id)) ) tracks = add_query_pagination(base_query, limit, offset).all() tracks = helpers.query_result_to_list(tracks) track_ids = list(map(lambda track: track["track_id"], tracks)) return (tracks, track_ids) key = make_cache_key(args) (tracks, track_ids) = use_redis_cache( key, UNPOPULATED_REMIX_PARENTS_CACHE_DURATION_SEC, get_unpopulated_remix_parents, ) tracks = populate_track_metadata(session, track_ids, tracks, current_user_id) if args.get("with_users", False): add_users_to_tracks(session, tracks, current_user_id) return tracks
def get_remix_track_parents(track_id, args): db = get_db_read_replica() with db.scoped_session() as session: base_query = (session.query(Track).join( Remix, and_(Remix.parent_track_id == Track.track_id, Remix.child_track_id == track_id)).filter( Track.is_current == True, Track.is_unlisted == False).order_by( desc(Track.created_at), desc(Track.track_id))) tracks = paginate_query(base_query).all() tracks = helpers.query_result_to_list(tracks) track_ids = list(map(lambda track: track["track_id"], tracks)) current_user_id = get_current_user_id(required=False) tracks = populate_track_metadata(session, track_ids, tracks, current_user_id) if args.get("with_users", False): add_users_to_tracks(session, tracks) return tracks
def get_remixes_of(args): track_id = args.get("track_id") current_user_id = args.get("current_user_id") limit, offset = args.get("limit"), args.get("offset") db = get_db_read_replica() with db.scoped_session() as session: def get_unpopulated_remixes(): # Fetch the parent track to get the track's owner id parent_track_res = get_unpopulated_tracks(session, [track_id], False, False) if not parent_track_res or parent_track_res[0] is None: raise exceptions.ArgumentError("Invalid track_id provided") parent_track = parent_track_res[0] track_owner_id = parent_track['owner_id'] # Create subquery for save counts for sorting save_count_subquery = create_save_count_subquery( session, SaveType.track) # Create subquery for repost counts for sorting repost_count_subquery = create_repost_count_subquery( session, RepostType.track) # Get the 'children' remix tracks # Use the track owner id to fetch reposted/saved tracks returned first base_query = ( session.query( Track ) .join( Remix, and_( Remix.child_track_id == Track.track_id, Remix.parent_track_id == track_id ) ).outerjoin( Save, and_( Save.save_item_id == Track.track_id, Save.save_type == SaveType.track, Save.is_current == True, Save.is_delete == False, Save.user_id == track_owner_id ) ).outerjoin( Repost, and_( Repost.repost_item_id == Track.track_id, Repost.user_id == track_owner_id, Repost.repost_type == RepostType.track, Repost.is_current == True, Repost.is_delete == False ) ).outerjoin( repost_count_subquery, repost_count_subquery.c['id'] == Track.track_id ).outerjoin( save_count_subquery, save_count_subquery.c['id'] == Track.track_id ) .filter( Track.is_current == True, Track.is_delete == False, Track.is_unlisted == False ) # 1. Co-signed tracks ordered by save + repost count # 2. Other tracks ordered by save + repost count .order_by( desc( # If there is no "co-sign" for the track (no repost or save from the parent owner), # defer to secondary sort case( [ (and_(Repost.created_at == None, Save.created_at == None), 0), ], else_=( func.coalesce(repost_count_subquery.c.repost_count, 0) + \ func.coalesce(save_count_subquery.c.save_count, 0) ) ) ), # Order by saves + reposts desc( func.coalesce(repost_count_subquery.c.repost_count, 0) + \ func.coalesce(save_count_subquery.c.save_count, 0) ), # Ties, pick latest track id desc(Track.track_id) ) ) (tracks, count) = add_query_pagination(base_query, limit, offset, True, True) tracks = tracks.all() tracks = helpers.query_result_to_list(tracks) track_ids = list(map(lambda track: track["track_id"], tracks)) return (tracks, track_ids, count) key = make_cache_key(args) (tracks, track_ids, count) = use_redis_cache(key, UNPOPULATED_REMIXES_CACHE_DURATION_SEC, get_unpopulated_remixes) tracks = populate_track_metadata(session, track_ids, tracks, current_user_id) if args.get("with_users", False): add_users_to_tracks(session, tracks, current_user_id) return {'tracks': tracks, 'count': count}
def get_remixable_tracks(args): """Gets a list of remixable tracks""" db = get_db_read_replica() limit = args.get("limit", 25) current_user_id = args.get("current_user_id", None) StemTrack = aliased(Track) with db.scoped_session() as session: # Subquery to get current tracks that have stems remixable_tracks_subquery = (session.query(Track).join( Stem, Stem.parent_track_id == Track.track_id).join( StemTrack, Stem.child_track_id == StemTrack.track_id).filter( Track.is_current == True, Track.is_unlisted == False, Track.is_delete == False, StemTrack.is_current == True, StemTrack.is_unlisted == False, StemTrack.is_delete == False, ).distinct(Track.track_id).subquery()) track_alias = aliased(Track, remixable_tracks_subquery) count_subquery = session.query( AggregateTrack.track_id.label("id"), (AggregateTrack.repost_count + AggregateTrack.save_count).label("count"), ).subquery() query = (session.query( track_alias, count_subquery.c["count"], decayed_score(count_subquery.c["count"], track_alias.created_at).label("score"), ).join( count_subquery, count_subquery.c["id"] == track_alias.track_id, ).order_by(desc("score"), desc(track_alias.track_id)).limit(limit)) results = query.all() tracks = [] for result in results: track = result[0] score = result[-1] track = helpers.model_to_dictionary(track) track["score"] = score tracks.append(track) track_ids = list(map(lambda track: track["track_id"], tracks)) # Get user specific data for tracks tracks = populate_track_metadata(session, track_ids, tracks, current_user_id) if args.get("with_users", False): add_users_to_tracks(session, tracks, current_user_id) else: # Remove the user from the tracks tracks = [{key: val for key, val in dict.items() if key != "user"} for dict in tracks] return tracks
def get_trending_playlists(args, strategy): """Returns Trending Playlists. Checks Redis cache for unpopulated playlists.""" db = get_db_read_replica() with db.scoped_session() as session: current_user_id = args.get("current_user_id", None) with_tracks = args.get("with_tracks", False) time = args.get("time") limit, offset = args.get("limit"), args.get("offset") key = make_trending_cache_key(time, strategy.version) # Get unpopulated playlists, # cached if it exists. (playlists, playlist_ids) = use_redis_cache( key, None, make_get_unpopulated_playlists(session, time, strategy)) # Apply limit + offset early to reduce the amount of # population work we have to do if limit is not None and offset is not None: playlists = playlists[offset:limit + offset] playlist_ids = playlist_ids[offset:limit + offset] # Populate playlist metadata playlists = populate_playlist_metadata( session, playlist_ids, playlists, [RepostType.playlist, RepostType.album], [SaveType.playlist, SaveType.album], current_user_id) trimmed_track_ids = None for playlist in playlists: playlist["track_count"] = len(playlist["tracks"]) playlist["tracks"] = playlist["tracks"][:PLAYLIST_TRACKS_LIMIT] # Trim track_ids, which ultimately become added_timestamps # and need to match the tracks. trimmed_track_ids = { track["track_id"] for track in playlist["tracks"] } playlist_track_ids = playlist["playlist_contents"]["track_ids"] playlist_track_ids = list( filter(lambda track_id: track_id["track"] in trimmed_track_ids, playlist_track_ids)) playlist["playlist_contents"]["track_ids"] = playlist_track_ids playlists_map = { playlist['playlist_id']: playlist for playlist in playlists } if with_tracks: # populate track metadata tracks = [] for playlist in playlists: playlist_tracks = playlist["tracks"] tracks.extend(playlist_tracks) track_ids = [track["track_id"] for track in tracks] populated_tracks = populate_track_metadata(session, track_ids, tracks, current_user_id) # Add users if necessary add_users_to_tracks(session, populated_tracks, current_user_id) # Re-associate tracks with playlists # track_id -> populated_track populated_track_map = { track["track_id"]: track for track in populated_tracks } for playlist in playlists_map.values(): for i in range(len(playlist["tracks"])): track_id = playlist["tracks"][i]["track_id"] populated = populated_track_map[track_id] playlist["tracks"][i] = populated playlist["tracks"] = list(map(extend_track, playlist["tracks"])) # re-sort playlists to original order, because populate_playlist_metadata # unsorts. sorted_playlists = [ playlists_map[playlist_id] for playlist_id in playlist_ids ] # Add users to playlists user_id_list = get_users_ids(sorted_playlists) users = get_users_by_id(session, user_id_list, current_user_id) for playlist in sorted_playlists: user = users[playlist['playlist_owner_id']] if user: playlist['user'] = user # Extend the playlists playlists = list(map(extend_playlist, playlists)) return sorted_playlists
def get_playlist_tracks(session, args): """Accepts args: { # optionally pass in full playlists to avoid having to fetch "playlists": Playlist[] # not needed if playlists are passed "playlist_ids": string[] "current_user_id": int "populate_tracks": boolean # whether to add users & metadata to tracks } Returns: { playlist_id: Playlist } """ try: playlists = args.get("playlists") if not playlists: playlist_ids = args.get("playlist_ids", []) playlists = (session.query(Playlist).filter( Playlist.is_current == True, Playlist.playlist_id.in_(playlist_ids))) playlists = list(map(helpers.model_to_dictionary, playlists)) if not playlists: return {} # track_id -> [playlist_id] track_ids_set = set() for playlist in playlists: playlist_id = playlist['playlist_id'] for track_id_dict in playlist['playlist_contents']['track_ids']: track_id = track_id_dict['track'] track_ids_set.add(track_id) playlist_tracks = (session.query(Track).filter( Track.is_current == True, Track.track_id.in_(list(track_ids_set))).all()) tracks = helpers.query_result_to_list(playlist_tracks) if args.get("populate_tracks"): current_user_id = args.get("current_user_id") tracks = populate_track_metadata(session, list(track_ids_set), tracks, current_user_id) add_users_to_tracks(session, tracks, current_user_id) # { track_id => track } track_ids_map = {track["track_id"]: track for track in tracks} # { playlist_id => [track]} playlists_map = {} for playlist in playlists: playlist_id = playlist["playlist_id"] playlists_map[playlist_id] = [] for track_id_dict in playlist['playlist_contents']['track_ids']: track_id = track_id_dict['track'] track = track_ids_map[track_id] playlists_map[playlist_id].append(track) return playlists_map except sqlalchemy.orm.exc.NoResultFound: return {}
def get_tracks(args: GetTrackArgs): """ Gets tracks. A note on caching strategy: - This method is cached at two layers: at the API via the @cache decorator, and within this method using the shared get_unpopulated_tracks cache. The shared cache only works when fetching via ID, so calls to fetch tracks via handle, asc/desc sort, or filtering by block_number won't hit the shared cache. These will hit the API cache unless they have a current_user_id included. """ tracks = [] db = get_db_read_replica() with db.scoped_session() as session: def get_tracks_and_ids(): if "handle" in args: handle = args.get("handle") user = (session.query(User.user_id).filter( User.handle_lc == handle.lower()).first()) args["user_id"] = user.user_id if "routes" in args: # Convert the handles to user_ids routes = args.get("routes") handles = [route["handle"].lower() for route in routes] user_id_tuples = (session.query( User.user_id, User.handle_lc).filter(User.handle_lc.in_(handles), User.is_current == True).all()) user_id_map = { handle: user_id for (user_id, handle) in user_id_tuples } args["routes"] = [] for route in routes: if route["handle"].lower() in user_id_map: args["routes"].append({ "slug": route["slug"], "owner_id": user_id_map[route["handle"].lower()], }) # If none of the handles were found, return empty lists if not args["routes"]: return ([], []) can_use_shared_cache = ("id" in args and "min_block_number" not in args and "sort" not in args and "user_id" not in args) if can_use_shared_cache: should_filter_deleted = args.get("filter_deleted", False) tracks = get_unpopulated_tracks(session, args["id"], should_filter_deleted) track_ids = list(map(lambda track: track["track_id"], tracks)) return (tracks, track_ids) (limit, offset) = get_pagination_vars() args["limit"] = limit args["offset"] = offset tracks = _get_tracks(session, args) track_ids = list(map(lambda track: track["track_id"], tracks)) return (tracks, track_ids) (tracks, track_ids) = get_tracks_and_ids() # bundle peripheral info into track results current_user_id = args.get("current_user_id") # remove track segments and download cids from deactivated user tracks and deleted tracks for track in tracks: if track["user"][0]["is_deactivated"] or track["is_delete"]: track["track_segments"] = [] if track["download"] is not None: track["download"]["cid"] = None tracks = populate_track_metadata(session, track_ids, tracks, current_user_id) if args.get("with_users", False): add_users_to_tracks(session, tracks, current_user_id) else: # Remove the user from the tracks tracks = [{key: val for key, val in dict.items() if key != "user"} for dict in tracks] return tracks