mosquito / aiohttp-xmlrpc

XMLRPC for aiohttp
MIT License
35 stars 19 forks source link

Support for nested function calls #47

Closed ArmandBENETEAU closed 2 years ago

ArmandBENETEAU commented 3 years ago

Hi all!

In my company, we are really interested to use aiohttp-xmlrpc library to create a python client talking with a LAVA master.

However, we figured out that aiohttp-xmlrpc seems to not support the "nested remote method calls". Unfortunately, LAVA is using only nested methods as you can see in the link here above.

My first question is do you plan to support that in the future? My second question is: are you interested in integrating this support if we submit it in a pull requests?

Thank you by advance for your answers!

ArmandBENETEAU commented 3 years ago

Actually I've just noticed that a workaround exists for this problem! I just have to call explicitly the getattr() function...

# Get the list of available devices

# First attempt (not working)
# devices = await async_client.scheduler.devices.list()

# Second attempt (working)
devices = await getattr(async_client, "scheduler.devices.list")()
mosquito commented 3 years ago

It is not very difficult to implement, rather it is difficult to implement correctly.

For example, there is a class A with the foo method, should a call to the A.foo instantiate class A and with what parameters?

ArmandBENETEAU commented 3 years ago

I am not sure that I completely understood your question.

For my specific need, the full name of the method (with the point) needs to be passed to the <methodName> field in the body of the HTTP request.

So, no needs to instantiate the "class A" method, only pass the following method in the HTTP request:

<?xml version='1.0' encoding='ASCII'?>
<methodCall><methodName>A.foo</methodName><params/></methodCall>

It is basically what the (synchronous) xmlrpc library built-in python 3 does. As we can see here:

mosquito commented 3 years ago

Well, try to imagine a code example that would work both from the server-side and from the client-side, and write it here.

ArmandBENETEAU commented 3 years ago

Okay, I thing I get your point now. It will be easier to do that on the client-side than on the server-side. I will see what I can do about that.

ArmandBENETEAU commented 3 years ago

Hi again Mosquito,

So I've seen with my hierarchy today and since we have a lot of work currently and that we are not using the server-side of aiohttp-xmlrpc, I cannot spend time "right now" on the server-side of this feature. Nevertheless, I could go back on it later when the peak of activities will have been passed.

In the mid-time, I can do a pull request with the correction I have done on the client side. The modifications I plan to add are in the following patch:

From f47ace384e953db25a8f95560cf32645bcbe68e9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Armand=20B=C3=A9n=C3=A9teau?= <armand.beneteau@iot.bzh>
Date: Mon, 15 Nov 2021 15:25:46 +0000
Subject: [PATCH] Allow nested methods to be called on client side
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Armand Bénéteau <armand.beneteau@iot.bzh>
---
 aiohttp_xmlrpc/client.py | 29 +++++++++++++++++++++--------
 1 file changed, 21 insertions(+), 8 deletions(-)

diff --git a/aiohttp_xmlrpc/client.py b/aiohttp_xmlrpc/client.py
index 2047d74..ed1440d 100644
--- a/aiohttp_xmlrpc/client.py
+++ b/aiohttp_xmlrpc/client.py
@@ -12,6 +12,20 @@ from .exceptions import xml2py_exception
 log = logging.getLogger(__name__)

+class _Method:
+    # some magic to bind an XML-RPC method to an RPC server.
+    # supports "nested" methods (e.g. examples.getStateName)
+    def __init__(self, send, name):
+        self.__send = send
+        self.__name = name
+
+    def __getattr__(self, name):
+        return _Method(self.__send, "%s.%s" % (self.__name, name))
+
+    def __call__(self, *args, **kwargs):
+        return self.__send(self.__name, args, kwargs)
+
+
 class ServerProxy(object):
     __slots__ = "client", "url", "loop", "headers", "encoding", "huge_tree"

@@ -107,13 +121,12 @@ class ServerProxy(object):
             return self._parse_response((await response.read()), method_name)

     def __getattr__(self, method_name):
-        return self[method_name]
-
-    def __getitem__(self, method_name):
-        def method(*args, **kwargs):
-            return self.__remote_call(method_name, *args, **kwargs)
-
-        return method
+        # Trick to keep the "close" method available
+        if method_name == "close":
+            return self.__close
+        else:
+            # Magic method dispatcher
+            return _Method(self.__remote_call, method_name)

     def __aenter__(self):
         return self.client.__aenter__()
@@ -121,5 +134,5 @@ class ServerProxy(object):
     def __aexit__(self, exc_type, exc_val, exc_tb):
         return self.client.__aexit__(exc_type, exc_val, exc_tb)

-    def close(self):
+    def __close(self):
         return self.client.close()
-- 
2.27.0
mosquito commented 3 years ago

Ok. Just open PR and let’s continue this dialogue there.