RebelWithoutAPulse
RebelWithoutAPulse

Reputation: 442

Call arbitrary methods on class like xmlrpclib.ServerProxy

I have a remote api accepting XMLs only that may be called like

api = xmlrpclib.ServerProxy(IP)
result = api.Execute({"Method": 'Method_name', "Params": [P1, P2],
                      ...  # technical values - authentication, etc
                      })

The technical values are not subject to change frequently - whole script would usually use same values for them, so I created a class that has a method

def call(self, method_name, params):
    self.api.Execute(<constructed dictionary>)

I was wondering if it would be possible to call these methods directly as methods of self.api like:

self.api.method_name(params)

Which would in turn fill in the .Execute and the rest for me, with the general idea like:

def __getattr__(self, item):
    if item in self.__dict__:
        return self.item
    else:
        return functools.partial(self.call, method_name=item)

So, if I defined a method in class (self.foo, self.bar and the like) - calling it would produce true results of self.foo and self.bar.

Calling it like self.api.METHOD(params) where METHOD is a custom one works, but this approach "pollutes" other methods that I didn't define, like self.api.__repr__ and the like

Is it true that according to this question my approach of overriding __getitem__ is incorrect entirely? If so - how should I implement this?

Upvotes: 1

Views: 200

Answers (1)

wbadart
wbadart

Reputation: 2911

I recently watched a talk by Raymond Hettinger which this post loosely reminded of, so I thought I'd link to it. I think it makes a great argument for the approach you described in you post:

Make the API work for you

First of all, I don't think overriding __getattr__ is as sinful as that post might have you believe.

class MyAPI(object):
  base_args = {'token': 'foobarauthtoekn'}

  def __init__(self, ip):
    self._api = ServerProxy(ip)

  def _execute(self, method='', **kwargs):
    argdict = {'Method': method}
    argdict.update(MyAPI2.base_args)
    argdict.update(kwargs)
    self._api.Execute(argdict)

  def __getattr__(self, attr):
    return self.__dict__.get(
      attr, partial(self._execute, method=attr))


api = MyAPI('127.0.0.1')

# Execute({'token': ..., 'Method': 'get_users', 'arg1': 'foo' ...}) =
api.get_users(arg1='foo', arg2='bar')

# Execute({'token': ..., 'Method': 'get_data', 'foo': 'stuff' ...}) =
api.get_data(foo='stuff', bar='zxcv')

I like this because it's not a lot of code, and it lets us use Execute in a more convenient way (with keyword args), without relying on support for that from the internal API.

Special behavior via metaclasses

The other approach prevents us having to override __getattr__ in case I've underestimated how much of a mistake it is, and it could also be considered more pythonic, as we explicitly enumerate the methods we'll be providing with our API wrapper:

class APIMeta(type):
  def __new__(cls, clsname, bases, attrs):

    def exec_generic(name):
      base_args = {'token': 'foobarauthtoekn'}
      def exec_wrapper(self, params):
        args = {'Method': name}
        args.update(base_args)
        args.update(params)
        return self._api.Execute(args)
      return exec_wrapper

    new_attrs = {
      name: val if name.startswith('__') else exec_generic(name)
      for name, val in attrs.items() }

    return super(APIMeta, cls).__new__(cls, clsname, bases, new_attrs)

class MyAPI(object, metaclass=APIMeta):
  def __init__(self, ip):
    self._api = ServerProxy(ip)

  def get_users(self):
    pass

  def get_data(self):
    pass

  # ...

In reality, I don't like this, since it's little more than a very convoluted way to write:

class MyAPI(object):
  _base_args = {'token': 'foobarauthtoken'}

  def __init__(self, ip):
    self._api = ServerProxy(ip)

  def get_users(self, params):
    argdict = {'Method': 'get_users'}
    argdict.update(MyAPI._base_args)
    argdict.update(params)
    return self._api.Execute(argdict)

  # ...

It will save you a considerable amount of typing if you have a lot of methods though, and it's also good practice in meta-programming if you're looking to get into that or deepen your understanding of the python language.

Here's a demo with both.

Upvotes: 1

Related Questions