From 8f3490c87ed78a6d0b1f0570e4e1d449f0c942b2 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Fri, 11 Apr 2025 18:24:59 +0200 Subject: [PATCH] recursive config file move plugin variables into sub dictionaries of user config --- electrum/plugin.py | 18 +++++++------- electrum/plugins/swapserver/__init__.py | 6 ++--- electrum/plugins/watchtower/__init__.py | 6 ++--- electrum/simple_config.py | 33 ++++++++++++++++++++----- tests/regtest.py | 12 ++++----- tests/test_simple_config.py | 11 +++++++++ 6 files changed, 59 insertions(+), 27 deletions(-) diff --git a/electrum/plugin.py b/electrum/plugin.py index 6f86f5608..f272f9220 100644 --- a/electrum/plugin.py +++ b/electrum/plugin.py @@ -112,7 +112,7 @@ class Plugins(DaemonThread): if loader.__class__.__qualname__ == "PyiFrozenImporter": continue module_path = os.path.join(pkg_path, name) - if self.cmd_only and not self.config.get('enable_plugin_' + name) is True: + if self.cmd_only and not self.config.get(f'plugins.{name}.enabled') is True: continue try: with open(os.path.join(module_path, 'manifest.json'), 'r') as f: @@ -174,7 +174,7 @@ class Plugins(DaemonThread): def load_plugins(self): for name, d in chain(self.internal_plugin_metadata.items(), self.external_plugin_metadata.items()): - if not d.get('requires_wallet_type') and self.config.get('enable_plugin_' + name): + if not d.get('requires_wallet_type') and self.config.get(f'plugins.{name}.enabled'): try: if self.cmd_only: # only load init method to register commands self.maybe_load_plugin_init_method(name) @@ -300,7 +300,7 @@ class Plugins(DaemonThread): raise Exception(f"duplicate plugins for name={name}") if name in self.external_plugin_metadata: raise Exception(f"duplicate plugins for name={name}") - if self.cmd_only and not self.config.get('enable_plugin_' + name): + if self.cmd_only and not self.config.get(f'plugins.{name}.enabled'): continue min_version = d.get('min_electrum_version') if min_version and StrictVersion(min_version) > StrictVersion(ELECTRUM_VERSION): @@ -416,7 +416,7 @@ class Plugins(DaemonThread): return False filename = self.zip_plugin_path(name) plugin_hash = get_file_hash256(filename) - sig = self.config.get('authorize_plugin_' + name) + sig = self.config.get(f'plugins.{name}.authorized') if not sig: return False pubkey = ECPubkey(pubkey_bytes) @@ -428,17 +428,17 @@ class Plugins(DaemonThread): plugin_hash = get_file_hash256(filename) sig = privkey.ecdsa_sign(plugin_hash) value = sig.hex() - self.config.set_key('authorize_plugin_' + name, value, save=True) + self.config.set_key(f'plugins.{name}.authorized', value, save=True) def enable(self, name: str) -> 'BasePlugin': - self.config.set_key('enable_plugin_' + name, True, save=True) + self.config.set_key(f'plugins.{name}.enabled', True, save=True) p = self.get(name) if p: return p return self.load_plugin(name) def disable(self, name: str) -> None: - self.config.set_key('enable_plugin_' + name, False, save=True) + self.config.set_key(f'plugins.{name}.enabled', False, save=True) p = self.get(name) if not p: return @@ -448,7 +448,7 @@ class Plugins(DaemonThread): @classmethod def is_plugin_enabler_config_key(cls, key: str) -> bool: - return key.startswith('enable_plugin_') or key.startswith('authorize_plugin_') + return key.startswith('plugins.') def toggle(self, name: str) -> Optional['BasePlugin']: p = self.get(name) @@ -604,7 +604,7 @@ class BasePlugin(Logger): return [] def is_enabled(self): - return self.is_available() and self.config.get('enable_plugin_' + self.name) is True + return self.is_available() and self.config.get(f'plugins.{self.name}.enabled') is True def is_available(self): return True diff --git a/electrum/plugins/swapserver/__init__.py b/electrum/plugins/swapserver/__init__.py index f5a98487f..69c216a56 100644 --- a/electrum/plugins/swapserver/__init__.py +++ b/electrum/plugins/swapserver/__init__.py @@ -1,5 +1,5 @@ from electrum.simple_config import ConfigVar, SimpleConfig -SimpleConfig.SWAPSERVER_PORT = ConfigVar('swapserver_port', default=None, type_=int, plugin=__name__) -SimpleConfig.SWAPSERVER_FEE_MILLIONTHS = ConfigVar('swapserver_fee_millionths', default=5000, type_=int, plugin=__name__) -SimpleConfig.SWAPSERVER_ANN_POW_NONCE = ConfigVar('swapserver_ann_pow_nonce', default=0, type_=int, plugin=__name__) +SimpleConfig.SWAPSERVER_PORT = ConfigVar('plugins.swapserver.port', default=None, type_=int, plugin=__name__) +SimpleConfig.SWAPSERVER_FEE_MILLIONTHS = ConfigVar('plugins.swapserver.fee_millionths', default=5000, type_=int, plugin=__name__) +SimpleConfig.SWAPSERVER_ANN_POW_NONCE = ConfigVar('plugins.swapserver.ann_pow_nonce', default=0, type_=int, plugin=__name__) diff --git a/electrum/plugins/watchtower/__init__.py b/electrum/plugins/watchtower/__init__.py index b4d66af1e..ca4687680 100644 --- a/electrum/plugins/watchtower/__init__.py +++ b/electrum/plugins/watchtower/__init__.py @@ -1,5 +1,5 @@ from electrum.simple_config import ConfigVar, SimpleConfig -SimpleConfig.WATCHTOWER_SERVER_PORT = ConfigVar('watchtower_server_port', default=None, type_=int, plugin=__name__) -SimpleConfig.WATCHTOWER_SERVER_USER = ConfigVar('watchtower_server_user', default=None, type_=str, plugin=__name__) -SimpleConfig.WATCHTOWER_SERVER_PASSWORD = ConfigVar('watchtower_server_password', default=None, type_=str, plugin=__name__) +SimpleConfig.WATCHTOWER_SERVER_PORT = ConfigVar('plugins.watchtower.server_port', default=None, type_=int, plugin=__name__) +SimpleConfig.WATCHTOWER_SERVER_USER = ConfigVar('plugins.watchtower.server_user', default=None, type_=str, plugin=__name__) +SimpleConfig.WATCHTOWER_SERVER_PASSWORD = ConfigVar('plugins.watchtower.server_password', default=None, type_=str, plugin=__name__) diff --git a/electrum/simple_config.py b/electrum/simple_config.py index a93b8764f..9ac225372 100644 --- a/electrum/simple_config.py +++ b/electrum/simple_config.py @@ -57,13 +57,13 @@ class ConfigVar(property): assert long_desc is None or callable(long_desc) self._short_desc = short_desc self._long_desc = long_desc - if plugin: # enforce "key" starts with name of plugin + if plugin: # enforce "key" starts with 'plugins..' pkg_prefix = "electrum.plugins." # for internal plugins if plugin.startswith(pkg_prefix): plugin = plugin[len(pkg_prefix):] assert "." not in plugin, plugin - key_prefix = plugin + "_" - assert key.startswith(key_prefix), f"ConfigVar {key=} must be prefixed with the plugin name ({key_prefix})" + key_prefix = f'plugins.{plugin}.' + assert key.startswith(key_prefix), f"ConfigVar {key=} must be prefixed with ({key_prefix})" property.__init__(self, self._get_config_value, self._set_config_value) assert key not in _config_var_from_key, f"duplicate config key str: {key!r}" _config_var_from_key[key] = self @@ -299,9 +299,26 @@ class SimpleConfig(Logger): assert isinstance(key, str), key with self.lock: if value is not None: - self.user_config[key] = value + keypath = key.split('.') + d = self.user_config + for x in keypath[0:-1]: + d2 = d.get(x) + if d2 is None: + d2 = d[x] = {} + d = d2 + d[keypath[-1]] = value else: - self.user_config.pop(key, None) + def delete_key(d, key): + if '.' not in key: + d.pop(key, None) + else: + prefix, suffix = key.split('.', 1) + d2 = d.get(prefix) + empty = delete_key(d2, suffix) + if empty: + d.pop(prefix) + return len(d) == 0 + delete_key(self.user_config, key) if save: self.save_user_config() @@ -315,7 +332,11 @@ class SimpleConfig(Logger): with self.lock: out = self.cmdline_options.get(key) if out is None: - out = self.user_config.get(key, default) + d = self.user_config + path = key.split('.') + for key in path[0:-1]: + d = d.get(key, {}) + out = d.get(path[-1], default) return out def is_set(self, key: Union[str, ConfigVar, ConfigVarWithConfig]) -> bool: diff --git a/tests/regtest.py b/tests/regtest.py index 9246e3537..97851c2af 100644 --- a/tests/regtest.py +++ b/tests/regtest.py @@ -89,8 +89,8 @@ class TestLightningSwapserver(TestLightning): }, 'bob': { 'lightning_listen': 'localhost:9735', - 'enable_plugin_swapserver': 'true', - 'swapserver_port': '5455', + 'plugins.swapserver.enabled': 'true', + 'plugins.swapserver.port': '5455', 'nostr_relays': "''", } } @@ -115,10 +115,10 @@ class TestLightningWatchtower(TestLightning): 'watchtower_url': 'http://wtuser:wtpassword@127.0.0.1:12345', }, 'carol': { - 'enable_plugin_watchtower': 'true', - 'watchtower_server_user': 'wtuser', - 'watchtower_server_password': 'wtpassword', - 'watchtower_server_port': '12345', + 'plugins.watchtower.enabled': 'true', + 'plugins.watchtower.server_user': 'wtuser', + 'plugins.watchtower.server_password': 'wtpassword', + 'plugins.watchtower.server_port': '12345', } } diff --git a/tests/test_simple_config.py b/tests/test_simple_config.py index e87d197ce..9b77de76a 100644 --- a/tests/test_simple_config.py +++ b/tests/test_simple_config.py @@ -203,6 +203,17 @@ class Test_SimpleConfig(ElectrumTestCase): with self.assertRaises(KeyError): config.cv.from_key("server333") + def test_recursive_config(self): + config = SimpleConfig(self.options) + n = len(config.user_config) + config.set_key('x.y.z', 1) + self.assertEqual(len(config.user_config), n + 1) + config.set_key('x.y.w', 1) + self.assertEqual(len(config.user_config), n + 1) + config.set_key('x.y.z', None) + self.assertEqual(len(config.user_config), n + 1) + config.set_key('x.y.w', None) + self.assertEqual(len(config.user_config), n) class TestUserConfig(ElectrumTestCase):