Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 44 additions & 32 deletions contrib/pyln-client/pyln/client/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,46 @@ class Method(object):
"""
def __init__(self, name: str, func: Callable[..., JSONType],
mtype: MethodType = MethodType.RPCMETHOD,
deprecated: Union[bool, List[str]] = None):
deprecated: Union[bool, List[str]] = None,
description: str = None):
self.name = name
self.func = func
self.mtype = mtype
self.background = False
self.deprecated = deprecated
self.description = description
self.before: List[str] = []
self.after: List[str] = []

def get_usage(self):
# Handles out-of-order use of parameters like:
#
# ```python3
#
# def hello_obfus(arg1, arg2, plugin, thing3, request=None,
# thing5='at', thing6=21)
#
# ```
argspec = inspect.getfullargspec(self.func)
defaults = argspec.defaults
num_defaults = len(defaults) if defaults else 0
start_kwargs_idx = len(argspec.args) - num_defaults
args = []
for idx, arg in enumerate(argspec.args):
if arg in ('plugin', 'request'):
continue
# Positional arg
if idx < start_kwargs_idx:
args.append("%s" % arg)
# Keyword arg
else:
args.append("[%s]" % arg)

if self.description is not None:
args.append("\n%s" % self.description)

return " ".join(args)


class RpcException(Exception):
# -32600 == "Invalid Request"
Expand Down Expand Up @@ -323,7 +354,8 @@ def convert_featurebits(

def add_method(self, name: str, func: Callable[..., Any],
background: bool = False,
deprecated: Optional[Union[bool, List[str]]] = None) -> None:
deprecated: Optional[Union[bool, List[str]]] = None,
description: str = None) -> None:
"""Add a plugin method to the dispatch table.

The function will be expected at call time (see `_dispatch`)
Expand Down Expand Up @@ -360,7 +392,7 @@ def add_method(self, name: str, func: Callable[..., Any],
)

# Register the function with the name
method = Method(name, func, MethodType.RPCMETHOD, deprecated)
method = Method(name, func, MethodType.RPCMETHOD, deprecated, description)
method.background = background
self.methods[name] = method

Expand Down Expand Up @@ -493,7 +525,8 @@ def decorator(f: Callable[..., None]) -> Callable[..., None]:
def method(self, method_name: str, category: Optional[str] = None,
desc: Optional[str] = None,
long_desc: Optional[str] = None,
deprecated: Union[bool, List[str]] = None) -> JsonDecoratorType:
deprecated: Union[bool, List[str]] = None,
description: str = None) -> JsonDecoratorType:
"""Decorator to add a plugin method to the dispatch table.

Internally uses add_method.
Expand All @@ -502,7 +535,7 @@ def decorator(f: Callable[..., JSONType]) -> Callable[..., JSONType]:
for attr, attr_name in [(category, "Category"), (desc, "Description"), (long_desc, "Long description")]:
if attr is not None:
self.log("{} is deprecated but defined in method {}; it will be ignored by Core Lightning".format(attr_name, method_name), level="warn")
self.add_method(method_name, f, background=False, deprecated=deprecated)
self.add_method(method_name, f, background=False, deprecated=deprecated, description=f.__doc__)
return f
return decorator

Expand Down Expand Up @@ -773,7 +806,7 @@ def _multi_dispatch(self, msgs: List[bytes]) -> bytes:

return msgs[-1]

def print_usage(self):
def get_usage(self):
import textwrap

executable = os.path.abspath(sys.argv[0])
Expand Down Expand Up @@ -828,7 +861,7 @@ def print_usage(self):
methods_header = None

parts.append(method_tpl.format(
name=method.name,
name="%s %s" % (method.name, method.get_usage()),
))

options_header = textwrap.dedent("""
Expand Down Expand Up @@ -863,8 +896,10 @@ def print_usage(self):
default=opt.default,
typ=opt.opt_type,
))
return "".join(parts)

sys.stdout.write("".join(parts))
def print_usage(self):
sys.stdout.write(self.get_usage())
sys.stdout.write("\n")

def run(self) -> None:
Expand Down Expand Up @@ -913,32 +948,9 @@ def _getmanifest(self, **kwargs) -> JSONType:
doc = "Undocumented RPC method from a plugin."
doc = re.sub('\n+', ' ', doc)

# Handles out-of-order use of parameters like:
#
# ```python3
#
# def hello_obfus(arg1, arg2, plugin, thing3, request=None,
# thing5='at', thing6=21)
#
# ```
argspec = inspect.getfullargspec(method.func)
defaults = argspec.defaults
num_defaults = len(defaults) if defaults else 0
start_kwargs_idx = len(argspec.args) - num_defaults
args = []
for idx, arg in enumerate(argspec.args):
if arg in ('plugin', 'request'):
continue
# Positional arg
if idx < start_kwargs_idx:
args.append("%s" % arg)
# Keyword arg
else:
args.append("[%s]" % arg)

methods.append({
'name': method.name,
'usage': " ".join(args)
'usage': method.get_usage()
})

manifest = {
Expand Down
19 changes: 19 additions & 0 deletions contrib/pyln-client/tests/test_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -435,3 +435,22 @@ def test4(request):
ba = p._bind_kwargs(test4, {}, req)
with pytest.raises(ValueError, match=r'current state is RequestState\.FINISHED(.*\n*.*)*MARKER4'):
test4(*ba.args)


def test_usage():
p = Plugin(autopatch=False)

@p.method("some_method")
def some_method(some_arg: str = None):
"""some description"""
pass

manifest = p._getmanifest()
usage = p.get_usage()

assert manifest['rpcmethods'][0]['name'] == 'some_method'
assert "some_arg" in manifest['rpcmethods'][0]['usage']
assert "some description" in manifest['rpcmethods'][0]['usage']
assert "some_method" in usage
assert "some_arg" in usage
assert "some description" in usage
2 changes: 1 addition & 1 deletion tests/test_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ def test_rpc_passthrough(node_factory):
assert(len(cmd) == 1)

# Make sure usage message is present.
assert only_one(n.rpc.help('hello')['help'])['command'] == 'hello [name]'
assert only_one(n.rpc.help('hello')['help'])['command'].startswith('hello [name]')
# While we're at it, let's check that helloworld.py is logging
# correctly via the notifications plugin->lightningd
assert n.daemon.is_in_log('Plugin helloworld.py initialized')
Expand Down
Loading