mirror of
https://github.com/beetbox/beets.git
synced 2025-12-06 16:42:42 +01:00
Load the last plugin class found in the namespace (#6100)
- Modified `_get_plugin` function to use `reversed()` when iterating through `namespace.__dict__.values()` - This ensures that we load _the last_ plugin class found in the namespace. Fixes #6093
This commit is contained in:
commit
ecea47320c
7 changed files with 47 additions and 19 deletions
|
|
@ -228,9 +228,9 @@ class BeetsPlugin(metaclass=abc.ABCMeta):
|
||||||
# In order to verify the config we need to make sure the plugin is fully
|
# In order to verify the config we need to make sure the plugin is fully
|
||||||
# configured (plugins usually add the default configuration *after*
|
# configured (plugins usually add the default configuration *after*
|
||||||
# calling super().__init__()).
|
# calling super().__init__()).
|
||||||
self.register_listener("pluginload", self.verify_config)
|
self.register_listener("pluginload", self._verify_config)
|
||||||
|
|
||||||
def verify_config(self, *_, **__) -> None:
|
def _verify_config(self, *_, **__) -> None:
|
||||||
"""Verify plugin configuration.
|
"""Verify plugin configuration.
|
||||||
|
|
||||||
If deprecated 'source_weight' option is explicitly set by the user, they
|
If deprecated 'source_weight' option is explicitly set by the user, they
|
||||||
|
|
@ -422,6 +422,12 @@ def _get_plugin(name: str) -> BeetsPlugin | None:
|
||||||
Attempts to import the plugin module, locate the appropriate plugin class
|
Attempts to import the plugin module, locate the appropriate plugin class
|
||||||
within it, and return an instance. Handles import failures gracefully and
|
within it, and return an instance. Handles import failures gracefully and
|
||||||
logs warnings for missing plugins or loading errors.
|
logs warnings for missing plugins or loading errors.
|
||||||
|
|
||||||
|
Note we load the *last* plugin class found in the plugin namespace. This
|
||||||
|
allows plugins to define helper classes that inherit from BeetsPlugin
|
||||||
|
without those being loaded as the main plugin class.
|
||||||
|
|
||||||
|
Returns None if the plugin could not be loaded for any reason.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
try:
|
try:
|
||||||
|
|
@ -429,7 +435,7 @@ def _get_plugin(name: str) -> BeetsPlugin | None:
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
raise PluginImportError(name) from exc
|
raise PluginImportError(name) from exc
|
||||||
|
|
||||||
for obj in namespace.__dict__.values():
|
for obj in reversed(namespace.__dict__.values()):
|
||||||
if (
|
if (
|
||||||
inspect.isclass(obj)
|
inspect.isclass(obj)
|
||||||
and not isinstance(
|
and not isinstance(
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,9 @@ New features:
|
||||||
|
|
||||||
Bug fixes:
|
Bug fixes:
|
||||||
|
|
||||||
|
- |BeetsPlugin|: load the last plugin class defined in the plugin namespace.
|
||||||
|
:bug:`6093`
|
||||||
|
|
||||||
For packagers:
|
For packagers:
|
||||||
|
|
||||||
- Fixed dynamic versioning install not disabled for source distribution builds.
|
- Fixed dynamic versioning install not disabled for source distribution builds.
|
||||||
|
|
@ -23,7 +26,7 @@ For packagers:
|
||||||
Other changes:
|
Other changes:
|
||||||
|
|
||||||
- Removed outdated mailing list contact information from the documentation
|
- Removed outdated mailing list contact information from the documentation
|
||||||
(:bug:`5462`).
|
:bug:`5462`.
|
||||||
- :doc:`guides/main`: Modernized the *Getting Started* guide with tabbed
|
- :doc:`guides/main`: Modernized the *Getting Started* guide with tabbed
|
||||||
sections and dropdown menus. Installation instructions have been streamlined,
|
sections and dropdown menus. Installation instructions have been streamlined,
|
||||||
and a new subpage now provides additional setup details.
|
and a new subpage now provides additional setup details.
|
||||||
|
|
@ -66,7 +69,7 @@ Bug fixes:
|
||||||
- :doc:`plugins/discogs` Fixed inconsistency in stripping disambiguation from
|
- :doc:`plugins/discogs` Fixed inconsistency in stripping disambiguation from
|
||||||
artists but not labels. :bug:`5366`
|
artists but not labels. :bug:`5366`
|
||||||
- :doc:`plugins/chroma` :doc:`plugins/bpsync` Fix plugin loading issue caused by
|
- :doc:`plugins/chroma` :doc:`plugins/bpsync` Fix plugin loading issue caused by
|
||||||
an import of another :class:`beets.plugins.BeetsPlugin` class. :bug:`6033`
|
an import of another |BeetsPlugin| class. :bug:`6033`
|
||||||
- :doc:`/plugins/fromfilename`: Fix :bug:`5218`, improve the code (refactor
|
- :doc:`/plugins/fromfilename`: Fix :bug:`5218`, improve the code (refactor
|
||||||
regexps, allow for more cases, add some logging), add tests.
|
regexps, allow for more cases, add some logging), add tests.
|
||||||
- Metadata source plugins: Fixed data source penalty calculation that was
|
- Metadata source plugins: Fixed data source penalty calculation that was
|
||||||
|
|
@ -188,8 +191,8 @@ For plugin developers:
|
||||||
art sources might need to be adapted.
|
art sources might need to be adapted.
|
||||||
- We split the responsibilities of plugins into two base classes
|
- We split the responsibilities of plugins into two base classes
|
||||||
|
|
||||||
1. :class:`beets.plugins.BeetsPlugin` is the base class for all plugins, any
|
1. |BeetsPlugin| is the base class for all plugins, any plugin needs to
|
||||||
plugin needs to inherit from this class.
|
inherit from this class.
|
||||||
2. :class:`beets.metadata_plugin.MetadataSourcePlugin` allows plugins to act
|
2. :class:`beets.metadata_plugin.MetadataSourcePlugin` allows plugins to act
|
||||||
like metadata sources. E.g. used by the MusicBrainz plugin. All plugins in
|
like metadata sources. E.g. used by the MusicBrainz plugin. All plugins in
|
||||||
the beets repo are opted into this class where applicable. If you are
|
the beets repo are opted into this class where applicable. If you are
|
||||||
|
|
@ -5072,7 +5075,7 @@ BPD). To "upgrade" an old database, you can use the included ``albumify`` plugin
|
||||||
list of plugin names) and ``pluginpath`` (a colon-separated list of
|
list of plugin names) and ``pluginpath`` (a colon-separated list of
|
||||||
directories to search beyond ``sys.path``). Plugins are just Python modules
|
directories to search beyond ``sys.path``). Plugins are just Python modules
|
||||||
under the ``beetsplug`` namespace package containing subclasses of
|
under the ``beetsplug`` namespace package containing subclasses of
|
||||||
``beets.plugins.BeetsPlugin``. See `the beetsplug directory`_ for examples or
|
|BeetsPlugin|. See `the beetsplug directory`_ for examples or
|
||||||
:doc:`/plugins/index` for instructions.
|
:doc:`/plugins/index` for instructions.
|
||||||
- As a consequence of adding album art, the database was significantly
|
- As a consequence of adding album art, the database was significantly
|
||||||
refactored to keep track of some information at an album (rather than item)
|
refactored to keep track of some information at an album (rather than item)
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,7 @@ man_pages = [
|
||||||
rst_epilog = """
|
rst_epilog = """
|
||||||
.. |Album| replace:: :class:`~beets.library.models.Album`
|
.. |Album| replace:: :class:`~beets.library.models.Album`
|
||||||
.. |AlbumInfo| replace:: :class:`beets.autotag.hooks.AlbumInfo`
|
.. |AlbumInfo| replace:: :class:`beets.autotag.hooks.AlbumInfo`
|
||||||
|
.. |BeetsPlugin| replace:: :class:`beets.plugins.BeetsPlugin`
|
||||||
.. |ImportSession| replace:: :class:`~beets.importer.session.ImportSession`
|
.. |ImportSession| replace:: :class:`~beets.importer.session.ImportSession`
|
||||||
.. |ImportTask| replace:: :class:`~beets.importer.tasks.ImportTask`
|
.. |ImportTask| replace:: :class:`~beets.importer.tasks.ImportTask`
|
||||||
.. |Item| replace:: :class:`~beets.library.models.Item`
|
.. |Item| replace:: :class:`~beets.library.models.Item`
|
||||||
|
|
|
||||||
|
|
@ -95,9 +95,9 @@ starting points include:
|
||||||
Migration guidance
|
Migration guidance
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
Older metadata plugins that extend :py:class:`beets.plugins.BeetsPlugin` should
|
Older metadata plugins that extend |BeetsPlugin| should be migrated to
|
||||||
be migrated to :py:class:`MetadataSourcePlugin`. Legacy support will be removed
|
:py:class:`MetadataSourcePlugin`. Legacy support will be removed in **beets
|
||||||
in **beets v3.0.0**.
|
v3.0.0**.
|
||||||
|
|
||||||
.. seealso::
|
.. seealso::
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,8 +40,8 @@ or your plugin subpackage
|
||||||
anymore.
|
anymore.
|
||||||
|
|
||||||
The meat of your plugin goes in ``myawesomeplugin.py``. Every plugin has to
|
The meat of your plugin goes in ``myawesomeplugin.py``. Every plugin has to
|
||||||
extend the :class:`beets.plugins.BeetsPlugin` abstract base class [2]_ . For
|
extend the |BeetsPlugin| abstract base class [2]_ . For instance, a minimal
|
||||||
instance, a minimal plugin without any functionality would look like this:
|
plugin without any functionality would look like this:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
|
|
@ -52,6 +52,12 @@ instance, a minimal plugin without any functionality would look like this:
|
||||||
class MyAwesomePlugin(BeetsPlugin):
|
class MyAwesomePlugin(BeetsPlugin):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
.. attention::
|
||||||
|
|
||||||
|
If your plugin is composed of intermediate |BeetsPlugin| subclasses, make
|
||||||
|
sure that your plugin is defined *last* in the namespace. We only load the
|
||||||
|
last subclass of |BeetsPlugin| we find in your plugin namespace.
|
||||||
|
|
||||||
To use your new plugin, you need to package [3]_ your plugin and install it into
|
To use your new plugin, you need to package [3]_ your plugin and install it into
|
||||||
your ``beets`` (virtual) environment. To enable your plugin, add it it to the
|
your ``beets`` (virtual) environment. To enable your plugin, add it it to the
|
||||||
beets configuration
|
beets configuration
|
||||||
|
|
|
||||||
|
|
@ -77,10 +77,10 @@ pluginpath
|
||||||
~~~~~~~~~~
|
~~~~~~~~~~
|
||||||
|
|
||||||
Directories to search for plugins. Each Python file or directory in a plugin
|
Directories to search for plugins. Each Python file or directory in a plugin
|
||||||
path represents a plugin and should define a subclass of :class:`BeetsPlugin`. A
|
path represents a plugin and should define a subclass of |BeetsPlugin|. A plugin
|
||||||
plugin can then be loaded by adding the filename to the ``plugins``
|
can then be loaded by adding the plugin name to the ``plugins`` configuration.
|
||||||
configuration. The plugin path can either be a single string or a list of
|
The plugin path can either be a single string or a list of strings---so, if you
|
||||||
strings---so, if you have multiple paths, format them as a YAML list like so:
|
have multiple paths, format them as a YAML list like so:
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ from packaging.version import Version, parse
|
||||||
from sphinx.ext import intersphinx
|
from sphinx.ext import intersphinx
|
||||||
from typing_extensions import TypeAlias
|
from typing_extensions import TypeAlias
|
||||||
|
|
||||||
|
from docs.conf import rst_epilog
|
||||||
|
|
||||||
BASE = Path(__file__).parent.parent.absolute()
|
BASE = Path(__file__).parent.parent.absolute()
|
||||||
PYPROJECT = BASE / "pyproject.toml"
|
PYPROJECT = BASE / "pyproject.toml"
|
||||||
CHANGELOG = BASE / "docs" / "changelog.rst"
|
CHANGELOG = BASE / "docs" / "changelog.rst"
|
||||||
|
|
@ -104,11 +106,21 @@ def create_rst_replacements() -> list[Replacement]:
|
||||||
plugins = "|".join(
|
plugins = "|".join(
|
||||||
r.split("/")[-1] for r in refs if r.startswith("plugins/")
|
r.split("/")[-1] for r in refs if r.startswith("plugins/")
|
||||||
)
|
)
|
||||||
|
explicit_replacements = dict(
|
||||||
|
line.removeprefix(".. ").split(" replace:: ")
|
||||||
|
for line in filter(None, rst_epilog.splitlines())
|
||||||
|
)
|
||||||
return [
|
return [
|
||||||
# Replace Sphinx :ref: and :doc: directives by documentation URLs
|
# Replace explicitly defined substitutions from rst_epilog
|
||||||
|
# |BeetsPlugin| -> :class:`beets.plugins.BeetsPlugin`
|
||||||
|
(
|
||||||
|
r"\|\w[^ ]*\|",
|
||||||
|
lambda m: explicit_replacements.get(m[0], m[0]),
|
||||||
|
),
|
||||||
|
# Replace Sphinx directives by documentation URLs, e.g.,
|
||||||
# :ref:`/plugins/autobpm` -> [AutoBPM Plugin](DOCS/plugins/autobpm.html)
|
# :ref:`/plugins/autobpm` -> [AutoBPM Plugin](DOCS/plugins/autobpm.html)
|
||||||
(
|
(
|
||||||
r":(?:ref|doc):`+(?:([^`<]+)<)?/?([\w./_-]+)>?`+",
|
r":(?:ref|doc|class):`+(?:([^`<]+)<)?/?([\w./_-]+)>?`+",
|
||||||
lambda m: make_ref_link(m[2], m[1]),
|
lambda m: make_ref_link(m[2], m[1]),
|
||||||
),
|
),
|
||||||
# Convert command references to documentation URLs
|
# Convert command references to documentation URLs
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue