Использование ** 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 обертывает функцию, создавая базу данных перед вызовом и открывая ее, а затем закрывая ее после завершения вызова перед возвратом результата.

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


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

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
15
0
5 916
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

Насколько мне известно, базовый протокол не поддерживает именованные 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 действительно не имеет концепции «аргументов ключевого слова», поэтому 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.

Что такое патч обезьяны? Быстрый поиск в Google не дал мне определения, только пример с использованием декораторов.

Staale 23.09.2008 13:32

monkeypatching - это изменение модуля или другого фрагмента кода без изменения источника этого кода. См. en.wikipedia.org/wiki/Monkey_patch

Thomas Wouters 23.09.2008 13:40

отстрел обезьян часто считается вредным

Florian Bösch 23.09.2008 15:05

Это действительно так. Однако попробуйте заменить класс _Method, используемый xmlrpclib без него. Пожалуйста :-) Вы увидите, что есть более вредные вещи, чем обезьяньи патчи.

Thomas Wouters 23.09.2008 17:08

@ Томас, я думаю, что это так, что в этом такого вредного?

Florian Bösch 28.09.2008 20:28

Что вредно, так это то, что класс ServerProxy использует глобальный _Method напрямую (нет способа отменить этот выбор), и единственный способ изменить это - создать подкласс и переопределить getattr. Но getattr нуждается в доступе к запросу self .__, которого он не может из-за искажения имени. Как ты сделал это?

Thomas Wouters 29.09.2008 14:21

Хорошее решение. Незначительный момент: нет необходимости ограничивать передачу только одного из аргументов позиции и ключевого слова. В итоге я отправил такие аргументы: ("hello", "world", "** kwargs", "text", "Browser"), что является не чем иным, как ("hello", "world", text = "Browser "). Сервер автоматически переводит это. Если кто-то желает сделать патч обезьяны, он может поступить так, как указал Томас Воутерс, или просто назвать это расширенным способом.

rsmoorthy 18.09.2013 15:14

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

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

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

Оболочка серверного метода:

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

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

Мне не нравится код, который вторгается в другие модули. Я стараюсь полностью обойти другой код, который более гибкий, менее загадочный и с меньшей вероятностью взорвется вам в лицо.

Florian Bösch 23.09.2008 18:09

Я согласен, но все еще новичок в питоне, поэтому просто пробую путь наименьшего сопротивления. А так все в одном проекте банкомат. это не такая уж большая проблема.

Staale 23.09.2008 23:04
Ответ принят как подходящий

Вы не можете сделать это с помощью простого 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})

Идея хороша, но ничего не работает, если существует более одного уровня вызовов API (например, server.level.test ()). В таких случаях функция getattr не будет знать, не является ли «уровень» вызовом функции, и также вернет прокси-функцию для этого, что не сработает. Но это хорошая идея и в правильном направлении! В итоге я использовал это решение вместе с модификацией ответа Томаса Воутерса ниже. Спасибо!

rsmoorthy 18.09.2013 15:06

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