Adapt code to fully typed confuse library

This commit is contained in:
Šarūnas Nejus 2026-01-25 06:45:07 +00:00
parent 942638ae28
commit 37e18fbb46
No known key found for this signature in database
11 changed files with 42 additions and 41 deletions

View file

@ -37,11 +37,11 @@ class IncludeLazyConfig(confuse.LazyConfig):
YAML files specified in an `include` setting.
"""
def read(self, user=True, defaults=True):
def read(self, user: bool = True, defaults: bool = True) -> None:
super().read(user, defaults)
try:
for view in self["include"]:
for view in self["include"].sequence():
self.set_file(view.as_filename())
except confuse.NotFoundError:
pass

View file

@ -37,7 +37,7 @@ from beets.util.deprecation import deprecate_for_maintainers, deprecate_for_user
if TYPE_CHECKING:
from collections.abc import Callable, Iterable, Iterator, Sequence
from confuse import ConfigView
from confuse import Subview
from beets.dbcore import Query
from beets.dbcore.db import FieldQueryType
@ -162,7 +162,7 @@ class BeetsPlugin(metaclass=BeetsPluginMeta):
album_template_fields: TFuncMap[Album]
name: str
config: ConfigView
config: Subview
early_import_stages: list[ImportStageFunc]
import_stages: list[ImportStageFunc]

View file

@ -30,7 +30,6 @@ import textwrap
import traceback
from difflib import SequenceMatcher
from functools import cache
from itertools import chain
from typing import TYPE_CHECKING, Any, Literal
import confuse
@ -551,18 +550,20 @@ def get_color_config() -> dict[ColorName, str]:
legacy single-color format. Validates all color names against known codes
and raises an error for any invalid entries.
"""
colors_by_color_name: dict[ColorName, list[str]] = {
k: (v if isinstance(v, list) else LEGACY_COLORS.get(v, [v]))
for k, v in config["ui"]["colors"].flatten().items()
}
if invalid_colors := (
set(chain.from_iterable(colors_by_color_name.values()))
- CODE_BY_COLOR.keys()
):
raise UserError(
f"Invalid color(s) in configuration: {', '.join(invalid_colors)}"
template_dict: dict[ColorName, confuse.OneOf[str | list[str]]] = {
n: confuse.OneOf(
[
confuse.Choice(sorted(LEGACY_COLORS)),
confuse.Sequence(confuse.Choice(sorted(CODE_BY_COLOR))),
]
)
for n in ColorName.__args__ # type: ignore[attr-defined]
}
template = confuse.MappingTemplate(template_dict)
colors_by_color_name = {
k: (v if isinstance(v, list) else LEGACY_COLORS.get(v, [v]))
for k, v in config["ui"]["colors"].get(template).items()
}
return {
n: ";".join(str(CODE_BY_COLOR[c]) for c in colors)

View file

@ -327,7 +327,7 @@ def summarize_items(items, singleton):
return ", ".join(summary_parts)
def _summary_judgment(rec):
def _summary_judgment(rec: Recommendation) -> importer.Action | None:
"""Determines whether a decision should be made without even asking
the user. This occurs in quiet mode and when an action is chosen for
NONE recommendations. Return None if the user should be queried.
@ -335,6 +335,7 @@ def _summary_judgment(rec):
summary judgment is made.
"""
action: importer.Action | None
if config["import"]["quiet"]:
if rec == Recommendation.strong:
return importer.Action.APPLY

View file

@ -355,10 +355,9 @@ class DiscogsPlugin(MetadataSourcePlugin):
style = self.format(result.data.get("styles"))
base_genre = self.format(result.data.get("genres"))
if self.config["append_style_genre"] and style:
genre = self.config["separator"].as_str().join([base_genre, style])
else:
genre = base_genre
genre = base_genre
if self.config["append_style_genre"] and genre is not None and style:
genre += f"{self.config['separator'].as_str()}{style}"
discogs_albumid = self._extract_id(result.data.get("uri"))

View file

@ -288,7 +288,8 @@ class Candidate:
elif check == ImageAction.REFORMAT:
self.path = ArtResizer.shared.reformat(
self.path,
plugin.cover_format,
# TODO: fix this gnarly logic to remove the need for type ignore
plugin.cover_format, # type: ignore[arg-type]
deinterlaced=plugin.deinterlace,
)
@ -1367,7 +1368,7 @@ class FetchArtPlugin(plugins.BeetsPlugin, RequestMixin):
# allow both pixel and percentage-based margin specifications
self.enforce_ratio = self.config["enforce_ratio"].get(
confuse.OneOf(
confuse.OneOf[bool | str](
[
bool,
confuse.String(pattern=self.PAT_PX),

View file

@ -41,6 +41,7 @@ if TYPE_CHECKING:
import optparse
from collections.abc import Callable
from beets.importer import ImportSession, ImportTask
from beets.library import LibModel
LASTFM = pylast.LastFMNetwork(api_key=plugins.LASTFM_KEY)
@ -178,14 +179,13 @@ class LastGenrePlugin(plugins.BeetsPlugin):
"""A tuple of allowed genre sources. May contain 'track',
'album', or 'artist.'
"""
source = self.config["source"].as_choice(("track", "album", "artist"))
if source == "track":
return "track", "album", "artist"
if source == "album":
return "album", "artist"
if source == "artist":
return ("artist",)
return tuple()
return self.config["source"].as_choice(
{
"track": ("track", "album", "artist"),
"album": ("album", "artist"),
"artist": ("artist",),
}
)
# More canonicalization and general helpers.
@ -603,10 +603,8 @@ class LastGenrePlugin(plugins.BeetsPlugin):
lastgenre_cmd.func = lastgenre_func
return [lastgenre_cmd]
def imported(
self, session: library.Session, task: library.ImportTask
) -> None:
self._process(task.album if task.is_album else task.item, write=False)
def imported(self, _: ImportSession, task: ImportTask) -> None:
self._process(task.album if task.is_album else task.item, write=False) # type: ignore[attr-defined]
def _tags_for(
self,

View file

@ -358,7 +358,7 @@ class LRCLib(Backend):
for group in self.fetch_candidates(artist, title, album, length):
candidates = [evaluate_item(item) for item in group]
if item := self.pick_best_match(candidates):
lyrics = item.get_text(self.config["synced"])
lyrics = item.get_text(self.config["synced"].get(bool))
return lyrics, f"{self.GET_URL}/{item.id}"
return None

View file

@ -69,7 +69,7 @@ class PlaylistQuery(InQuery[bytes]):
relative_to = os.path.dirname(playlist_path)
else:
relative_to = config["relative_to"].as_filename()
relative_to = beets.util.bytestring_path(relative_to)
relative_to_bytes = beets.util.bytestring_path(relative_to)
for line in f:
if line[0] == "#":
@ -78,7 +78,7 @@ class PlaylistQuery(InQuery[bytes]):
paths.append(
beets.util.normpath(
os.path.join(relative_to, line.rstrip())
os.path.join(relative_to_bytes, line.rstrip())
)
)
f.close()

View file

@ -262,8 +262,9 @@ class SmartPlaylistPlugin(BeetsPlugin):
"Updating {} smart playlists...", len(self._matched_playlists)
)
playlist_dir = self.config["playlist_dir"].as_filename()
playlist_dir = bytestring_path(playlist_dir)
playlist_dir = bytestring_path(
self.config["playlist_dir"].as_filename()
)
tpl = self.config["uri_format"].get()
prefix = bytestring_path(self.config["prefix"].as_str())
relative_to = self.config["relative_to"].get()

View file

@ -104,7 +104,7 @@ class TitlecasePlugin(BeetsPlugin):
@cached_property
def replace(self) -> list[tuple[str, str]]:
return self.config["replace"].as_pairs()
return self.config["replace"].as_pairs(default_value="")
@cached_property
def the_artist(self) -> bool: