From 64dc3ec2a2e55078632830359224af0c68a45f13 Mon Sep 17 00:00:00 2001 From: wisp3rwind <17089248+wisp3rwind@users.noreply.github.com> Date: Mon, 22 Mar 2021 21:48:30 +0100 Subject: [PATCH] plugins: Python 3.10 compatibility In 766c8b190bb782a5ef7bafbfa657007d4f158872 from [1] a slight hack was introduced that allowed to add new arguments to plugin events without breaking legacy plugins that did not feature those arguments in the signature of their handler functions. When improving logging management for plugins in 327b62b6103771bd19465f10a6d4f0412c2b9f1c from [2] this hack was removed, to be restored with 7f34c101d70126497afbf448c9d7ffb6faf6655a from [3] Now in addition to inspecting the function signature, an assumption is made about the string message of the TypeError that occurs for legacy handler functions (namely, that it start with func.__name__). In Python 3.10, the TypeError uses func.__qualname__ [4], however, due to the patch [5]. Thus, checking whether the TypeError is due to an outdated function signature or something internal to the handler fais. As a fix, instead of using __name__ or __qualname__ depending on Python version, let's just revert to the old hack from [1], I'm not seeing a significant advantage in [3]. The latter might be a bit faster since it doesn't construct a new dict for each call, but only for legacy calls in the excption handler. I doubt that really matters, and if it does, we should try to think of a better fix than inspecting error messages with no guarantees about their content in such a central place in the code. [1] https://github.com/beetbox/beets/pull/652 [2] https://github.com/beetbox/beets/pull/1320 [3] https://github.com/beetbox/beets/pull/1359 [4] https://docs.python.org/3.10/glossary.html#term-qualified-name [5] https://bugs.python.org/issue40679 --- beets/plugins.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/beets/plugins.py b/beets/plugins.py index 3abd911c9..23f938169 100644 --- a/beets/plugins.py +++ b/beets/plugins.py @@ -130,29 +130,30 @@ class BeetsPlugin(object): be sent for backwards-compatibility. """ if six.PY2: - func_args = inspect.getargspec(func).args + argspec = inspect.getargspec(func) + func_args = argspec.args + has_varkw = argspec.keywords is not None else: - func_args = inspect.getfullargspec(func).args + argspec = inspect.getfullargspec(func) + func_args = argspec.args + has_varkw = argspec.varkw is not None @wraps(func) def wrapper(*args, **kwargs): assert self._log.level == logging.NOTSET + verbosity = beets.config['verbose'].get(int) log_level = max(logging.DEBUG, base_log_level - 10 * verbosity) self._log.setLevel(log_level) + if not has_varkw: + kwargs = dict((k, v) for k, v in kwargs.items() + if k in func_args) + try: - try: - return func(*args, **kwargs) - except TypeError as exc: - if exc.args[0].startswith(func.__name__): - # caused by 'func' and not stuff internal to 'func' - kwargs = dict((arg, val) for arg, val in kwargs.items() - if arg in func_args) - return func(*args, **kwargs) - else: - raise + return func(*args, **kwargs) finally: self._log.setLevel(logging.NOTSET) + return wrapper def queries(self):