Skip to content

Commit bde4397

Browse files
committed
plugins: actually load plugins in load_plugins()
Instead of the first call to find_plugins(). This could change behavior slighly, since plugin load (and errors that happen in the process) could occur earlier now. But: - I'd argue this is better anyway, since plugin load errors now happen in the initial call to load_plugins() instead of somewhere in the middle of the first operation that uses plugins. - In our ui code, both happen immediately after each other anyway: ui._setup calls 1) ui._load_plugins -> plugins.load_plugins 2) plugins.types, plugins.named_queries, ...
1 parent 8df222f commit bde4397

File tree

4 files changed

+39
-50
lines changed

4 files changed

+39
-50
lines changed

beets/plugins.py

Lines changed: 30 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
import sys
2323
import traceback
2424
from collections import defaultdict
25-
from collections.abc import Iterable
25+
from collections.abc import Iterable, Iterator
2626
from functools import wraps
2727
from importlib import import_module
2828
from typing import (
@@ -340,7 +340,7 @@ def helper(func: TFunc[Item]) -> TFunc[Item]:
340340
return helper
341341

342342

343-
_classes: set[type[BeetsPlugin]] = set()
343+
_instances: dict[type[BeetsPlugin], BeetsPlugin] = {}
344344

345345

346346
def load_plugins(names: Sequence[str] = ()) -> None:
@@ -349,6 +349,8 @@ def load_plugins(names: Sequence[str] = ()) -> None:
349349
package in sys.path; the module indicated should contain the
350350
BeetsPlugin subclasses desired.
351351
"""
352+
classes = set()
353+
352354
for name in names:
353355
try:
354356
mod = import_module(f".{name}", package=PLUGIN_NAMESPACE)
@@ -363,43 +365,39 @@ def load_plugins(names: Sequence[str] = ()) -> None:
363365
)
364366
continue
365367

366-
for _name, obj in inspect.getmembers(mod, inspect.isclass):
368+
for _name, cls in inspect.getmembers(mod, inspect.isclass):
367369
if (
368-
issubclass(obj, BeetsPlugin)
369-
and obj != BeetsPlugin
370-
and obj != MetadataSourcePlugin
370+
issubclass(cls, BeetsPlugin)
371+
and cls != BeetsPlugin
372+
and cls != MetadataSourcePlugin
373+
and cls not in _instances
371374
):
372-
_classes.add(obj)
375+
classes.add(cls)
373376

377+
for cls in classes:
378+
_register_plugin(cls)
374379

375-
_instances: dict[type[BeetsPlugin], BeetsPlugin] = {}
376380

381+
def _register_plugin(cls: type[BeetsPlugin]) -> None:
382+
if cls in _instances:
383+
return
377384

378-
def find_plugins() -> list[BeetsPlugin]:
379-
"""Returns a list of BeetsPlugin subclass instances from all
380-
currently loaded beets plugins. Loads the default plugin set
381-
first.
385+
try:
386+
_instances[cls] = cls()
387+
except Exception:
388+
log.error(
389+
"failed to initialize plugin class {}.{}",
390+
cls.__module__,
391+
cls.__qualname__,
392+
)
393+
raise
394+
395+
396+
def find_plugins() -> Iterator[BeetsPlugin]:
397+
"""Returns an iterator of BeetsPlugin subclass instances from all
398+
currently loaded beets plugins.
382399
"""
383-
if _instances:
384-
# After the first call, use cached instances for performance reasons.
385-
# See https://github.com/beetbox/beets/pull/3810
386-
return list(_instances.values())
387-
388-
plugins = []
389-
for cls in _classes:
390-
# Only instantiate each plugin class once.
391-
if cls not in _instances:
392-
try:
393-
_instances[cls] = cls()
394-
except Exception:
395-
log.error(
396-
"failed to initialize plugin class {}.{}",
397-
cls.__module__,
398-
cls.__qualname__,
399-
)
400-
raise
401-
plugins.append(_instances[cls])
402-
return plugins
400+
yield from _instances.values()
403401

404402

405403
# Communication with plugins.

beets/test/helper.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -497,7 +497,6 @@ def unload_plugins(self) -> None:
497497
for plugin_class in beets.plugins._instances:
498498
plugin_class.listeners = None
499499
self.config["plugins"] = []
500-
beets.plugins._classes = set()
501500
beets.plugins._instances = {}
502501
Item._types = getattr(Item, "_original_types", {})
503502
Album._types = getattr(Album, "_original_types", {})

test/test_plugins.py

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -43,30 +43,22 @@
4343

4444

4545
class PluginLoaderTestCase(BasePluginTestCase):
46-
def setup_plugin_loader(self):
47-
# FIXME the mocking code is horrific, but this is the lowest and
48-
# earliest level of the plugin mechanism we can hook into.
49-
self._plugin_loader_patch = patch("beets.plugins.load_plugins")
50-
self._plugin_classes = set()
51-
load_plugins = self._plugin_loader_patch.start()
46+
"""A test case that adds an additional plugin to beets' plugin method
47+
resolution mechanism.
5248
53-
def myload(names=()):
54-
plugins._classes.update(self._plugin_classes)
55-
56-
load_plugins.side_effect = myload
57-
58-
def teardown_plugin_loader(self):
59-
self._plugin_loader_patch.stop()
49+
It does _not_ go through the full `plugins.load_plugins` code, which would
50+
require
51+
"""
6052

6153
def register_plugin(self, plugin_class):
62-
self._plugin_classes.add(plugin_class)
54+
plugins._register_plugin(plugin_class)
6355

6456
def setUp(self):
65-
self.setup_plugin_loader()
6657
super().setUp()
58+
self._prev_plugin_instances = plugins._instances.copy()
6759

6860
def tearDown(self):
69-
self.teardown_plugin_loader()
61+
plugins._instances = self._prev_plugin_instances
7062
super().tearDown()
7163

7264

test/test_ui.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1063,7 +1063,7 @@ def test_cli_config_file_loads_plugin_commands(self):
10631063
file.write("plugins: test")
10641064

10651065
self.run_command("--config", cli_config_path, "plugin", lib=None)
1066-
assert plugins.find_plugins()[0].is_test_plugin
1066+
assert next(iter(plugins.find_plugins())).is_test_plugin
10671067
self.unload_plugins()
10681068

10691069
def test_beetsdir_config(self):

0 commit comments

Comments
 (0)