mirror of
https://github.com/beetbox/beets.git
synced 2025-12-06 16:42:42 +01:00
Add deprecation warning for musicbrainz.enabled but use it to load the plugin, centralise deprecations handling (#6127)
Fixes: #6121 This PR introduces a centralized deprecation system and adjusts `musicbrainz` plugin loading to properly handle the deprecated `musicbrainz.enabled` configuration option. #### MusicBrainz - Added deprecation warnings for the `musicbrainz.enabled` configuration option: - When set to `true`, warns users to explicitly add `musicbrainz` to their `plugins` configuration and adds it if not already present - When set to `false`, warns users and adds the plugin to `disabled_plugins` (list received by the `--disable-plugins` flag) #### Deprecations - Created new `beets/util/deprecation.py` module with standardized deprecation helpers: - `deprecate_for_user()` - logs warnings visible to end users - `deprecate_for_maintainers()` - emits `DeprecationWarning` for developers - `deprecate_imports()` - handles deprecated module imports with automatic version calculation - `_format_message()` - generates consistent deprecation messages that auto-calculate next major version - Migrated all deprecation handling to use the new centralized functions: - Replaced inline `warnings.warn()` calls throughout codebase - Updated `deprecate_imports()` signature to remove explicit `version` parameter - Converted user-facing deprecation warnings in plugins to use logger-based `deprecate_for_user()`
This commit is contained in:
commit
6abb901b6b
16 changed files with 174 additions and 115 deletions
|
|
@ -17,7 +17,7 @@ from sys import stderr
|
||||||
|
|
||||||
import confuse
|
import confuse
|
||||||
|
|
||||||
from .util import deprecate_imports
|
from .util.deprecation import deprecate_imports
|
||||||
|
|
||||||
__version__ = "2.5.1"
|
__version__ = "2.5.1"
|
||||||
__author__ = "Adrian Sampson <adrian@radbox.org>"
|
__author__ = "Adrian Sampson <adrian@radbox.org>"
|
||||||
|
|
@ -26,13 +26,9 @@ __author__ = "Adrian Sampson <adrian@radbox.org>"
|
||||||
def __getattr__(name: str):
|
def __getattr__(name: str):
|
||||||
"""Handle deprecated imports."""
|
"""Handle deprecated imports."""
|
||||||
return deprecate_imports(
|
return deprecate_imports(
|
||||||
old_module=__name__,
|
__name__,
|
||||||
new_module_by_name={
|
{"art": "beetsplug._utils", "vfs": "beetsplug._utils"},
|
||||||
"art": "beetsplug._utils",
|
name,
|
||||||
"vfs": "beetsplug._utils",
|
|
||||||
},
|
|
||||||
name=name,
|
|
||||||
version="3.0.0",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,6 @@
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import warnings
|
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
|
@ -24,8 +23,8 @@ from beets import config, logging
|
||||||
|
|
||||||
# Parts of external interface.
|
# Parts of external interface.
|
||||||
from beets.util import unique_list
|
from beets.util import unique_list
|
||||||
|
from beets.util.deprecation import deprecate_for_maintainers, deprecate_imports
|
||||||
|
|
||||||
from ..util import deprecate_imports
|
|
||||||
from .hooks import AlbumInfo, AlbumMatch, TrackInfo, TrackMatch
|
from .hooks import AlbumInfo, AlbumMatch, TrackInfo, TrackMatch
|
||||||
from .match import Proposal, Recommendation, tag_album, tag_item
|
from .match import Proposal, Recommendation, tag_album, tag_item
|
||||||
|
|
||||||
|
|
@ -37,18 +36,13 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
def __getattr__(name: str):
|
def __getattr__(name: str):
|
||||||
if name == "current_metadata":
|
if name == "current_metadata":
|
||||||
warnings.warn(
|
deprecate_for_maintainers(
|
||||||
(
|
f"'beets.autotag.{name}'", "'beets.util.get_most_common_tags'"
|
||||||
f"'beets.autotag.{name}' is deprecated and will be removed in"
|
|
||||||
" 3.0.0. Use 'beets.util.get_most_common_tags' instead."
|
|
||||||
),
|
|
||||||
DeprecationWarning,
|
|
||||||
stacklevel=2,
|
|
||||||
)
|
)
|
||||||
return import_module("beets.util").get_most_common_tags
|
return import_module("beets.util").get_most_common_tags
|
||||||
|
|
||||||
return deprecate_imports(
|
return deprecate_imports(
|
||||||
__name__, {"Distance": "beets.autotag.distance"}, name, "3.0.0"
|
__name__, {"Distance": "beets.autotag.distance"}, name
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from beets.util import deprecate_imports
|
from beets.util.deprecation import deprecate_imports
|
||||||
|
|
||||||
from .exceptions import FileOperationError, ReadError, WriteError
|
from .exceptions import FileOperationError, ReadError, WriteError
|
||||||
from .library import Library
|
from .library import Library
|
||||||
|
|
@ -13,7 +13,7 @@ NEW_MODULE_BY_NAME = dict.fromkeys(
|
||||||
|
|
||||||
|
|
||||||
def __getattr__(name: str):
|
def __getattr__(name: str):
|
||||||
return deprecate_imports(__name__, NEW_MODULE_BY_NAME, name, "3.0.0")
|
return deprecate_imports(__name__, NEW_MODULE_BY_NAME, name)
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
|
|
||||||
|
|
@ -13,17 +13,11 @@
|
||||||
# included in all copies or substantial portions of the Software.
|
# included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
import mediafile
|
import mediafile
|
||||||
|
|
||||||
warnings.warn(
|
from .util.deprecation import deprecate_for_maintainers
|
||||||
"beets.mediafile is deprecated; use mediafile instead",
|
|
||||||
# Show the location of the `import mediafile` statement as the warning's
|
deprecate_for_maintainers("'beets.mediafile'", "'mediafile'", stacklevel=2)
|
||||||
# source, rather than this file, such that the offending module can be
|
|
||||||
# identified easily.
|
|
||||||
stacklevel=2,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Import everything from the mediafile module into this module.
|
# Import everything from the mediafile module into this module.
|
||||||
for key, value in mediafile.__dict__.items():
|
for key, value in mediafile.__dict__.items():
|
||||||
|
|
@ -31,4 +25,4 @@ for key, value in mediafile.__dict__.items():
|
||||||
globals()[key] = value
|
globals()[key] = value
|
||||||
|
|
||||||
# Cleanup namespace.
|
# Cleanup namespace.
|
||||||
del key, value, warnings, mediafile
|
del key, value, mediafile
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ import abc
|
||||||
import inspect
|
import inspect
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import warnings
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from functools import cached_property, wraps
|
from functools import cached_property, wraps
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
|
|
@ -33,6 +32,7 @@ from typing_extensions import ParamSpec
|
||||||
import beets
|
import beets
|
||||||
from beets import logging
|
from beets import logging
|
||||||
from beets.util import unique_list
|
from beets.util import unique_list
|
||||||
|
from beets.util.deprecation import deprecate_for_maintainers, deprecate_for_user
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from collections.abc import Callable, Iterable, Sequence
|
from collections.abc import Callable, Iterable, Sequence
|
||||||
|
|
@ -184,11 +184,12 @@ class BeetsPlugin(metaclass=abc.ABCMeta):
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
|
|
||||||
warnings.warn(
|
deprecate_for_maintainers(
|
||||||
f"{cls.__name__} is used as a legacy metadata source. "
|
(
|
||||||
"It should extend MetadataSourcePlugin instead of BeetsPlugin. "
|
f"'{cls.__name__}' is used as a legacy metadata source since it"
|
||||||
"Support for this will be removed in the v3.0.0 release!",
|
" inherits 'beets.plugins.BeetsPlugin'. Support for this"
|
||||||
DeprecationWarning,
|
),
|
||||||
|
"'beets.metadata_plugins.MetadataSourcePlugin'",
|
||||||
stacklevel=3,
|
stacklevel=3,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -256,16 +257,19 @@ class BeetsPlugin(metaclass=abc.ABCMeta):
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
|
|
||||||
message = (
|
|
||||||
"'source_weight' configuration option is deprecated and will be"
|
|
||||||
" removed in v3.0.0. Use 'data_source_mismatch_penalty' instead"
|
|
||||||
)
|
|
||||||
for source in self.config.root().sources:
|
for source in self.config.root().sources:
|
||||||
if "source_weight" in (source.get(self.name) or {}):
|
if "source_weight" in (source.get(self.name) or {}):
|
||||||
if source.filename: # user config
|
if source.filename: # user config
|
||||||
self._log.warning(message)
|
deprecate_for_user(
|
||||||
|
self._log,
|
||||||
|
f"'{self.name}.source_weight' configuration option",
|
||||||
|
f"'{self.name}.data_source_mismatch_penalty'",
|
||||||
|
)
|
||||||
else: # 3rd-party plugin config
|
else: # 3rd-party plugin config
|
||||||
warnings.warn(message, DeprecationWarning, stacklevel=0)
|
deprecate_for_maintainers(
|
||||||
|
"'source_weight' configuration option",
|
||||||
|
"'data_source_mismatch_penalty'",
|
||||||
|
)
|
||||||
|
|
||||||
def commands(self) -> Sequence[Subcommand]:
|
def commands(self) -> Sequence[Subcommand]:
|
||||||
"""Should return a list of beets.ui.Subcommand objects for
|
"""Should return a list of beets.ui.Subcommand objects for
|
||||||
|
|
@ -410,16 +414,22 @@ def get_plugin_names() -> list[str]:
|
||||||
# *contain* a `beetsplug` package.
|
# *contain* a `beetsplug` package.
|
||||||
sys.path += paths
|
sys.path += paths
|
||||||
plugins = unique_list(beets.config["plugins"].as_str_seq())
|
plugins = unique_list(beets.config["plugins"].as_str_seq())
|
||||||
# TODO: Remove in v3.0.0
|
|
||||||
if (
|
|
||||||
"musicbrainz" not in plugins
|
|
||||||
and "musicbrainz" in beets.config
|
|
||||||
and beets.config["musicbrainz"].get().get("enabled")
|
|
||||||
):
|
|
||||||
plugins.append("musicbrainz")
|
|
||||||
|
|
||||||
beets.config.add({"disabled_plugins": []})
|
beets.config.add({"disabled_plugins": []})
|
||||||
disabled_plugins = set(beets.config["disabled_plugins"].as_str_seq())
|
disabled_plugins = set(beets.config["disabled_plugins"].as_str_seq())
|
||||||
|
# TODO: Remove in v3.0.0
|
||||||
|
mb_enabled = beets.config["musicbrainz"].flatten().get("enabled")
|
||||||
|
if mb_enabled:
|
||||||
|
deprecate_for_user(
|
||||||
|
log,
|
||||||
|
"'musicbrainz.enabled' configuration option",
|
||||||
|
"'plugins' configuration to explicitly add 'musicbrainz'",
|
||||||
|
)
|
||||||
|
if "musicbrainz" not in plugins:
|
||||||
|
plugins.append("musicbrainz")
|
||||||
|
elif mb_enabled is False:
|
||||||
|
deprecate_for_user(log, "'musicbrainz.enabled' configuration option")
|
||||||
|
disabled_plugins.add("musicbrainz")
|
||||||
|
|
||||||
return [p for p in plugins if p not in disabled_plugins]
|
return [p for p in plugins if p not in disabled_plugins]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,6 @@ import sqlite3
|
||||||
import sys
|
import sys
|
||||||
import textwrap
|
import textwrap
|
||||||
import traceback
|
import traceback
|
||||||
import warnings
|
|
||||||
from difflib import SequenceMatcher
|
from difflib import SequenceMatcher
|
||||||
from functools import cache
|
from functools import cache
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
|
|
@ -40,6 +39,7 @@ from beets import config, library, logging, plugins, util
|
||||||
from beets.dbcore import db
|
from beets.dbcore import db
|
||||||
from beets.dbcore import query as db_query
|
from beets.dbcore import query as db_query
|
||||||
from beets.util import as_string
|
from beets.util import as_string
|
||||||
|
from beets.util.deprecation import deprecate_for_maintainers
|
||||||
from beets.util.functemplate import template
|
from beets.util.functemplate import template
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
|
@ -114,11 +114,7 @@ def decargs(arglist):
|
||||||
.. deprecated:: 2.4.0
|
.. deprecated:: 2.4.0
|
||||||
This function will be removed in 3.0.0.
|
This function will be removed in 3.0.0.
|
||||||
"""
|
"""
|
||||||
warnings.warn(
|
deprecate_for_maintainers("'beets.ui.decargs'")
|
||||||
"decargs() is deprecated and will be removed in version 3.0.0.",
|
|
||||||
DeprecationWarning,
|
|
||||||
stacklevel=2,
|
|
||||||
)
|
|
||||||
return arglist
|
return arglist
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
interface.
|
interface.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from beets.util import deprecate_imports
|
from beets.util.deprecation import deprecate_imports
|
||||||
|
|
||||||
from .completion import completion_cmd
|
from .completion import completion_cmd
|
||||||
from .config import config_cmd
|
from .config import config_cmd
|
||||||
|
|
@ -36,14 +36,12 @@ from .write import write_cmd
|
||||||
def __getattr__(name: str):
|
def __getattr__(name: str):
|
||||||
"""Handle deprecated imports."""
|
"""Handle deprecated imports."""
|
||||||
return deprecate_imports(
|
return deprecate_imports(
|
||||||
old_module=__name__,
|
__name__,
|
||||||
new_module_by_name={
|
{
|
||||||
"TerminalImportSession": "beets.ui.commands.import_.session",
|
"TerminalImportSession": "beets.ui.commands.import_.session",
|
||||||
"PromptChoice": "beets.ui.commands.import_.session",
|
"PromptChoice": "beets.util",
|
||||||
# TODO: We might want to add more deprecated imports here
|
|
||||||
},
|
},
|
||||||
name=name,
|
name,
|
||||||
version="3.0.0",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
from typing import Any, NamedTuple
|
|
||||||
|
|
||||||
from beets import autotag, config, importer, logging, plugins, ui
|
from beets import autotag, config, importer, logging, plugins, ui
|
||||||
from beets.autotag import Recommendation
|
from beets.autotag import Recommendation
|
||||||
from beets.util import displayable_path
|
from beets.util import PromptChoice, displayable_path
|
||||||
from beets.util.units import human_bytes, human_seconds_short
|
from beets.util.units import human_bytes, human_seconds_short
|
||||||
|
|
||||||
from .display import (
|
from .display import (
|
||||||
|
|
@ -368,12 +367,6 @@ def _summary_judgment(rec):
|
||||||
return action
|
return action
|
||||||
|
|
||||||
|
|
||||||
class PromptChoice(NamedTuple):
|
|
||||||
short: str
|
|
||||||
long: str
|
|
||||||
callback: Any
|
|
||||||
|
|
||||||
|
|
||||||
def choose_candidate(
|
def choose_candidate(
|
||||||
candidates,
|
candidates,
|
||||||
singleton,
|
singleton,
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,6 @@ import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import traceback
|
import traceback
|
||||||
import warnings
|
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
from collections.abc import Callable, Sequence
|
from collections.abc import Callable, Sequence
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
|
|
@ -168,6 +167,12 @@ class MoveOperation(Enum):
|
||||||
REFLINK_AUTO = 5
|
REFLINK_AUTO = 5
|
||||||
|
|
||||||
|
|
||||||
|
class PromptChoice(NamedTuple):
|
||||||
|
short: str
|
||||||
|
long: str
|
||||||
|
callback: Any
|
||||||
|
|
||||||
|
|
||||||
def normpath(path: PathLike) -> bytes:
|
def normpath(path: PathLike) -> bytes:
|
||||||
"""Provide the canonical form of the path suitable for storing in
|
"""Provide the canonical form of the path suitable for storing in
|
||||||
the database.
|
the database.
|
||||||
|
|
@ -1195,26 +1200,3 @@ def get_temp_filename(
|
||||||
def unique_list(elements: Iterable[T]) -> list[T]:
|
def unique_list(elements: Iterable[T]) -> list[T]:
|
||||||
"""Return a list with unique elements in the original order."""
|
"""Return a list with unique elements in the original order."""
|
||||||
return list(dict.fromkeys(elements))
|
return list(dict.fromkeys(elements))
|
||||||
|
|
||||||
|
|
||||||
def deprecate_imports(
|
|
||||||
old_module: str, new_module_by_name: dict[str, str], name: str, version: str
|
|
||||||
) -> Any:
|
|
||||||
"""Handle deprecated module imports by redirecting to new locations.
|
|
||||||
|
|
||||||
Facilitates gradual migration of module structure by intercepting import
|
|
||||||
attempts for relocated functionality. Issues deprecation warnings while
|
|
||||||
transparently providing access to the moved implementation, allowing
|
|
||||||
existing code to continue working during transition periods.
|
|
||||||
"""
|
|
||||||
if new_module := new_module_by_name.get(name):
|
|
||||||
warnings.warn(
|
|
||||||
(
|
|
||||||
f"'{old_module}.{name}' is deprecated and will be removed"
|
|
||||||
f" in {version}. Use '{new_module}.{name}' instead."
|
|
||||||
),
|
|
||||||
DeprecationWarning,
|
|
||||||
stacklevel=2,
|
|
||||||
)
|
|
||||||
return getattr(import_module(new_module), name)
|
|
||||||
raise AttributeError(f"module '{old_module}' has no attribute '{name}'")
|
|
||||||
|
|
|
||||||
60
beets/util/deprecation.py
Normal file
60
beets/util/deprecation.py
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import warnings
|
||||||
|
from importlib import import_module
|
||||||
|
from typing import TYPE_CHECKING, Any
|
||||||
|
|
||||||
|
from packaging.version import Version
|
||||||
|
|
||||||
|
import beets
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from logging import Logger
|
||||||
|
|
||||||
|
|
||||||
|
def _format_message(old: str, new: str | None = None) -> str:
|
||||||
|
next_major = f"{Version(beets.__version__).major + 1}.0.0"
|
||||||
|
msg = f"{old} is deprecated and will be removed in version {next_major}."
|
||||||
|
if new:
|
||||||
|
msg += f" Use {new} instead."
|
||||||
|
|
||||||
|
return msg
|
||||||
|
|
||||||
|
|
||||||
|
def deprecate_for_user(
|
||||||
|
logger: Logger, old: str, new: str | None = None
|
||||||
|
) -> None:
|
||||||
|
logger.warning(_format_message(old, new))
|
||||||
|
|
||||||
|
|
||||||
|
def deprecate_for_maintainers(
|
||||||
|
old: str, new: str | None = None, stacklevel: int = 1
|
||||||
|
) -> None:
|
||||||
|
"""Issue a deprecation warning visible to maintainers during development.
|
||||||
|
|
||||||
|
Emits a DeprecationWarning that alerts developers about deprecated code
|
||||||
|
patterns. Unlike user-facing warnings, these are primarily for internal
|
||||||
|
code maintenance and appear during test runs or with warnings enabled.
|
||||||
|
"""
|
||||||
|
warnings.warn(
|
||||||
|
_format_message(old, new), DeprecationWarning, stacklevel=stacklevel + 1
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def deprecate_imports(
|
||||||
|
old_module: str, new_module_by_name: dict[str, str], name: str
|
||||||
|
) -> Any:
|
||||||
|
"""Handle deprecated module imports by redirecting to new locations.
|
||||||
|
|
||||||
|
Facilitates gradual migration of module structure by intercepting import
|
||||||
|
attempts for relocated functionality. Issues deprecation warnings while
|
||||||
|
transparently providing access to the moved implementation, allowing
|
||||||
|
existing code to continue working during transition periods.
|
||||||
|
"""
|
||||||
|
if new_module := new_module_by_name.get(name):
|
||||||
|
deprecate_for_maintainers(
|
||||||
|
f"'{old_module}.{name}'", f"'{new_module}.{name}'", stacklevel=2
|
||||||
|
)
|
||||||
|
|
||||||
|
return getattr(import_module(new_module), name)
|
||||||
|
raise AttributeError(f"module '{old_module}' has no attribute '{name}'")
|
||||||
|
|
@ -25,8 +25,8 @@ import yaml
|
||||||
from beets import plugins, ui, util
|
from beets import plugins, ui, util
|
||||||
from beets.dbcore import types
|
from beets.dbcore import types
|
||||||
from beets.importer import Action
|
from beets.importer import Action
|
||||||
from beets.ui.commands.import_.session import PromptChoice
|
|
||||||
from beets.ui.commands.utils import do_query
|
from beets.ui.commands.utils import do_query
|
||||||
|
from beets.util import PromptChoice
|
||||||
|
|
||||||
# These "safe" types can avoid the format/parse cycle that most fields go
|
# These "safe" types can avoid the format/parse cycle that most fields go
|
||||||
# through: they are safe to edit with native YAML types.
|
# through: they are safe to edit with native YAML types.
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,7 @@ import subprocess
|
||||||
from beets import ui
|
from beets import ui
|
||||||
from beets.autotag import Recommendation
|
from beets.autotag import Recommendation
|
||||||
from beets.plugins import BeetsPlugin
|
from beets.plugins import BeetsPlugin
|
||||||
from beets.ui.commands import PromptChoice
|
from beets.util import PromptChoice, displayable_path
|
||||||
from beets.util import displayable_path
|
|
||||||
from beetsplug.info import print_data
|
from beetsplug.info import print_data
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ import beets
|
||||||
import beets.autotag.hooks
|
import beets.autotag.hooks
|
||||||
from beets import config, plugins, util
|
from beets import config, plugins, util
|
||||||
from beets.metadata_plugins import MetadataSourcePlugin
|
from beets.metadata_plugins import MetadataSourcePlugin
|
||||||
|
from beets.util.deprecation import deprecate_for_user
|
||||||
from beets.util.id_extractors import extract_release_id
|
from beets.util.id_extractors import extract_release_id
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
|
@ -403,9 +404,10 @@ class MusicBrainzPlugin(MetadataSourcePlugin):
|
||||||
self.config["search_limit"] = self.config["match"][
|
self.config["search_limit"] = self.config["match"][
|
||||||
"searchlimit"
|
"searchlimit"
|
||||||
].get()
|
].get()
|
||||||
self._log.warning(
|
deprecate_for_user(
|
||||||
"'musicbrainz.searchlimit' option is deprecated and will be "
|
self._log,
|
||||||
"removed in 3.0.0. Use 'musicbrainz.search_limit' instead."
|
"'musicbrainz.searchlimit' configuration option",
|
||||||
|
"'musicbrainz.search_limit'",
|
||||||
)
|
)
|
||||||
hostname = self.config["host"].as_str()
|
hostname = self.config["host"].as_str()
|
||||||
https = self.config["https"].get(bool)
|
https = self.config["https"].get(bool)
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,7 @@ from os.path import relpath
|
||||||
from beets import config, ui, util
|
from beets import config, ui, util
|
||||||
from beets.plugins import BeetsPlugin
|
from beets.plugins import BeetsPlugin
|
||||||
from beets.ui import Subcommand
|
from beets.ui import Subcommand
|
||||||
from beets.ui.commands import PromptChoice
|
from beets.util import PromptChoice, get_temp_filename
|
||||||
from beets.util import get_temp_filename
|
|
||||||
|
|
||||||
# Indicate where arguments should be inserted into the command string.
|
# Indicate where arguments should be inserted into the command string.
|
||||||
# If this is missing, they're placed at the end.
|
# If this is missing, they're placed at the end.
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ shall expose to the user:
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
from beets.plugins import BeetsPlugin
|
from beets.plugins import BeetsPlugin
|
||||||
from beets.ui.commands import PromptChoice
|
from beets.util import PromptChoice
|
||||||
|
|
||||||
|
|
||||||
class ExamplePlugin(BeetsPlugin):
|
class ExamplePlugin(BeetsPlugin):
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ from beets.test.helper import (
|
||||||
PluginTestCase,
|
PluginTestCase,
|
||||||
TerminalImportMixin,
|
TerminalImportMixin,
|
||||||
)
|
)
|
||||||
from beets.util import displayable_path, syspath
|
from beets.util import PromptChoice, displayable_path, syspath
|
||||||
|
|
||||||
|
|
||||||
class TestPluginRegistration(PluginTestCase):
|
class TestPluginRegistration(PluginTestCase):
|
||||||
|
|
@ -292,8 +292,8 @@ class PromptChoicesTest(TerminalImportMixin, PluginImportTestCase):
|
||||||
|
|
||||||
def return_choices(self, session, task):
|
def return_choices(self, session, task):
|
||||||
return [
|
return [
|
||||||
ui.commands.PromptChoice("f", "Foo", None),
|
PromptChoice("f", "Foo", None),
|
||||||
ui.commands.PromptChoice("r", "baR", None),
|
PromptChoice("r", "baR", None),
|
||||||
]
|
]
|
||||||
|
|
||||||
self.register_plugin(DummyPlugin)
|
self.register_plugin(DummyPlugin)
|
||||||
|
|
@ -328,8 +328,8 @@ class PromptChoicesTest(TerminalImportMixin, PluginImportTestCase):
|
||||||
|
|
||||||
def return_choices(self, session, task):
|
def return_choices(self, session, task):
|
||||||
return [
|
return [
|
||||||
ui.commands.PromptChoice("f", "Foo", None),
|
PromptChoice("f", "Foo", None),
|
||||||
ui.commands.PromptChoice("r", "baR", None),
|
PromptChoice("r", "baR", None),
|
||||||
]
|
]
|
||||||
|
|
||||||
self.register_plugin(DummyPlugin)
|
self.register_plugin(DummyPlugin)
|
||||||
|
|
@ -363,10 +363,10 @@ class PromptChoicesTest(TerminalImportMixin, PluginImportTestCase):
|
||||||
|
|
||||||
def return_choices(self, session, task):
|
def return_choices(self, session, task):
|
||||||
return [
|
return [
|
||||||
ui.commands.PromptChoice("a", "A foo", None), # dupe
|
PromptChoice("a", "A foo", None), # dupe
|
||||||
ui.commands.PromptChoice("z", "baZ", None), # ok
|
PromptChoice("z", "baZ", None), # ok
|
||||||
ui.commands.PromptChoice("z", "Zupe", None), # dupe
|
PromptChoice("z", "Zupe", None), # dupe
|
||||||
ui.commands.PromptChoice("z", "Zoo", None),
|
PromptChoice("z", "Zoo", None),
|
||||||
] # dupe
|
] # dupe
|
||||||
|
|
||||||
self.register_plugin(DummyPlugin)
|
self.register_plugin(DummyPlugin)
|
||||||
|
|
@ -399,7 +399,7 @@ class PromptChoicesTest(TerminalImportMixin, PluginImportTestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
def return_choices(self, session, task):
|
def return_choices(self, session, task):
|
||||||
return [ui.commands.PromptChoice("f", "Foo", self.foo)]
|
return [PromptChoice("f", "Foo", self.foo)]
|
||||||
|
|
||||||
def foo(self, session, task):
|
def foo(self, session, task):
|
||||||
pass
|
pass
|
||||||
|
|
@ -441,7 +441,7 @@ class PromptChoicesTest(TerminalImportMixin, PluginImportTestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
def return_choices(self, session, task):
|
def return_choices(self, session, task):
|
||||||
return [ui.commands.PromptChoice("f", "Foo", self.foo)]
|
return [PromptChoice("f", "Foo", self.foo)]
|
||||||
|
|
||||||
def foo(self, session, task):
|
def foo(self, session, task):
|
||||||
return Action.SKIP
|
return Action.SKIP
|
||||||
|
|
@ -543,3 +543,39 @@ class TestDeprecationCopy:
|
||||||
assert hasattr(LegacyMetadataPlugin, "data_source_mismatch_penalty")
|
assert hasattr(LegacyMetadataPlugin, "data_source_mismatch_penalty")
|
||||||
assert hasattr(LegacyMetadataPlugin, "_extract_id")
|
assert hasattr(LegacyMetadataPlugin, "_extract_id")
|
||||||
assert hasattr(LegacyMetadataPlugin, "get_artist")
|
assert hasattr(LegacyMetadataPlugin, "get_artist")
|
||||||
|
|
||||||
|
|
||||||
|
class TestMusicBrainzPluginLoading:
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def config(self):
|
||||||
|
_config = config
|
||||||
|
_config.sources = []
|
||||||
|
_config.read(user=False, defaults=True)
|
||||||
|
return _config
|
||||||
|
|
||||||
|
def test_default(self):
|
||||||
|
assert "musicbrainz" in plugins.get_plugin_names()
|
||||||
|
|
||||||
|
def test_other_plugin_enabled(self, config):
|
||||||
|
config["plugins"] = ["anything"]
|
||||||
|
|
||||||
|
assert "musicbrainz" not in plugins.get_plugin_names()
|
||||||
|
|
||||||
|
def test_deprecated_enabled(self, config, caplog):
|
||||||
|
config["plugins"] = ["anything"]
|
||||||
|
config["musicbrainz"]["enabled"] = True
|
||||||
|
|
||||||
|
assert "musicbrainz" in plugins.get_plugin_names()
|
||||||
|
assert (
|
||||||
|
"musicbrainz.enabled' configuration option is deprecated"
|
||||||
|
in caplog.text
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_deprecated_disabled(self, config, caplog):
|
||||||
|
config["musicbrainz"]["enabled"] = False
|
||||||
|
|
||||||
|
assert "musicbrainz" not in plugins.get_plugin_names()
|
||||||
|
assert (
|
||||||
|
"musicbrainz.enabled' configuration option is deprecated"
|
||||||
|
in caplog.text
|
||||||
|
)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue