Skip to content

Commit e0621fa

Browse files
committed
pylightning: Better inject arguments in plugin method calls
If the `request` or `plugin` parameter that are injected by the framework where before or inbetween positional arguments we'd be injecting them incorrectly, i.e., we'd be providing them both as `args` (and mismapping another argument) as well as `kwargs`. This is a better way to map arguments, which takes advantage of the fact that JSON-RPC calls are either all positional or named arguments. I also included a test for various scenarios, that hopefull cover the most common cases. Reported-by: Rene Pickhardt <@renepickhardt> Signed-off-by: Christian Decker <decker.christian@gmail.com>
1 parent efa3887 commit e0621fa

File tree

2 files changed

+79
-8
lines changed

2 files changed

+79
-8
lines changed

contrib/pylightning/lightning/plugin.py

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from collections import OrderedDict
2+
13
import sys
24
import os
35
import json
@@ -131,18 +133,36 @@ def decorator(f):
131133

132134
def _exec_func(self, func, request):
133135
params = request['params']
134-
args = params.copy() if isinstance(params, list) else []
135-
kwargs = params.copy() if isinstance(params, dict) else {}
136-
137136
sig = inspect.signature(func)
138137

139-
if 'plugin' in sig.parameters:
140-
kwargs['plugin'] = self
138+
arguments = OrderedDict()
139+
for name, value in sig.parameters.items():
140+
arguments[name] = inspect.Signature.empty
141141

142-
if 'request' in sig.parameters:
143-
kwargs['request'] = request
142+
# Fill in any injected parameters
143+
if 'plugin' in arguments:
144+
arguments['plugin'] = self
144145

145-
ba = sig.bind(*args, **kwargs)
146+
if 'request' in arguments:
147+
arguments['request'] = request
148+
149+
# Now zip the provided arguments and the prefilled a together
150+
if isinstance(params, dict):
151+
arguments.update(params)
152+
else:
153+
pos = 0
154+
for k, v in arguments.items():
155+
if v is not inspect.Signature.empty:
156+
continue
157+
if pos < len(params):
158+
# Apply positional args if we have them
159+
arguments[k] = params[pos]
160+
else:
161+
# For the remainder apply default args
162+
arguments[k] = sig.parameters[k].default
163+
pos += 1
164+
165+
ba = sig.bind(**arguments)
146166
ba.apply_defaults()
147167
return func(*ba.args, **ba.kwargs)
148168

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
from .plugin import Plugin
2+
import itertools
3+
4+
5+
def test_positional_inject():
6+
p = Plugin()
7+
rdict = {
8+
'id': 1,
9+
'jsonrpc':
10+
'2.0',
11+
'method': 'func',
12+
'params': {'a': 1, 'b': 2, 'kwa': 3, 'kwb': 4}
13+
}
14+
rarr = {
15+
'id': 1,
16+
'jsonrpc': '2.0',
17+
'method': 'func',
18+
'params': [1, 2, 3, 4]
19+
}
20+
21+
def pre_args(plugin, a, b, kwa=3, kwb=4):
22+
assert (plugin, a, b, kwa, kwb) == (p, 1, 2, 3, 4)
23+
24+
def in_args(a, plugin, b, kwa=3, kwb=4):
25+
assert (plugin, a, b, kwa, kwb) == (p, 1, 2, 3, 4)
26+
27+
def post_args(a, b, plugin, kwa=3, kwb=4):
28+
assert (plugin, a, b, kwa, kwb) == (p, 1, 2, 3, 4)
29+
30+
def post_kwargs(a, b, kwa=3, kwb=4, plugin=None):
31+
assert (plugin, a, b, kwa, kwb) == (p, 1, 2, 3, 4)
32+
33+
def in_multi_args(a, request, plugin, b, kwa=3, kwb=4):
34+
assert request in [rarr, rdict]
35+
assert (plugin, a, b, kwa, kwb) == (p, 1, 2, 3, 4)
36+
37+
def in_multi_mix_args(a, plugin, b, request=None, kwa=3, kwb=4):
38+
assert request in [rarr, rdict]
39+
assert (plugin, a, b, kwa, kwb) == (p, 1, 2, 3, 4)
40+
41+
def extra_def_arg(a, b, c, d, e=42):
42+
""" Also uses a different name for kwa and kwb
43+
"""
44+
assert (a, b, c, d, e) == (1, 2, 3, 4, 42)
45+
46+
funcs = [pre_args, in_args, post_args, post_kwargs, in_multi_args]
47+
48+
for func, request in itertools.product(funcs, [rdict, rarr]):
49+
p._exec_func(func, request)
50+
51+
p._exec_func(extra_def_arg, rarr)

0 commit comments

Comments
 (0)