1

I have a xmlrpc server running looking like the following

from SimpleXMLRPCServer import SimpleXMLRPCServer

def add(x,y):
    return x+y

server = SimpleXMLRPCServer(("localhost", 8000))
server.register_function(add, 'add.numbers')
server.serve_forever()

which is called used within the following code:

import xmlrpclib

class DeviceProxy(object):
    def __init__(self, uri):
        self.rpc = xmlrpclib.ServerProxy(uri)
    def __getattr__(self, attr):
        return getattr(self.rpc, attr)

original = DeviceProxy.__getattr__

def mygetattr(device, attr):
    def wrapper(*args, **kw):
        print('called with %r and %r' % (args, kw))
        return original(device, attr)(*args, **kw)
    return wrapper

DeviceProxy.__getattr__ = mygetattr

dev = DeviceProxy("http://localhost:8000/RPC2")
print dev.add.numbers(4,6)

As you can see, the Proxy class wraps the xmlrpc proxy for reasons outside the scope of this question, forwarding arbitrary calls via the __getattr__ method . For further reasons outside the scope for this question, I need to wrap/replace this __getattr__ method by a different method to e.g. print out the name of the function called, the arguments etc. (see related question here).

But this approach does not work, it gives the following error:

AttributeError: 'function' object has no attribute 'numbers'

The example works as expected when I

  • do not replace DeviceProxy.__getattr__ with something else
  • replace DeviceProxy.__getattr__ with the function

    def dummy(instance, attr): return original(device,attr)

  • replace the name of the xmlrpc function by a zero-dotted name (e.g. just sum instead of sum.numbers)

You can verify yourself that the following, direct call via the xmlrpc proxy will work as expected:

dev = xmlrpclib.ServerProxy("http://localhost:8000/RPC2")
print dev.add.numbers(4,6)

My question: How to solve my problem, i.e. how to be able to wrap/overwrite the DeviceProxy.__getattr__ correctly to be able to see the function called, all arguments etc WITHOUT making changes in the xmlrpc server or the DeviceProxy class?

Community
  • 1
  • 1
Alex
  • 41,580
  • 88
  • 260
  • 469

1 Answers1

0

I can see two problems here:

  1. Are all attributes of a DeviceProxy functions? If they're not, then you're sometimes returning a function when an object is expected
  2. When you wrap the function, you're not copying across members - use functools.wraps to achieve that.

This ought to work

from functools import wraps

@wraps(original)  # probably not needed, but sensible
def mygetattr(device, key):
    attr = original(device, key)
    if callable(attr):
        @wraps(attr)  # copy across __name__, __dict__ etc
        def wrapper(*args, **kw):
            print('called with %r and %r' % (args, kw))
            return attr(*args, **kw)
        return wrapper
    else:  # handle (or rather, don't) non-callable attributes
        return attr
Eric
  • 95,302
  • 53
  • 242
  • 374
  • Your point (1) is a valid point, and I need to have a look at point (2) still. With your code I do not get an error any more, but neither any output! I just get the number `10` printed but not the additional information I was looking for. Maybe I forgot something etc., can post the complete code please? – Alex Dec 08 '12 at 13:51
  • When I again do `DeviceProxy.__getattr__ = mygetattr` with your code and execute the test, I get an error `Traceback (most recent call last): File "code.py", line 50, in print dev.add.numbers(4,6) File "code.py", line 22, in mygetattr @wraps(attr) # copy across __name__, __dict__ etc File "/usr/lib/python2.7/functools.py", line 33, in update_wrapper setattr(wrapper, attr, getattr(wrapped, attr)) TypeError: __name__ must be set to a string object`. – Alex Dec 08 '12 at 14:01
  • @Alex: What do you get if you add a `print attr, attr.__name__` just before that? – Eric Dec 08 '12 at 14:35
  • Your extra information is not printed because `DeviceProxy.__getattr__` is invoked for `x = dev.add`, but not for `x.numbers` – Eric Dec 08 '12 at 14:38
  • So how can I fix this? As you can see, the `xmlrpc` server has some definitions for how functions are called, and they can be `add`, `add.numbers`, `add.numbers.max` or whatever. I do not have any control on the naming within the `xmlrpc` server, but need to print out what exactly has been called. – Alex Dec 08 '12 at 14:47
  • If I try to `print attr, attr.__name__` just before the `@wraps(attr)` call I get an error: xmlrpclib.Fault: :method "addthem.__str__" is not supported'> – Alex Dec 08 '12 at 14:52