Skip to content

Plugins loaded multiple times #6033

@semohr

Description

@semohr

Hi @semohr! I wanted to create a separate issue but I'll hijack this one.

Just the other day I was experimenting with plugins and beets[chroma] but could not get it to load up:

$ beet config -e
...
plugins: musicbrainz missing fetchart embedart lyrics lastgenre chroma
...

Then I noticed the musicbrainz plugin listed twice in the --version command:

$ beet -v --version
...
plugin paths: []
Loading plugins: chroma, embedart, fetchart, lastgenre, lyrics, missing, musicbrainz
...
Sending event: pluginload
...
Sending event: library_opened
beets version 2.4.0
Python version 3.12.11
plugins: embedart, fetchart, lastgenre, lyrics, missing, musicbrainz, musicbrainz
Sending event: cli_exit

After a dive into beets/plugins.py and its _get_plugin I came up with this fix (notice the and obj.__name__ == "MusicBrainzPlugin" line):

loadedMusicBrainzOnce = False

def _get_plugin(name: str) -> BeetsPlugin | None:
    """Dynamically load and instantiate a plugin class by name.

    Attempts to import the plugin module, locate the appropriate plugin class
    within it, and return an instance. Handles import failures gracefully and
    logs warnings for missing plugins or loading errors.
    """
    global loadedMusicBrainzOnce

    try:
        try:
            namespace = __import__(f"{PLUGIN_NAMESPACE}.{name}", None, None)
        except Exception as exc:
            raise PluginImportError(name) from exc

        for obj in getattr(namespace, name).__dict__.values():
            if (
                inspect.isclass(obj)
                and not isinstance(
                    obj, GenericAlias
                )  # seems to be needed for python <= 3.9 only
                and issubclass(obj, BeetsPlugin)
                and obj != BeetsPlugin
                and not inspect.isabstract(obj)
            ):
                if obj.__name__ == "MusicBrainzPlugin":
                    if loadedMusicBrainzOnce:
                        continue
                    else:
                        loadedMusicBrainzOnce = True
                return obj()

    except Exception:
        log.warning("** error loading plugin {}", name, exc_info=True)

    return None

In other words, it looks like the Chroma plugin file is somehow exporting the MusicBrainz plugin as well, which in turn gets picked first by the loader and loaded twice.

I hope this helps with the real fix.

  • btw, using venv with Python 3.12.11 on Ubuntu 24.04.

Edit: updated the code to allow loading the MusicBrainzPlugin once.
This way my --version command looks like this:

$ beet -v --version
...
plugin paths: []
Loading plugins: chroma, embedart, fetchart, lastgenre, lyrics, missing, musicbrainz
...
Sending event: pluginload
...
Sending event: library_opened
beets version 2.4.0
Python version 3.12.11
plugins: chroma, embedart, fetchart, lastgenre, lyrics, missing, musicbrainz
Sending event: cli_exit

Originally posted by @OancaAndrei in #6023

Metadata

Metadata

Assignees

No one assigned

    Labels

    corePull requests that modify the beets core `beets`pluginPull requests that are plugins related

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions