From 670c300625b0dd7ef7a5c62c3ae2106ed5dfbf15 Mon Sep 17 00:00:00 2001 From: Sebastian Mohr Date: Tue, 14 Oct 2025 17:15:02 +0200 Subject: [PATCH] Fixed issue with legacy plugin copy not copying properties. Also added test for it --- beets/plugins.py | 25 +++++++++++++++---------- docs/changelog.rst | 2 ++ test/test_plugins.py | 20 ++++++++++++++++++++ 3 files changed, 37 insertions(+), 10 deletions(-) diff --git a/beets/plugins.py b/beets/plugins.py index a8e803efd..9c7a93b7f 100644 --- a/beets/plugins.py +++ b/beets/plugins.py @@ -22,7 +22,7 @@ import re import sys import warnings from collections import defaultdict -from functools import wraps +from functools import cached_property, wraps from importlib import import_module from pathlib import Path from types import GenericAlias @@ -192,15 +192,20 @@ class BeetsPlugin(metaclass=abc.ABCMeta): stacklevel=3, ) - for name, method in inspect.getmembers( - MetadataSourcePlugin, - predicate=lambda f: ( - inspect.isfunction(f) - and f.__name__ not in MetadataSourcePlugin.__abstractmethods__ - and not hasattr(cls, f.__name__) - ), - ): - setattr(cls, name, method) + abstracts = MetadataSourcePlugin.__abstractmethods__ + + for name, method in inspect.getmembers(MetadataSourcePlugin): + # Skip if already defined in the subclass + if hasattr(cls, name) or name in abstracts: + continue + + # Copy functions, methods, and properties + if ( + inspect.isfunction(method) + or inspect.ismethod(method) + or isinstance(method, cached_property) + ): + setattr(cls, name, method) def __init__(self, name: str | None = None): """Perform one-time plugin setup.""" diff --git a/docs/changelog.rst b/docs/changelog.rst index 6d08d6bdb..88c157ec5 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -22,6 +22,8 @@ For packagers: - Fixed dynamic versioning install not disabled for source distribution builds. :bug:`6089` +- Fixed issue with legacy metadata plugins not copying properties from the base + class. Other changes: diff --git a/test/test_plugins.py b/test/test_plugins.py index df338f924..07bbf0966 100644 --- a/test/test_plugins.py +++ b/test/test_plugins.py @@ -523,3 +523,23 @@ class TestImportPlugin(PluginMixin): assert "PluginImportError" not in caplog.text, ( f"Plugin '{plugin_name}' has issues during import." ) + + +class TestDeprecationCopy: + # TODO: remove this test in Beets 3.0.0 + def test_legacy_metadata_plugin_deprecation(self): + """Test that a MetadataSourcePlugin with 'legacy' data_source + raises a deprecation warning and all function and properties are + copied from the base class. + """ + with pytest.warns(DeprecationWarning, match="LegacyMetadataPlugin"): + + class LegacyMetadataPlugin(plugins.BeetsPlugin): + data_source = "legacy" + + # Assert all methods are present + assert hasattr(LegacyMetadataPlugin, "albums_for_ids") + assert hasattr(LegacyMetadataPlugin, "tracks_for_ids") + assert hasattr(LegacyMetadataPlugin, "data_source_mismatch_penalty") + assert hasattr(LegacyMetadataPlugin, "_extract_id") + assert hasattr(LegacyMetadataPlugin, "get_artist")