From 932c256509e78130732323e4987cd6f647c3da19 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Thu, 11 Apr 2024 16:08:24 +0200 Subject: [PATCH] lib/msgpackrpc: Require a name when binding callables. This change adds a required `name` argument to `MsgPackRPC.bind()` to avoid any ambiguity when binding multiple instances of the same class for example. Additionally, `bind()` now supports binding whole objects, by binding all of their public methods using their respective qualified names. Signed-off-by: iabdalkader --- lib/msgpackrpc/example.py | 48 ++++++++++++++++++++++++++++-------- lib/msgpackrpc/msgpackrpc.py | 33 +++++++++++++------------ 2 files changed, 55 insertions(+), 26 deletions(-) diff --git a/lib/msgpackrpc/example.py b/lib/msgpackrpc/example.py index 1b32e3a..5ec5d06 100644 --- a/lib/msgpackrpc/example.py +++ b/lib/msgpackrpc/example.py @@ -8,13 +8,9 @@ import gc -class Adder: - def __init__(self): - pass - - def add(self, a, b): - logging.info(f"add({a}, {b}) is called") - return a + b +def add(a, b): + logging.info(f"add({a}, {b}) is called") + return a + b def sub(a, b): @@ -22,6 +18,28 @@ def sub(a, b): return a - b +class Foo: + def __init__(self, name): + self.name = name + + def add(self, a, b): + logging.info(f"{self.name}.add({a}, {b}) is called") + return a + b + + def sub(self, a, b): + logging.info(f"{self.name}.sub({a}, {b}) is called") + return a - b + + +class Adder: + def __init__(self): + pass + + def __call__(self, a, b): + logging.info(f"Adder({a}, {b}) is called") + return a + b + + if __name__ == "__main__": # Configure the logger. # All message equal to or higher than the logger level are printed. @@ -34,9 +52,19 @@ def sub(a, b): # Create an RPC object rpc = msgpackrpc.MsgPackRPC() - # Register objects or functions to be called by the remote processor. - rpc.bind(Adder()) - rpc.bind(sub) + # Register remote functions. + rpc.bind("sub", sub) + rpc.bind("add", add) + + # Register a callable object. + rpc.bind("adder", Adder()) + + # Register full objects. The following binds all public methods of an object to their + # respective qualified names. For instance, `foo1`'s methods will be bound to `foo1.add` + # and `foo1.sub`. Alternatively, bound methods can be registered individually, by calling + # bind on each method. For example, `rpc.bind("foo.add", foo.add)`. + rpc.bind("foo1", Foo("foo1")) + rpc.bind("foo2", Foo("foo2")) # Start the remote processor and wait for it to be ready to communicate. rpc.start(firmware=0x08180000, timeout=1000, num_channels=2) diff --git a/lib/msgpackrpc/msgpackrpc.py b/lib/msgpackrpc/msgpackrpc.py index 279de88..df5090a 100644 --- a/lib/msgpackrpc/msgpackrpc.py +++ b/lib/msgpackrpc/msgpackrpc.py @@ -98,7 +98,7 @@ def __init__(self, streaming=False): self.msgid = 0 self.msgbuf = {} self.msgio = MsgPackIO() if streaming else None - self.servers = [] + self.callables = {} def _bind_callback(self, src, name): if log_level_enabled(logging.INFO): @@ -135,30 +135,31 @@ def _send_msg(self, msgid, msgtype, fname, fargs, **kwargs): return Future(msgid, self.msgbuf, fname, fargs) def _dispatch(self, msgid, fname, fargs): - func = None retobj = None error = None - for obj in self.servers: - if callable(obj) and obj.__name__ == fname: - func = obj - elif hasattr(obj, fname): - func = getattr(obj, fname) - if func is not None: - break - - if func is not None: - retobj = func(*fargs) + + if fname in self.callables: + retobj = self.callables[fname](*fargs) else: error = "Unbound function called %s" % (fname) self._send_msg(msgid, _MSG_TYPE_RESPONSE, error, retobj) - def bind(self, obj): + def bind(self, name, obj): """ - Register an object or a function to be called by the remote processor. - obj: An object whose methods can be called by remote processors, or a function. + Bind a callable or an object to a name. + name: The name to which the callable or object is bound. + obj: A callable or an object to bind to the name. If an object is passed, all of its + public methods will be bound to their respective qualified names. """ - self.servers.append(obj) + if callable(obj): + # Bind a single callable to its name. + self.callables[name] = obj + else: + # Bind all public methods of an object to their respective qualified names. + for k, v in obj.__class__.__dict__.items(): + if callable(v) and not k.startswith("_"): + self.callables[name + "." + k] = getattr(obj, k) def start(self, firmware=None, num_channels=2, timeout=3000): """