Использование ** kwargs с SimpleXMLRPCServer в python - PullRequest
13 голосов
/ 23 сентября 2008

У меня есть класс, который я хочу представить как удаленный сервис с использованием 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 ]

13 голосов
/ 23 сентября 2008

Вы не можете сделать это с простым 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})
3 голосов
/ 23 сентября 2008

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.

1 голос
/ 23 сентября 2008

Насколько я знаю, базовый протокол не поддерживает именованные 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)

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

0 голосов
/ 23 сентября 2008

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

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

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

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

0 голосов
/ 23 сентября 2008

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

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

...