Использование **kwargs с SimpleXMLRPCServer в python

У меня есть класс, который я хочу представить как удаленный сервис, использующий pythons SimpleXMLRPCServer. Запуск сервера выглядит следующим образом:

server = SimpleXMLRPCServer((serverSettings.LISTEN_IP,serverSettings.LISTEN_PORT))

service = Service()

server.register_instance(service)
server.serve_forever()

Затем у меня есть класс ServiceRemote, который выглядит следующим образом:

def __init__(self,ip,port):
    self.rpcClient = xmlrpclib.Server('http://%s:%d' %(ip,port))

def __getattr__(self, name):
    # forward all calls to the rpc client
    return getattr(self.rpcClient, name)

Таким образом, все вызовы объекта ServiceRemote будут перенаправлены в xmlrpclib.Server, который затем перенаправит его на удаленный сервер. Проблема заключается в методе службы, который принимает именованные varargs:

@useDb
def select(self, db, fields, **kwargs):
    pass

Декоратор @useDb оборачивает функцию, создавая базу данных перед вызовом и открывая ее, затем закрывая после завершения вызова перед возвратом результата.

Когда я вызываю этот метод, я получаю сообщение об ошибке "call() получил неожиданный аргумент ключевого слова" имя "". Итак, возможно ли вызывать методы, принимающие переменные с именованными аргументами удаленно? Или мне придется создавать переопределение для каждого варианта метода, который мне нужен.


Спасибо за ответы. Я немного изменил свой код, поэтому вопрос больше не является проблемой. Однако теперь я знаю это для дальнейшего использования, если мне действительно нужно реализовать позиционные аргументы и поддерживать удаленный вызов. Я думаю, что сочетание подходов Томаса и Праптакса было бы хорошо. Превращение kwargs в позиционные аргументы на клиенте через xmlrpclient и использование оболочки для методов на стороне сервера для распаковки позиционных аргументов.

5 ответов

Решение

Вы не можете сделать это с простым xmlrpc, так как он не имеет понятия аргументов ключевых слов. Однако вы можете наложить это как протокол поверх xmlrpc, который всегда будет передавать список в качестве первого аргумента и словарь в качестве второго, а затем предоставлять надлежащий код поддержки, чтобы это стало прозрачным для вашего использования, пример ниже:

сервер

from SimpleXMLRPCServer import SimpleXMLRPCServer

class Server(object):
    def __init__(self, hostport):
        self.server = SimpleXMLRPCServer(hostport)

    def register_function(self, function, name=None):
        def _function(args, kwargs):
            return function(*args, **kwargs)
        _function.__name__ = function.__name__
        self.server.register_function(_function, name)

    def serve_forever(self):
        self.server.serve_forever()

#example usage
server = Server(('localhost', 8000))
def test(arg1, arg2):
    print 'arg1: %s arg2: %s' % (arg1, arg2)
    return 0
server.register_function(test)
server.serve_forever()

клиент

import xmlrpclib

class ServerProxy(object):
    def __init__(self, url):
        self._xmlrpc_server_proxy = xmlrpclib.ServerProxy(url)
    def __getattr__(self, name):
        call_proxy = getattr(self._xmlrpc_server_proxy, name)
        def _call(*args, **kwargs):
            return call_proxy(args, kwargs)
        return _call

#example usage
server = ServerProxy('http://localhost:8000')
server.test(1, 2)
server.test(arg2=2, arg1=1)
server.test(1, arg2=2)
server.test(*[1,2])
server.test(**{'arg1':1, 'arg2':2})

XML-RPC на самом деле не имеет понятия "аргументы ключевых слов", поэтому xmlrpclib не пытается их поддерживать. Вам нужно будет выбрать соглашение, затем изменить xmlrpclib._Method, чтобы принимать аргументы ключевых слов и передавать их, используя это соглашение.

Например, я работал с сервером XML-RPC, который передавал аргументы ключевого слова в виде двух аргументов: "-KEYWORD", за которым следовал фактический аргумент, в плоском списке. У меня больше нет доступа к коду, который я написал для доступа к этому серверу XML-RPC из Python, но это было довольно просто:

import xmlrpclib

_orig_Method = xmlrpclib._Method

class KeywordArgMethod(_orig_Method):     
    def __call__(self, *args, **kwargs):
        if args and kwargs:
            raise TypeError, "Can't pass both positional and keyword args"
        args = list(args) 
        for key in kwargs:
            args.append('-%s' % key.upper())
            args.append(kwargs[key])
       return _orig_Method.__call__(self, *args)     

xmlrpclib._Method = KeywordArgMethod

Он использует monkeypatching, потому что это самый простой способ сделать это из-за некоторого неуклюжего использования глобальных переменных модуля и атрибутов с именами (например, __request) в классе ServerProxy.

Используя приведенный выше совет, я создал некоторый рабочий код.

Обертка метода сервера:

def unwrap_kwargs(func):
    def wrapper(*args, **kwargs):
        print args
        if args and isinstance(args[-1], list) and len(args[-1]) == 2 and "kwargs" == args[-1][0]:
            func(*args[:-1], **args[-1][1])
        else:
            func(*args, **kwargs)
    return wrapper

Настройка клиента (сделать один раз):

_orig_Method = xmlrpclib._Method

class KeywordArgMethod(_orig_Method):     
    def __call__(self, *args, **kwargs):
        args = list(args) 
        if kwargs:
            args.append(("kwargs", kwargs))
        return _orig_Method.__call__(self, *args)

xmlrpclib._Method = KeywordArgMethod

Я проверил это, и он поддерживает метод с фиксированными, позиционными и ключевыми аргументами.

Насколько я знаю, базовый протокол не поддерживает именованные varargs (или любые именованные аргументы в этом отношении). Обходной путь для этого - создать упаковщик, который будет принимать **kwargs и передавать его как обычный словарь методу, который вы хотите вызвать. Что-то вроде этого

Сторона сервера:

def select_wrapper(self, db, fields, kwargs):
    """accepts an ordinary dict which can pass through xmlrpc"""
    return select(self,db,fields, **kwargs)

На стороне клиента:

def select(self, db, fields, **kwargs):
    """you can call it with keyword arguments and they will be packed into a dict"""
    return self.rpcClient.select_wrapper(self,db,fields,kwargs)

Отказ от ответственности: код показывает общую идею, вы можете сделать это немного чище (например, написать для этого декоратор).

Как сказал Томас Воутерс, у XML-RPC нет аргументов с ключевыми словами. С точки зрения протокола имеет значение только порядок аргументов, и они могут быть вызваны как угодно в XML: arg0, arg1, arg2 отлично подходят, как и сыр, конфеты и бекон для тех же аргументов.

Возможно, вам следует просто переосмыслить использование протокола? Использование чего-то вроде SOAP документа / литерала было бы намного лучше, чем обходной путь, такой как те, что представлены в других ответах здесь. Конечно, это может быть неосуществимо.

Другие вопросы по тегам