From 557b5fee46ef0d413d2a02c348574002b5dff2b8 Mon Sep 17 00:00:00 2001 From: Erik De Smedt Date: Thu, 14 Dec 2023 11:22:44 +0100 Subject: [PATCH] Allow dynamic option in python plugin --- contrib/pyln-client/pyln/client/plugin.py | 21 +++++++++++++++------ tests/plugins/dynamic_option.py | 12 ++++++++++++ tests/test_plugin.py | 11 +++++++++++ 3 files changed, 38 insertions(+), 6 deletions(-) create mode 100755 tests/plugins/dynamic_option.py diff --git a/contrib/pyln-client/pyln/client/plugin.py b/contrib/pyln-client/pyln/client/plugin.py index 59d99bf7886a..541a30d4bedb 100644 --- a/contrib/pyln-client/pyln/client/plugin.py +++ b/contrib/pyln-client/pyln/client/plugin.py @@ -220,7 +220,8 @@ def __init__(self, stdout: Optional[io.TextIOBase] = None, invoice_features: Optional[Union[int, str, bytes]] = None, custom_msgs: Optional[List[int]] = None): self.methods = { - 'init': Method('init', self._init, MethodType.RPCMETHOD) + 'init': Method('init', self._init, MethodType.RPCMETHOD), + 'setconfig': Method('setconfig', self._set_config, MethodType.RPCMETHOD) } self.options: Dict[str, Dict[str, Any]] = {} @@ -389,7 +390,8 @@ def decorator(f: Callable[..., None]) -> Callable[..., None]: def add_option(self, name: str, default: Optional[str], description: Optional[str], opt_type: str = "string", deprecated: bool = False, - multi: bool = False) -> None: + multi: bool = False, + dynamic=False) -> None: """Add an option that we'd like to register with lightningd. Needs to be called before `Plugin.run`, otherwise we might not @@ -414,10 +416,11 @@ def add_option(self, name: str, default: Optional[str], 'value': None, 'multi': multi, 'deprecated': deprecated, + "dynamic": dynamic } def add_flag_option(self, name: str, description: str, - deprecated: bool = False) -> None: + deprecated: bool = False, dynamic: bool = False) -> None: """Add a flag option that we'd like to register with lightningd. Needs to be called before `Plugin.run`, otherwise we might not @@ -425,7 +428,7 @@ def add_flag_option(self, name: str, description: str, """ self.add_option(name, None, description, opt_type="flag", - deprecated=deprecated) + deprecated=deprecated, dynamic=dynamic) def add_notification_topic(self, topic: str): """Announce that the plugin will emit notifications for the topic. @@ -784,7 +787,7 @@ def print_usage(self): """) for method in self.methods.values(): - if method.name in ['init', 'getmanifest']: + if method.name in ['init', 'getmanifest', 'setconfig']: # Skip internal methods provided by all plugins continue @@ -864,7 +867,7 @@ def _getmanifest(self, **kwargs) -> JSONType: hooks = [] for method in self.methods.values(): # Skip the builtin ones, they don't get reported - if method.name in ['getmanifest', 'init']: + if method.name in ['getmanifest', 'init', 'setconfig']: continue if method.mtype == MethodType.HOOK: @@ -970,6 +973,12 @@ def verify_bool(d: Dict[str, JSONType], key: str) -> bool: return self._exec_func(self.child_init, request) return None + def _set_config(self, **_) -> None: + """Called when the value of a dynamic option is changed + For now we don't do anything. + """ + pass + class PluginStream(object): """Sink that turns everything that is written to it into a notification. diff --git a/tests/plugins/dynamic_option.py b/tests/plugins/dynamic_option.py new file mode 100755 index 000000000000..181bc176bea1 --- /dev/null +++ b/tests/plugins/dynamic_option.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python3 +from pyln.client import Plugin + +plugin = Plugin() + +plugin.add_option( + name="test-dynamic-config", + description="A config option which can be changed at run-time", + default="initial", + dynamic=True) + +plugin.run() diff --git a/tests/test_plugin.py b/tests/test_plugin.py index 58eb81368756..0e6ca7c6c5ad 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -4258,6 +4258,17 @@ def test_all_subscription(node_factory, directory): assert not l2.daemon.is_in_log(f'.*test_libplugin: all: connect.*') +def test_dynamic_option_python_plugin(node_factory): + plugin = os.path.join(os.getcwd(), "tests/plugins/dynamic_option.py") + ln = node_factory.get_node(options={"plugin": plugin}) + result = ln.rpc.listconfigs("test-dynamic-config") + + assert result["configs"]["test-dynamic-config"]["value_str"] == "initial" + + result = ln.rpc.setconfig("test-dynamic-config", "changed") + assert result["config"]["value_str"] == "changed" + + def test_renepay_not_important(node_factory): # I mean, it's *important*, it's just not "mission-critical" just yet! l1 = node_factory.get_node(options={'allow-deprecated-apis': True})