PyMOTW: xmlrpclib
24 Julio 2008
Traducción de PyMOTW: xmlrpclib el módulo xmlrpclib de la columna semanal de Doug Hellmann.
Como continuación del artículo de la semana pasada sobre
SimpleXMLRPCServer
(original),
esta semana cubre la librería para clientes xmlrpclib.
| Módulo: | xmlrpclib |
|---|---|
| Propósito: | Librería de comunicación para clientes XML-RPC |
| Versión de Python: | 2.2 y posterior |
Descripción:
La semana pasada vimos la librería para crear un servidor XML-RPC. El módulo
xmlrpclib te permite comunicar desde Python con cualquier servidor XML-RPC
escrito en cualquier lenguaje. Todos los ejemplos a continuación usan el
servidor definido en xmlrpclib_server.py, disponible en el código distribuido
y que se repite aquí para cualquier referencia:
Conectando a un servidor:
La manera más simple de conectar un cliente al servidor es instanciar un objeto
ServerProxy, proporcionándole el URI del servidor. Por ejemplo, el servidor
de demostración corre en el puerto 9000 de localhost:
import xmlrpclib
server = xmlrpclib.ServerProxy('http://localhost:9000')
print 'Ping:', server.ping()
En este caso, el método ping() del servicio no necesita argumentos y devuelve
un sólo valor binario.
$ python xmlrpclib_ServerProxy.py
Ping: True
Otras opciones están disponibles para soportar transporte alternativo. Ambos, HTTP y HTTPS, son soportados directamente, como lo es autenticación básica. Sólo necesitarás proveer una clase de transporte si tu canal de comunicación no es uno de los tipos soportados. Sería un ejercicio interesante, por ejemplo, implementar XML-RPC sobre SMTP. Nada extremadamente útil, pero interesante.
La opción verbose te da información útil para identificar dónde pueden estar
ocurriendo errores de comunicación.
import xmlrpclib
server = xmlrpclib.ServerProxy('http://localhost:9000', verbose=True)
print 'Ping:', server.ping()
Ping: connect: (localhost, 9000)
send: 'POST /RPC2 HTTP/1.0\r\nHost: localhost:9000\r\nUser-Agent: xmlrpclib.py/1.0.1 (by www.pythonware.com)\r\nContent-Type: text/xml\r\nContent-Length: 98\r\n\r\n'
send: "<?xml version='1.0'?>\n<methodCall>\n<methodName>ping</methodName>\n<params>\n</params>\n</methodCall>\n"
reply: 'HTTP/1.0 200 OK\r\n'
header: Server: BaseHTTP/0.3 Python/2.5.1
header: Date: Fri, 25 Jul 2008 00:38:28 GMT
header: Content-type: text/xml
header: Content-length: 129
body: "<?xml version='1.0'?>\n<methodResponse>\n<params>\n<param>\n<value><boolean>1</boolean></value>\n</param>\n</params>\n</methodResponse>\n"
True
Puedes cambiar la codificación por defecto de UTF-8 si necesitas para usar un sistema alternativo.
import xmlrpclib
server = xmlrpclib.ServerProxy('http://localhost:9000', encoding='ISO-8859-1')
print 'Ping:', server.ping()
El servidor debería detectar automáticamente la codificación correcta.
$ python xmlrpclib_ServerProxy_encoding.py
Ping: True
La opción allow_none controla si el valor None de Python es traducido
automáticamente al valor nil o si ocasiona un error.
import xmlrpclib
server = xmlrpclib.ServerProxy('http://localhost:9000', allow_none=True)
print 'Permitido:', server.show_type(None)
server = xmlrpclib.ServerProxy('http://localhost:9000', allow_none=False)
print 'No permitido:', server.show_type(None)
Nota que el error es elevado localmente si el cliente no permite None, pero puede ser también elevado desde el servidor si no está configurado para permitir None/
$ python xmlrpclib_ServerProxy_allow_none.py
Permitido: ['None', "<type 'NoneType'>", None]
No permitido:
Traceback (most recent call last):
File "xmlrpclib_ServerProxy_allow_none.py", line 17, in <module>
print 'No permitido:', server.show_type(None)
File "/usr/lib/python2.5/xmlrpclib.py", line 1147, in __call__
return self.__send(self.__name, args)
File "/usr/lib/python2.5/xmlrpclib.py", line 1431, in __request
allow_none=self.__allow_none)
File "/usr/lib/python2.5/xmlrpclib.py", line 1080, in dumps
data = m.dumps(params)
File "/usr/lib/python2.5/xmlrpclib.py", line 623, in dumps
dump(v, write)
File "/usr/lib/python2.5/xmlrpclib.py", line 635, in __dump
f(self, value, write)
File "/usr/lib/python2.5/xmlrpclib.py", line 639, in dump_nil
raise TypeError, "cannot marshal None unless allow_none is enabled"
TypeError: cannot marshal None unless allow_none is enabled
La opción use_datetime te permite pasarle datetime.datetime y objetos
relacionados al proxy o recibirlos del servidor. Si use_datetime es False,
la clase interna DateTime es usada para representar fechas.
Tipos de datos:
El protocolo XML-RPC reconoce un conjunto limitado de tipos de datos comunes. Los tipos pueden ser pasados como argumentos o valores de retorno y combinados para crear estructuras de datos más complejas.
import xmlrpclib
import datetime
server = xmlrpclib.ServerProxy('http://localhost:9000')
for t, v in [ ('binario', True),
('numero entero', 1),
('numero de punto flotante', 2.5),
('cadena', 'un texto'),
('fecha y hora', datetime.datetime.now()),
('array', ['a', 'lista']),
('array', ('a', 'tupla')),
('estructura', {'a': 'diccionario'}),
]:
print '%-25s:' % t, server.show_type(v)
Los tipos (de datos) simples:
$ python xmlrpclib_types.py
binario : ['True', "<type 'bool'>", True]
numero entero : ['1', "<type 'int'>", 1]
numero de punto flotante : ['2.5', "<type 'float'>", 2.5]
cadena : ['un texto', "<type 'str'>", 'un texto']
fecha y hora : ['20080724T20:47:59', "<type 'instance'>", <DateTime '20080724T20:47:59' at b7d605ec>]
array : ["['a', 'lista']", "<type 'list'>", ['a', 'lista']]
array : ["['a', 'tupla']", "<type 'list'>", ['a', 'tupla']]
estructura : ["{'a': 'diccionario'}", "<type 'dict'>", {'a': 'diccionario'}]
Por supuesto, ellos pueden ser combinados para creas valores de complejidad arbitraria.
import xmlrpclib
import datetime
import pprint
server = xmlrpclib.ServerProxy('http://localhost:9000')
data = { 'binario': True,
'numero entero': 1,
'numero de punto flotante': 2.5,
'cadena': 'un texto',
'fecha y hora': datetime.datetime.now(),
'array': ['a', 'list'],
'array': ('a', 'tuple'),
'estructura': {'a': 'dictionary'},
}
arg = []
for i in range(3):
d = {}
d.update(data)
d['numero entero'] = i
arg.append(d)
print 'Antes:'
pprint.pprint(arg)
print
print 'Después:'
pprint.pprint(server.show_type(arg)[-1])
$ python xmlrpclib_types_nested.py
Antes:
[{'array': ('a', 'tuple'),
'binario': True,
'cadena': 'un texto',
'estructura': {'a': 'dictionary'},
'fecha y hora': datetime.datetime(2008, 7, 24, 20, 52, 33, 437064),
'numero de punto flotante': 2.5,
'numero entero': 0},
{'array': ('a', 'tuple'),
'binario': True,
'cadena': 'un texto',
'estructura': {'a': 'dictionary'},
'fecha y hora': datetime.datetime(2008, 7, 24, 20, 52, 33, 437064),
'numero de punto flotante': 2.5,
'numero entero': 1},
{'array': ('a', 'tuple'),
'binario': True,
'cadena': 'un texto',
'estructura': {'a': 'dictionary'},
'fecha y hora': datetime.datetime(2008, 7, 24, 20, 52, 33, 437064),
'numero de punto flotante': 2.5,
'numero entero': 2}]
Después:
[{'array': ['a', 'tuple'],
'binario': True,
'cadena': 'un texto',
'estructura': {'a': 'dictionary'},
'fecha y hora': <DateTime '20080724T20:52:33' at b7cbdeac>,
'numero de punto flotante': 2.5,
'numero entero': 0},
{'array': ['a', 'tuple'],
'binario': True,
'cadena': 'un texto',
'estructura': {'a': 'dictionary'},
'fecha y hora': <DateTime '20080724T20:52:33' at b7a5ad2c>,
'numero de punto flotante': 2.5,
'numero entero': 1},
{'array': ['a', 'tuple'],
'binario': True,
'cadena': 'un texto',
'estructura': {'a': 'dictionary'},
'fecha y hora': <DateTime '20080724T20:52:33' at b7a5af2c>,
'numero de punto flotante': 2.5,
'numero entero': 2}]
Pasando objetos:
Instancias de clases Python son tratadas como estructuras y pasadas como un diccionario, con atributos del objeto como valores en el diccionario.
import xmlrpclib
class MyObj:
def __init__(self, a, b):
self.a = a
self.b = b
def __repr__(self):
return 'MyObj(%s, %s)' % (repr(self.a), repr(self.b))
server = xmlrpclib.ServerProxy('http://localhost:9000')
o = MyObj(1, 'aqui viene b')
print 'o=', o
print server.show_type(o)
o2 = MyObj(2, o)
print 'o2=', o2
print server.show_type(o2)
Iterando el valor ofrece un diccionario en el cliente, ya que no hay nada codificado en los valores que la diga al servidor (o al cliente) que debería ser instanciado como parte de una clase.
$ python xmlrpclib_types_object.py
o= MyObj(1, 'aqui viene b')
["{'a': 1, 'b': 'aqui viene b'}", "<type 'dict'>", {'a': 1, 'b': 'aqui viene b'}]
o2= MyObj(2, MyObj(1, 'aqui viene b'))
["{'a': 2, 'b': {'a': 1, 'b': 'aqui viene b'}}", "<type 'dict'>", {'a': 2, 'b': {'a': 1, 'b': 'aqui viene b'}}]
Datos binarios:
Todos los valores pasados al servidor están codificados y escapados
automáticamente. Sin embargo, algunos tipos de datos pueden contener caracteres
que no son válidos en XML. Por ejemplo datos binarios de imágenes pueden
incluir valores de byte en el rango de control de ASCI de 0 a 31. Si necesitas
pasar datos binarios, lo mejor es utilizar la clase Binary para codificarlos
para el transporte.
import xmlrpclib
server = xmlrpclib.ServerProxy('http://localhost:9000')
s = 'Ésta es una cadena con caracteres de control' + '\0'
print 'Cadena local:', s
data = xmlrpclib.Binary(s)
print 'Como binario:', server.send_back_binary(data)
print 'Como cadena:', server.show_type(s)
Si pasamos la cadena que contenga un byte NULL a show_type(), se eleva una
excepción en el parser de XML.
$ python xmlrpclib_Binary.py
Cadena local: Ésta es una cadena con caracteres de control
Como binario: Ésta es una cadena con caracteres de control
Como cadena:
Traceback (most recent call last):
File "xmlrpclib_Binary.py", line 21, in <module>
print 'Como cadena:', server.show_type(s)
File "/usr/lib/python2.5/xmlrpclib.py", line 1147, in __call__
return self.__send(self.__name, args)
File "/usr/lib/python2.5/xmlrpclib.py", line 1437, in __request
verbose=self.__verbose
File "/usr/lib/python2.5/xmlrpclib.py", line 1201, in request
return self._parse_response(h.getfile(), sock)
File "/usr/lib/python2.5/xmlrpclib.py", line 1340, in _parse_response
return u.close()
File "/usr/lib/python2.5/xmlrpclib.py", line 787, in close
raise Fault(**self._stack[0])
xmlrpclib.Fault: <Fault 1: "<class 'xml.parsers.expat.ExpatError'>:not well-formed (invalid token): line 6, column 59">
También se puede usar datos binarios para enviar objetos usando pickle. Las
cuestiones normales de seguridad relacionadas al envío de lo que viene a ser un
ejecutable a través de la red son aplicables aquí (es decir, no lo hagas a
menos que estés seguro que tu canal de comunicación es seguro).
import xmlrpclib
import cPickle as pickle
class MyObj:
def __init__(self, a, b):
self.a = a
self.b = b
def __repr__(self):
return 'MyObj(%s, %s)' % (repr(self.a), repr(self.b))
server = xmlrpclib.ServerProxy('http://localhost:9000')
o = MyObj(1, 'aqui viene b')
print 'Local:', o, id(o)
print 'Como objeto:', server.show_type(o)
p = pickle.dumps(o)
b = xmlrpclib.Binary(p)
r = server.send_back_binary(b)
o2 = pickle.loads(r.data)
print 'Desde pickle:', o2, id(o2)
Recuerda, el atributo data de la instancia Binary contiene la versión
pickle del objeto, osea que tiene que ser convertida antes de poder ser
usada. Ésto resulta en un objeto diferente (con un nuevo valor id).
$ python xmlrpclib_Binary_pickle.py
Local: MyObj(1, 'aqui viene b') 3083488236
Como objeto: ["{'a': 1, 'b': 'aqui viene b'}", "<type 'dict'>", {'a': 1, 'b': 'aqui viene b'}]
Desde pickle: MyObj(1, 'aqui viene b') 3083632108
Manejando excepciones:
Dado que el servidor XML-RPC puede estar escrito en cualquier lenguaje, las
clases de excepciones no pueden ser traducidas directamente. En su lugar, las
excepciones elevadas en el servidor son convertidas en objetos Fault y
elevadas como excepciones localmente.
import xmlrpclib
server = xmlrpclib.ServerProxy('http://localhost:9000')
try:
server.raises_exception('Un mensaje')
except Exception, err:
print 'Código de error:', err.faultCode
print 'Mensaje :', err.faultString
$ python xmlrpclib_exception.py
Código de error: 1
Mensaje : <type 'exceptions.RuntimeError'>:Un mensaje
MultiCall:
MultiCall es una extensión al protocolo XML-RPC que permite enviar más de una
invocación a la vez, que recolecta las respuestas (los resultados) y los
devuelve al invocador. La clase MultiCall fue añadida a xmlrpclib en
Python 2.4. Para usar una instancia MultiCall, invoca los métodos en ella
como con un ServerProxy, luego invoca al objeto sin argumentos. El resultado
es un iterador con los resultados.
import xmlrpclib
server = xmlrpclib.ServerProxy('http://localhost:9000')
multicall = xmlrpclib.MultiCall(server)
multicall.ping()
multicall.show_type(1)
multicall.show_type('cadena')
for i, r in enumerate(multicall()):
print i, r
$ python xmlrpclib_MultiCall.py
0 True
1 ['1', "<type 'int'>", 1]
2 ['cadena', "<type 'str'>", 'cadena']
Si una de las invocaciones ocasiona un error (Fault) o eleva de otra manera
una excepción, la excepción es elevada cuando el resultado es producido por el
iterador y no hay más resultados disponibles.
import xmlrpclib
server = xmlrpclib.ServerProxy('http://localhost:9000')
multicall = xmlrpclib.MultiCall(server)
multicall.ping()
multicall.show_type(1)
multicall.raises_exception('Penultima invocacion termina la ejecucion')
multicall.show_type('cadena')
for i, r in enumerate(multicall()):
print i, r
$ python xmlrpclib_MultiCall_exception.py
0 True
1 ['1', "<type 'int'>", 1]
Traceback (most recent call last):
File "xmlrpclib_MultiCall_exception.py", line 21, in <module>
for i, r in enumerate(multicall()):
File "/usr/lib/python2.5/xmlrpclib.py", line 949, in __getitem__
raise Fault(item['faultCode'], item['faultString'])
xmlrpclib.Fault: <Fault 1: "<type 'exceptions.RuntimeError'>:Penultima invocacion termina la ejecucion">
Referencias:
PyMOTW: SimpleXMLRPCServer (original),
PyMOTW: SimpleXMLRPCServer (traducción)
XML-RPC How To
XML-RPC Extensions
Python Module of the Week Home
Descarga el código
Copyright 2008 Doug Hellmann
blog comments powered by Disqus
