From ae7e265fd83758e20322e2010d4c445babcf8d30 Mon Sep 17 00:00:00 2001 From: J0J0 Todos Date: Sat, 7 Feb 2026 18:46:52 +0100 Subject: [PATCH] lastgenre: File loaders module --- beetsplug/lastgenre/__init__.py | 58 ++------------ beetsplug/lastgenre/loaders.py | 133 ++++++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+), 51 deletions(-) create mode 100644 beetsplug/lastgenre/loaders.py diff --git a/beetsplug/lastgenre/__init__.py b/beetsplug/lastgenre/__init__.py index cec41bffa..72647f15c 100644 --- a/beetsplug/lastgenre/__init__.py +++ b/beetsplug/lastgenre/__init__.py @@ -24,18 +24,16 @@ https://gist.github.com/1241307 from __future__ import annotations -import os from functools import singledispatchmethod from pathlib import Path from typing import TYPE_CHECKING, Any -import yaml - from beets import config, library, plugins, ui from beets.library import Album, Item from beets.util import plurality, unique_list from .client import LastFmClient +from .loaders import DataFileLoader from .utils import make_tunelog if TYPE_CHECKING: @@ -81,12 +79,6 @@ def find_parents(candidate: str, branches: list[list[str]]) -> list[str]: return [candidate] -# Main plugin logic. - -WHITELIST = os.path.join(os.path.dirname(__file__), "genres.txt") -C14N_TREE = os.path.join(os.path.dirname(__file__), "genres-tree.yaml") - - class LastGenrePlugin(plugins.BeetsPlugin): def __init__(self) -> None: super().__init__() @@ -119,49 +111,13 @@ class LastGenrePlugin(plugins.BeetsPlugin): self.client = LastFmClient( self._log, self.config["min_weight"].get(int) ) - self.whitelist = self._load_whitelist() - 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, empty string or set to "nothing". - """ - whitelist = set() - wl_filename = self.config["whitelist"].get() - 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("#"): - whitelist.add(line) - - 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, empty string, set to "nothing" - or if prefer_specific is enabled. - """ - c14n_branches: list[list[str]] = [] - c14n_filename = self.config["canonical"].get() - canonicalize = c14n_filename is not False - # Default tree - if c14n_filename in (True, "", None) 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: - self._log.debug("Loading canonicalization tree {}", c14n_filename) - 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 + loader = DataFileLoader.from_config( + self.config, self._log, Path(__file__).parent, flatten_tree + ) + self.whitelist = loader.whitelist + self.c14n_branches = loader.c14n_branches + self.canonicalize = loader.canonicalize @property def sources(self) -> tuple[str, ...]: diff --git a/beetsplug/lastgenre/loaders.py b/beetsplug/lastgenre/loaders.py new file mode 100644 index 000000000..de71fb94d --- /dev/null +++ b/beetsplug/lastgenre/loaders.py @@ -0,0 +1,133 @@ +# This file is part of beets. +# Copyright 2016, Adrian Sampson. +# Copyright 2026, J0J0 Todos. +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + + +"""Data file loaders for the lastgenre plugin.""" + +from __future__ import annotations + +from pathlib import Path +from typing import TYPE_CHECKING + +import yaml + +if TYPE_CHECKING: + from confuse import ConfigView + + from beets.logging import Logger + + +class DataFileLoader: + """Loads genre-related data files for the lastgenre plugin.""" + + def __init__( + self, + log: Logger, + plugin_dir: Path, + whitelist: set[str], + c14n_branches: list[list[str]], + canonicalize: bool, + ): + """Initialize with pre-loaded data. + + Use from_config() classmethod to construct from plugin config. + """ + self._log = log + self._plugin_dir = plugin_dir + self.whitelist = whitelist + self.c14n_branches = c14n_branches + self.canonicalize = canonicalize + + @classmethod + def from_config( + cls, + config: ConfigView, + log: Logger, + plugin_dir: Path, + flatten_func: Callable, + ) -> DataFileLoader: + """Create a DataFileLoader from plugin configuration. + + Reads config values and loads all data files during construction. + """ + # Default paths + default_whitelist = str(plugin_dir / "genres.txt") + default_tree = str(plugin_dir / "genres-tree.yaml") + + # Load whitelist + whitelist = cls._load_whitelist( + log, config["whitelist"].get(), default_whitelist + ) + + # Load tree + c14n_branches, canonicalize = cls._load_tree( + log, + config["canonical"].get(), + default_tree, + config["prefer_specific"].get(), + flatten_func, + ) + + return cls(log, plugin_dir, whitelist, c14n_branches, canonicalize) + + @staticmethod + def _load_whitelist( + log: Logger, config_value: str | bool | None, default_path: str + ) -> set[str]: + """Load the whitelist from a text file. + + Returns set of valid genre names (lowercase). + """ + whitelist = set() + wl_filename = config_value + if wl_filename in (True, "", None): # Indicates the default whitelist. + wl_filename = default_path + if wl_filename: + 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("#"): + whitelist.add(line) + + return whitelist + + @staticmethod + def _load_tree( + log: Logger, + config_value: str | bool | None, + default_path: str, + prefer_specific: bool, + flatten_func: Callable, + ) -> tuple[list[list[str]], bool]: + """Load the canonicalization tree from a YAML file. + + Returns tuple of (branches, canonicalize_enabled). + """ + c14n_branches: list[list[str]] = [] + c14n_filename = config_value + canonicalize = c14n_filename is not False + # Default tree + if c14n_filename in (True, "", None) or ( + # prefer_specific requires a tree, load default tree + not canonicalize and prefer_specific + ): + c14n_filename = default_path + # Read the tree + if c14n_filename: + log.debug("Loading canonicalization tree {}", c14n_filename) + with Path(c14n_filename).expanduser().open(encoding="utf-8") as f: + genres_tree = yaml.safe_load(f) + flatten_func(genres_tree, [], c14n_branches) + return c14n_branches, canonicalize