[Plugins] Fix and refactor get_plugin_info method

A new metadata version 2.1 has optional Description that is causing an
TypeError when looking up the key in plugin_info since clients are
assuming values are always strings but the default is None.

Fixed TypeError by ensuring that the info dict has a default empty
string set for all keys.

Separated the parsing of the pkg_info into static method to make it
easier to test.

Changed the missing plugin info to only set the Name and Version as
'not available' since all other fields are optional.

Ref: https://dev.deluge-torrent.org/ticket/3476
Ref: https://packaging.python.org/en/latest/specifications/core-metadata/#description
This commit is contained in:
Calum Lind 2022-01-12 19:03:07 +00:00
parent e50927f575
commit 2351d65844
No known key found for this signature in database
GPG Key ID: 90597A687B836BA3
2 changed files with 26 additions and 16 deletions

View File

@ -254,28 +254,37 @@ class PluginManagerBase:
def get_plugin_info(self, name): def get_plugin_info(self, name):
"""Returns a dictionary of plugin info from the metadata""" """Returns a dictionary of plugin info from the metadata"""
info = {}.fromkeys(METADATA_KEYS)
last_header = ''
cont_lines = []
# Missing plugin info
if not self.pkg_env[name]: if not self.pkg_env[name]:
log.warning('Failed to retrieve info for plugin: %s', name) log.warning('Failed to retrieve info for plugin: %s', name)
for k in info: info = {}.fromkeys(METADATA_KEYS, '')
info[k] = 'not available' info['Name'] = info['Version'] = 'not available'
return info return info
for line in self.pkg_env[name][0].get_metadata('PKG-INFO').splitlines():
pkg_info = self.pkg_env[name][0].get_metadata('PKG-INFO')
return self.parse_pkg_info(pkg_info)
@staticmethod
def parse_pkg_info(pkg_info):
last_header = ''
cont_lines = []
info = {}.fromkeys(METADATA_KEYS, '')
for line in pkg_info.splitlines():
if not line: if not line:
continue continue
if line[0] in ' \t' and ( if line[0] in ' \t' and (
len(line.split(':', 1)) == 1 or line.split(':', 1)[0] not in info len(line.split(':', 1)) == 1 or line.split(':', 1)[0] not in info
): ):
# This is a continuation # This is a continuation
cont_lines.append(line.strip()) cont_lines.append(line.strip())
else: continue
if cont_lines:
info[last_header] = '\n'.join(cont_lines).strip() if cont_lines:
cont_lines = [] info[last_header] = '\n'.join(cont_lines).strip()
if line.split(':', 1)[0] in info: cont_lines = []
last_header = line.split(':', 1)[0] if line.split(':', 1)[0] in info:
info[last_header] = line.split(':', 1)[1].strip() last_header = line.split(':', 1)[0]
info[last_header] = line.split(':', 1)[1].strip()
return info return info

View File

@ -20,9 +20,10 @@ class PluginManagerBaseTestCase(BaseTestCase):
pm = PluginManagerBase('core.conf', 'deluge.plugin.core') pm = PluginManagerBase('core.conf', 'deluge.plugin.core')
for p in pm.get_available_plugins(): for p in pm.get_available_plugins():
for key, value in pm.get_plugin_info(p).items(): for key, value in pm.get_plugin_info(p).items():
self.assertTrue(isinstance(f'{key}: {value}', ''.__class__)) self.assertTrue(isinstance(f'{key}: {value}', str))
def test_get_plugin_info_invalid_name(self): def test_get_plugin_info_invalid_name(self):
pm = PluginManagerBase('core.conf', 'deluge.plugin.core') pm = PluginManagerBase('core.conf', 'deluge.plugin.core')
for key, value in pm.get_plugin_info('random').items(): for key, value in pm.get_plugin_info('random').items():
self.assertEqual(value, 'not available') result = 'not available' if key in ('Name', 'Version') else ''
self.assertEqual(value, result)