Использование **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 документа / литерала было бы намного лучше, чем обходной путь, такой как те, что представлены в других ответах здесь. Конечно, это может быть неосуществимо.