Skip to content

Commit 230399f

Browse files
authored
Adjust types for fully typed confuse (#6268)
* Update configuration handling to use fully typed confuse API which will be released in confuse `v2.2.0`. * Use `Subview`, `.sequence()`, `MappingTemplate`, and typed `OneOf`. * Replace 'naked' configuration dictionary access with typed `.get/.as_*` equivalents. * Add typing annotations and `cached_property` where appropriate. * Fix related issues in `discogs`, `fetchart`, `lyrics`, `playlist`, `smartplaylist`, and `titlecase` plugins. > [!IMPORTANT] > Depends on beetbox/confuse#187 being merged and released (as `v2.2.0`)
2 parents 942638a + 15755c1 commit 230399f

13 files changed

Lines changed: 50 additions & 46 deletions

File tree

beets/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,11 @@ class IncludeLazyConfig(confuse.LazyConfig):
3737
YAML files specified in an `include` setting.
3838
"""
3939

40-
def read(self, user=True, defaults=True):
40+
def read(self, user: bool = True, defaults: bool = True) -> None:
4141
super().read(user, defaults)
4242

4343
try:
44-
for view in self["include"]:
44+
for view in self["include"].sequence():
4545
self.set_file(view.as_filename())
4646
except confuse.NotFoundError:
4747
pass

beets/plugins.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
if TYPE_CHECKING:
3838
from collections.abc import Callable, Iterable, Iterator, Sequence
3939

40-
from confuse import ConfigView
40+
from confuse import Subview
4141

4242
from beets.dbcore import Query
4343
from beets.dbcore.db import FieldQueryType
@@ -162,7 +162,7 @@ class BeetsPlugin(metaclass=BeetsPluginMeta):
162162
album_template_fields: TFuncMap[Album]
163163

164164
name: str
165-
config: ConfigView
165+
config: Subview
166166
early_import_stages: list[ImportStageFunc]
167167
import_stages: list[ImportStageFunc]
168168

beets/ui/__init__.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
import traceback
3131
from difflib import SequenceMatcher
3232
from functools import cache
33-
from itertools import chain
3433
from typing import TYPE_CHECKING, Any, Literal
3534

3635
import confuse
@@ -551,19 +550,21 @@ def get_color_config() -> dict[ColorName, str]:
551550
legacy single-color format. Validates all color names against known codes
552551
and raises an error for any invalid entries.
553552
"""
554-
colors_by_color_name: dict[ColorName, list[str]] = {
553+
template_dict: dict[ColorName, confuse.OneOf[str | list[str]]] = {
554+
n: confuse.OneOf(
555+
[
556+
confuse.Choice(sorted(LEGACY_COLORS)),
557+
confuse.Sequence(confuse.Choice(sorted(CODE_BY_COLOR))),
558+
]
559+
)
560+
for n in ColorName.__args__ # type: ignore[attr-defined]
561+
}
562+
template = confuse.MappingTemplate(template_dict)
563+
colors_by_color_name = {
555564
k: (v if isinstance(v, list) else LEGACY_COLORS.get(v, [v]))
556-
for k, v in config["ui"]["colors"].flatten().items()
565+
for k, v in config["ui"]["colors"].get(template).items()
557566
}
558567

559-
if invalid_colors := (
560-
set(chain.from_iterable(colors_by_color_name.values()))
561-
- CODE_BY_COLOR.keys()
562-
):
563-
raise UserError(
564-
f"Invalid color(s) in configuration: {', '.join(invalid_colors)}"
565-
)
566-
567568
return {
568569
n: ";".join(str(CODE_BY_COLOR[c]) for c in colors)
569570
for n, colors in colors_by_color_name.items()

beets/ui/commands/import_/session.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -327,14 +327,15 @@ def summarize_items(items, singleton):
327327
return ", ".join(summary_parts)
328328

329329

330-
def _summary_judgment(rec):
330+
def _summary_judgment(rec: Recommendation) -> importer.Action | None:
331331
"""Determines whether a decision should be made without even asking
332332
the user. This occurs in quiet mode and when an action is chosen for
333333
NONE recommendations. Return None if the user should be queried.
334334
Otherwise, returns an action. May also print to the console if a
335335
summary judgment is made.
336336
"""
337337

338+
action: importer.Action | None
338339
if config["import"]["quiet"]:
339340
if rec == Recommendation.strong:
340341
return importer.Action.APPLY

beetsplug/discogs/__init__.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -355,10 +355,9 @@ def get_album_info(self, result: Release) -> AlbumInfo | None:
355355
style = self.format(result.data.get("styles"))
356356
base_genre = self.format(result.data.get("genres"))
357357

358-
if self.config["append_style_genre"] and style:
359-
genre = self.config["separator"].as_str().join([base_genre, style])
360-
else:
361-
genre = base_genre
358+
genre = base_genre
359+
if self.config["append_style_genre"] and genre is not None and style:
360+
genre += f"{self.config['separator'].as_str()}{style}"
362361

363362
discogs_albumid = self._extract_id(result.data.get("uri"))
364363

beetsplug/fetchart.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,8 @@ def _resize(
288288
elif check == ImageAction.REFORMAT:
289289
self.path = ArtResizer.shared.reformat(
290290
self.path,
291-
plugin.cover_format,
291+
# TODO: fix this gnarly logic to remove the need for type ignore
292+
plugin.cover_format, # type: ignore[arg-type]
292293
deinterlaced=plugin.deinterlace,
293294
)
294295

@@ -1367,7 +1368,7 @@ def __init__(self) -> None:
13671368

13681369
# allow both pixel and percentage-based margin specifications
13691370
self.enforce_ratio = self.config["enforce_ratio"].get(
1370-
confuse.OneOf(
1371+
confuse.OneOf[bool | str](
13711372
[
13721373
bool,
13731374
confuse.String(pattern=self.PAT_PX),

beetsplug/lastgenre/__init__.py

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import optparse
4242
from collections.abc import Callable
4343

44+
from beets.importer import ImportSession, ImportTask
4445
from beets.library import LibModel
4546

4647
LASTFM = pylast.LastFMNetwork(api_key=plugins.LASTFM_KEY)
@@ -178,14 +179,13 @@ def sources(self) -> tuple[str, ...]:
178179
"""A tuple of allowed genre sources. May contain 'track',
179180
'album', or 'artist.'
180181
"""
181-
source = self.config["source"].as_choice(("track", "album", "artist"))
182-
if source == "track":
183-
return "track", "album", "artist"
184-
if source == "album":
185-
return "album", "artist"
186-
if source == "artist":
187-
return ("artist",)
188-
return tuple()
182+
return self.config["source"].as_choice(
183+
{
184+
"track": ("track", "album", "artist"),
185+
"album": ("album", "artist"),
186+
"artist": ("artist",),
187+
}
188+
)
189189

190190
# More canonicalization and general helpers.
191191

@@ -603,10 +603,8 @@ def lastgenre_func(
603603
lastgenre_cmd.func = lastgenre_func
604604
return [lastgenre_cmd]
605605

606-
def imported(
607-
self, session: library.Session, task: library.ImportTask
608-
) -> None:
609-
self._process(task.album if task.is_album else task.item, write=False)
606+
def imported(self, _: ImportSession, task: ImportTask) -> None:
607+
self._process(task.album if task.is_album else task.item, write=False) # type: ignore[attr-defined]
610608

611609
def _tags_for(
612610
self,

beetsplug/lyrics.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,7 @@ def fetch(
358358
for group in self.fetch_candidates(artist, title, album, length):
359359
candidates = [evaluate_item(item) for item in group]
360360
if item := self.pick_best_match(candidates):
361-
lyrics = item.get_text(self.config["synced"])
361+
lyrics = item.get_text(self.config["synced"].get(bool))
362362
return lyrics, f"{self.GET_URL}/{item.id}"
363363

364364
return None

beetsplug/playlist.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ def __init__(self, _, pattern: str, __):
6969
relative_to = os.path.dirname(playlist_path)
7070
else:
7171
relative_to = config["relative_to"].as_filename()
72-
relative_to = beets.util.bytestring_path(relative_to)
72+
relative_to_bytes = beets.util.bytestring_path(relative_to)
7373

7474
for line in f:
7575
if line[0] == "#":
@@ -78,7 +78,7 @@ def __init__(self, _, pattern: str, __):
7878

7979
paths.append(
8080
beets.util.normpath(
81-
os.path.join(relative_to, line.rstrip())
81+
os.path.join(relative_to_bytes, line.rstrip())
8282
)
8383
)
8484
f.close()

beetsplug/smartplaylist.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -262,8 +262,9 @@ def update_playlists(self, lib: Library, pretend: bool = False) -> None:
262262
"Updating {} smart playlists...", len(self._matched_playlists)
263263
)
264264

265-
playlist_dir = self.config["playlist_dir"].as_filename()
266-
playlist_dir = bytestring_path(playlist_dir)
265+
playlist_dir = bytestring_path(
266+
self.config["playlist_dir"].as_filename()
267+
)
267268
tpl = self.config["uri_format"].get()
268269
prefix = bytestring_path(self.config["prefix"].as_str())
269270
relative_to = self.config["relative_to"].get()

0 commit comments

Comments
 (0)