Utilizzo di ** kwargs con SimpleXMLRPCServer in python
-
02-07-2019 - |
Domanda
Ho una classe che desidero esporre come servizio remoto usando Pythons SimpleXMLRPCServer. L'avvio del server è simile al seguente:
server = SimpleXMLRPCServer((serverSettings.LISTEN_IP,serverSettings.LISTEN_PORT))
service = Service()
server.register_instance(service)
server.serve_forever()
Ho quindi una classe ServiceRemote che assomiglia a questa:
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)
Quindi tutte le chiamate sull'oggetto ServiceRemote verranno inoltrate a xmlrpclib.Server, che quindi lo inoltra al server remoto. Il problema è un metodo nel servizio che prende il nome varargs:
@useDb
def select(self, db, fields, **kwargs):
pass
Il decoratore @useDb esegue il wrapping della funzione, creando il db prima della chiamata e aprendolo, quindi chiudendolo al termine della chiamata prima di restituire il risultato.
Quando chiamo questo metodo, visualizzo l'errore " call () ho ricevuto un argomento inaspettato "name" " ;. Quindi, è possibile chiamare i metodi prendendo da remoto argomenti denominati variabili? O dovrò creare una sostituzione per ogni variazione di metodo di cui ho bisogno.
Grazie per le risposte. Ho modificato un po 'il mio codice, quindi la domanda non è più un problema. Tuttavia ora lo so per riferimento futuro se effettivamente dovessi implementare argomenti posizionali e supportare l'invocazione remota. Penso che una combinazione di approcci Thomas e Praptaks sarebbe buona. Trasformare i kwarg in argomenti posizionali sul client tramite xmlrpclient e disporre di un wrapper sul lato server dei metodi per decomprimere gli argomenti posizionali.
Soluzione
Non puoi farlo con xmlrpc semplice poiché non ha nozioni di argomenti di parole chiave. Tuttavia, è possibile sovrapporre questo come protocollo in cima a xmlrpc che passerebbe sempre un elenco come primo argomento e un dizionario come secondo, quindi fornire il codice di supporto adeguato in modo che diventi trasparente per l'uso, esempio di seguito:
Server
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()
Client
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})
Altri suggerimenti
XML-RPC non ha davvero un concetto di "argomenti di parole chiave", quindi xmlrpclib non cerca di supportarli. Dovresti scegliere una convenzione, quindi modificare xmlrpclib._Method per accettare gli argomenti delle parole chiave e trasmetterli utilizzando quella convenzione.
Ad esempio, lavoravo con un server XML-RPC che passava argomenti di parole chiave come due argomenti, "-KEYWORD" seguito dall'argomento effettivo, in un elenco semplice. Non ho più accesso al codice che ho scritto per accedere a quel server XML-RPC da Python, ma era abbastanza semplice, sulla falsariga di:
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
Usa il monkeypatching perché è di gran lunga il metodo più semplice per farlo, a causa di alcuni usi ingombranti dei globuli di modulo e degli attributi alterati del nome (__quest, ad esempio) nella classe ServerProxy.
Per quanto ne so, il protocollo sottostante non supporta varargs nominati (o qualsiasi args nominato per quella materia). Per risolvere il problema, creare un wrapper che prenda i ** kwarg e lo passi come un normale dizionario al metodo che si desidera chiamare. Qualcosa del genere
Lato server:
def select_wrapper(self, db, fields, kwargs):
"""accepts an ordinary dict which can pass through xmlrpc"""
return select(self,db,fields, **kwargs)
Sul lato client:
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)
Disclaimer: il codice mostra l'idea generale, puoi farlo un po 'più pulito (ad esempio scrivendo un decoratore per farlo).
Come ha detto Thomas Wouters, XML-RPC non ha argomenti di parole chiave. Solo l'ordine degli argomenti è importante per quanto riguarda il protocollo e possono essere chiamati qualsiasi cosa in XML: arg0, arg1, arg2 va benissimo, così come formaggio, caramelle e pancetta per gli stessi argomenti.
Forse dovresti semplicemente ripensare il tuo uso del protocollo? Usare qualcosa come SOAP documento / letterale sarebbe molto meglio di una soluzione alternativa come quelle presentate in altre risposte qui. Naturalmente, questo potrebbe non essere fattibile.
Usando i consigli di cui sopra, ho creato del codice funzionante.
wrapper del metodo server:
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
Configurazione client (eseguire una volta):
_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
L'ho provato e supporta il metodo con argomenti fissi, posizionali e di parole chiave.