-
Notifications
You must be signed in to change notification settings - Fork 3.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Plugins that add command-line commands #1814
Comments
Just had a much better idea, call it Option 3:
|
why not add a hook in the command line parser? |
this feature would be nice currently if we want to add a command, it means forking Electrum is the only way to do so |
It is not possible to add commands to Electrum from a plugin, because we use index 6d713fb25..69296d9fc 100644
--- a/electrum/commands.py
+++ b/electrum/commands.py
@@ -1410,6 +1410,12 @@ class Commands:
"source": self.daemon.fx.exchange.name(),
}
+ @command('')
+ async def to_plugins(self, *args, **kwargs):
+ """send a command to plugin"""
+ response = run_hook('to_plugins', *args, **kwargs)
+ return response
+
def eval_bool(x: str) -> bool:
if x == 'false': return False
Note: one restriction is that run_hook is not async |
what if we need network |
it makes sense to add |
hmm, |
but wait, doesn't importing and using |
what is your use case? do you want to replace an existing command? |
no, my use case is to bring bisq client's functionality to electrum as a plugin |
the issue is, |
hmm, can we change it to make it possible? I am willing to work on it if it means maintaining the project i want to work on will become easier |
well, what is it exactly that you need? |
to be able to register commands without a prefix like |
This is not really workable.
That's actually a non-issue. It is true we currently don't have any examples of async functions being decorated with To illustrate most of my points above, I have a working example implementation of your suggestion below -- it is quite ugly :) patchdiff --git a/electrum/commands.py b/electrum/commands.py
index 6d713fb25b..a01e8d7a0e 100644
--- a/electrum/commands.py
+++ b/electrum/commands.py
@@ -1137,6 +1137,17 @@ class Commands:
"confirmations": wallet.adb.get_tx_height(txid).conf,
}
+ @command('')
+ async def to_plugins(self, cmdname, kwargs=None):
+ """send a command to plugin"""
+ if kwargs is None:
+ kwargs = {}
+ hook_result = run_hook(cmdname, **kwargs)
+ if asyncio.iscoroutine(hook_result):
+ return await hook_result
+ else:
+ return hook_result
+
@command('')
async def help(self):
# for the python console
@@ -1491,6 +1502,7 @@ command_options = {
'from_ccy': (None, "Currency to convert from"),
'to_ccy': (None, "Currency to convert to"),
'public': (None, 'Channel will be announced'),
+ 'kwargs': (None, "custom kwargs to pass to command"),
}
@@ -1516,6 +1528,7 @@ arg_types = {
'encrypt_file': eval_bool,
'rbf': eval_bool,
'timeout': float,
+ 'kwargs': ast.literal_eval,
}
config_variables = {
@@ -1555,7 +1568,6 @@ argparse.ArgumentParser.set_default_subparser = set_default_subparser
# workaround https://bugs.python.org/issue23058
-# see https://github.com/nickstenning/honcho/pull/121
def subparser_call(self, parser, namespace, values, option_string=None):
from argparse import ArgumentError, SUPPRESS, _UNRECOGNIZED_ARGS_ATTR
diff --git a/electrum/plugin.py b/electrum/plugin.py
index eb7a8f49d5..9a4039d557 100644
--- a/electrum/plugin.py
+++ b/electrum/plugin.py
@@ -365,13 +365,13 @@ def hook(func):
return func
-def run_hook(name, *args):
+def run_hook(name, *args, **kwargs):
results = []
f_list = hooks.get(name, [])
for p, f in f_list:
if p.is_enabled():
try:
- r = f(*args)
+ r = f(*args, **kwargs)
except Exception:
_logger.exception(f"Plugin error. plugin: {p}, hook: {name}")
r = False
diff --git a/electrum/plugins/labels/labels.py b/electrum/plugins/labels/labels.py
index 20e9946e93..1a1711586f 100644
--- a/electrum/plugins/labels/labels.py
+++ b/electrum/plugins/labels/labels.py
@@ -127,6 +127,15 @@ class LabelsPlugin(BasePlugin):
'externalId': encoded_key})
await self.do_post("/labels", bundle)
+ @hook
+ async def pull_labels_async(self, **kwargs):
+ await asyncio.sleep(2)
+ return f"done stuff 1. called with: {kwargs=}"
+
+ @hook
+ def pull_labels_sync(self, **kwargs):
+ return f"done stuff 2. called with: {kwargs=}"
+
async def pull_thread(self, wallet: 'Abstract_Wallet', force: bool):
wallet_data = self.wallets.get(wallet, None)
if not wallet_data:
|
Can't we make it a special case and load |
We could have a special keyword e.g. "plugin"/"plugincmd" (or maybe even the name of specific plugins), and if that keyword is the first argument in And then an example command invocation would be: Note that we probably don't want to unconditionally load the plugins before argparse (hence this |
like rewriting it so each command lives as a file with the same name as command in a directory, if not found try each plugin's |
okay I have changed my approach so I don't exactly need it to be directly after run_electrum, edit: I didn't need this all along. I decided to do it as a standalone project, later to be used as a lib inside the plugin, so yeah, not needed |
I'd like to make a plugin that defines a new command-line command. At this point, that doesn't seem possible, and existing plugins are only adding support for hardware wallets so they can interact with standard command-line commands.
Challenges include:
lib.commands.get_parser
is called, so they are not listed inknown_commands
when the command-line parser is constructed.lib.commands.command
decorator outside of that file.unknown command:
error message hacked into URI handling for the GUI: electrum:310My proposed changes (which I would make and submit as a PR for further review):
step 1:
argparse.Action()
handler forurl
argument so it's only set if starts withbitcoin:
electrum bitcoin:1foo
step 2A:
argparser
qt
,cmdline
are now, plugins may defineplugins/FOO/argparser.py
and it gets hooked into the argument processing stuff, based on theavailable_for
value defined inplugins/FOO/__init__.py
knowncommands
OR a different approach: step 2B:
early
known_commands
From my point of view, approach 2A is better, but 2B is more general purpose and flexible. The only real difference is that 2A is "more objected oriented".
What do you think of these changes? Other ideas?
The text was updated successfully, but these errors were encountered: