Improve logging management for plugins: fixes

Delete the remaining usages of BeetsPlugin.listen().

Since BeetsPlugin.listeners are wrapped by a loglevel-setting function,
we cannot easily check their unicity anymore.
BeetsPlugin._raw_listeners set holds the raw listeners.

Legacy plugins that did not handle enough arguments in their listenings
functions may break: dedicated code is now deleted for it would not work
with the decorated listeners.

Tests got fixed. Some modifications were done empirically: if it passes
then it's okay.
This commit is contained in:
Bruno Cauet 2015-02-10 16:55:06 +01:00
parent 4578c4f0e1
commit 327b62b610
5 changed files with 24 additions and 24 deletions

View file

@ -18,7 +18,6 @@ from __future__ import (division, absolute_import, print_function,
unicode_literals) unicode_literals)
import traceback import traceback
import inspect
import re import re
from collections import defaultdict from collections import defaultdict
from functools import wraps from functools import wraps
@ -180,17 +179,21 @@ class BeetsPlugin(object):
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 = None
listeners = None listeners = None
def register_listener(self, event, func): def register_listener(self, event, func):
"""Add a function as a listener for the specified event. """Add a function as a listener for the specified event.
""" """
func = self._set_log_level(logging.WARNING, func) wrapped_func = self._set_log_level(logging.WARNING, func)
if self.listeners is None: cls = self.__class__
self.listeners = defaultdict(list) if cls.listeners is None or cls._raw_listeners is None:
if func not in self.listeners[event]: cls._raw_listeners = defaultdict(list)
self.listeners[event].append(func) 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 = None template_funcs = None
template_fields = None template_fields = None
@ -440,9 +443,7 @@ def send(event, **arguments):
results = [] results = []
for handler in event_handlers()[event]: for handler in event_handlers()[event]:
# Don't break legacy plugins if we want to pass more arguments # Don't break legacy plugins if we want to pass more arguments
argspec = inspect.getargspec(handler).args result = handler(**arguments)
args = dict((k, v) for k, v in arguments.items() if k in argspec)
result = handler(**args)
if result is not None: if result is not None:
results.append(result) results.append(result)
return results return results

View file

@ -136,6 +136,7 @@ class AcoustidPlugin(plugins.BeetsPlugin):
if self.config['auto']: if self.config['auto']:
self.register_listener('import_task_start', self.fingerprint_task) self.register_listener('import_task_start', self.fingerprint_task)
self.register_listener('import_task_apply', apply_acoustid_metadata)
def fingerprint_task(self, task, session): def fingerprint_task(self, task, session):
return fingerprint_task(self._log, task, session) return fingerprint_task(self._log, task, session)
@ -211,7 +212,6 @@ def fingerprint_task(log, task, session):
acoustid_match(log, item.path) acoustid_match(log, item.path)
@AcoustidPlugin.listen('import_task_apply')
def apply_acoustid_metadata(task, session): def apply_acoustid_metadata(task, session):
"""Apply Acoustid metadata (fingerprint and ID) to the task's items. """Apply Acoustid metadata (fingerprint and ID) to the task's items.
""" """

View file

@ -140,10 +140,11 @@ def apply_matches(d):
# Plugin structure and hook into import process. # Plugin structure and hook into import process.
class FromFilenamePlugin(plugins.BeetsPlugin): class FromFilenamePlugin(plugins.BeetsPlugin):
pass def __init__(self):
super(FromFilenamePlugin, self).__init__()
self.register_listener('import_task_start', filename_task)
@FromFilenamePlugin.listen('import_task_start')
def filename_task(task, session): def filename_task(task, session):
"""Examine each item in the task to see if we can extract a title """Examine each item in the task to see if we can extract a title
from the filename. Try to match all filenames to a number of from the filename. Try to match all filenames to a number of

View file

@ -37,9 +37,10 @@ class Permissions(BeetsPlugin):
u'file': 644 u'file': 644
}) })
self.register_listener('item_imported', permissions)
self.register_listener('album_imported', permissions)
@Permissions.listen('item_imported')
@Permissions.listen('album_imported')
def permissions(lib, item=None, album=None): def permissions(lib, item=None, album=None):
"""Running the permission fixer. """Running the permission fixer.
""" """

View file

@ -35,6 +35,7 @@ class TestHelper(helper.TestHelper):
def setup_plugin_loader(self): def setup_plugin_loader(self):
# FIXME the mocking code is horrific, but this is the lowest and # FIXME the mocking code is horrific, but this is the lowest and
# earliest level of the plugin mechanism we can hook into. # earliest level of the plugin mechanism we can hook into.
self.load_plugins()
self._plugin_loader_patch = patch('beets.plugins.load_plugins') self._plugin_loader_patch = patch('beets.plugins.load_plugins')
self._plugin_classes = set() self._plugin_classes = set()
load_plugins = self._plugin_loader_patch.start() load_plugins = self._plugin_loader_patch.start()
@ -95,7 +96,7 @@ class ItemWriteTest(unittest.TestCase, TestHelper):
class EventListenerPlugin(plugins.BeetsPlugin): class EventListenerPlugin(plugins.BeetsPlugin):
pass pass
self.event_listener_plugin = EventListenerPlugin self.event_listener_plugin = EventListenerPlugin()
self.register_plugin(EventListenerPlugin) self.register_plugin(EventListenerPlugin)
def tearDown(self): def tearDown(self):
@ -298,19 +299,15 @@ class ListenersTest(unittest.TestCase, TestHelper):
pass pass
d = DummyPlugin() d = DummyPlugin()
self.assertEqual(DummyPlugin.listeners['cli_exit'], [d.dummy]) self.assertEqual(DummyPlugin._raw_listeners['cli_exit'], [d.dummy])
d2 = DummyPlugin() d2 = DummyPlugin()
DummyPlugin.register_listener('cli_exit', d.dummy) self.assertEqual(DummyPlugin._raw_listeners['cli_exit'],
self.assertEqual(DummyPlugin.listeners['cli_exit'],
[d.dummy, d2.dummy]) [d.dummy, d2.dummy])
@DummyPlugin.listen('cli_exit') d.register_listener('cli_exit', d2.dummy)
def dummy(lib): self.assertEqual(DummyPlugin._raw_listeners['cli_exit'],
pass [d.dummy, d2.dummy])
self.assertEqual(DummyPlugin.listeners['cli_exit'],
[d.dummy, d2.dummy, dummy])
def suite(): def suite():