diff --git a/beets/config_default.yaml b/beets/config_default.yaml index c0bab8056..dfc0378a1 100644 --- a/beets/config_default.yaml +++ b/beets/config_default.yaml @@ -10,6 +10,8 @@ plugins: [musicbrainz] pluginpath: [] +raise_on_error: no + # --------------- Import --------------- clutter: ["Thumbs.DB", ".DS_Store"] diff --git a/beets/metadata_plugins.py b/beets/metadata_plugins.py index c44687d03..18d259ae0 100644 --- a/beets/metadata_plugins.py +++ b/beets/metadata_plugins.py @@ -8,19 +8,16 @@ implemented as plugins. from __future__ import annotations import abc -import inspect import re -from functools import cache, cached_property, wraps +from functools import cache, cached_property from typing import ( TYPE_CHECKING, Callable, - ClassVar, Generic, Literal, Sequence, TypedDict, TypeVar, - overload, ) import unidecode @@ -397,30 +394,22 @@ class SafeProxy(base): """ _plugin: MetadataSourcePlugin - _SAFE_METHODS: ClassVar[set[str]] = { - "candidates", - "item_candidates", - "album_for_id", - "track_for_id", - } def __init__(self, plugin: MetadataSourcePlugin): self._plugin = plugin def __getattribute__(self, name): - if ( - name == "_plugin" - or name == "_handle_exception" - or name == "_SAFE_METHODS" - or name == "_safe_execute" - ): + if name in { + "_plugin", + "_handle_exception", + "candidates", + "item_candidates", + "album_for_id", + "track_for_id", + }: return super().__getattribute__(name) - - attr = getattr(self._plugin, name) - - if callable(attr) and name in SafeProxy._SAFE_METHODS: - return self._safe_execute(attr) - return attr + else: + return getattr(self._plugin, name) def __setattr__(self, name, value): if name == "_plugin": @@ -428,43 +417,6 @@ class SafeProxy(base): else: self._plugin.__setattr__(name, value) - @overload - def _safe_execute( - self, - func: Callable[P, Iterable[R]], - ) -> Callable[P, Iterable[R]]: ... - @overload - def _safe_execute(self, func: Callable[P, R]) -> Callable[P, R | None]: ... - def _safe_execute( - self, func: Callable[P, R] - ) -> Callable[P, R | Iterable[R] | None]: - """Wrap any function (generator or regular) and safely execute it. - - Limitation: This does not work on properties! - """ - - @wraps(func) - def wrapper( - *args: P.args, **kwargs: P.kwargs - ) -> R | Iterable[R] | None: - try: - result = func(*args, **kwargs) - except Exception as e: - self._handle_exception(func, e) - - return None - - if inspect.isgenerator(result): - try: - yield from result - except Exception as e: - self._handle_exception(func, e) - return None - else: - return result - - return wrapper - def _handle_exception(self, func: Callable[P, R], e: Exception) -> None: """Helper function to log exceptions from metadata source plugins.""" if config["raise_on_error"].get(bool): @@ -477,17 +429,26 @@ class SafeProxy(base): ) log.debug("Exception details:", exc_info=True) - # Implement abstract methods to satisfy the ABC - # this is only needed because of the typing hack above. - - def album_for_id(self, album_id: str): - raise NotImplementedError + def album_for_id(self, *args, **kwargs): + try: + return self._plugin.album_for_id(*args, **kwargs) + except Exception as e: + return self._handle_exception(self._plugin.album_for_id, e) def track_for_id(self, track_id: str): - raise NotImplementedError + try: + return self._plugin.track_for_id(track_id) + except Exception as e: + return self._handle_exception(self._plugin.track_for_id, e) def candidates(self, *args, **kwargs): - raise NotImplementedError + try: + yield from self._plugin.candidates(*args, **kwargs) + except Exception as e: + return self._handle_exception(self._plugin.candidates, e) def item_candidates(self, *args, **kwargs): - raise NotImplementedError + try: + yield from self._plugin.item_candidates(*args, **kwargs) + except Exception as e: + return self._handle_exception(self._plugin.item_candidates, e)