recursive config file
move plugin variables into sub dictionaries of user config
This commit is contained in:
@@ -112,7 +112,7 @@ class Plugins(DaemonThread):
|
|||||||
if loader.__class__.__qualname__ == "PyiFrozenImporter":
|
if loader.__class__.__qualname__ == "PyiFrozenImporter":
|
||||||
continue
|
continue
|
||||||
module_path = os.path.join(pkg_path, name)
|
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
|
continue
|
||||||
try:
|
try:
|
||||||
with open(os.path.join(module_path, 'manifest.json'), 'r') as f:
|
with open(os.path.join(module_path, 'manifest.json'), 'r') as f:
|
||||||
@@ -174,7 +174,7 @@ class Plugins(DaemonThread):
|
|||||||
|
|
||||||
def load_plugins(self):
|
def load_plugins(self):
|
||||||
for name, d in chain(self.internal_plugin_metadata.items(), self.external_plugin_metadata.items()):
|
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:
|
try:
|
||||||
if self.cmd_only: # only load init method to register commands
|
if self.cmd_only: # only load init method to register commands
|
||||||
self.maybe_load_plugin_init_method(name)
|
self.maybe_load_plugin_init_method(name)
|
||||||
@@ -300,7 +300,7 @@ class Plugins(DaemonThread):
|
|||||||
raise Exception(f"duplicate plugins for name={name}")
|
raise Exception(f"duplicate plugins for name={name}")
|
||||||
if name in self.external_plugin_metadata:
|
if name in self.external_plugin_metadata:
|
||||||
raise Exception(f"duplicate plugins for name={name}")
|
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
|
continue
|
||||||
min_version = d.get('min_electrum_version')
|
min_version = d.get('min_electrum_version')
|
||||||
if min_version and StrictVersion(min_version) > StrictVersion(ELECTRUM_VERSION):
|
if min_version and StrictVersion(min_version) > StrictVersion(ELECTRUM_VERSION):
|
||||||
@@ -416,7 +416,7 @@ class Plugins(DaemonThread):
|
|||||||
return False
|
return False
|
||||||
filename = self.zip_plugin_path(name)
|
filename = self.zip_plugin_path(name)
|
||||||
plugin_hash = get_file_hash256(filename)
|
plugin_hash = get_file_hash256(filename)
|
||||||
sig = self.config.get('authorize_plugin_' + name)
|
sig = self.config.get(f'plugins.{name}.authorized')
|
||||||
if not sig:
|
if not sig:
|
||||||
return False
|
return False
|
||||||
pubkey = ECPubkey(pubkey_bytes)
|
pubkey = ECPubkey(pubkey_bytes)
|
||||||
@@ -428,17 +428,17 @@ class Plugins(DaemonThread):
|
|||||||
plugin_hash = get_file_hash256(filename)
|
plugin_hash = get_file_hash256(filename)
|
||||||
sig = privkey.ecdsa_sign(plugin_hash)
|
sig = privkey.ecdsa_sign(plugin_hash)
|
||||||
value = sig.hex()
|
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':
|
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)
|
p = self.get(name)
|
||||||
if p:
|
if p:
|
||||||
return p
|
return p
|
||||||
return self.load_plugin(name)
|
return self.load_plugin(name)
|
||||||
|
|
||||||
def disable(self, name: str) -> None:
|
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)
|
p = self.get(name)
|
||||||
if not p:
|
if not p:
|
||||||
return
|
return
|
||||||
@@ -448,7 +448,7 @@ class Plugins(DaemonThread):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def is_plugin_enabler_config_key(cls, key: str) -> bool:
|
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']:
|
def toggle(self, name: str) -> Optional['BasePlugin']:
|
||||||
p = self.get(name)
|
p = self.get(name)
|
||||||
@@ -604,7 +604,7 @@ class BasePlugin(Logger):
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
def is_enabled(self):
|
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):
|
def is_available(self):
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from electrum.simple_config import ConfigVar, SimpleConfig
|
from electrum.simple_config import ConfigVar, SimpleConfig
|
||||||
|
|
||||||
SimpleConfig.SWAPSERVER_PORT = ConfigVar('swapserver_port', default=None, type_=int, plugin=__name__)
|
SimpleConfig.SWAPSERVER_PORT = ConfigVar('plugins.swapserver.port', default=None, type_=int, plugin=__name__)
|
||||||
SimpleConfig.SWAPSERVER_FEE_MILLIONTHS = ConfigVar('swapserver_fee_millionths', default=5000, 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('swapserver_ann_pow_nonce', default=0, type_=int, plugin=__name__)
|
SimpleConfig.SWAPSERVER_ANN_POW_NONCE = ConfigVar('plugins.swapserver.ann_pow_nonce', default=0, type_=int, plugin=__name__)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from electrum.simple_config import ConfigVar, SimpleConfig
|
from electrum.simple_config import ConfigVar, SimpleConfig
|
||||||
|
|
||||||
SimpleConfig.WATCHTOWER_SERVER_PORT = ConfigVar('watchtower_server_port', default=None, type_=int, plugin=__name__)
|
SimpleConfig.WATCHTOWER_SERVER_PORT = ConfigVar('plugins.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_USER = ConfigVar('plugins.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_PASSWORD = ConfigVar('plugins.watchtower.server_password', default=None, type_=str, plugin=__name__)
|
||||||
|
|||||||
@@ -57,13 +57,13 @@ class ConfigVar(property):
|
|||||||
assert long_desc is None or callable(long_desc)
|
assert long_desc is None or callable(long_desc)
|
||||||
self._short_desc = short_desc
|
self._short_desc = short_desc
|
||||||
self._long_desc = long_desc
|
self._long_desc = long_desc
|
||||||
if plugin: # enforce "key" starts with name of plugin
|
if plugin: # enforce "key" starts with 'plugins.<name of plugin>.'
|
||||||
pkg_prefix = "electrum.plugins." # for internal plugins
|
pkg_prefix = "electrum.plugins." # for internal plugins
|
||||||
if plugin.startswith(pkg_prefix):
|
if plugin.startswith(pkg_prefix):
|
||||||
plugin = plugin[len(pkg_prefix):]
|
plugin = plugin[len(pkg_prefix):]
|
||||||
assert "." not in plugin, plugin
|
assert "." not in plugin, plugin
|
||||||
key_prefix = plugin + "_"
|
key_prefix = f'plugins.{plugin}.'
|
||||||
assert key.startswith(key_prefix), f"ConfigVar {key=} must be prefixed with the plugin name ({key_prefix})"
|
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)
|
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}"
|
assert key not in _config_var_from_key, f"duplicate config key str: {key!r}"
|
||||||
_config_var_from_key[key] = self
|
_config_var_from_key[key] = self
|
||||||
@@ -299,9 +299,26 @@ class SimpleConfig(Logger):
|
|||||||
assert isinstance(key, str), key
|
assert isinstance(key, str), key
|
||||||
with self.lock:
|
with self.lock:
|
||||||
if value is not None:
|
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:
|
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:
|
if save:
|
||||||
self.save_user_config()
|
self.save_user_config()
|
||||||
|
|
||||||
@@ -315,7 +332,11 @@ class SimpleConfig(Logger):
|
|||||||
with self.lock:
|
with self.lock:
|
||||||
out = self.cmdline_options.get(key)
|
out = self.cmdline_options.get(key)
|
||||||
if out is None:
|
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
|
return out
|
||||||
|
|
||||||
def is_set(self, key: Union[str, ConfigVar, ConfigVarWithConfig]) -> bool:
|
def is_set(self, key: Union[str, ConfigVar, ConfigVarWithConfig]) -> bool:
|
||||||
|
|||||||
@@ -89,8 +89,8 @@ class TestLightningSwapserver(TestLightning):
|
|||||||
},
|
},
|
||||||
'bob': {
|
'bob': {
|
||||||
'lightning_listen': 'localhost:9735',
|
'lightning_listen': 'localhost:9735',
|
||||||
'enable_plugin_swapserver': 'true',
|
'plugins.swapserver.enabled': 'true',
|
||||||
'swapserver_port': '5455',
|
'plugins.swapserver.port': '5455',
|
||||||
'nostr_relays': "''",
|
'nostr_relays': "''",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -115,10 +115,10 @@ class TestLightningWatchtower(TestLightning):
|
|||||||
'watchtower_url': 'http://wtuser:wtpassword@127.0.0.1:12345',
|
'watchtower_url': 'http://wtuser:wtpassword@127.0.0.1:12345',
|
||||||
},
|
},
|
||||||
'carol': {
|
'carol': {
|
||||||
'enable_plugin_watchtower': 'true',
|
'plugins.watchtower.enabled': 'true',
|
||||||
'watchtower_server_user': 'wtuser',
|
'plugins.watchtower.server_user': 'wtuser',
|
||||||
'watchtower_server_password': 'wtpassword',
|
'plugins.watchtower.server_password': 'wtpassword',
|
||||||
'watchtower_server_port': '12345',
|
'plugins.watchtower.server_port': '12345',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -203,6 +203,17 @@ class Test_SimpleConfig(ElectrumTestCase):
|
|||||||
with self.assertRaises(KeyError):
|
with self.assertRaises(KeyError):
|
||||||
config.cv.from_key("server333")
|
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):
|
class TestUserConfig(ElectrumTestCase):
|
||||||
|
|||||||
Reference in New Issue
Block a user