Skip to content

Commit 835cf4f

Browse files
committed
Bring back album_for_id, track_for_id
Restore album_for_id and track_for_id functions in metadata_plugins to support data source-specific lookups. These functions accept both an ID and data_source parameter, enabling plugins like mbsync and missing to retrieve metadata from the correct source. Update mbsync and missing plugins to use the restored functions with explicit data_source parameters. Add data_source validation to prevent lookups when the source is not specified. Add get_metadata_source helper function to retrieve plugins by their data_source name, cached for performance.
1 parent 86d3daf commit 835cf4f

File tree

4 files changed

+76
-24
lines changed

4 files changed

+76
-24
lines changed

beets/metadata_plugins.py

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
from beets.util import cached_classproperty
2828
from beets.util.id_extractors import extract_release_id
2929

30-
from .plugins import BeetsPlugin, find_plugins, notify_info_yielded
30+
from .plugins import BeetsPlugin, find_plugins, notify_info_yielded, send
3131

3232
Ret = TypeVar("Ret")
3333
QueryType = Literal["album", "track"]
@@ -49,14 +49,27 @@ def find_metadata_source_plugins() -> list[MetadataSourcePlugin]:
4949
return [p for p in find_plugins() if hasattr(p, "data_source")] # type: ignore[misc]
5050

5151

52+
@cache
53+
def get_metadata_source(name: str) -> MetadataSourcePlugin | None:
54+
"""Get metadata source plugin by name."""
55+
name = name.lower()
56+
plugins = find_metadata_source_plugins()
57+
return next((p for p in plugins if p.data_source.lower() == name), None)
58+
59+
5260
@contextmanager
53-
def handle_plugin_error(plugin: MetadataSourcePlugin, method_name: str):
61+
def maybe_handle_plugin_error(plugin: MetadataSourcePlugin, method_name: str):
5462
"""Safely call a plugin method, catching and logging exceptions."""
55-
try:
63+
if config["raise_on_error"]:
5664
yield
57-
except Exception as e:
58-
log.error("Error in '{}.{}': {}", plugin.data_source, method_name, e)
59-
log.debug("Exception details:", exc_info=True)
65+
else:
66+
try:
67+
yield
68+
except Exception as e:
69+
log.error(
70+
"Error in '{}.{}': {}", plugin.data_source, method_name, e
71+
)
72+
log.debug("Exception details:", exc_info=True)
6073

6174

6275
def _yield_from_plugins(
@@ -68,11 +81,7 @@ def _yield_from_plugins(
6881
def wrapper(*args, **kwargs) -> Iterator[Ret]:
6982
for plugin in find_metadata_source_plugins():
7083
method = getattr(plugin, method_name)
71-
with (
72-
nullcontext()
73-
if config["raise_on_error"]
74-
else handle_plugin_error(plugin, method_name)
75-
):
84+
with maybe_handle_plugin_error(plugin, method_name):
7685
yield from filter(None, method(*args, **kwargs))
7786

7887
return wrapper
@@ -102,12 +111,26 @@ def tracks_for_ids(*args, **kwargs) -> Iterator[TrackInfo]:
102111
yield from ()
103112

104113

105-
def album_for_id(_id: str) -> AlbumInfo | None:
106-
return next(albums_for_ids([_id]), None)
114+
def album_for_id(_id: str, data_source: str) -> AlbumInfo | None:
115+
"""Get AlbumInfo object for the given ID and data source."""
116+
if plugin := get_metadata_source(data_source):
117+
with maybe_handle_plugin_error(plugin, "album_for_id"):
118+
if info := plugin.album_for_id(_id):
119+
send("albuminfo_received", info=info)
120+
return info
121+
122+
return None
123+
107124

125+
def track_for_id(_id: str, data_source: str) -> TrackInfo | None:
126+
"""Get TrackInfo object for the given ID and data source."""
127+
if plugin := get_metadata_source(data_source):
128+
with maybe_handle_plugin_error(plugin, "track_for_id"):
129+
if info := plugin.track_for_id(_id):
130+
send("albuminfo_received", info=info)
131+
return info
108132

109-
def track_for_id(_id: str) -> TrackInfo | None:
110-
return next(tracks_for_ids([_id]), None)
133+
return None
111134

112135

113136
@cache

beetsplug/mbsync.py

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -72,17 +72,25 @@ def singletons(self, lib, query, move, pretend, write):
7272
query.
7373
"""
7474
for item in lib.items([*query, "singleton:true"]):
75-
if not item.mb_trackid:
75+
if not (track_id := item.mb_trackid):
7676
self._log.info(
7777
"Skipping singleton with no mb_trackid: {}", item
7878
)
7979
continue
8080

81+
if not (data_source := item.get("data_source")):
82+
self._log.info(
83+
"Skipping singleton without data source: {}", item
84+
)
85+
continue
86+
8187
if not (
82-
track_info := metadata_plugins.track_for_id(item.mb_trackid)
88+
track_info := metadata_plugins.track_for_id(
89+
track_id, data_source
90+
)
8391
):
8492
self._log.info(
85-
"Recording ID not found: {0.mb_trackid} for track {0}", item
93+
"Recording ID not found: {} for track {}", track_id, item
8694
)
8795
continue
8896

@@ -97,15 +105,24 @@ def albums(self, lib, query, move, pretend, write):
97105
"""
98106
# Process matching albums.
99107
for album in lib.albums(query):
100-
if not album.mb_albumid:
108+
if not (album_id := album.mb_albumid):
101109
self._log.info("Skipping album with no mb_albumid: {}", album)
102110
continue
103111

104112
if not (
105-
album_info := metadata_plugins.album_for_id(album.mb_albumid)
113+
data_source := album.get("data_source")
114+
or album.items()[0].get("data_source")
115+
):
116+
self._log.info("Skipping album without data source: {}", album)
117+
continue
118+
119+
if not (
120+
album_info := metadata_plugins.album_for_id(
121+
album_id, data_source
122+
)
106123
):
107124
self._log.info(
108-
"Release ID {0.mb_albumid} not found for album {0}", album
125+
"Release ID {} not found for album {}", album_id, album
109126
)
110127
continue
111128

beetsplug/missing.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -227,10 +227,17 @@ def _missing(self, album: Album) -> Iterator[Item]:
227227
if len(album.items()) == album.albumtotal:
228228
return
229229

230-
item_mbids = {x.mb_trackid for x in album.items()}
231230
# fetch missing items
232231
# TODO: Implement caching that without breaking other stuff
233-
if album_info := metadata_plugins.album_for_id(album.mb_albumid):
232+
if (
233+
data_source := album.get("data_source")
234+
or album.items()[0].get("data_source")
235+
) and (
236+
album_info := metadata_plugins.album_for_id(
237+
album.mb_albumid, data_source
238+
)
239+
):
240+
item_mbids = {x.mb_trackid for x in album.items()}
234241
for track_info in album_info.tracks:
235242
if track_info.track_id not in item_mbids:
236243
self._log.debug(

test/plugins/test_mbsync.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,15 @@ def test_update_library(self):
4545
album="old album",
4646
mb_albumid="album id",
4747
mb_trackid="track id",
48+
data_source="data_source",
4849
)
4950
self.lib.add_album([album_item])
5051

51-
singleton = Item(title="old title", mb_trackid="singleton id")
52+
singleton = Item(
53+
title="old title",
54+
mb_trackid="singleton id",
55+
data_source="data_source",
56+
)
5257
self.lib.add(singleton)
5358

5459
self.run_command("mbsync")

0 commit comments

Comments
 (0)