Browse Source

pyln: add RpcException for finer method failure control.

Allows caller to set code and exact message to be returned.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
Changelog-Added: pyln-client: plugins can now raise RpcException for finer control over error returns.
ppa
Rusty Russell 4 years ago
committed by Christian Decker
parent
commit
77478408f9
  1. 3
      contrib/pyln-client/README.md
  2. 3
      contrib/pyln-client/pyln/client/__init__.py
  3. 22
      contrib/pyln-client/pyln/client/plugin.py
  4. 35
      contrib/pyln-client/tests/test_plugin.py

3
contrib/pyln-client/README.md

@ -78,6 +78,9 @@ def hello(plugin, name="world"):
It gets reported as the description when registering the function
as a method with `lightningd`.
If this returns (a dict), that's the JSON "result" returned. If
it raises an exception, that causes a JSON "error" return (raising
pyln.client.RpcException allows finer control over the return).
"""
greeting = plugin.get_option('greeting')
s = '{} {}'.format(greeting, name)

3
contrib/pyln-client/pyln/client/__init__.py

@ -1,5 +1,5 @@
from .lightning import LightningRpc, RpcError, Millisatoshi
from .plugin import Plugin, monkey_patch
from .plugin import Plugin, monkey_patch, RpcException
__version__ = "0.8.0"
@ -9,6 +9,7 @@ __all__ = [
"LightningRpc",
"Plugin",
"RpcError",
"RpcException",
"Millisatoshi",
"__version__",
"monkey_patch"

22
contrib/pyln-client/pyln/client/plugin.py

@ -61,6 +61,14 @@ class Method(object):
self.after: List[str] = []
class RpcException(Exception):
# -32600 == "Invalid Request"
def __init__(self, message: str, code: int = -32600):
self.code = code
self.message = message
super().__init__("RpcException: {}".format(message))
class Request(dict):
"""A request object that wraps params and allows async return
"""
@ -102,7 +110,7 @@ class Request(dict):
self.state = RequestState.FINISHED
self.termination_tb = "".join(traceback.extract_stack().format()[:-1])
def set_exception(self, exc: Exception) -> None:
def set_exception(self, exc: Union[Exception, RpcException]) -> None:
if self.state != RequestState.PENDING:
assert(self.termination_tb is not None)
raise ValueError(
@ -110,13 +118,19 @@ class Request(dict):
"current state is {state}. Request previously terminated at\n"
"{tb}".format(state=self.state, tb=self.termination_tb))
self.exc = exc
if isinstance(exc, RpcException):
code = exc.code
message = exc.message
else:
code = -32600 # "Invalid Request"
message = ("Error while processing {method}: {exc}"
.format(method=self.method, exc=str(exc)))
self._write_result({
'jsonrpc': '2.0',
'id': self.id,
"error": {
"code": -32600, # "Invalid Request"
"message": "Error while processing {method}: {exc}"
.format(method=self.method, exc=str(exc)),
"code": code,
"message": message,
# 'data' field "may be omitted."
"traceback": traceback.format_exc(),
},

35
contrib/pyln-client/tests/test_plugin.py

@ -1,5 +1,5 @@
from pyln.client import Plugin
from pyln.client.plugin import Request, Millisatoshi
from pyln.client.plugin import Request, Millisatoshi, RpcException
import itertools
import pytest # type: ignore
@ -172,6 +172,39 @@ def test_methods_errors():
assert call_list == []
def test_method_exceptions():
"""A bunch of tests that should fail calling the methods."""
p = Plugin(autopatch=False)
def fake_write_result(resultdict):
global result_dict
result_dict = resultdict
@p.method("test_raise")
def test_raise():
raise RpcException("testing RpcException", code=-1000)
req = Request(p, 1, "test_raise", {})
req._write_result = fake_write_result
p._dispatch_request(req)
assert result_dict['jsonrpc'] == '2.0'
assert result_dict['id'] == 1
assert result_dict['error']['code'] == -1000
assert result_dict['error']['message'] == "testing RpcException"
@p.method("test_raise2")
def test_raise2():
raise Exception("normal exception")
req = Request(p, 1, "test_raise2", {})
req._write_result = fake_write_result
p._dispatch_request(req)
assert result_dict['jsonrpc'] == '2.0'
assert result_dict['id'] == 1
assert result_dict['error']['code'] == -32600
assert result_dict['error']['message'] == "Error while processing test_raise2: normal exception"
def test_positional_inject():
p = Plugin()
rdict = Request(

Loading…
Cancel
Save