mirror of
https://github.com/beetbox/beets.git
synced 2025-12-06 08:39:17 +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
|
||||
# configured (plugins usually add the default configuration *after*
|
||||
# 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.
|
||||
|
||||
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
|
||||
within it, and return an instance. Handles import failures gracefully and
|
||||
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:
|
||||
|
|
@ -429,7 +435,7 @@ def _get_plugin(name: str) -> BeetsPlugin | None:
|
|||
except Exception as exc:
|
||||
raise PluginImportError(name) from exc
|
||||
|
||||
for obj in namespace.__dict__.values():
|
||||
for obj in reversed(namespace.__dict__.values()):
|
||||
if (
|
||||
inspect.isclass(obj)
|
||||
and not isinstance(
|
||||
|
|
|
|||
|
|
@ -15,6 +15,9 @@ New features:
|
|||
|
||||
Bug fixes:
|
||||
|
||||
- |BeetsPlugin|: load the last plugin class defined in the plugin namespace.
|
||||
:bug:`6093`
|
||||
|
||||
For packagers:
|
||||
|
||||
- Fixed dynamic versioning install not disabled for source distribution builds.
|
||||
|
|
@ -23,7 +26,7 @@ For packagers:
|
|||
Other changes:
|
||||
|
||||
- Removed outdated mailing list contact information from the documentation
|
||||
(:bug:`5462`).
|
||||
:bug:`5462`.
|
||||
- :doc:`guides/main`: Modernized the *Getting Started* guide with tabbed
|
||||
sections and dropdown menus. Installation instructions have been streamlined,
|
||||
and a new subpage now provides additional setup details.
|
||||
|
|
@ -66,7 +69,7 @@ Bug fixes:
|
|||
- :doc:`plugins/discogs` Fixed inconsistency in stripping disambiguation from
|
||||
artists but not labels. :bug:`5366`
|
||||
- :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
|
||||
regexps, allow for more cases, add some logging), add tests.
|
||||
- Metadata source plugins: Fixed data source penalty calculation that was
|
||||
|
|
@ -188,8 +191,8 @@ For plugin developers:
|
|||
art sources might need to be adapted.
|
||||
- We split the responsibilities of plugins into two base classes
|
||||
|
||||
1. :class:`beets.plugins.BeetsPlugin` is the base class for all plugins, any
|
||||
plugin needs to inherit from this class.
|
||||
1. |BeetsPlugin| is the base class for all plugins, any plugin needs to
|
||||
inherit from this class.
|
||||
2. :class:`beets.metadata_plugin.MetadataSourcePlugin` allows plugins to act
|
||||
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
|
||||
|
|
@ -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
|
||||
directories to search beyond ``sys.path``). Plugins are just Python modules
|
||||
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.
|
||||
- As a consequence of adding album art, the database was significantly
|
||||
refactored to keep track of some information at an album (rather than item)
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@ man_pages = [
|
|||
rst_epilog = """
|
||||
.. |Album| replace:: :class:`~beets.library.models.Album`
|
||||
.. |AlbumInfo| replace:: :class:`beets.autotag.hooks.AlbumInfo`
|
||||
.. |BeetsPlugin| replace:: :class:`beets.plugins.BeetsPlugin`
|
||||
.. |ImportSession| replace:: :class:`~beets.importer.session.ImportSession`
|
||||
.. |ImportTask| replace:: :class:`~beets.importer.tasks.ImportTask`
|
||||
.. |Item| replace:: :class:`~beets.library.models.Item`
|
||||
|
|
|
|||
|
|
@ -95,9 +95,9 @@ starting points include:
|
|||
Migration guidance
|
||||
------------------
|
||||
|
||||
Older metadata plugins that extend :py:class:`beets.plugins.BeetsPlugin` should
|
||||
be migrated to :py:class:`MetadataSourcePlugin`. Legacy support will be removed
|
||||
in **beets v3.0.0**.
|
||||
Older metadata plugins that extend |BeetsPlugin| should be migrated to
|
||||
:py:class:`MetadataSourcePlugin`. Legacy support will be removed in **beets
|
||||
v3.0.0**.
|
||||
|
||||
.. seealso::
|
||||
|
||||
|
|
|
|||
|
|
@ -40,8 +40,8 @@ or your plugin subpackage
|
|||
anymore.
|
||||
|
||||
The meat of your plugin goes in ``myawesomeplugin.py``. Every plugin has to
|
||||
extend the :class:`beets.plugins.BeetsPlugin` abstract base class [2]_ . For
|
||||
instance, a minimal plugin without any functionality would look like this:
|
||||
extend the |BeetsPlugin| abstract base class [2]_ . For instance, a minimal
|
||||
plugin without any functionality would look like this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
|
@ -52,6 +52,12 @@ instance, a minimal plugin without any functionality would look like this:
|
|||
class MyAwesomePlugin(BeetsPlugin):
|
||||
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
|
||||
your ``beets`` (virtual) environment. To enable your plugin, add it it to the
|
||||
beets configuration
|
||||
|
|
|
|||
|
|
@ -77,10 +77,10 @@ pluginpath
|
|||
~~~~~~~~~~
|
||||
|
||||
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
|
||||
plugin can then be loaded by adding the filename to the ``plugins``
|
||||
configuration. The plugin path can either be a single string or a list of
|
||||
strings---so, if you have multiple paths, format them as a YAML list like so:
|
||||
path represents a plugin and should define a subclass of |BeetsPlugin|. A plugin
|
||||
can then be loaded by adding the plugin name to the ``plugins`` configuration.
|
||||
The plugin path can either be a single string or a list of strings---so, if you
|
||||
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 typing_extensions import TypeAlias
|
||||
|
||||
from docs.conf import rst_epilog
|
||||
|
||||
BASE = Path(__file__).parent.parent.absolute()
|
||||
PYPROJECT = BASE / "pyproject.toml"
|
||||
CHANGELOG = BASE / "docs" / "changelog.rst"
|
||||
|
|
@ -104,11 +106,21 @@ def create_rst_replacements() -> list[Replacement]:
|
|||
plugins = "|".join(
|
||||
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 [
|
||||
# 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)
|
||||
(
|
||||
r":(?:ref|doc):`+(?:([^`<]+)<)?/?([\w./_-]+)>?`+",
|
||||
r":(?:ref|doc|class):`+(?:([^`<]+)<)?/?([\w./_-]+)>?`+",
|
||||
lambda m: make_ref_link(m[2], m[1]),
|
||||
),
|
||||
# Convert command references to documentation URLs
|
||||
|
|
|
|||
Loading…
Reference in a new issue