diff --git a/lyricsgenius/genius.py b/lyricsgenius/genius.py index af2ae65..e796198 100644 --- a/lyricsgenius/genius.py +++ b/lyricsgenius/genius.py @@ -352,6 +352,7 @@ def search_album( album_id: int | None = None, get_full_info: bool = True, text_format: TextFormatT | None = None, + fetch_lyrics: bool = True, ) -> Album | None: """Searches for a specific album and gets its songs. @@ -365,6 +366,10 @@ def search_album( for the album (slower if no album_id present). text_format (:obj:`str`, optional): Text format of the results ('dom', 'html', 'markdown' or 'plain'). + fetch_lyrics (:obj:`bool`, optional): If `True` (default), scrapes + lyrics for each track. Set to `False` to skip lyrics fetching + and return only track metadata — significantly faster for large + albums. Returns: :class:`Album ` \\| :obj:`None`: On success, @@ -431,8 +436,10 @@ def search_album( for track_data in tracks_list_response["tracks"]: song_info = track_data["song"] song_lyrics = None - if song_info["lyrics_state"] == "complete" and not song_info.get( - "instrumental" + if ( + fetch_lyrics + and song_info["lyrics_state"] == "complete" + and not song_info.get("instrumental") ): song_lyrics = self.lyrics(song_url=song_info["url"]) diff --git a/pyproject.toml b/pyproject.toml index b07c77d..52b7386 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "lyricsgenius" -version = "3.10.0" +version = "3.10.1" dependencies = ["beautifulsoup4>=4.12.3", "requests>=2.27.1"] requires-python = ">=3.11" authors = [{ name = "John W. R. Miller", email = "john.w.millr+lg@gmail.com" }] diff --git a/tests/test_album.py b/tests/test_album.py index 40f1734..0fca0be 100644 --- a/tests/test_album.py +++ b/tests/test_album.py @@ -6,6 +6,7 @@ import pytest +from lyricsgenius import Genius from lyricsgenius.types import Album, Artist, Song from lyricsgenius.utils import sanitize_filename @@ -213,3 +214,93 @@ def test_saving_txt_file(album_object: Album, tmp_path: Path) -> None: content_after_overwrite = expected_filepath.read_text() assert "Overwritten TXT Test" in content_after_overwrite, content_after_overwrite album_object.tracks[0][1].lyrics = original_lyrics + + +@pytest.fixture +def genius_client() -> Genius: + return Genius("dummy_access_token", verbose=False, sleep_time=0) + + +def _make_search_all_response(album_data: dict[str, Any]) -> dict[str, Any]: + hit = {"index": "album", "result": album_data} + return { + "sections": [ + {"type": "top_hits", "hits": [hit]}, + {"type": "album", "hits": [hit]}, + ] + } + + +def _make_album_tracks_response(songs: list[dict[str, Any]]) -> dict[str, Any]: + return { + "tracks": [{"song": s, "number": i + 1} for i, s in enumerate(songs)], + "next_page": None, + } + + +def test_fetch_lyrics_true_calls_lyrics( + genius_client: Genius, + mock_album_data: dict[str, Any], + mock_track_data_list: list[dict[str, Any]], +) -> None: + """When fetch_lyrics=True (default), lyrics() should be called for each track.""" + with ( + mock.patch.object( + genius_client, + "search_all", + return_value=_make_search_all_response(mock_album_data), + ), + mock.patch.object( + genius_client, "album", return_value={"album": mock_album_data} + ), + mock.patch.object( + genius_client, + "album_tracks", + return_value=_make_album_tracks_response(mock_track_data_list), + ), + mock.patch.object( + genius_client, "lyrics", return_value="some lyrics" + ) as mock_lyrics, + ): + result = genius_client.search_album( + name=mock_album_data["name"], fetch_lyrics=True + ) + + assert result is not None + assert mock_lyrics.call_count == len(mock_track_data_list) + for _, track in result.tracks: + assert track.lyrics == "some lyrics" + + +def test_fetch_lyrics_false_skips_lyrics( + genius_client: Genius, + mock_album_data: dict[str, Any], + mock_track_data_list: list[dict[str, Any]], +) -> None: + """When fetch_lyrics=False, lyrics() is not called and tracks have empty lyrics.""" + with ( + mock.patch.object( + genius_client, + "search_all", + return_value=_make_search_all_response(mock_album_data), + ), + mock.patch.object( + genius_client, "album", return_value={"album": mock_album_data} + ), + mock.patch.object( + genius_client, + "album_tracks", + return_value=_make_album_tracks_response(mock_track_data_list), + ), + mock.patch.object( + genius_client, "lyrics", return_value="some lyrics" + ) as mock_lyrics, + ): + result = genius_client.search_album( + name=mock_album_data["name"], fetch_lyrics=False + ) + + assert result is not None + assert mock_lyrics.call_count == 0 + for _, track in result.tracks: + assert track.lyrics == "" diff --git a/uv.lock b/uv.lock index bb5578a..2259ecb 100644 --- a/uv.lock +++ b/uv.lock @@ -254,7 +254,7 @@ wheels = [ [[package]] name = "lyricsgenius" -version = "3.10.0" +version = "3.10.1" source = { editable = "." } dependencies = [ { name = "beautifulsoup4" },