Legacy plugin copy not copying properties. (#6101)

The recently introduces `data_source_mismatch_penalty` property in the MetadataPlugin
class was not copied in the backwards compatibility layer. This PR
introduces a fixes this such that `cached_properties` are copied to
legacy metadata plugins.

This also includes a test for the expected behavior.

See also [beetcamp
issue](https://github.com/snejus/beetcamp/issues/85#issuecomment-3399273892).
This commit is contained in:
Sebastian Mohr 2025-10-14 20:41:31 +02:00 committed by GitHub
commit af022683fe
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 47 additions and 7 deletions

View file

@ -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,12 +192,23 @@ class BeetsPlugin(metaclass=abc.ABCMeta):
stacklevel=3,
)
method: property | cached_property[Any] | Callable[..., Any]
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__)
predicate=lambda f: ( # type: ignore[arg-type]
(
isinstance(f, (property, cached_property))
and not hasattr(
BeetsPlugin,
getattr(f, "attrname", None) or f.fget.__name__, # type: ignore[union-attr]
)
)
or (
inspect.isfunction(f)
and f.__name__
and not getattr(f, "__isabstractmethod__", False)
and not hasattr(BeetsPlugin, f.__name__)
)
),
):
setattr(cls, name, method)

View file

@ -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:

View file

@ -11,6 +11,7 @@ from beets.autotag.distance import (
)
from beets.library import Item
from beets.metadata_plugins import MetadataSourcePlugin, get_penalty
from beets.plugins import BeetsPlugin
from beets.test.helper import ConfigMixin
_p = pytest.param
@ -310,8 +311,13 @@ class TestDataSourceDistance:
def candidates(self, *args, **kwargs): ...
def item_candidates(self, *args, **kwargs): ...
class OriginalPlugin(TestMetadataSourcePlugin):
pass
# We use BeetsPlugin here to check if our compatibility layer
# for pre 2.4.0 MetadataPlugins is working as expected
# TODO: Replace BeetsPlugin with TestMetadataSourcePlugin in v3.0.0
with pytest.deprecated_call():
class OriginalPlugin(BeetsPlugin):
data_source = "Original"
class OtherPlugin(TestMetadataSourcePlugin):
@property
@ -332,6 +338,7 @@ class TestDataSourceDistance:
[
_p("Original", "Original", 0.5, 1.0, True, MATCH, id="match"),
_p("Original", "Other", 0.5, 1.0, True, MISMATCH, id="mismatch"),
_p("Other", "Original", 0.5, 1.0, True, MISMATCH, id="mismatch"),
_p("Original", "unknown", 0.5, 1.0, True, MISMATCH, id="mismatch-unknown"), # noqa: E501
_p("Original", None, 0.5, 1.0, True, MISMATCH, id="mismatch-no-info"), # noqa: E501
_p(None, "Other", 0.5, 1.0, True, MISMATCH, id="mismatch-no-original-multiple-sources"), # noqa: E501

View file

@ -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")