[Python-ideas] Improving xmlrpc introspection

Claudiu Popa pcmanticore at gmail.com
Wed Jun 18 12:23:26 CEST 2014


Hello.

This idea proposes enhancing the xmlrpc library by adding a couple
of introspectable servers and proxies. For instance, here's an output of
using the current idioms.

>>> proxy = ServerProxy('http://localhost:8000')
>>> dir(proxy)
['_ServerProxy__allow_none', '_ServerProxy__close',
'_ServerProxy__encoding', '_ServerProxy__handler',
'_ServerProxy__host', '_ServerProxy__request',
'_ServerProxy__transport', '_ServerProxy__verbose', '__call__',
'__class__', '__delattr__', '__dict__', '__dir__', '__doc__',
'__enter__', '__eq__', '__exit__', '__format__', '__ge__',
'__getattr__'
, '__getattribute__', '__gt__', '__hash__', '__init__', '__le__',
'__lt__', '__module__', '__ne__', '__new__', '__reduce__',
'__reduce_ex__', '__repr__', '__setattr__',
'__sizeof__', '__str__', '__subclasshook__', '__weakref__']


Nothing useful in dir. The following works only if the server enables
introspection:

>>> proxy.system.listMethods()
['mul', 'pow', 'system.listMethods', 'system.methodHelp',
'system.methodSignature']

Now, let's see what mul does:

>>> proxy.mul
<xmlrpc.client._Method object at 0x02AFB690>
>>> help(proxy.mul)
Help on _Method in module xmlrpc.client object:

class _Method(builtins.object)
 |  Methods defined here:
 |
 |  __call__(self, *args)
 |
 |  __getattr__(self, name)
 |
 |  __init__(self, send, name)
 |      # some magic to bind an XML-RPC method to an RPC server.
 |      # supports "nested" methods (e.g. examples.getStateName)
 |
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |
 |  __dict__
 |      dictionary for instance variables (if defined)
 |
 |  __weakref__
 |      list of weak references to the object (if defined)



Nothing useful for us. Neither methodHelp, nor methodSignature are very useful:

>>> proxy.system.methodHelp('mul')
'multiplication'
>>> proxy.system.methodSignature('mul')
'signatures not supported'


We can find out something about that method by calling it.

>>> proxy.mul(1, 2, 3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "D:\Projects\cpython\lib\xmlrpc\client.py", line 1091, in __call__
    return self.__send(self.__name, args)
  File "D:\Projects\cpython\lib\xmlrpc\client.py", line 1421, in __request
    verbose=self.__verbose
  File "D:\Projects\cpython\lib\xmlrpc\client.py", line 1133, in request
    return self.single_request(host, handler, request_body, verbose)
  File "D:\Projects\cpython\lib\xmlrpc\client.py", line 1149, in single_request
    return self.parse_response(resp)
  File "D:\Projects\cpython\lib\xmlrpc\client.py", line 1320, in parse_response
    return u.close()
  File "D:\Projects\cpython\lib\xmlrpc\client.py", line 658, in close
    raise Fault(**self._stack[0])
xmlrpc.client.Fault: <Fault 1: "<class 'TypeError'>:mul() takes 3
positional arguments but 4 were given">


So, only after calling a method, one can find meaningful informations about it.
My idea behaves like this:

>>> from xmlrpc.client import MagicProxy # not a very good name, but it does some magic behind
>>> proxy = MagicProxy('http://localhost:8000')
>>> dir(proxy)
['_ServerProxy__allow_none', '_ServerProxy__close',
'_ServerProxy__encoding', '_ServerProxy__handler',
'_ServerProxy__host', '_ServerProxy__request', '_ServerProxy__trans
', '_ServerProxy__verbose', '__call__', '__class__', '__delattr__',
'__dict__', '__dir__', '__doc__', '__enter__', '__eq__', '__exit__',
'__format__', '__ge__',
'__getattr__', '__getattribute__', '__gt__', '__hash__', '__init__',
'__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__',
'__reduce_ex__', '__repr__', '__setattr__',
'__sizeof__', '__str__', '__subclasshook__', '__weakref__',
'_collect_methods', '_original_mul', '_original_pow', 'mul', 'pow']
>>> proxy.mul
<function mul at 0x035AD5D8>
>>> proxy.pow
<function pow at 0x035AD638>
>>> help(proxy.mul)
Help on function mul in module xmlrpc.client:

mul(x:1, y) -> 2
    multiplication

>>> help(proxy.pow)
Help on function pow in module xmlrpc.client:

pow(*args, **kwargs)
    pow(x, y[, z]) -> number

    With two arguments, equivalent to x**y.  With three arguments,
    equivalent to (x**y) % z, but may be more efficient (e.g. for ints).

>>> proxy.mul(1)
Traceback (most recent call last):
  File "<console>", line 1, in <module>
TypeError: mul() missing 1 required positional argument: 'y'
>>> proxy.mul(1, 2, 3)
Traceback (most recent call last):
  File "<console>", line 1, in <module>
TypeError: mul() takes 2 positional arguments but 3 were given
>>> proxy.mul(1, 2)
2
>>> import inspect
>>> inspect.signature(proxy.mul)
<Signature at 0x35d4b98 "(x:1, y) -> 2">
>>>

As we can  see, the registered methods can be introspected and calling
one with the wrong number of arguments will not trigger a request to
the server, but will fail right in the user's code.
As a problem, it will work only for servers written in Python. For
others will fallback to the current idiom.
Would something like this be useful as an addition to the stdlib's
xmlrpc module?
If someone wants to test it, here's a rough patch against tip:
https://gist.github.com/PCManticore/cf82ab421d4dc5c7f6ff.

Thanks!


More information about the Python-ideas mailing list