Browse Source

Merge pull request #8609 from SomberNight/202309_getconfig_default_value

getconfig/setconfig to use configvars
master
ThomasV 2 years ago committed by GitHub
parent
commit
f245b347f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 14
      electrum/commands.py
  2. 54
      electrum/plugin.py
  3. 22
      electrum/simple_config.py
  4. 6
      electrum/tests/test_simple_config.py

14
electrum/commands.py

@ -59,7 +59,7 @@ from .lnutil import SENT, RECEIVED
from .lnutil import LnFeatures from .lnutil import LnFeatures
from .lnutil import extract_nodeid from .lnutil import extract_nodeid
from .lnpeer import channel_id_from_funding_tx from .lnpeer import channel_id_from_funding_tx
from .plugin import run_hook, DeviceMgr from .plugin import run_hook, DeviceMgr, Plugins
from .version import ELECTRUM_VERSION from .version import ELECTRUM_VERSION
from .simple_config import SimpleConfig from .simple_config import SimpleConfig
from .invoices import Invoice from .invoices import Invoice
@ -305,7 +305,11 @@ class Commands:
@command('') @command('')
async def getconfig(self, key): async def getconfig(self, key):
"""Return a configuration variable. """ """Return a configuration variable. """
return self.config.get(key) if Plugins.is_plugin_enabler_config_key(key):
return self.config.get(key)
else:
cv = self.config.cv.from_key(key)
return cv.get()
@classmethod @classmethod
def _setconfig_normalize_value(cls, key, value): def _setconfig_normalize_value(cls, key, value):
@ -326,7 +330,11 @@ class Commands:
self.daemon.commands_server.rpc_user = value self.daemon.commands_server.rpc_user = value
if self.daemon and key == SimpleConfig.RPC_PASSWORD.key(): if self.daemon and key == SimpleConfig.RPC_PASSWORD.key():
self.daemon.commands_server.rpc_password = value self.daemon.commands_server.rpc_password = value
self.config.set_key(key, value) if Plugins.is_plugin_enabler_config_key(key):
self.config.set_key(key, value)
else:
cv = self.config.cv.from_key(key)
cv.set(value)
return True return True
@command('') @command('')

54
electrum/plugin.py

@ -29,7 +29,7 @@ import time
import threading import threading
import sys import sys
from typing import (NamedTuple, Any, Union, TYPE_CHECKING, Optional, Tuple, from typing import (NamedTuple, Any, Union, TYPE_CHECKING, Optional, Tuple,
Dict, Iterable, List, Sequence, Callable, TypeVar, Mapping) Dict, Iterable, List, Sequence, Callable, TypeVar, Mapping, Set)
import concurrent import concurrent
from concurrent import futures from concurrent import futures
from functools import wraps, partial from functools import wraps, partial
@ -56,12 +56,13 @@ hooks = {}
class Plugins(DaemonThread): class Plugins(DaemonThread):
LOGGING_SHORTCUT = 'p' LOGGING_SHORTCUT = 'p'
pkgpath = os.path.dirname(plugins.__file__)
_all_found_plugins = None # type: Optional[Dict[str, dict]]
@profiler @profiler
def __init__(self, config: SimpleConfig, gui_name): def __init__(self, config: SimpleConfig, gui_name):
DaemonThread.__init__(self) DaemonThread.__init__(self)
self.name = 'Plugins' # set name of thread self.name = 'Plugins' # set name of thread
self.pkgpath = os.path.dirname(plugins.__file__)
self.config = config self.config = config
self.hw_wallets = {} self.hw_wallets = {}
self.plugins = {} # type: Dict[str, BasePlugin] self.plugins = {} # type: Dict[str, BasePlugin]
@ -72,21 +73,33 @@ class Plugins(DaemonThread):
self.add_jobs(self.device_manager.thread_jobs()) self.add_jobs(self.device_manager.thread_jobs())
self.start() self.start()
@classmethod
def find_all_plugins(cls) -> Mapping[str, dict]:
"""Return a map of all found plugins: name -> description.
Note that plugins not available for the current GUI are also included.
"""
if cls._all_found_plugins is None:
cls._all_found_plugins = dict()
for loader, name, ispkg in pkgutil.iter_modules([cls.pkgpath]):
full_name = f'electrum.plugins.{name}'
spec = importlib.util.find_spec(full_name)
if spec is None: # pkgutil found it but importlib can't ?!
raise Exception(f"Error pre-loading {full_name}: no spec")
try:
module = importlib.util.module_from_spec(spec)
# sys.modules needs to be modified for relative imports to work
# see https://stackoverflow.com/a/50395128
sys.modules[spec.name] = module
spec.loader.exec_module(module)
except Exception as e:
raise Exception(f"Error pre-loading {full_name}: {repr(e)}") from e
d = module.__dict__
assert name not in cls._all_found_plugins
cls._all_found_plugins[name] = d
return cls._all_found_plugins
def load_plugins(self): def load_plugins(self):
for loader, name, ispkg in pkgutil.iter_modules([self.pkgpath]): for name, d in self.find_all_plugins().items():
full_name = f'electrum.plugins.{name}'
spec = importlib.util.find_spec(full_name)
if spec is None: # pkgutil found it but importlib can't ?!
raise Exception(f"Error pre-loading {full_name}: no spec")
try:
module = importlib.util.module_from_spec(spec)
# sys.modules needs to be modified for relative imports to work
# see https://stackoverflow.com/a/50395128
sys.modules[spec.name] = module
spec.loader.exec_module(module)
except Exception as e:
raise Exception(f"Error pre-loading {full_name}: {repr(e)}") from e
d = module.__dict__
gui_good = self.gui_name in d.get('available_for', []) gui_good = self.gui_name in d.get('available_for', [])
if not gui_good: if not gui_good:
continue continue
@ -147,6 +160,15 @@ class Plugins(DaemonThread):
p.close() p.close()
self.logger.info(f"closed {name}") self.logger.info(f"closed {name}")
@classmethod
def is_plugin_enabler_config_key(cls, key: str) -> bool:
if not key.startswith('use_'):
return False
# note: the 'use_' prefix is not sufficient to check, there are
# non-plugin-related config keys that also have it... hence:
name = key[4:]
return name in cls.find_all_plugins()
def toggle(self, name: str) -> Optional['BasePlugin']: def toggle(self, name: str) -> Optional['BasePlugin']:
p = self.get(name) p = self.get(name)
return self.disable(name) if p else self.enable(name) return self.disable(name) if p else self.enable(name)

