From bf903fc27dfa5782436c7dd98597ced609ecd7bb Mon Sep 17 00:00:00 2001 From: J0J0 Todos Date: Sun, 3 Aug 2025 22:05:16 +0200 Subject: [PATCH 01/16] lastgenre: Move file loading to helpers and add return types. --- beetsplug/lastgenre/__init__.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/beetsplug/lastgenre/__init__.py b/beetsplug/lastgenre/__init__.py index dacd72f93..934874b97 100644 --- a/beetsplug/lastgenre/__init__.py +++ b/beetsplug/lastgenre/__init__.py @@ -111,9 +111,11 @@ class LastGenrePlugin(plugins.BeetsPlugin): self.import_stages = [self.imported] self._genre_cache = {} + self.whitelist = self._load_whitelist() + self.c14n_branches, self.canonicalize = self._load_c14n_tree() - # Read the whitelist file if enabled. - self.whitelist = set() + def _load_whitelist(self) -> set[str]: + whitelist = set() wl_filename = self.config["whitelist"].get() if wl_filename in (True, ""): # Indicates the default whitelist. wl_filename = WHITELIST @@ -123,27 +125,27 @@ class LastGenrePlugin(plugins.BeetsPlugin): for line in f: line = line.decode("utf-8").strip().lower() if line and not line.startswith("#"): - self.whitelist.add(line) + whitelist.add(line) + return whitelist - # Read the genres tree for canonicalization if enabled. - self.c14n_branches = [] + def _load_c14n_tree(self) -> tuple[list[list[str]], bool]: + c14n_branches = [] c14n_filename = self.config["canonical"].get() - self.canonicalize = c14n_filename is not False - + canonicalize = c14n_filename is not False # Default tree if c14n_filename in (True, ""): c14n_filename = C14N_TREE - elif not self.canonicalize and self.config["prefer_specific"].get(): + elif not canonicalize and self.config["prefer_specific"].get(): # prefer_specific requires a tree, load default tree c14n_filename = C14N_TREE - # Read the tree if c14n_filename: self._log.debug("Loading canonicalization tree {}", c14n_filename) c14n_filename = normpath(c14n_filename) with codecs.open(c14n_filename, "r", encoding="utf-8") as f: genres_tree = yaml.safe_load(f) - flatten_tree(genres_tree, [], self.c14n_branches) + flatten_tree(genres_tree, [], c14n_branches) + return c14n_branches, canonicalize @property def sources(self) -> tuple[str, ...]: From 6ed17912b498d04cfde8d061f26a1a5d73dc623f Mon Sep 17 00:00:00 2001 From: J0J0 Todos Date: Sat, 30 Aug 2025 20:18:06 +0200 Subject: [PATCH 02/16] lastgenre: Fix _load_whitelist return type bytes --- beetsplug/lastgenre/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beetsplug/lastgenre/__init__.py b/beetsplug/lastgenre/__init__.py index 934874b97..6c16fc6fe 100644 --- a/beetsplug/lastgenre/__init__.py +++ b/beetsplug/lastgenre/__init__.py @@ -114,7 +114,7 @@ class LastGenrePlugin(plugins.BeetsPlugin): self.whitelist = self._load_whitelist() self.c14n_branches, self.canonicalize = self._load_c14n_tree() - def _load_whitelist(self) -> set[str]: + def _load_whitelist(self) -> set[bytes]: whitelist = set() wl_filename = self.config["whitelist"].get() if wl_filename in (True, ""): # Indicates the default whitelist. From 8ae29e42bf9c045b5a823002aadb68f46365365b Mon Sep 17 00:00:00 2001 From: J0J0 Todos Date: Sun, 31 Aug 2025 17:59:03 +0200 Subject: [PATCH 03/16] lastgenre: Fix mypy errors in file load methods --- beetsplug/lastgenre/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/beetsplug/lastgenre/__init__.py b/beetsplug/lastgenre/__init__.py index 6c16fc6fe..aa627ca3d 100644 --- a/beetsplug/lastgenre/__init__.py +++ b/beetsplug/lastgenre/__init__.py @@ -114,7 +114,7 @@ class LastGenrePlugin(plugins.BeetsPlugin): self.whitelist = self._load_whitelist() self.c14n_branches, self.canonicalize = self._load_c14n_tree() - def _load_whitelist(self) -> set[bytes]: + def _load_whitelist(self) -> set[str]: whitelist = set() wl_filename = self.config["whitelist"].get() if wl_filename in (True, ""): # Indicates the default whitelist. @@ -122,14 +122,14 @@ class LastGenrePlugin(plugins.BeetsPlugin): if wl_filename: wl_filename = normpath(wl_filename) with open(wl_filename, "rb") as f: - for line in f: - line = line.decode("utf-8").strip().lower() + for raw_line in f: + line = raw_line.decode("utf-8").strip().lower() if line and not line.startswith("#"): whitelist.add(line) return whitelist def _load_c14n_tree(self) -> tuple[list[list[str]], bool]: - c14n_branches = [] + c14n_branches: list[list[str]] = [] c14n_filename = self.config["canonical"].get() canonicalize = c14n_filename is not False # Default tree From 5ff88b46cf9e428dfc56f01817501516ba6d2f9c Mon Sep 17 00:00:00 2001 From: J0J0 Todos Date: Sun, 31 Aug 2025 19:24:45 +0200 Subject: [PATCH 04/16] lastgenre: Fix another mypy error in c14n load (that was only happening in CI and not by local poe check-types) --- beetsplug/lastgenre/__init__.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/beetsplug/lastgenre/__init__.py b/beetsplug/lastgenre/__init__.py index aa627ca3d..be329e8b5 100644 --- a/beetsplug/lastgenre/__init__.py +++ b/beetsplug/lastgenre/__init__.py @@ -140,9 +140,10 @@ class LastGenrePlugin(plugins.BeetsPlugin): c14n_filename = C14N_TREE # Read the tree if c14n_filename: - self._log.debug("Loading canonicalization tree {}", c14n_filename) - c14n_filename = normpath(c14n_filename) - with codecs.open(c14n_filename, "r", encoding="utf-8") as f: + self._log.debug("Loading canonicalization tree {0}", c14n_filename) + with codecs.open( + str(normpath(c14n_filename)), "r", encoding="utf-8" + ) as f: genres_tree = yaml.safe_load(f) flatten_tree(genres_tree, [], c14n_branches) return c14n_branches, canonicalize From a98fa054fe97c204461b370de6ee90d03e6f4ac4 Mon Sep 17 00:00:00 2001 From: J0J0 Todos Date: Mon, 1 Sep 2025 00:32:27 +0200 Subject: [PATCH 05/16] lastgenre: Fix failing CI tests by using syspath when loading c14n file. --- beetsplug/lastgenre/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/beetsplug/lastgenre/__init__.py b/beetsplug/lastgenre/__init__.py index be329e8b5..f9a37d874 100644 --- a/beetsplug/lastgenre/__init__.py +++ b/beetsplug/lastgenre/__init__.py @@ -32,7 +32,7 @@ import yaml from beets import config, library, plugins, ui from beets.library import Album, Item -from beets.util import normpath, plurality, unique_list +from beets.util import normpath, plurality, syspath, unique_list LASTFM = pylast.LastFMNetwork(api_key=plugins.LASTFM_KEY) @@ -142,7 +142,7 @@ class LastGenrePlugin(plugins.BeetsPlugin): if c14n_filename: self._log.debug("Loading canonicalization tree {0}", c14n_filename) with codecs.open( - str(normpath(c14n_filename)), "r", encoding="utf-8" + syspath(normpath(c14n_filename)), "r", encoding="utf-8" ) as f: genres_tree = yaml.safe_load(f) flatten_tree(genres_tree, [], c14n_branches) From bbde63d87efe99b02128a56058c5946e60e5722c Mon Sep 17 00:00:00 2001 From: J0J0 Todos <2733783+JOJ0@users.noreply.github.com> Date: Mon, 1 Sep 2025 07:29:25 +0200 Subject: [PATCH 06/16] lastgenre: Accept AI suggested use open() for tree file instead of codecs.open(), which most probably is a relict of beets' Python2/3 compatibility area. Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> --- beetsplug/lastgenre/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/beetsplug/lastgenre/__init__.py b/beetsplug/lastgenre/__init__.py index f9a37d874..20a757a55 100644 --- a/beetsplug/lastgenre/__init__.py +++ b/beetsplug/lastgenre/__init__.py @@ -22,7 +22,6 @@ The scraper script used is available here: https://gist.github.com/1241307 """ -import codecs import os import traceback from typing import Union @@ -141,7 +140,7 @@ class LastGenrePlugin(plugins.BeetsPlugin): # Read the tree if c14n_filename: self._log.debug("Loading canonicalization tree {0}", c14n_filename) - with codecs.open( + with open( syspath(normpath(c14n_filename)), "r", encoding="utf-8" ) as f: genres_tree = yaml.safe_load(f) From 856bde1efbf67b7170603878b91e09ced3292bd5 Mon Sep 17 00:00:00 2001 From: J0J0 Todos Date: Mon, 1 Sep 2025 07:44:39 +0200 Subject: [PATCH 07/16] Changelog for #5979 lastgenre move file loading --- docs/changelog.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 4b4134cae..6532c5beb 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -131,6 +131,8 @@ Other changes: beets/library directory. - Added a test to check that all plugins can be imported without errors. - :doc:`/guides/main`: Add instructions to install beets on Void Linux. +- :doc:`plugins/lastgenre`: Refactor loading whitelist and canonicalization + file. :bug:`5979` 2.3.1 (May 14, 2025) -------------------- From 0cdb1224b9feeb3fa61ea5ac9ae4c8d3cbad919d Mon Sep 17 00:00:00 2001 From: J0J0 Todos Date: Thu, 4 Sep 2025 22:54:30 +0200 Subject: [PATCH 08/16] lastgenre: Fix c14n load log msg format --- beetsplug/lastgenre/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beetsplug/lastgenre/__init__.py b/beetsplug/lastgenre/__init__.py index 20a757a55..c314a69ab 100644 --- a/beetsplug/lastgenre/__init__.py +++ b/beetsplug/lastgenre/__init__.py @@ -139,7 +139,7 @@ class LastGenrePlugin(plugins.BeetsPlugin): c14n_filename = C14N_TREE # Read the tree if c14n_filename: - self._log.debug("Loading canonicalization tree {0}", c14n_filename) + self._log.debug("Loading canonicalization tree {}", c14n_filename) with open( syspath(normpath(c14n_filename)), "r", encoding="utf-8" ) as f: From fbd90b050733a2445d3c10ac182e8704b342616c Mon Sep 17 00:00:00 2001 From: J0J0 Todos <2733783+JOJ0@users.noreply.github.com> Date: Sun, 7 Sep 2025 17:45:41 +0200 Subject: [PATCH 09/16] lastgenre: Use pathlib for opening tree file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit instead of syspath(normpath()) Co-authored-by: Šarūnas Nejus --- beetsplug/lastgenre/__init__.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/beetsplug/lastgenre/__init__.py b/beetsplug/lastgenre/__init__.py index c314a69ab..3e9186106 100644 --- a/beetsplug/lastgenre/__init__.py +++ b/beetsplug/lastgenre/__init__.py @@ -24,6 +24,7 @@ https://gist.github.com/1241307 import os import traceback +from pathlib import Path from typing import Union import pylast @@ -140,9 +141,7 @@ class LastGenrePlugin(plugins.BeetsPlugin): # Read the tree if c14n_filename: self._log.debug("Loading canonicalization tree {}", c14n_filename) - with open( - syspath(normpath(c14n_filename)), "r", encoding="utf-8" - ) as f: + with Path(c14n_filename).open(encoding="utf-8") as f: genres_tree = yaml.safe_load(f) flatten_tree(genres_tree, [], c14n_branches) return c14n_branches, canonicalize From c54a54682f1a55305547d03810334b9298ed2579 Mon Sep 17 00:00:00 2001 From: J0J0 Todos Date: Sun, 7 Sep 2025 18:36:45 +0200 Subject: [PATCH 10/16] lastgenre: Use pathlib and simplify whitelist load - Read the whole file using Path().read_text() - Strip whitespace and lower() transform Drawbacks: - Normalization gets lost (normpath did that) --- beetsplug/lastgenre/__init__.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/beetsplug/lastgenre/__init__.py b/beetsplug/lastgenre/__init__.py index 3e9186106..9e3f22445 100644 --- a/beetsplug/lastgenre/__init__.py +++ b/beetsplug/lastgenre/__init__.py @@ -32,7 +32,7 @@ import yaml from beets import config, library, plugins, ui from beets.library import Album, Item -from beets.util import normpath, plurality, syspath, unique_list +from beets.util import plurality, unique_list LASTFM = pylast.LastFMNetwork(api_key=plugins.LASTFM_KEY) @@ -120,12 +120,11 @@ class LastGenrePlugin(plugins.BeetsPlugin): if wl_filename in (True, ""): # Indicates the default whitelist. wl_filename = WHITELIST if wl_filename: - wl_filename = normpath(wl_filename) - with open(wl_filename, "rb") as f: - for raw_line in f: - line = raw_line.decode("utf-8").strip().lower() - if line and not line.startswith("#"): - whitelist.add(line) + text = Path(wl_filename).read_text(encoding="utf-8") + for line in text.splitlines(): + if (line := line.strip().lower()) and not line.startswith("#"): + whitelist.add(line) + return whitelist def _load_c14n_tree(self) -> tuple[list[list[str]], bool]: From 81d342b79fb76049d089be3ed5aa83176119165e Mon Sep 17 00:00:00 2001 From: J0J0 Todos Date: Sun, 7 Sep 2025 18:42:52 +0200 Subject: [PATCH 11/16] lastgenre: Simplify default tree loading --- beetsplug/lastgenre/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/beetsplug/lastgenre/__init__.py b/beetsplug/lastgenre/__init__.py index 9e3f22445..bf4081261 100644 --- a/beetsplug/lastgenre/__init__.py +++ b/beetsplug/lastgenre/__init__.py @@ -132,10 +132,10 @@ class LastGenrePlugin(plugins.BeetsPlugin): c14n_filename = self.config["canonical"].get() canonicalize = c14n_filename is not False # Default tree - if c14n_filename in (True, ""): - c14n_filename = C14N_TREE - elif not canonicalize and self.config["prefer_specific"].get(): + if c14n_filename in (True, "") or ( # prefer_specific requires a tree, load default tree + not canonicalize and self.config["prefer_specific"].get() + ): c14n_filename = C14N_TREE # Read the tree if c14n_filename: From 6da72beeb092e05a8f1bcecc35c216e71e499797 Mon Sep 17 00:00:00 2001 From: J0J0 Todos Date: Sun, 7 Sep 2025 18:58:42 +0200 Subject: [PATCH 12/16] lastgenre: Add expanduser to whitelist/tree load --- beetsplug/lastgenre/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/beetsplug/lastgenre/__init__.py b/beetsplug/lastgenre/__init__.py index bf4081261..343c50888 100644 --- a/beetsplug/lastgenre/__init__.py +++ b/beetsplug/lastgenre/__init__.py @@ -120,7 +120,7 @@ class LastGenrePlugin(plugins.BeetsPlugin): if wl_filename in (True, ""): # Indicates the default whitelist. wl_filename = WHITELIST if wl_filename: - text = Path(wl_filename).read_text(encoding="utf-8") + text = Path(wl_filename).expanduser().read_text(encoding="utf-8") for line in text.splitlines(): if (line := line.strip().lower()) and not line.startswith("#"): whitelist.add(line) @@ -140,7 +140,7 @@ class LastGenrePlugin(plugins.BeetsPlugin): # Read the tree if c14n_filename: self._log.debug("Loading canonicalization tree {}", c14n_filename) - with Path(c14n_filename).open(encoding="utf-8") as f: + with Path(c14n_filename).expanduser().open(encoding="utf-8") as f: genres_tree = yaml.safe_load(f) flatten_tree(genres_tree, [], c14n_branches) return c14n_branches, canonicalize From 6bc30eaf185d6f04b0942c34a874da9cc7f5784d Mon Sep 17 00:00:00 2001 From: J0J0 Todos Date: Sun, 7 Sep 2025 19:41:43 +0200 Subject: [PATCH 13/16] lastgenre: Add docstrings to file load methods --- beetsplug/lastgenre/__init__.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/beetsplug/lastgenre/__init__.py b/beetsplug/lastgenre/__init__.py index 343c50888..53371e114 100644 --- a/beetsplug/lastgenre/__init__.py +++ b/beetsplug/lastgenre/__init__.py @@ -115,6 +115,10 @@ class LastGenrePlugin(plugins.BeetsPlugin): self.c14n_branches, self.canonicalize = self._load_c14n_tree() def _load_whitelist(self) -> set[str]: + """Load the whitelist from a text file. + + Default whitelist is used if config is True or empty string. + """ whitelist = set() wl_filename = self.config["whitelist"].get() if wl_filename in (True, ""): # Indicates the default whitelist. @@ -128,6 +132,11 @@ class LastGenrePlugin(plugins.BeetsPlugin): return whitelist def _load_c14n_tree(self) -> tuple[list[list[str]], bool]: + """Load the canonicalization tree from a YAML file. + + Default tree is used if config is True or empty string, or if + prefer_specific is enabled. + """ c14n_branches: list[list[str]] = [] c14n_filename = self.config["canonical"].get() canonicalize = c14n_filename is not False From 6601cbf8c02e1541bed950e7e0c0ea43553b4746 Mon Sep 17 00:00:00 2001 From: J0J0 Todos Date: Sun, 7 Sep 2025 22:43:22 +0200 Subject: [PATCH 14/16] lastgenre: canonical/whitelist setting None load default files --- beetsplug/lastgenre/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/beetsplug/lastgenre/__init__.py b/beetsplug/lastgenre/__init__.py index 53371e114..f8619ec97 100644 --- a/beetsplug/lastgenre/__init__.py +++ b/beetsplug/lastgenre/__init__.py @@ -121,7 +121,7 @@ class LastGenrePlugin(plugins.BeetsPlugin): """ whitelist = set() wl_filename = self.config["whitelist"].get() - if wl_filename in (True, ""): # Indicates the default whitelist. + if wl_filename in (True, "", None): # Indicates the default whitelist. wl_filename = WHITELIST if wl_filename: text = Path(wl_filename).expanduser().read_text(encoding="utf-8") @@ -141,7 +141,7 @@ class LastGenrePlugin(plugins.BeetsPlugin): c14n_filename = self.config["canonical"].get() canonicalize = c14n_filename is not False # Default tree - if c14n_filename in (True, "") or ( + if c14n_filename in (True, "", None) or ( # prefer_specific requires a tree, load default tree not canonicalize and self.config["prefer_specific"].get() ): From d2caed3971772dd79a8996e8d1febc3425bd0302 Mon Sep 17 00:00:00 2001 From: J0J0 Todos Date: Sun, 7 Sep 2025 22:50:05 +0200 Subject: [PATCH 15/16] lastgenre: Also log which whitelist file is loading --- beetsplug/lastgenre/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/beetsplug/lastgenre/__init__.py b/beetsplug/lastgenre/__init__.py index f8619ec97..cd385677f 100644 --- a/beetsplug/lastgenre/__init__.py +++ b/beetsplug/lastgenre/__init__.py @@ -124,6 +124,7 @@ class LastGenrePlugin(plugins.BeetsPlugin): if wl_filename in (True, "", None): # Indicates the default whitelist. wl_filename = WHITELIST if wl_filename: + self._log.debug("Loading whitelist {}", wl_filename) text = Path(wl_filename).expanduser().read_text(encoding="utf-8") for line in text.splitlines(): if (line := line.strip().lower()) and not line.startswith("#"): From 7a5cfa8f466bcb2af23f55a61f79b440378b3ec6 Mon Sep 17 00:00:00 2001 From: J0J0 Todos Date: Sun, 7 Sep 2025 22:59:08 +0200 Subject: [PATCH 16/16] lastgerne: Update wl/tree load methods docstrings --- beetsplug/lastgenre/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/beetsplug/lastgenre/__init__.py b/beetsplug/lastgenre/__init__.py index cd385677f..8c09eefea 100644 --- a/beetsplug/lastgenre/__init__.py +++ b/beetsplug/lastgenre/__init__.py @@ -117,7 +117,7 @@ class LastGenrePlugin(plugins.BeetsPlugin): def _load_whitelist(self) -> set[str]: """Load the whitelist from a text file. - Default whitelist is used if config is True or empty string. + Default whitelist is used if config is True, empty string or set to "nothing". """ whitelist = set() wl_filename = self.config["whitelist"].get() @@ -135,8 +135,8 @@ class LastGenrePlugin(plugins.BeetsPlugin): def _load_c14n_tree(self) -> tuple[list[list[str]], bool]: """Load the canonicalization tree from a YAML file. - Default tree is used if config is True or empty string, or if - prefer_specific is enabled. + Default tree is used if config is True, empty string, set to "nothing" + or if prefer_specific is enabled. """ c14n_branches: list[list[str]] = [] c14n_filename = self.config["canonical"].get()