lastgenre: Named types; Document flatten_tree

- Define types for whitelist and canonicalization tree
- Better document the flatten_tree function
This commit is contained in:
J0J0 Todos 2026-02-15 11:38:40 +01:00
parent 9116de0c56
commit 0f131b5dee
4 changed files with 60 additions and 9 deletions

View file

@ -41,6 +41,8 @@ if TYPE_CHECKING:
from beets.library import LibModel
from .types import CanonTree
class LastGenrePlugin(plugins.BeetsPlugin):
def __init__(self) -> None:
@ -116,7 +118,7 @@ class LastGenrePlugin(plugins.BeetsPlugin):
return [p[1] for p in depth_tag_pairs]
@staticmethod
def find_parents(candidate: str, branches: list[list[str]]) -> list[str]:
def find_parents(candidate: str, branches: CanonTree) -> list[str]:
"""Find parent genres of a given genre, ordered from closest to furthest."""
for branch in branches:
try:

View file

@ -31,6 +31,8 @@ if TYPE_CHECKING:
from beets.logging import Logger
from .types import GenreCache
LASTFM = pylast.LastFMNetwork(api_key=plugins.LASTFM_KEY)
PYLAST_EXCEPTIONS = (
@ -51,7 +53,7 @@ class LastFmClient:
self._log = log
self._tunelog = make_tunelog(log)
self._min_weight = min_weight
self._genre_cache: dict[str, list[str]] = {}
self._genre_cache: GenreCache = {}
def fetch_genre(
self, lastfm_obj: pylast.Album | pylast.Artist | pylast.Track

View file

@ -28,6 +28,8 @@ if TYPE_CHECKING:
from beets.logging import Logger
from .types import CanonTree, Whitelist
class DataFileLoader:
"""Loads genre-related data files for the lastgenre plugin."""
@ -36,8 +38,8 @@ class DataFileLoader:
self,
log: Logger,
plugin_dir: Path,
whitelist: set[str],
c14n_branches: list[list[str]],
whitelist: Whitelist,
c14n_branches: CanonTree,
canonicalize: bool,
):
"""Initialize with pre-loaded data.
@ -83,7 +85,7 @@ class DataFileLoader:
@staticmethod
def _load_whitelist(
log: Logger, config_value: str | bool | None, default_path: str
) -> set[str]:
) -> Whitelist:
"""Load the whitelist from a text file.
Returns set of valid genre names (lowercase).
@ -107,12 +109,12 @@ class DataFileLoader:
config_value: str | bool | None,
default_path: str,
prefer_specific: bool,
) -> tuple[list[list[str]], bool]:
) -> tuple[CanonTree, bool]:
"""Load the canonicalization tree from a YAML file.
Returns tuple of (branches, canonicalize_enabled).
"""
c14n_branches: list[list[str]] = []
c14n_branches: CanonTree = []
c14n_filename = config_value
canonicalize = c14n_filename is not False
# Default tree
@ -133,9 +135,24 @@ class DataFileLoader:
def flatten_tree(
elem: dict[str, Any] | list[Any] | str,
path: list[str],
branches: list[list[str]],
branches: CanonTree,
) -> None:
"""Flatten nested lists/dictionaries into lists of strings (branches)."""
"""Flatten nested YAML structure into genre hierarchy branches.
Recursively converts nested dicts/lists from YAML into a flat list
of genre paths, where each path goes from general to specific genre.
Args:
elem: The YAML element to process (dict, list, or string leaf).
path: Current path from root to this element (used in recursion).
branches: OUTPUT PARAMETER - Empty list that will be populated
with genre paths. Gets mutated by this method.
Example:
branches = []
flatten_tree({'rock': ['indie', 'punk']}, [], branches)
# branches is now: [['rock', 'indie'], ['rock', 'punk']]
"""
if not path:
path = []

View file

@ -0,0 +1,30 @@
# This file is part of beets.
# 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.
"""Type aliases for the lastgenre plugin."""
from __future__ import annotations
Whitelist = set[str]
"""Set of valid genre names (lowercase). Empty set means all genres allowed."""
CanonTree = list[list[str]]
"""Genre hierarchy as list of paths from general to specific.
Example: [['electronic', 'house'], ['electronic', 'techno']]"""
GenreCache = dict[str, list[str]]
"""Cache mapping entity keys to their genre lists.
Keys are formatted as 'entity.arg1-arg2-...' (e.g., 'album.artist-title').
Values are lists of lowercase genre strings."""