Skip to content

Commit

Permalink
Populate the track/items.album attributes from the parent Album objec…
Browse files Browse the repository at this point in the history
…t. Updated tests (Fixes #281)
  • Loading branch information
tehkillerbee committed Sep 11, 2024
1 parent be80b26 commit 156836d
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 18 deletions.
21 changes: 19 additions & 2 deletions tests/test_album.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@
from dateutil import tz

import tidalapi
from tidalapi.album import Album
from tidalapi.exceptions import MetadataNotAvailable, ObjectNotFound
from tidalapi.exceptions import ObjectNotFound
from tidalapi.media import AudioMode, Quality

from .cover import verify_image_cover, verify_video_cover
Expand Down Expand Up @@ -72,11 +71,20 @@ def test_get_tracks(session):
assert tracks[0].id == 17927864
assert tracks[0].volume_num == 1
assert tracks[0].track_num == 1
assert tracks[0].album == album

assert tracks[-1].name == "Pray"
assert tracks[-1].id == 17927885
assert tracks[-1].volume_num == 2
assert tracks[-1].track_num == 8
assert tracks[-1].album == album

# Getting album.tracks with sparse_album=True will result in a track.album containing only essential fields
tracks_sparse = album.tracks(sparse_album=True)
assert tracks_sparse[0].album.audio_quality is None
assert tracks_sparse[0].album.id == 17927863
assert tracks_sparse[-1].album.audio_quality is None
assert tracks_sparse[-1].album.id == 17927863


def test_get_items(session):
Expand All @@ -87,11 +95,20 @@ def test_get_items(session):
assert items[0].id == 108043415
assert items[0].volume_num == 1
assert items[0].track_num == 1
assert items[0].album == album

assert items[-1].name == "Lemonade Film"
assert items[-1].id == 108043437
assert items[-1].volume_num == 1
assert items[-1].track_num == 15
assert items[-1].album == album

# Getting album.items with sparse_album=True will result in a track.album containing only essential fields
items_sparse = album.items(sparse_album=True)
assert items_sparse[0].album.id == 108043414
assert items_sparse[0].album.audio_quality is None
assert items_sparse[-1].album.id == 108043414
assert items_sparse[-1].album.audio_quality is None


def test_image_cover(session):
Expand Down
37 changes: 33 additions & 4 deletions tidalapi/album.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.

import copy
import functools
from datetime import datetime
from typing import TYPE_CHECKING, List, Optional, Union, cast

Expand Down Expand Up @@ -180,31 +181,59 @@ def available_release_date(self) -> Optional[datetime]:
return self.tidal_release_date
return None

def tracks(self, limit: Optional[int] = None, offset: int = 0) -> List["Track"]:
def tracks(
self,
limit: Optional[int] = None,
offset: int = 0,
sparse_album: bool = False,
) -> List["Track"]:
"""Returns the tracks in classes album.
:param limit: The amount of items you want returned.
:param offset: The position of the first item you want to include.
:param sparse_album: Provide a sparse track.album, containing only essential attributes from track JSON
False: Populate the track.album attributes from the parent Album object (self)
:return: A list of the :class:`Tracks <.Track>` in the album.
"""
params = {"limit": limit, "offset": offset}

if sparse_album:
parse_track_callable = self.session.parse_track
else:
# Parse tracks attributes but provide the Album object directly from self
parse_track_callable = functools.partial(
self.session.parse_track, album=self
)

tracks = self.request.map_request(
"albums/%s/tracks" % self.id, params, parse=self.session.parse_track
"albums/%s/tracks" % self.id, params, parse=parse_track_callable
)
assert isinstance(tracks, list)
return cast(List["Track"], tracks)

def items(self, limit: int = 100, offset: int = 0) -> List[Union["Track", "Video"]]:
def items(
self, limit: int = 100, offset: int = 0, sparse_album: bool = False
) -> List[Union["Track", "Video"]]:
"""Gets the first 'limit' tracks and videos in the album from TIDAL.
:param limit: The number of items you want to retrieve
:param offset: The index you want to start retrieving items from
:param sparse_album: Provide a sparse track.album, containing only essential attributes from track JSON
False: Populate the track.album attributes from the parent Album object (self)
:return: A list of :class:`Tracks<.Track>` and :class:`Videos`<.Video>`
"""
params = {"offset": offset, "limit": limit}

if sparse_album:
parse_media_callable = self.session.parse_media
else:
# Parse tracks attributes but provide the Album object directly from self
parse_media_callable = functools.partial(
self.session.parse_media, album=self
)

items = self.request.map_request(
"albums/%s/items" % self.id, params=params, parse=self.session.parse_media
"albums/%s/items" % self.id, params=params, parse=parse_media_callable
)
assert isinstance(items, list)
return cast(List[Union["Track", "Video"]], items)
Expand Down
29 changes: 17 additions & 12 deletions tidalapi/media.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
from isodate import parse_duration
from mpegdash.parser import MPEGDASHParser

from tidalapi.album import Album
from tidalapi.exceptions import (
ManifestDecodeError,
MetadataNotAvailable,
Expand Down Expand Up @@ -223,9 +224,11 @@ def _get(self, media_id: str) -> Media:
"You are not supposed to use the media class directly."
)

def parse(self, json_obj: JsonObj) -> None:
def parse(self, json_obj: JsonObj, album: Optional[Album] = None) -> None:
"""Assigns all :param json_obj:
:param json_obj: The JSON object to parse
:param album: The (optional) album to use, instead of parsing the JSON object
:return:
"""
artists = self.session.parse_artists(json_obj["artists"])
Expand All @@ -236,9 +239,9 @@ def parse(self, json_obj: JsonObj) -> None:
else:
artist = artists[0]

album = None
if json_obj["album"]:
if album is None and json_obj["album"]:
album = self.session.album().parse(json_obj["album"], artist, artists)
self.album = album

self.id = json_obj["id"]
self.name = json_obj["title"]
Expand All @@ -265,21 +268,23 @@ def parse(self, json_obj: JsonObj) -> None:
self.popularity = json_obj["popularity"]
self.artist = artist
self.artists = artists
self.album = album
self.type = json_obj.get("type")

self.artist_roles = json_obj.get("artistRoles")

def parse_media(self, json_obj: JsonObj) -> Union["Track", "Video"]:
def parse_media(
self, json_obj: JsonObj, album: Optional[Album] = None
) -> Union["Track", "Video"]:
"""Selects the media type when checking lists that can contain both.
:param json_obj: The json containing the media
:param album: The (optional) album to use, instead of parsing the JSON object
:return: Returns a new Video or Track object.
"""
if json_obj.get("type") is None or json_obj["type"] == "Track":
return Track(self.session).parse_track(json_obj)
# There are other types like Event, Live, and Video witch match the video class
return Video(self.session).parse_video(json_obj)
return Track(self.session).parse_track(json_obj, album)
# There are other types like Event, Live, and Video which match the video class
return Video(self.session).parse_video(json_obj, album)


class Track(Media):
Expand All @@ -295,8 +300,8 @@ class Track(Media):
copyright = None
media_metadata_tags = None

def parse_track(self, json_obj: JsonObj) -> Track:
Media.parse(self, json_obj)
def parse_track(self, json_obj: JsonObj, album: Optional[Album] = None) -> Track:
Media.parse(self, json_obj, album)
self.replay_gain = json_obj["replayGain"]
# Tracks from the pages endpoints might not actually exist
if "peak" in json_obj and "isrc" in json_obj:
Expand Down Expand Up @@ -846,8 +851,8 @@ class Video(Media):
video_quality: Optional[str] = None
cover: Optional[str] = None

def parse_video(self, json_obj: JsonObj) -> Video:
Media.parse(self, json_obj)
def parse_video(self, json_obj: JsonObj, album: Optional[Album] = None) -> Video:
Media.parse(self, json_obj, album)
release_date = json_obj.get("releaseDate")
self.release_date = (
dateutil.parser.isoparse(release_date) if release_date else None
Expand Down

0 comments on commit 156836d

Please sign in to comment.