diff --git a/docs/_templates/autosummary/namedtuple.rst b/docs/_templates/autosummary/namedtuple.rst new file mode 100644 index 000000000..f1ba4e65d --- /dev/null +++ b/docs/_templates/autosummary/namedtuple.rst @@ -0,0 +1,5 @@ +{{ name | escape | underline }} + +.. currentmodule:: {{ module }} + +.. autonamedtuple:: {{ objname }} diff --git a/docs/api/plugins.rst b/docs/api/plugins.rst index 2ce8dbed6..63d64fae4 100644 --- a/docs/api/plugins.rst +++ b/docs/api/plugins.rst @@ -15,3 +15,4 @@ Plugins MetadataSourcePlugin SearchApiMetadataSourcePlugin + SearchParams diff --git a/docs/changelog.rst b/docs/changelog.rst index 18da86627..323fdfcb1 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -21,31 +21,25 @@ Unreleased For plugin developers ~~~~~~~~~~~~~~~~~~~~~ -.. - Other changes - ~~~~~~~~~~~~~ +Other changes +~~~~~~~~~~~~~ + +- API-backed metadata source plugins can now use + :py:class:`~beets.metadata_plugins.SearchApiMetadataSourcePlugin` for shared + search orchestration. Implement provider behavior in + :py:meth:`~beets.metadata_plugins.SearchApiMetadataSourcePlugin.get_search_query_with_filters` + and + :py:meth:`~beets.metadata_plugins.SearchApiMetadataSourcePlugin.get_search_response`. 2.7.1 (March 08, 2026) ---------------------- -.. - New features - ~~~~~~~~~~~~ - Bug fixes ~~~~~~~~~ - Tests that depend on the optional ``langdetect`` package are now skipped when the package is not installed. :bug:`6421` -.. - For plugin developers - ~~~~~~~~~~~~~~~~~~~~~ - -.. - Other changes - ~~~~~~~~~~~~~ - 2.7.0 (March 07, 2026) ---------------------- @@ -141,10 +135,6 @@ Other changes 2.6.2 (February 22, 2026) ------------------------- -.. - New features - ~~~~~~~~~~~~ - Bug fixes ~~~~~~~~~ @@ -163,10 +153,6 @@ Bug fixes - :ref:`config-cmd`: Improved error message when user-configured editor does not exist. :bug:`6176` -.. - For plugin developers - ~~~~~~~~~~~~~~~~~~~~~ - Other changes ~~~~~~~~~~~~~ diff --git a/docs/conf.py b/docs/conf.py index 360fbddac..00ad8dc1d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -33,6 +33,7 @@ extensions = [ "sphinx_copybutton", "conf", "sphinx_toolbox.more_autodoc.autotypeddict", + "sphinx_toolbox.more_autodoc.autonamedtuple", ] autosummary_generate = True diff --git a/docs/dev/plugins/autotagger.rst b/docs/dev/plugins/autotagger.rst index 8b6df6fb5..fd08c8138 100644 --- a/docs/dev/plugins/autotagger.rst +++ b/docs/dev/plugins/autotagger.rst @@ -5,12 +5,13 @@ Extending the Autotagger Beets supports **metadata source plugins**, which allow it to fetch and match metadata from external services (such as Spotify, Discogs, or Deezer). This -guide explains how to build your own metadata source plugin by extending the -:py:class:`MetadataSourcePlugin`. +guide explains how to build your own metadata source plugin by extending either +:py:class:`MetadataSourcePlugin` or :py:class:`SearchApiMetadataSourcePlugin`. These plugins integrate directly with the autotagger, providing candidate metadata during lookups. To implement one, you must subclass -:py:class:`MetadataSourcePlugin` and implement its abstract methods. +:py:class:`MetadataSourcePlugin` (or the search API helper base class) and +implement its abstract methods. Overview -------- @@ -18,7 +19,8 @@ Overview Creating a metadata source plugin is very similar to writing a standard plugin (see :ref:`basic-plugin-setup`). The main difference is that your plugin must: -1. Subclass :py:class:`MetadataSourcePlugin`. +1. Subclass :py:class:`MetadataSourcePlugin` or + :py:class:`SearchApiMetadataSourcePlugin`. 2. Implement all required abstract methods. Here`s a minimal example: @@ -28,7 +30,7 @@ Here`s a minimal example: # beetsplug/myawesomeplugin.py from typing import Sequence from beets.autotag.hooks import Item - from beets.metadata_plugin import MetadataSourcePlugin + from beets.metadata_plugins import MetadataSourcePlugin class MyAwesomePlugin(MetadataSourcePlugin): @@ -47,6 +49,36 @@ Here`s a minimal example: def album_for_id(self, album_id: str): ... +For API-backed metadata sources, prefer +:py:class:`SearchApiMetadataSourcePlugin` to reuse shared search orchestration: + +.. code-block:: python + + from beets.metadata_plugins import SearchApiMetadataSourcePlugin, SearchParams + + + class MyApiPlugin(SearchApiMetadataSourcePlugin): + + def get_search_query_with_filters(self, query_type, items, artist, name, va_likely): + query = f'album:"{name}"' if query_type == "album" else name + if query_type == "track" or not va_likely: + query += f' artist:"{artist}"' + return query, {} + + def get_search_response(self, params: SearchParams): + # Execute provider API request and return results with "id" fields. + ... + + def track_for_id(self, track_id: str): ... + + def album_for_id(self, album_id: str): ... + +The shared base class centralizes query normalization, ``search_limit`` +handling, candidate wiring, and consistent error logging for search requests. +Provider-specific behavior is implemented in +:py:meth:`~SearchApiMetadataSourcePlugin.get_search_query_with_filters` and +:py:meth:`~SearchApiMetadataSourcePlugin.get_search_response`. + Each metadata source plugin automatically gets a unique identifier. You can access this identifier using the :py:meth:`~MetadataSourcePlugin.data_source` class property to tell plugins apart. @@ -96,8 +128,9 @@ Migration guidance ------------------ Older metadata plugins that extend |BeetsPlugin| should be migrated to -:py:class:`MetadataSourcePlugin`. Legacy support will be removed in **beets -v3.0.0**. +:py:class:`MetadataSourcePlugin`. API-backed sources should generally migrate to +:py:class:`SearchApiMetadataSourcePlugin` to avoid duplicating search +orchestration. Legacy support will be removed in **beets v3.0.0**. .. seealso::