mirror of
https://github.com/beetbox/beets.git
synced 2025-12-06 08:39:17 +01:00
Slightly simplify listener registration
This commit is contained in:
parent
c2d1bc3aaf
commit
788e31b619
3 changed files with 32 additions and 61 deletions
|
|
@ -23,7 +23,7 @@ import traceback
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from types import GenericAlias
|
from types import GenericAlias
|
||||||
from typing import TYPE_CHECKING, Any, Callable, Sequence, TypeVar
|
from typing import TYPE_CHECKING, Any, ClassVar, TypeVar
|
||||||
|
|
||||||
import mediafile
|
import mediafile
|
||||||
from typing_extensions import ParamSpec
|
from typing_extensions import ParamSpec
|
||||||
|
|
@ -32,17 +32,14 @@ import beets
|
||||||
from beets import logging
|
from beets import logging
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from beets.event_types import EventType
|
from collections.abc import Callable, Iterable, Sequence
|
||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from collections.abc import Iterable
|
|
||||||
|
|
||||||
from confuse import ConfigView
|
from confuse import ConfigView
|
||||||
|
|
||||||
from beets.dbcore import Query
|
from beets.dbcore import Query
|
||||||
from beets.dbcore.db import FieldQueryType
|
from beets.dbcore.db import FieldQueryType
|
||||||
from beets.dbcore.types import Type
|
from beets.dbcore.types import Type
|
||||||
|
from beets.event_types import EventType
|
||||||
from beets.importer import ImportSession, ImportTask
|
from beets.importer import ImportSession, ImportTask
|
||||||
from beets.library import Album, Item, Library
|
from beets.library import Album, Item, Library
|
||||||
from beets.ui import Subcommand
|
from beets.ui import Subcommand
|
||||||
|
|
@ -58,7 +55,7 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
P = ParamSpec("P")
|
P = ParamSpec("P")
|
||||||
Ret = TypeVar("Ret", bound=Any)
|
Ret = TypeVar("Ret", bound=Any)
|
||||||
Listener = Callable[..., None]
|
Listener = Callable[..., Any]
|
||||||
IterF = Callable[P, Iterable[Ret]]
|
IterF = Callable[P, Iterable[Ret]]
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -105,6 +102,14 @@ class BeetsPlugin(metaclass=abc.ABCMeta):
|
||||||
the abstract methods defined here.
|
the abstract methods defined here.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
_raw_listeners: ClassVar[dict[EventType, list[Listener]]] = defaultdict(
|
||||||
|
list
|
||||||
|
)
|
||||||
|
listeners: ClassVar[dict[EventType, list[Listener]]] = defaultdict(list)
|
||||||
|
template_funcs: TFuncMap[str] | None = None
|
||||||
|
template_fields: TFuncMap[Item] | None = None
|
||||||
|
album_template_fields: TFuncMap[Album] | None = None
|
||||||
|
|
||||||
name: str
|
name: str
|
||||||
config: ConfigView
|
config: ConfigView
|
||||||
early_import_stages: list[ImportStageFunc]
|
early_import_stages: list[ImportStageFunc]
|
||||||
|
|
@ -218,25 +223,13 @@ class BeetsPlugin(metaclass=abc.ABCMeta):
|
||||||
mediafile.MediaFile.add_field(name, descriptor)
|
mediafile.MediaFile.add_field(name, descriptor)
|
||||||
library.Item._media_fields.add(name)
|
library.Item._media_fields.add(name)
|
||||||
|
|
||||||
_raw_listeners: dict[str, list[Listener]] | None = None
|
def register_listener(self, event: EventType, func: Listener) -> None:
|
||||||
listeners: dict[str, list[Listener]] | None = None
|
|
||||||
|
|
||||||
def register_listener(self, event: "EventType", func: Listener):
|
|
||||||
"""Add a function as a listener for the specified event."""
|
"""Add a function as a listener for the specified event."""
|
||||||
wrapped_func = self._set_log_level_and_params(logging.WARNING, func)
|
if func not in self._raw_listeners[event]:
|
||||||
|
self._raw_listeners[event].append(func)
|
||||||
cls = self.__class__
|
self.listeners[event].append(
|
||||||
|
self._set_log_level_and_params(logging.WARNING, func)
|
||||||
if cls.listeners is None or cls._raw_listeners is None:
|
)
|
||||||
cls._raw_listeners = defaultdict(list)
|
|
||||||
cls.listeners = defaultdict(list)
|
|
||||||
if func not in cls._raw_listeners[event]:
|
|
||||||
cls._raw_listeners[event].append(func)
|
|
||||||
cls.listeners[event].append(wrapped_func)
|
|
||||||
|
|
||||||
template_funcs: TFuncMap[str] | None = None
|
|
||||||
template_fields: TFuncMap[Item] | None = None
|
|
||||||
album_template_fields: TFuncMap[Album] | None = None
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def template_func(cls, name: str) -> Callable[[TFunc[str]], TFunc[str]]:
|
def template_func(cls, name: str) -> Callable[[TFunc[str]], TFunc[str]]:
|
||||||
|
|
@ -383,7 +376,9 @@ def named_queries(model_cls: type[AnyModel]) -> dict[str, FieldQueryType]:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def notify_info_yielded(event: str) -> Callable[[IterF[P, Ret]], IterF[P, Ret]]:
|
def notify_info_yielded(
|
||||||
|
event: EventType,
|
||||||
|
) -> Callable[[IterF[P, Ret]], IterF[P, Ret]]:
|
||||||
"""Makes a generator send the event 'event' every time it yields.
|
"""Makes a generator send the event 'event' every time it yields.
|
||||||
This decorator is supposed to decorate a generator, but any function
|
This decorator is supposed to decorate a generator, but any function
|
||||||
returning an iterable should work.
|
returning an iterable should work.
|
||||||
|
|
@ -474,19 +469,7 @@ def album_field_getters() -> TFuncMap[Album]:
|
||||||
# Event dispatch.
|
# Event dispatch.
|
||||||
|
|
||||||
|
|
||||||
def event_handlers() -> dict[str, list[Listener]]:
|
def send(event: EventType, **arguments: Any) -> list[Any]:
|
||||||
"""Find all event handlers from plugins as a dictionary mapping
|
|
||||||
event names to sequences of callables.
|
|
||||||
"""
|
|
||||||
all_handlers: dict[str, list[Listener]] = defaultdict(list)
|
|
||||||
for plugin in find_plugins():
|
|
||||||
if plugin.listeners:
|
|
||||||
for event, handlers in plugin.listeners.items():
|
|
||||||
all_handlers[event] += handlers
|
|
||||||
return all_handlers
|
|
||||||
|
|
||||||
|
|
||||||
def send(event: str, **arguments: Any) -> list[Any]:
|
|
||||||
"""Send an event to all assigned event listeners.
|
"""Send an event to all assigned event listeners.
|
||||||
|
|
||||||
`event` is the name of the event to send, all other named arguments
|
`event` is the name of the event to send, all other named arguments
|
||||||
|
|
@ -495,12 +478,11 @@ def send(event: str, **arguments: Any) -> list[Any]:
|
||||||
Return a list of non-None values returned from the handlers.
|
Return a list of non-None values returned from the handlers.
|
||||||
"""
|
"""
|
||||||
log.debug("Sending event: {0}", event)
|
log.debug("Sending event: {0}", event)
|
||||||
results: list[Any] = []
|
return [
|
||||||
for handler in event_handlers()[event]:
|
r
|
||||||
result = handler(**arguments)
|
for handler in BeetsPlugin.listeners[event]
|
||||||
if result is not None:
|
if (r := handler(**arguments)) is not None
|
||||||
results.append(result)
|
]
|
||||||
return results
|
|
||||||
|
|
||||||
|
|
||||||
def feat_tokens(for_artist: bool = True) -> str:
|
def feat_tokens(for_artist: bool = True) -> str:
|
||||||
|
|
|
||||||
|
|
@ -498,8 +498,8 @@ class PluginMixin(ConfigMixin):
|
||||||
def unload_plugins(self) -> None:
|
def unload_plugins(self) -> None:
|
||||||
"""Unload all plugins and remove them from the configuration."""
|
"""Unload all plugins and remove them from the configuration."""
|
||||||
# FIXME this should eventually be handled by a plugin manager
|
# FIXME this should eventually be handled by a plugin manager
|
||||||
for plugin_class in beets.plugins._instances:
|
beets.plugins.BeetsPlugin.listeners.clear()
|
||||||
plugin_class.listeners = None
|
beets.plugins.BeetsPlugin._raw_listeners.clear()
|
||||||
self.config["plugins"] = []
|
self.config["plugins"] = []
|
||||||
beets.plugins._classes = set()
|
beets.plugins._classes = set()
|
||||||
beets.plugins._instances = {}
|
beets.plugins._instances = {}
|
||||||
|
|
|
||||||
|
|
@ -243,15 +243,7 @@ class ListenersTest(PluginLoaderTestCase):
|
||||||
d.register_listener("cli_exit", d2.dummy)
|
d.register_listener("cli_exit", d2.dummy)
|
||||||
assert DummyPlugin._raw_listeners["cli_exit"] == [d.dummy, d2.dummy]
|
assert DummyPlugin._raw_listeners["cli_exit"] == [d.dummy, d2.dummy]
|
||||||
|
|
||||||
@patch("beets.plugins.find_plugins")
|
def test_events_called(self):
|
||||||
@patch("inspect.getfullargspec")
|
|
||||||
def test_events_called(self, mock_gfa, mock_find_plugins):
|
|
||||||
mock_gfa.return_value = Mock(
|
|
||||||
args=(),
|
|
||||||
varargs="args",
|
|
||||||
varkw="kwargs",
|
|
||||||
)
|
|
||||||
|
|
||||||
class DummyPlugin(plugins.BeetsPlugin):
|
class DummyPlugin(plugins.BeetsPlugin):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
@ -261,7 +253,6 @@ class ListenersTest(PluginLoaderTestCase):
|
||||||
self.register_listener("event_bar", self.bar)
|
self.register_listener("event_bar", self.bar)
|
||||||
|
|
||||||
d = DummyPlugin()
|
d = DummyPlugin()
|
||||||
mock_find_plugins.return_value = (d,)
|
|
||||||
|
|
||||||
plugins.send("event")
|
plugins.send("event")
|
||||||
d.foo.assert_has_calls([])
|
d.foo.assert_has_calls([])
|
||||||
|
|
@ -271,8 +262,7 @@ class ListenersTest(PluginLoaderTestCase):
|
||||||
d.foo.assert_called_once_with(var="tagada")
|
d.foo.assert_called_once_with(var="tagada")
|
||||||
d.bar.assert_has_calls([])
|
d.bar.assert_has_calls([])
|
||||||
|
|
||||||
@patch("beets.plugins.find_plugins")
|
def test_listener_params(self):
|
||||||
def test_listener_params(self, mock_find_plugins):
|
|
||||||
class DummyPlugin(plugins.BeetsPlugin):
|
class DummyPlugin(plugins.BeetsPlugin):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
@ -316,8 +306,7 @@ class ListenersTest(PluginLoaderTestCase):
|
||||||
def dummy9(self, **kwargs):
|
def dummy9(self, **kwargs):
|
||||||
assert kwargs == {"foo": 5}
|
assert kwargs == {"foo": 5}
|
||||||
|
|
||||||
d = DummyPlugin()
|
DummyPlugin()
|
||||||
mock_find_plugins.return_value = (d,)
|
|
||||||
|
|
||||||
plugins.send("event1", foo=5)
|
plugins.send("event1", foo=5)
|
||||||
plugins.send("event2", foo=5)
|
plugins.send("event2", foo=5)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue