From a707ae091d862dbdca4ad7b20d5e5507736a10b2 Mon Sep 17 00:00:00 2001 From: Christian Decker Date: Tue, 18 Dec 2018 17:32:16 +0100 Subject: [PATCH] pylightning: Add hooks as a new type of method Instead of creating a new map I opted to re-use the Plugin.methods map, since the semantics are really similar and we don't allow duplicates. The only difference is in how they are announced to lightningd, so we use an enum to differentiate rpcmethods from hooks, since only the former will get added to the JSON-RPC dispatch table in lightningd. Signed-off-by: Christian Decker --- contrib/pylightning/lightning/plugin.py | 42 ++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/contrib/pylightning/lightning/plugin.py b/contrib/pylightning/lightning/plugin.py index 44ec78840578..f4e2d05fb49a 100644 --- a/contrib/pylightning/lightning/plugin.py +++ b/contrib/pylightning/lightning/plugin.py @@ -1,5 +1,6 @@ from collections import OrderedDict from lightning import LightningRpc +from enum import Enum import inspect import json @@ -9,6 +10,11 @@ import traceback +class MethodType(Enum): + RPCMETHOD = 0 + HOOK = 1 + + class Plugin(object): """Controls interactions with lightningd, and bundles functionality. @@ -66,7 +72,7 @@ def add_method(self, name, func): ) # Register the function with the name - self.methods[name] = func + self.methods[name] = (func, MethodType.RPCMETHOD) def add_subscription(self, topic, func): """Add a subscription to our list of subscriptions. @@ -133,6 +139,25 @@ def decorator(f): return f return decorator + def add_hook(self, name, func): + """Register a hook that is called synchronously by lightningd on events + """ + if name in self.methods: + raise ValueError( + "Method {} was already registered".format(name, self.methods[name]) + ) + self.methods[name] = (func, MethodType.HOOK) + + def hook(self, method_name): + """Decorator to add a plugin hook to the dispatch table. + + Internally uses add_hook. + """ + def decorator(f): + self.add_hook(method_name, f) + return f + return decorator + def _exec_func(self, func, request): params = request['params'] sig = inspect.signature(func) @@ -173,7 +198,7 @@ def _dispatch_request(self, request): if name not in self.methods: raise ValueError("No method {} found.".format(name)) - func = self.methods[name] + func, _ = self.methods[name] try: result = { @@ -244,7 +269,7 @@ def run(self): # then unstash this and call it. if 'init' in self.methods: self.init = self.methods['init'] - self.methods['init'] = self._init + self.methods['init'] = (self._init, MethodType.RPCMETHOD) partial = "" for l in self.stdin: @@ -258,11 +283,17 @@ def run(self): def _getmanifest(self): methods = [] - for name, func in self.methods.items(): + hooks = [] + for name, entry in self.methods.items(): + func, typ = entry # Skip the builtin ones, they don't get reported if name in ['getmanifest', 'init']: continue + if typ == MethodType.HOOK: + hooks.append(name) + continue + doc = inspect.getdoc(func) doc = re.sub('\n+', ' ', doc) if not doc: @@ -280,6 +311,7 @@ def _getmanifest(self): 'options': list(self.options.values()), 'rpcmethods': methods, 'subscriptions': list(self.subscriptions.keys()), + 'hooks': hooks, } def _init(self, options, configuration, request): @@ -293,7 +325,7 @@ def _init(self, options, configuration, request): # Swap the registered `init` method handler back in and # re-dispatch if self.init: - self.methods['init'] = self.init + self.methods['init'], _ = self.init self.init = None return self._exec_func(self.methods['init'], request) return None