From 27453e3457c2df8925edae58b19b5d1858c77f7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0ar=C5=ABnas=20Nejus?= Date: Fri, 20 Sep 2024 13:56:37 +0100 Subject: [PATCH] Add types to lyrics fetch methods --- beetsplug/lyrics.py | 73 ++++++++++++++++++++----------------- test/plugins/test_lyrics.py | 4 +- 2 files changed, 41 insertions(+), 36 deletions(-) diff --git a/beetsplug/lyrics.py b/beetsplug/lyrics.py index b8077490c9..ddbc6872e5 100644 --- a/beetsplug/lyrics.py +++ b/beetsplug/lyrics.py @@ -26,13 +26,20 @@ from contextlib import contextmanager from functools import cached_property from html import unescape -from typing import Any, ClassVar, Iterator +from typing import TYPE_CHECKING, Any, ClassVar, Iterator from urllib.parse import urlparse import requests from typing_extensions import TypedDict from unidecode import unidecode +import beets +from beets import plugins, ui +from beets.autotag.hooks import string_dist + +if TYPE_CHECKING: + from beets.library import Item + try: from bs4 import BeautifulSoup @@ -47,11 +54,6 @@ except ImportError: HAS_LANGDETECT = False - -import beets -from beets import plugins, ui -from beets.autotag.hooks import string_dist - BREAK_RE = re.compile(r"\n?\s*]*)*>\s*\n?", re.I) USER_AGENT = f"beets/{beets.__version__}" @@ -269,7 +271,9 @@ def __init__(self, config, log): self._log = log self.config = config - def fetch(self, artist, title, album=None, length=None): + def fetch( + self, artist: str, title: str, album: str, length: float + ) -> str | None: raise NotImplementedError @@ -319,11 +323,7 @@ def pick_lyrics( return min(data, key=lambda item: cls.get_rank(target_duration, item)) def fetch( - self, - artist: str, - title: str, - album: str | None = None, - length: float = 0.0, + self, artist: str, title: str, album: str, length: float ) -> str | None: """Fetch lyrics for the given artist, title, and album.""" params = { @@ -370,7 +370,9 @@ def build_url(self, artist, title): self._encode(title.title()), ) - def fetch(self, artist, title, album=None, length=None): + def fetch( + self, artist: str, title: str, album: str, length: float + ) -> str | None: url = self.build_url(artist, title) html = self.fetch_text(url) @@ -412,7 +414,9 @@ def __init__(self, config, log): self.api_key = config["genius_api_key"].as_str() self.headers = {"Authorization": f"Bearer {self.api_key}"} - def fetch(self, artist, title, album=None, length=None): + def fetch( + self, artist: str, title: str, album: str, length: float + ) -> str | None: """Fetch lyrics from genius.com Because genius doesn't allow accessing lyrics via the api, @@ -513,7 +517,9 @@ def build_url(self, artist, title): urllib.parse.quote_plus(unidecode(artistitle)) ) - def fetch(self, artist, title, album=None, length=None): + def fetch( + self, artist: str, title: str, album: str, length: float + ) -> str | None: search_results = self.fetch_text(self.build_url(title, artist)) song_page_url = self.parse_search_results(search_results) if not song_page_url: @@ -696,7 +702,9 @@ def is_page_candidate(self, url_link, url_title, title, artist): ratio = difflib.SequenceMatcher(None, song_title, title).ratio() return ratio >= typo_ratio - def fetch(self, artist, title, album=None, length=None): + def fetch( + self, artist: str, title: str, album: str, length: float + ) -> str | None: query = f"{artist} {title}" url = "https://www.googleapis.com/customsearch/v1?key=%s&cx=%s&q=%s" % ( self.api_key, @@ -869,10 +877,7 @@ def func(lib, opts, args): for item in items: if not opts.local_only and not self.config["local"]: self.fetch_item_lyrics( - lib, - item, - write, - opts.force_refetch or self.config["force"], + item, write, opts.force_refetch or self.config["force"] ) if item.lyrics: if opts.printlyr: @@ -966,7 +971,7 @@ def imported(self, session, task): session.lib, item, False, self.config["force"] ) - def fetch_item_lyrics(self, lib, item, write, force): + def fetch_item_lyrics(self, item: Item, write: bool, force: bool) -> None: """Fetch and store lyrics for a single item. If ``write``, then the lyrics will also be written to the file itself. """ @@ -976,17 +981,17 @@ def fetch_item_lyrics(self, lib, item, write, force): return lyrics = None - album = item.album - length = round(item.length) - for artist, titles in search_pairs(item): - lyrics = [ - self.get_lyrics(artist, title, album=album, length=length) - for title in titles + album, length = item.album, item.length + lyrics_matches = [ + [ + lyrics + for t in titles + if (lyrics := self.get_lyrics(artist, t, album, length)) ] - if any(lyrics): - break + for artist, titles in search_pairs(item) + ] - lyrics = "\n\n---\n\n".join(filter(None, lyrics)) + lyrics = "\n\n---\n\n".join(next(filter(None, lyrics_matches), [])) if lyrics: self.info("Lyrics found: {}", item) @@ -1011,18 +1016,18 @@ def fetch_item_lyrics(self, lib, item, write, force): item.try_write() item.store() - def get_lyrics(self, artist, title, album=None, length=None): + def get_lyrics(self, artist: str, title: str, *args) -> str | None: """Fetch lyrics, trying each source in turn. Return a string or None if no lyrics were found. """ self.debug("Fetching lyrics for {} - {}", artist, title) for backend in self.backends.values(): with backend.handle_request(): - if lyrics := backend.fetch( - artist, title, album=album, length=length - ): + if lyrics := backend.fetch(artist, title, *args): return lyrics + return None + def append_translation(self, text, to_lang): from xml.etree import ElementTree diff --git a/test/plugins/test_lyrics.py b/test/plugins/test_lyrics.py index a93f271deb..53446f57d9 100644 --- a/test/plugins/test_lyrics.py +++ b/test/plugins/test_lyrics.py @@ -208,7 +208,7 @@ def test_backend_source(self, backend): databases. """ title = "Lady Madonna" - res = backend.fetch("The Beatles", title) + res = backend.fetch("The Beatles", title, "", 0.0) assert PHRASE_BY_TITLE[title] in res.lower() @@ -235,7 +235,7 @@ def test_error_handling( requests_mock.get(lyrics.LRCLib.base_url, **request_kwargs) plugin = lyrics.LyricsPlugin() - assert plugin.get_lyrics("", "") is None + assert plugin.get_lyrics("", "", "", 0.0) is None assert caplog.messages last_log = caplog.messages[-1] assert last_log