mirror of
https://github.com/beetbox/beets.git
synced 2025-12-06 08:39:17 +01:00
Use only plugins/disabled_plugins config in plugin loading
This commit is contained in:
parent
e9feb41709
commit
54b31d01e9
5 changed files with 58 additions and 41 deletions
|
|
@ -31,6 +31,7 @@ from typing_extensions import ParamSpec
|
||||||
|
|
||||||
import beets
|
import beets
|
||||||
from beets import logging
|
from beets import logging
|
||||||
|
from beets.util import unique_list
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from collections.abc import Callable, Iterable, Sequence
|
from collections.abc import Callable, Iterable, Sequence
|
||||||
|
|
@ -275,9 +276,7 @@ class BeetsPlugin(metaclass=abc.ABCMeta):
|
||||||
return helper
|
return helper
|
||||||
|
|
||||||
|
|
||||||
def get_plugin_names(
|
def get_plugin_names() -> list[str]:
|
||||||
include: set[str] | None = None, exclude: set[str] | None = None
|
|
||||||
) -> set[str]:
|
|
||||||
"""Discover and return the set of plugin names to be loaded.
|
"""Discover and return the set of plugin names to be loaded.
|
||||||
|
|
||||||
Configures the plugin search paths and resolves the final set of plugins
|
Configures the plugin search paths and resolves the final set of plugins
|
||||||
|
|
@ -298,14 +297,18 @@ def get_plugin_names(
|
||||||
# For backwards compatibility, also support plugin paths that
|
# For backwards compatibility, also support plugin paths that
|
||||||
# *contain* a `beetsplug` package.
|
# *contain* a `beetsplug` package.
|
||||||
sys.path += paths
|
sys.path += paths
|
||||||
plugins = include or set(beets.config["plugins"].as_str_seq())
|
plugins = unique_list(beets.config["plugins"].as_str_seq())
|
||||||
# TODO: Remove in v3.0.0
|
# TODO: Remove in v3.0.0
|
||||||
if "musicbrainz" in beets.config and beets.config["musicbrainz"].get().get(
|
if (
|
||||||
"enabled"
|
"musicbrainz" not in plugins
|
||||||
|
and "musicbrainz" in beets.config
|
||||||
|
and beets.config["musicbrainz"].get().get("enabled")
|
||||||
):
|
):
|
||||||
plugins.add("musicbrainz")
|
plugins.append("musicbrainz")
|
||||||
|
|
||||||
return plugins - (exclude or set())
|
beets.config.add({"disabled_plugins": []})
|
||||||
|
disabled_plugins = set(beets.config["disabled_plugins"].as_str_seq())
|
||||||
|
return [p for p in plugins if p not in disabled_plugins]
|
||||||
|
|
||||||
|
|
||||||
def _get_plugin(name: str) -> BeetsPlugin | None:
|
def _get_plugin(name: str) -> BeetsPlugin | None:
|
||||||
|
|
@ -342,7 +345,7 @@ def _get_plugin(name: str) -> BeetsPlugin | None:
|
||||||
_instances: list[BeetsPlugin] = []
|
_instances: list[BeetsPlugin] = []
|
||||||
|
|
||||||
|
|
||||||
def load_plugins(*args, **kwargs) -> None:
|
def load_plugins() -> None:
|
||||||
"""Initialize the plugin system by loading all configured plugins.
|
"""Initialize the plugin system by loading all configured plugins.
|
||||||
|
|
||||||
Performs one-time plugin discovery and instantiation, storing loaded plugin
|
Performs one-time plugin discovery and instantiation, storing loaded plugin
|
||||||
|
|
@ -350,7 +353,7 @@ def load_plugins(*args, **kwargs) -> None:
|
||||||
to notify other components.
|
to notify other components.
|
||||||
"""
|
"""
|
||||||
if not _instances:
|
if not _instances:
|
||||||
names = get_plugin_names(*args, **kwargs)
|
names = get_plugin_names()
|
||||||
log.info("Loading plugins: {}", ", ".join(sorted(names)))
|
log.info("Loading plugins: {}", ", ".join(sorted(names)))
|
||||||
_instances.extend(filter(None, map(_get_plugin, names)))
|
_instances.extend(filter(None, map(_get_plugin, names)))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1579,13 +1579,7 @@ def _setup(
|
||||||
"""
|
"""
|
||||||
config = _configure(options)
|
config = _configure(options)
|
||||||
|
|
||||||
def _parse_list(option: str | None) -> set[str]:
|
plugins.load_plugins()
|
||||||
return set((option or "").split(",")) - {""}
|
|
||||||
|
|
||||||
plugins.load_plugins(
|
|
||||||
include=_parse_list(options.plugins),
|
|
||||||
exclude=_parse_list(options.exclude),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Get the default subcommands.
|
# Get the default subcommands.
|
||||||
from beets.ui.commands import default_commands
|
from beets.ui.commands import default_commands
|
||||||
|
|
@ -1704,16 +1698,31 @@ def _raw_main(args: list[str], lib=None) -> None:
|
||||||
parser.add_option(
|
parser.add_option(
|
||||||
"-c", "--config", dest="config", help="path to configuration file"
|
"-c", "--config", dest="config", help="path to configuration file"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def parse_csl_callback(
|
||||||
|
option: optparse.Option, _, value: str, parser: SubcommandsOptionParser
|
||||||
|
):
|
||||||
|
"""Parse a comma-separated list of values."""
|
||||||
|
setattr(
|
||||||
|
parser.values,
|
||||||
|
option.dest, # type: ignore[arg-type]
|
||||||
|
list(filter(None, value.split(","))),
|
||||||
|
)
|
||||||
|
|
||||||
parser.add_option(
|
parser.add_option(
|
||||||
"-p",
|
"-p",
|
||||||
"--plugins",
|
"--plugins",
|
||||||
dest="plugins",
|
dest="plugins",
|
||||||
|
action="callback",
|
||||||
|
callback=parse_csl_callback,
|
||||||
help="a comma-separated list of plugins to load",
|
help="a comma-separated list of plugins to load",
|
||||||
)
|
)
|
||||||
parser.add_option(
|
parser.add_option(
|
||||||
"-P",
|
"-P",
|
||||||
"--disable-plugins",
|
"--disable-plugins",
|
||||||
dest="exclude",
|
dest="disabled_plugins",
|
||||||
|
action="callback",
|
||||||
|
callback=parse_csl_callback,
|
||||||
help="a comma-separated list of plugins to disable",
|
help="a comma-separated list of plugins to disable",
|
||||||
)
|
)
|
||||||
parser.add_option(
|
parser.add_option(
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ class LoadExtPlugin(BeetsPlugin):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
if not Database.supports_extensions:
|
if not Database.supports_extensions:
|
||||||
self._log.warn(
|
self._log.warning(
|
||||||
"loadext is enabled but the current SQLite "
|
"loadext is enabled but the current SQLite "
|
||||||
"installation does not support extensions"
|
"installation does not support extensions"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -97,6 +97,9 @@ For plugin developers:
|
||||||
type checking for downstream users of the beets API.
|
type checking for downstream users of the beets API.
|
||||||
* ``plugins.find_plugins`` function does not anymore load plugins. You need to
|
* ``plugins.find_plugins`` function does not anymore load plugins. You need to
|
||||||
explicitly call ``plugins.load_plugins()`` to load them.
|
explicitly call ``plugins.load_plugins()`` to load them.
|
||||||
|
* ``plugins.load_plugins`` function does not anymore accept the list of plugins
|
||||||
|
to load. Instead, it loads all plugins that are configured by
|
||||||
|
:ref:`plugins-config` configuration.
|
||||||
|
|
||||||
Other changes:
|
Other changes:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -91,9 +91,6 @@ class PluginImportTestCase(ImportHelper, PluginTestCase):
|
||||||
|
|
||||||
|
|
||||||
class EventsTest(PluginImportTestCase):
|
class EventsTest(PluginImportTestCase):
|
||||||
def setUp(self):
|
|
||||||
super().setUp()
|
|
||||||
|
|
||||||
def test_import_task_created(self):
|
def test_import_task_created(self):
|
||||||
self.importer = self.setup_importer(pretend=True)
|
self.importer = self.setup_importer(pretend=True)
|
||||||
|
|
||||||
|
|
@ -472,10 +469,22 @@ def get_available_plugins():
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class TestImportAllPlugins(PluginMixin):
|
class TestImportPlugin(PluginMixin):
|
||||||
def unimport_plugins(self):
|
@pytest.fixture(params=get_available_plugins())
|
||||||
|
def plugin_name(self, request):
|
||||||
|
"""Fixture to provide the name of each available plugin."""
|
||||||
|
name = request.param
|
||||||
|
|
||||||
|
# skip gstreamer plugins on windows
|
||||||
|
gstreamer_plugins = {"bpd", "replaygain"}
|
||||||
|
if sys.platform == "win32" and name in gstreamer_plugins:
|
||||||
|
pytest.skip(f"GStreamer is not available on Windows: {name}")
|
||||||
|
|
||||||
|
return name
|
||||||
|
|
||||||
|
def unload_plugins(self):
|
||||||
"""Unimport plugins before each test to avoid conflicts."""
|
"""Unimport plugins before each test to avoid conflicts."""
|
||||||
self.unload_plugins()
|
super().unload_plugins()
|
||||||
for mod in list(sys.modules):
|
for mod in list(sys.modules):
|
||||||
if mod.startswith("beetsplug."):
|
if mod.startswith("beetsplug."):
|
||||||
del sys.modules[mod]
|
del sys.modules[mod]
|
||||||
|
|
@ -483,28 +492,21 @@ class TestImportAllPlugins(PluginMixin):
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def cleanup(self):
|
def cleanup(self):
|
||||||
"""Ensure plugins are unimported before and after each test."""
|
"""Ensure plugins are unimported before and after each test."""
|
||||||
self.unimport_plugins()
|
self.unload_plugins()
|
||||||
yield
|
yield
|
||||||
self.unimport_plugins()
|
self.unload_plugins()
|
||||||
|
|
||||||
@pytest.mark.skipif(
|
@pytest.mark.skipif(
|
||||||
os.environ.get("GITHUB_ACTIONS") != "true",
|
os.environ.get("GITHUB_ACTIONS") != "true",
|
||||||
reason="Requires all dependencies to be installed, "
|
reason=(
|
||||||
+ "which we can't guarantee in the local environment.",
|
"Requires all dependencies to be installed, which we can't"
|
||||||
|
" guarantee in the local environment."
|
||||||
|
),
|
||||||
)
|
)
|
||||||
@pytest.mark.parametrize("plugin_name", get_available_plugins())
|
def test_import_plugin(self, caplog, plugin_name):
|
||||||
def test_import_plugin(self, caplog, plugin_name): #
|
"""Test that a plugin is importable without an error."""
|
||||||
"""Test that a plugin is importable without an error using the
|
|
||||||
load_plugins function."""
|
|
||||||
|
|
||||||
# skip gstreamer plugins on windows
|
|
||||||
gstreamer_plugins = ["bpd", "replaygain"]
|
|
||||||
if sys.platform == "win32" and plugin_name in gstreamer_plugins:
|
|
||||||
pytest.xfail("GStreamer is not available on Windows: {plugin_name}")
|
|
||||||
|
|
||||||
caplog.set_level(logging.WARNING)
|
caplog.set_level(logging.WARNING)
|
||||||
caplog.clear()
|
self.load_plugins(plugin_name)
|
||||||
plugins.load_plugins(include={plugin_name})
|
|
||||||
|
|
||||||
assert "PluginImportError" not in caplog.text, (
|
assert "PluginImportError" not in caplog.text, (
|
||||||
f"Plugin '{plugin_name}' has issues during import."
|
f"Plugin '{plugin_name}' has issues during import."
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue