From 98bb7f12be06338877525e02f6d19391ef25a503 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0ar=C5=ABnas=20Nejus?= Date: Tue, 15 Jul 2025 13:03:58 +0100 Subject: [PATCH] refactor: convert _queries from class attributes to cached properties Convert _queries from mutable class attributes to cached class properties that dynamically fetch plugin queries. This eliminates the need for manual query registration and cleanup in plugin loading/unloading logic. --- beets/dbcore/db.py | 10 ++++++---- beets/library/models.py | 8 +++++++- beets/plugins.py | 12 ++++++------ beets/test/helper.py | 11 +---------- beets/ui/__init__.py | 4 ---- 5 files changed, 20 insertions(+), 25 deletions(-) diff --git a/beets/dbcore/db.py b/beets/dbcore/db.py index b1c9e18d4..b780c5756 100755 --- a/beets/dbcore/db.py +++ b/beets/dbcore/db.py @@ -299,10 +299,12 @@ class Model(ABC, Generic[D]): are subclasses of `Sort`. """ - _queries: dict[str, FieldQueryType] = {} - """Named queries that use a field-like `name:value` syntax but which - do not relate to any specific field. - """ + @cached_classproperty + def _queries(cls) -> dict[str, FieldQueryType]: + """Named queries that use a field-like `name:value` syntax but which + do not relate to any specific field. + """ + return {} _always_dirty = False """By default, fields only become "dirty" when their value actually diff --git a/beets/library/models.py b/beets/library/models.py index 8de1c2982..7501513a1 100644 --- a/beets/library/models.py +++ b/beets/library/models.py @@ -49,6 +49,10 @@ class LibModel(dbcore.Model["Library"]): "data_source": types.STRING, } + @cached_classproperty + def _queries(cls) -> dict[str, FieldQueryType]: + return plugins.named_queries(cls) # type: ignore[arg-type] + @cached_classproperty def writable_media_fields(cls) -> set[str]: return set(MediaFile.fields()) & cls._fields.keys() @@ -740,7 +744,9 @@ class Item(LibModel): _sorts = {"artist": dbcore.query.SmartArtistSort} - _queries = {"singleton": dbcore.query.SingletonQuery} + @cached_classproperty + def _queries(cls) -> dict[str, FieldQueryType]: + return {**super()._queries, "singleton": dbcore.query.SingletonQuery} _format_config_key = "format_item" diff --git a/beets/plugins.py b/beets/plugins.py index 9893633fb..81f423431 100644 --- a/beets/plugins.py +++ b/beets/plugins.py @@ -379,13 +379,13 @@ def types(model_cls: type[AnyModel]) -> dict[str, Type]: def named_queries(model_cls: type[AnyModel]) -> dict[str, FieldQueryType]: - # Gather `item_queries` and `album_queries` from the plugins. + """Return mapping between field names and queries for the given model.""" attr_name = f"{model_cls.__name__.lower()}_queries" - queries: dict[str, FieldQueryType] = {} - for plugin in find_plugins(): - plugin_queries = getattr(plugin, attr_name, {}) - queries.update(plugin_queries) - return queries + return { + field: query + for plugin in find_plugins() + for field, query in getattr(plugin, attr_name, {}).items() + } def notify_info_yielded(event: str) -> Callable[[IterF[P, Ret]], IterF[P, Ret]]: diff --git a/beets/test/helper.py b/beets/test/helper.py index a1d741b16..eb024a7aa 100644 --- a/beets/test/helper.py +++ b/beets/test/helper.py @@ -52,7 +52,7 @@ import beets.plugins from beets import importer, logging, util from beets.autotag.hooks import AlbumInfo, TrackInfo from beets.importer import ImportSession -from beets.library import Album, Item, Library +from beets.library import Item, Library from beets.test import _common from beets.ui.commands import TerminalImportSession from beets.util import ( @@ -472,9 +472,6 @@ class PluginMixin(ConfigMixin): plugin: ClassVar[str] preload_plugin: ClassVar[bool] = True - original_item_queries = dict(Item._queries) - original_album_queries = dict(Album._queries) - def setup_beets(self): super().setup_beets() if self.preload_plugin: @@ -498,10 +495,6 @@ class PluginMixin(ConfigMixin): beets.plugins.send("pluginload") beets.plugins.find_plugins() - # Take a backup of the original _queries to restore when unloading. - Item._queries.update(beets.plugins.named_queries(Item)) - Album._queries.update(beets.plugins.named_queries(Album)) - def unload_plugins(self) -> None: """Unload all plugins and remove them from the configuration.""" # FIXME this should eventually be handled by a plugin manager @@ -510,8 +503,6 @@ class PluginMixin(ConfigMixin): self.config["plugins"] = [] beets.plugins._classes = set() beets.plugins._instances = {} - Item._queries = self.original_item_queries - Album._queries = self.original_album_queries @contextmanager def configure_plugin(self, config: Any): diff --git a/beets/ui/__init__.py b/beets/ui/__init__.py index 85fdda254..8b2419a07 100644 --- a/beets/ui/__init__.py +++ b/beets/ui/__init__.py @@ -1609,10 +1609,6 @@ def _setup(options, lib=None): plugins = _load_plugins(options, config) - # Add queries defined by plugins. - library.Item._queries.update(plugins.named_queries(library.Item)) - library.Album._queries.update(plugins.named_queries(library.Album)) - plugins.send("pluginload") # Get the default subcommands.