22
electrum/simple_config.py

@ -52,6 +52,9 @@ _logger = get_logger(__name__)
FINAL_CONFIG_VERSION = 3 FINAL_CONFIG_VERSION = 3
_config_var_from_key = {} # type: Dict[str, 'ConfigVar']
class ConfigVar(property): class ConfigVar(property):
def __init__( def __init__(
@ -65,6 +68,8 @@ class ConfigVar(property):
self._default = default self._default = default
self._type = type_ self._type = type_
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}"
_config_var_from_key[key] = self
def _get_config_value(self, config: 'SimpleConfig'): def _get_config_value(self, config: 'SimpleConfig'):
with config.lock: with config.lock:
@ -101,8 +106,8 @@ class ConfigVar(property):
return f"<ConfigVar key={self._key!r}>" return f"<ConfigVar key={self._key!r}>"
def __deepcopy__(self, memo): def __deepcopy__(self, memo):
cv = ConfigVar(self._key, default=self._default, type_=self._type) # We can be considered ~stateless. State is stored in the config, which is external.
return cv return self
class ConfigVarWithConfig: class ConfigVarWithConfig:
@ -132,6 +137,11 @@ class ConfigVarWithConfig:
def __repr__(self): def __repr__(self):
return f"<ConfigVarWithConfig key={self.key()!r}>" return f"<ConfigVarWithConfig key={self.key()!r}>"
def __eq__(self, other) -> bool:
if not isinstance(other, ConfigVarWithConfig):
return False
return self._config is other._config and self._config_var is other._config_var
class SimpleConfig(Logger): class SimpleConfig(Logger):
""" """
@ -818,10 +828,18 @@ class SimpleConfig(Logger):
""" """
class CVLookupHelper: class CVLookupHelper:
def __getattribute__(self, name: str) -> ConfigVarWithConfig: def __getattribute__(self, name: str) -> ConfigVarWithConfig:
if name in ("from_key", ): # don't apply magic, just use standard lookup
return super().__getattribute__(name)
config_var = config.__class__.__getattribute__(type(config), name) config_var = config.__class__.__getattribute__(type(config), name)
if not isinstance(config_var, ConfigVar): if not isinstance(config_var, ConfigVar):
raise AttributeError() raise AttributeError()
return ConfigVarWithConfig(config=config, config_var=config_var) return ConfigVarWithConfig(config=config, config_var=config_var)
def from_key(self, key: str) -> ConfigVarWithConfig:
try:
config_var = _config_var_from_key[key]
except KeyError:
raise KeyError(f"No ConfigVar with key={key!r}") from None
return ConfigVarWithConfig(config=config, config_var=config_var)
def __setattr__(self, name, value): def __setattr__(self, name, value):
raise Exception( raise Exception(
f"Cannot assign value to config.cv.{name} directly. " f"Cannot assign value to config.cv.{name} directly. "

6
electrum/tests/test_simple_config.py

@ -199,6 +199,12 @@ class Test_SimpleConfig(ElectrumTestCase):
config.NETWORK_MAX_INCOMING_MSG_SIZE = 2_222_222 config.NETWORK_MAX_INCOMING_MSG_SIZE = 2_222_222
self.assertEqual(5_555_555, config.NETWORK_MAX_INCOMING_MSG_SIZE) self.assertEqual(5_555_555, config.NETWORK_MAX_INCOMING_MSG_SIZE)
def test_configvars_from_key(self):
config = SimpleConfig(self.options)
self.assertEqual(config.cv.NETWORK_SERVER, config.cv.from_key("server"))
with self.assertRaises(KeyError):
config.cv.from_key("server333")
def test_depth_target_to_fee(self): def test_depth_target_to_fee(self):
config = SimpleConfig(self.options) config = SimpleConfig(self.options)
config.mempool_fees = [[49, 100110], [10, 121301], [6, 153731], [5, 125872], [1, 36488810]] config.mempool_fees = [[49, 100110], [10, 121301], [6, 153731], [5, 125872], [1, 36488810]]

Loading…
Cancel
Save