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.
This commit is contained in:
Šarūnas Nejus 2025-07-15 13:03:58 +01:00
parent 3be4a89aee
commit 98bb7f12be
No known key found for this signature in database
GPG key ID: DD28F6704DBE3435
5 changed files with 20 additions and 25 deletions

View file

@ -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

View file

@ -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"

View file

@ -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]]:

View file

@ -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):

View file

@ -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.