From 0c2f3ed073d44a21efd5b7e6a28c99f52d3f8c03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0ar=C5=ABnas=20Nejus?= Date: Wed, 21 May 2025 02:28:28 +0100 Subject: [PATCH 1/3] autotag: use explicit imports --- beets/autotag/match.py | 16 ++++++-------- beets/importer/tasks.py | 23 ++++++++++---------- beets/ui/commands/import_/display.py | 32 ++++++++++++---------------- beets/ui/commands/import_/session.py | 21 +++++++++--------- beetsplug/bench.py | 4 ++-- beetsplug/deezer.py | 2 +- beetsplug/mbpseudo.py | 2 +- beetsplug/mbsubmit.py | 2 +- beetsplug/musicbrainz.py | 29 +++++++++---------------- test/autotag/test_distance.py | 2 +- test/autotag/test_match.py | 17 +++++++-------- test/plugins/test_art.py | 2 +- test/plugins/test_mbpseudo.py | 3 +-- test/test_importer.py | 2 +- test/ui/commands/test_import.py | 17 +++++++-------- 15 files changed, 78 insertions(+), 96 deletions(-) diff --git a/beets/autotag/match.py b/beets/autotag/match.py index 9d8a210cc..beeb7ee8f 100644 --- a/beets/autotag/match.py +++ b/beets/autotag/match.py @@ -25,18 +25,18 @@ import lap import numpy as np from beets import config, logging, metadata_plugins -from beets.autotag import AlbumMatch, TrackMatch, hooks from beets.util import get_most_common_tags from .distance import VA_ARTISTS, distance, track_distance -from .hooks import Info +from .hooks import AlbumMatch, Info, TrackMatch if TYPE_CHECKING: from collections.abc import Iterable, Sequence - from beets.autotag import AlbumInfo, TrackInfo from beets.library import Item + from .hooks import AlbumInfo, TrackInfo + AnyMatch = TypeVar("AnyMatch", TrackMatch, AlbumMatch) Candidates = dict[Info.Identifier, AnyMatch] @@ -159,7 +159,7 @@ def _recommendation( # Downgrade to the max rec if it is lower than the current rec for an # applied penalty. keys = set(min_dist.keys()) - if isinstance(results[0], hooks.AlbumMatch): + if isinstance(results[0], AlbumMatch): for track_dist in min_dist.tracks.values(): keys.update(list(track_dist.keys())) max_rec_view = config["match"]["max_rec"] @@ -232,7 +232,7 @@ def _add_candidate( return log.debug("Success. Distance: {}", dist) - results[info.identifier] = hooks.AlbumMatch( + results[info.identifier] = AlbumMatch( dist, info, dict(item_info_pairs), extra_items, extra_tracks ) @@ -349,7 +349,7 @@ def tag_item( log.debug("Searching for track IDs: {}", ", ".join(trackids)) for info in metadata_plugins.tracks_for_ids(trackids): dist = track_distance(item, info, incl_artist=True) - candidates[info.identifier] = hooks.TrackMatch(dist, info, item) + candidates[info.identifier] = TrackMatch(dist, info, item) # If this is a good match, then don't keep searching. rec = _recommendation(_sort_candidates(candidates.values())) @@ -375,9 +375,7 @@ def tag_item( item, search_artist, search_name ): dist = track_distance(item, track_info, incl_artist=True) - candidates[track_info.identifier] = hooks.TrackMatch( - dist, track_info, item - ) + candidates[track_info.identifier] = TrackMatch(dist, track_info, item) # Sort by distance and return with recommendation. log.debug("Found {} candidates.", len(candidates)) diff --git a/beets/importer/tasks.py b/beets/importer/tasks.py index 88157bff3..1f0debbc8 100644 --- a/beets/importer/tasks.py +++ b/beets/importer/tasks.py @@ -27,7 +27,9 @@ from typing import TYPE_CHECKING, Any import mediafile -from beets import autotag, config, library, plugins, util +from beets import config, library, plugins, util +from beets.autotag.hooks import AlbumMatch +from beets.autotag.match import tag_album, tag_item from beets.dbcore.query import PathQuery from .state import ImportState @@ -35,6 +37,7 @@ from .state import ImportState if TYPE_CHECKING: from collections.abc import Iterable, Sequence + from beets.autotag.hooks import TrackMatch from beets.autotag.match import Recommendation from .session import ImportSession @@ -159,12 +162,12 @@ class ImportTask(BaseImportTask): """ choice_flag: Action | None = None - match: autotag.AlbumMatch | autotag.TrackMatch | None = None + match: AlbumMatch | TrackMatch | None = None # Keep track of the current task item cur_album: str | None = None cur_artist: str | None = None - candidates: Sequence[autotag.AlbumMatch | autotag.TrackMatch] = [] + candidates: Sequence[AlbumMatch | TrackMatch] = [] rec: Recommendation | None = None def __init__( @@ -178,9 +181,7 @@ class ImportTask(BaseImportTask): self.should_merge_duplicates = False self.is_album = True - def set_choice( - self, choice: Action | autotag.AlbumMatch | autotag.TrackMatch - ): + def set_choice(self, choice: Action | AlbumMatch | TrackMatch): """Given an AlbumMatch or TrackMatch object or an action constant, indicates that an action has been selected for this task. @@ -249,7 +250,7 @@ class ImportTask(BaseImportTask): if self.choice_flag in (Action.ASIS, Action.RETAG): return self.items elif self.choice_flag == Action.APPLY and isinstance( - self.match, autotag.AlbumMatch + self.match, AlbumMatch ): return self.match.items else: @@ -363,7 +364,7 @@ class ImportTask(BaseImportTask): restricted to only those IDs. """ self.cur_artist, self.cur_album, (self.candidates, self.rec) = ( - autotag.tag_album(self.items, search_ids=search_ids) + tag_album(self.items, search_ids=search_ids) ) def find_duplicates(self, lib: library.Library) -> list[library.Album]: @@ -500,7 +501,7 @@ class ImportTask(BaseImportTask): self.album = lib.add_album(self.imported_items()) if self.choice_flag == Action.APPLY and isinstance( - self.match, autotag.AlbumMatch + self.match, AlbumMatch ): # Copy album flexible fields to the DB # TODO: change the flow so we create the `Album` object earlier, @@ -684,9 +685,7 @@ class SingletonImportTask(ImportTask): plugins.send("item_imported", lib=lib, item=item) def lookup_candidates(self, search_ids: list[str]) -> None: - self.candidates, self.rec = autotag.tag_item( - self.item, search_ids=search_ids - ) + self.candidates, self.rec = tag_item(self.item, search_ids=search_ids) def find_duplicates(self, lib: library.Library) -> list[library.Item]: # type: ignore[override] # Need splitting Singleton and Album tasks into separate classes """Return a list of items from `lib` that have the same artist diff --git a/beets/ui/commands/import_/display.py b/beets/ui/commands/import_/display.py index a89b8795f..b4903adae 100644 --- a/beets/ui/commands/import_/display.py +++ b/beets/ui/commands/import_/display.py @@ -7,7 +7,7 @@ from functools import cached_property from typing import TYPE_CHECKING from beets import config, ui -from beets.autotag import hooks +from beets.autotag.hooks import TrackInfo from beets.util import displayable_path from beets.util.color import colorize from beets.util.diff import colordiff @@ -17,7 +17,7 @@ from beets.util.units import human_seconds_short if TYPE_CHECKING: import confuse - from beets import autotag + from beets.autotag.hooks import AlbumMatch, Match, TrackMatch from beets.library.models import Item from beets.util.color import ColorName @@ -34,7 +34,7 @@ class ChangeRepresentation: cur_artist: str cur_name: str - match: autotag.hooks.Match + match: Match @cached_property def changed_prefix(self) -> str: @@ -123,7 +123,7 @@ class ChangeRepresentation: else: ui.print_(f"{self.indent_detail}*", f"{type_}:", name_r) - def make_medium_info_line(self, track_info: hooks.TrackInfo) -> str: + def make_medium_info_line(self, track_info: TrackInfo) -> str: """Construct a line with the current medium's info.""" track_media = track_info.get("media", "Media") # Build output string. @@ -138,11 +138,11 @@ class ChangeRepresentation: else: return "" - def format_index(self, track_info: hooks.TrackInfo | Item) -> str: + def format_index(self, track_info: TrackInfo | Item) -> str: """Return a string representing the track index of the given TrackInfo or Item object. """ - if isinstance(track_info, hooks.TrackInfo): + if isinstance(track_info, TrackInfo): index = track_info.index medium_index = track_info.medium_index medium = track_info.medium @@ -160,7 +160,7 @@ class ChangeRepresentation: return str(index) def make_track_numbers( - self, item: Item, track_info: hooks.TrackInfo + self, item: Item, track_info: TrackInfo ) -> tuple[str, str, bool]: """Format colored track indices.""" cur_track = self.format_index(item) @@ -183,7 +183,7 @@ class ChangeRepresentation: @staticmethod def make_track_titles( - item: Item, track_info: hooks.TrackInfo + item: Item, track_info: TrackInfo ) -> tuple[str, str, bool]: """Format colored track titles.""" new_title = track_info.name @@ -199,7 +199,7 @@ class ChangeRepresentation: @staticmethod def make_track_lengths( - item: Item, track_info: hooks.TrackInfo + item: Item, track_info: TrackInfo ) -> tuple[str, str, bool]: """Format colored track lengths.""" changed = False @@ -227,9 +227,7 @@ class ChangeRepresentation: return lhs_length, rhs_length, changed - def make_line( - self, item: Item, track_info: hooks.TrackInfo - ) -> tuple[Side, Side]: + def make_line(self, item: Item, track_info: TrackInfo) -> tuple[Side, Side]: """Extract changes from item -> new TrackInfo object, and colorize appropriately. Returns (lhs, rhs) for column printing. """ @@ -304,7 +302,7 @@ class ChangeRepresentation: class AlbumChange(ChangeRepresentation): - match: autotag.hooks.AlbumMatch + match: AlbumMatch def show_match_tracks(self) -> None: """Print out the tracks of the match, summarizing changes the match @@ -364,12 +362,10 @@ class AlbumChange(ChangeRepresentation): class TrackChange(ChangeRepresentation): """Track change representation, comparing item with match.""" - match: autotag.hooks.TrackMatch + match: TrackMatch -def show_change( - cur_artist: str, cur_album: str, match: hooks.AlbumMatch -) -> None: +def show_change(cur_artist: str, cur_album: str, match: AlbumMatch) -> None: """Print out a representation of the changes that will be made if an album's tags are changed according to `match`, which must be an AlbumMatch object. @@ -386,7 +382,7 @@ def show_change( change.show_match_tracks() -def show_item_change(item: Item, match: hooks.TrackMatch) -> None: +def show_item_change(item: Item, match: TrackMatch) -> None: """Print out the change that would occur by tagging `item` with the metadata from `match`, a TrackMatch object. """ diff --git a/beets/ui/commands/import_/session.py b/beets/ui/commands/import_/session.py index 8c3404bea..63a65ed01 100644 --- a/beets/ui/commands/import_/session.py +++ b/beets/ui/commands/import_/session.py @@ -3,8 +3,9 @@ from __future__ import annotations from collections import Counter from itertools import chain -from beets import autotag, config, importer, logging, plugins, ui -from beets.autotag import Recommendation +from beets import config, importer, logging, plugins, ui +from beets.autotag.hooks import AlbumMatch, TrackMatch +from beets.autotag.match import Proposal, Recommendation, tag_album, tag_item from beets.util import PromptChoice, displayable_path from beets.util.color import colorize from beets.util.units import human_bytes, human_seconds_short @@ -84,7 +85,7 @@ class TerminalImportSession(importer.ImportSession): post_choice = choice.callback(self, task) if isinstance(post_choice, importer.Action): return post_choice - elif isinstance(post_choice, autotag.Proposal): + elif isinstance(post_choice, Proposal): # Use the new candidates and continue around the loop. task.candidates = post_choice.candidates task.rec = post_choice.recommendation @@ -93,7 +94,7 @@ class TerminalImportSession(importer.ImportSession): else: # We have a candidate! Finish tagging. Here, choice is an # AlbumMatch object. - assert isinstance(choice, autotag.AlbumMatch) + assert isinstance(choice, AlbumMatch) return choice def choose_item(self, task): @@ -127,13 +128,13 @@ class TerminalImportSession(importer.ImportSession): post_choice = choice.callback(self, task) if isinstance(post_choice, importer.Action): return post_choice - elif isinstance(post_choice, autotag.Proposal): + elif isinstance(post_choice, Proposal): candidates = post_choice.candidates rec = post_choice.recommendation else: # Chose a candidate. - assert isinstance(choice, autotag.TrackMatch) + assert isinstance(choice, TrackMatch) return choice def resolve_duplicate(self, task, found_duplicates): @@ -519,10 +520,10 @@ def manual_search(session, task): name = ui.input_("Album:" if task.is_album else "Track:").strip() if task.is_album: - _, _, prop = autotag.tag_album(task.items, artist, name) + _, _, prop = tag_album(task.items, artist, name) return prop else: - return autotag.tag_item(task.item, artist, name) + return tag_item(task.item, artist, name) def manual_id(session, task): @@ -534,10 +535,10 @@ def manual_id(session, task): search_id = ui.input_(prompt).strip() if task.is_album: - _, _, prop = autotag.tag_album(task.items, search_ids=search_id.split()) + _, _, prop = tag_album(task.items, search_ids=search_id.split()) return prop else: - return autotag.tag_item(task.item, search_ids=search_id.split()) + return tag_item(task.item, search_ids=search_id.split()) def abort_action(session, task): diff --git a/beetsplug/bench.py b/beetsplug/bench.py index d77f1f92a..bc84d08e2 100644 --- a/beetsplug/bench.py +++ b/beetsplug/bench.py @@ -18,7 +18,7 @@ import cProfile import timeit from beets import importer, library, plugins, ui -from beets.autotag import match +from beets.autotag.match import tag_album from beets.plugins import BeetsPlugin from beets.util.functemplate import Template from beetsplug._utils import vfs @@ -83,7 +83,7 @@ def match_benchmark(lib, prof, query=None, album_id=None): # Run the match. def _run_match(): - match.tag_album(items, search_ids=[album_id]) + tag_album(items, search_ids=[album_id]) if prof: cProfile.runctx( diff --git a/beetsplug/deezer.py b/beetsplug/deezer.py index 5d7008401..aeed800fd 100644 --- a/beetsplug/deezer.py +++ b/beetsplug/deezer.py @@ -23,7 +23,7 @@ from typing import TYPE_CHECKING, ClassVar import requests from beets import ui -from beets.autotag import AlbumInfo, TrackInfo +from beets.autotag.hooks import AlbumInfo, TrackInfo from beets.dbcore import types from beets.metadata_plugins import IDResponse, SearchApiMetadataSourcePlugin diff --git a/beetsplug/mbpseudo.py b/beetsplug/mbpseudo.py index d084d1531..d04b27500 100644 --- a/beetsplug/mbpseudo.py +++ b/beetsplug/mbpseudo.py @@ -38,8 +38,8 @@ from beetsplug.musicbrainz import ( if TYPE_CHECKING: from collections.abc import Iterable, Sequence - from beets.autotag import AlbumMatch from beets.autotag.distance import Distance + from beets.autotag.hooks import AlbumMatch from beets.library import Item from beetsplug._typing import JSONDict diff --git a/beetsplug/mbsubmit.py b/beetsplug/mbsubmit.py index 7136f4c29..2d97c2bc9 100644 --- a/beetsplug/mbsubmit.py +++ b/beetsplug/mbsubmit.py @@ -24,7 +24,7 @@ implemented by MusicBrainz yet. import subprocess from beets import ui -from beets.autotag import Recommendation +from beets.autotag.match import Recommendation from beets.plugins import BeetsPlugin from beets.util import PromptChoice, displayable_path from beetsplug.info import print_data diff --git a/beetsplug/musicbrainz.py b/beetsplug/musicbrainz.py index 011a8e300..a8231dd82 100644 --- a/beetsplug/musicbrainz.py +++ b/beetsplug/musicbrainz.py @@ -25,9 +25,8 @@ from urllib.parse import urljoin from confuse.exceptions import NotFoundError -import beets -import beets.autotag.hooks from beets import config, plugins, util +from beets.autotag.hooks import AlbumInfo, TrackInfo from beets.metadata_plugins import IDResponse, SearchApiMetadataSourcePlugin from beets.util.deprecation import deprecate_for_user from beets.util.id_extractors import extract_release_id @@ -225,11 +224,7 @@ def _preferred_release_event( return release.get("country"), release.get("date") -def _set_date_str( - info: beets.autotag.hooks.AlbumInfo, - date_str: str, - original: bool = False, -): +def _set_date_str(info: AlbumInfo, date_str: str, original: bool = False): """Given a (possibly partial) YYYY-MM-DD string and an AlbumInfo object, set the object's release date fields appropriately. If `original`, then set the original_year, etc., fields. @@ -250,8 +245,8 @@ def _set_date_str( def _merge_pseudo_and_actual_album( - pseudo: beets.autotag.hooks.AlbumInfo, actual: beets.autotag.hooks.AlbumInfo -) -> beets.autotag.hooks.AlbumInfo: + pseudo: AlbumInfo, actual: AlbumInfo +) -> AlbumInfo: """ Merges a pseudo release with its actual release. @@ -333,7 +328,7 @@ class MusicBrainzPlugin( medium: int | None = None, medium_index: int | None = None, medium_total: int | None = None, - ) -> beets.autotag.hooks.TrackInfo: + ) -> TrackInfo: """Translates a MusicBrainz recording result dictionary into a beets ``TrackInfo`` object. Three parameters are optional and are used only for tracks that appear on releases (non-singletons): ``index``, @@ -343,7 +338,7 @@ class MusicBrainzPlugin( """ title = _key_with_preferred_alias(recording, key="title") - info = beets.autotag.hooks.TrackInfo( + info = TrackInfo( title=title, track_id=recording["id"], index=index, @@ -431,7 +426,7 @@ class MusicBrainzPlugin( return info - def album_info(self, release: JSONDict) -> beets.autotag.hooks.AlbumInfo: + def album_info(self, release: JSONDict) -> AlbumInfo: """Takes a MusicBrainz release result dictionary and returns a beets AlbumInfo object containing the interesting data about that release. """ @@ -553,7 +548,7 @@ class MusicBrainzPlugin( album_artist_ids = _artist_ids(release["artist-credit"]) release_title = _key_with_preferred_alias(release, key="title") - info = beets.autotag.hooks.AlbumInfo( + info = AlbumInfo( album=release_title, album_id=release["id"], artist=artist_name, @@ -758,9 +753,7 @@ class MusicBrainzPlugin( mb_entity, dict(params.filters), limit=params.limit ) - def album_for_id( - self, album_id: str - ) -> beets.autotag.hooks.AlbumInfo | None: + def album_for_id(self, album_id: str) -> AlbumInfo | None: """Fetches an album by its MusicBrainz ID and returns an AlbumInfo object or None if the album is not found. May raise a MusicBrainzAPIError. @@ -801,9 +794,7 @@ class MusicBrainzPlugin( else: return release - def track_for_id( - self, track_id: str - ) -> beets.autotag.hooks.TrackInfo | None: + def track_for_id(self, track_id: str) -> TrackInfo | None: """Fetches a track by its MusicBrainz ID. Returns a TrackInfo object or None if no track is found. May raise a MusicBrainzAPIError. """ diff --git a/test/autotag/test_distance.py b/test/autotag/test_distance.py index ac0864564..b37f4b637 100644 --- a/test/autotag/test_distance.py +++ b/test/autotag/test_distance.py @@ -2,13 +2,13 @@ import re import pytest -from beets.autotag import AlbumInfo, TrackInfo from beets.autotag.distance import ( Distance, distance, string_dist, track_distance, ) +from beets.autotag.hooks import AlbumInfo, TrackInfo from beets.library import Item from beets.metadata_plugins import MetadataSourcePlugin, get_penalty from beets.plugins import BeetsPlugin diff --git a/test/autotag/test_match.py b/test/autotag/test_match.py index 97c7dd8f8..daa6fb9db 100644 --- a/test/autotag/test_match.py +++ b/test/autotag/test_match.py @@ -3,7 +3,8 @@ from typing import ClassVar import pytest from beets import metadata_plugins -from beets.autotag import AlbumInfo, TrackInfo, match +from beets.autotag.hooks import AlbumInfo, TrackInfo +from beets.autotag.match import assign_items, tag_album, tag_item from beets.library import Item @@ -40,9 +41,7 @@ class TestAssignment: items = [Item(title=title) for title in item_titles] tracks = [TrackInfo(title=title) for title in track_titles] - item_info_pairs, extra_items, extra_tracks = match.assign_items( - items, tracks - ) + item_info_pairs, extra_items, extra_tracks = assign_items(items, tracks) assert ( {i.title: t.title for i, t in item_info_pairs}, @@ -94,7 +93,7 @@ class TestAssignment: expected = list(zip(items, trackinfo)), [], [] - assert match.assign_items(items, trackinfo) == expected + assert assign_items(items, trackinfo) == expected class TestTagMultipleDataSources: @@ -163,21 +162,21 @@ class TestTagMultipleDataSources: assert set(sources) == {"Discogs", "Deezer"} def test_search_album_ids(self, shared_album_id): - _, _, proposal = match.tag_album([Item()], search_ids=[shared_album_id]) + _, _, proposal = tag_album([Item()], search_ids=[shared_album_id]) self.check_proposal(proposal) def test_search_album_current_id(self, shared_album_id): - _, _, proposal = match.tag_album([Item(mb_albumid=shared_album_id)]) + _, _, proposal = tag_album([Item(mb_albumid=shared_album_id)]) self.check_proposal(proposal) def test_search_track_ids(self, shared_track_id): - proposal = match.tag_item(Item(), search_ids=[shared_track_id]) + proposal = tag_item(Item(), search_ids=[shared_track_id]) self.check_proposal(proposal) def test_search_track_current_id(self, shared_track_id): - proposal = match.tag_item(Item(mb_trackid=shared_track_id)) + proposal = tag_item(Item(mb_trackid=shared_track_id)) self.check_proposal(proposal) diff --git a/test/plugins/test_art.py b/test/plugins/test_art.py index 792f30e6a..4ed86ffad 100644 --- a/test/plugins/test_art.py +++ b/test/plugins/test_art.py @@ -28,8 +28,8 @@ import pytest import responses from beets import config, importer, logging, util -from beets.autotag import AlbumInfo, AlbumMatch from beets.autotag.distance import Distance +from beets.autotag.hooks import AlbumInfo, AlbumMatch from beets.test import _common from beets.test.helper import ( BeetsTestCase, diff --git a/test/plugins/test_mbpseudo.py b/test/plugins/test_mbpseudo.py index 2fb6321b3..f49f52215 100644 --- a/test/plugins/test_mbpseudo.py +++ b/test/plugins/test_mbpseudo.py @@ -6,9 +6,8 @@ from typing import TYPE_CHECKING import pytest -from beets.autotag import AlbumMatch from beets.autotag.distance import Distance -from beets.autotag.hooks import AlbumInfo, TrackInfo +from beets.autotag.hooks import AlbumInfo, AlbumMatch, TrackInfo from beets.library import Item from beets.test.helper import PluginMixin from beetsplug.mbpseudo import ( diff --git a/test/test_importer.py b/test/test_importer.py index c594b76f0..71d933359 100644 --- a/test/test_importer.py +++ b/test/test_importer.py @@ -36,8 +36,8 @@ import pytest from mediafile import MediaFile from beets import config, importer, logging, util -from beets.autotag import AlbumInfo, AlbumMatch, TrackInfo from beets.autotag.distance import Distance +from beets.autotag.hooks import AlbumInfo, AlbumMatch, TrackInfo from beets.importer.tasks import albums_in_dir from beets.test import _common from beets.test.helper import ( diff --git a/test/ui/commands/test_import.py b/test/ui/commands/test_import.py index 4e85f6812..54a8fad6c 100644 --- a/test/ui/commands/test_import.py +++ b/test/ui/commands/test_import.py @@ -4,7 +4,8 @@ from unittest.mock import Mock, patch import pytest -from beets import autotag, config, library, ui +from beets import config, library, ui +from beets.autotag.hooks import AlbumInfo, AlbumMatch, TrackInfo from beets.autotag.match import distance from beets.test import _common from beets.test.helper import BeetsTestCase, IOMixin @@ -66,16 +67,16 @@ class ShowChangeTestCase(IOMixin, BeetsTestCase): _common.item(track=3, title="caf\xe9"), _common.item(track=4, title=f"title with {long_name}"), ] - info = autotag.AlbumInfo( + info = AlbumInfo( album="caf\xe9", album_id="album id", artist="the artist", artist_id="artist id", tracks=[ - autotag.TrackInfo(title="first title", index=1), - autotag.TrackInfo(title="second title", index=2), - autotag.TrackInfo(title="third title", index=3), - autotag.TrackInfo(title="fourth title", index=4), + TrackInfo(title="first title", index=1), + TrackInfo(title="second title", index=2), + TrackInfo(title="third title", index=3), + TrackInfo(title="fourth title", index=4), ], ) item_info_pairs = list(zip(items, info.tracks)) @@ -86,9 +87,7 @@ class ShowChangeTestCase(IOMixin, BeetsTestCase): show_change( f"another artist with {long_name}", "another album", - autotag.AlbumMatch( - change_dist, info, dict(item_info_pairs), set(), set() - ), + AlbumMatch(change_dist, info, dict(item_info_pairs)), ) return self.io.getoutput() From 7b93269ae9f7328478305ff37c4604ebf5985d97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0ar=C5=ABnas=20Nejus?= Date: Mon, 23 Mar 2026 01:54:33 +0000 Subject: [PATCH 2/3] Add commit to git blame ignore revs --- .git-blame-ignore-revs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index e0b832012..e568ccbe9 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -175,3 +175,5 @@ a6fcb7ba0f237530ff394a423a7cbe2ac4853c91 ffb43290066c78cb72603b7e2a0a1c90056361dd # lastgenre: Move fetching to client module b4beee8ff3754b001e7504c05a2b838bfa689022 +# autotag: use explicit imports +0c2f3ed073d44a21efd5b7e6a28c99f52d3f8c03 From 7ea42c7d50b6deef57210bad77a04aa6957f2107 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0ar=C5=ABnas=20Nejus?= Date: Mon, 23 Mar 2026 01:56:16 +0000 Subject: [PATCH 3/3] Set ImportTask.candidates to None by default --- beets/importer/tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beets/importer/tasks.py b/beets/importer/tasks.py index 1f0debbc8..2964c1920 100644 --- a/beets/importer/tasks.py +++ b/beets/importer/tasks.py @@ -167,7 +167,7 @@ class ImportTask(BaseImportTask): # Keep track of the current task item cur_album: str | None = None cur_artist: str | None = None - candidates: Sequence[AlbumMatch | TrackMatch] = [] + candidates: Sequence[AlbumMatch | TrackMatch] | None = None rec: Recommendation | None = None def __init__(