12.6. Introspección de servicios web SOAP con WSDL

Como muchas otras cosas en el campo de los servicios web, WSDL tiene una historia larga y veleidosa, llena de conflictos e intrigas políticas. Me saltaré toda esta historia ya que me aburre hasta lo indecible. Hay otros estándares que intentaron hacer cosas similares, pero acabó ganando WSDL así que aprendamos cómo usarlo.

Lo más fundamental que nos permite hacer WSDL es descubrir los métodos que nos ofrece un servidor SOAP.

Ejemplo 12.8. Descubrimiento de los métodos disponibles

>>> from SOAPpy import WSDL          1
>>> wsdlFile = 'http://www.xmethods.net/sd/2001/TemperatureService.wsdl'
>>> server = WSDL.Proxy(wsdlFile)    2
>>> server.methods.keys()            3
[u'getTemp']
1 SOAPpy incluye un analizador de WSDL. En el momento en que escribo esto se indicaba que estaba en las fases tempranas de desarrollo, pero no he tenido problemas con ninguno de los ficheros WSDL que he probado.
2 Para usar un fichero WSDL debemos de nuevo utilizar una clase proxy, WSDL.Proxy, que toma un único argumento: el fichero WSDL. Observe que en este caso estamos pasando la URL de un fichero WSDL almacenado en un servidor remoto, pero las clases proxy trabajan igual de bien con una copia local del fichero WSDL. El acto de crear el proxy WSDL descargará el fichero y lo analizará, así que si hubiera algún error en el fichero WSDL (o si no pudiera acceder a él debido a problemas de red) lo sabría de inmediato.
3 La clase proxy WSDL expone las funciones disponibles como un diccionario de Python, server.methods. Así que obtener la lista de métodos disponibles es tan simple como invocar el método keys() del diccionario.

Bien, así que sabemos que este servidor SOAP ofrece un solo método: getTemp. Pero, ¿cómo lo invocamos? El objeto proxy WSDL también nos puede decir eso.

Ejemplo 12.9. Descubrimiento de los argumentos de un método

>>> callInfo = server.methods['getTemp']  1
>>> callInfo.inparams                     2
[<SOAPpy.wstools.WSDLTools.ParameterInfo instance at 0x00CF3AD0>]
>>> callInfo.inparams[0].name             3
u'zipcode'
>>> callInfo.inparams[0].type             4
(u'http://www.w3.org/2001/XMLSchema', u'string')
1 El diccionario server.methods contiene una estructura específica de SOAPpy llamada CallInfo. Un objeto CallInfo lleva información sobre una función específica, incluyendo sus argumentos.
2 Los argumentos de la función están almacenados en callInfo.inparams, que es una lista de objetos ParameterInfo que contienen información sobre cada parámetro.
3 Cada objeto ParameterInfo contiene un atributo name que es el nombre del argumento. No hace falta saber el nombre del argumento para llamar a la función mediante SOAP, pero SOAP admite invocación de funciones con argumentos por nombre (igual que Python), y WSDL.Proxy relacionará correctamente los argumentos nombrados con la función remota si escoge usarlos.
4 También se da el tipo explícito de cada parámetro, usando tipos definidos en XML Schema. Ya vio esto en la traza de datos de la sección anterior; el espacio de nombres de XML Schema era parte de la “plantilla” que le pedí que ignorase. Puede continuar ignorándolos para nuestros propósitos actuales. El parámetro zipcode es una cadena de texto y si pasa una cadena de Python al objeto WSDL.Proxy, lo convertirá correctamente para enviarlo al servidor.

WSDL también le permite hacer introspección sobre los valores de vuelta de una función.

Ejemplo 12.10. Descubrimiento de los valores de retorno de un método

>>> callInfo.outparams            1
[<SOAPpy.wstools.WSDLTools.ParameterInfo instance at 0x00CF3AF8>]
>>> callInfo.outparams[0].name    2
u'return'
>>> callInfo.outparams[0].type
(u'http://www.w3.org/2001/XMLSchema', u'float')
1 El adjunto de callInfo.inparams para los argumentos de las funciones es callInfo.outparams para sus valores de retorno. También es una lista, porque las funciones invocadas mediante SOAP pueden devolver varios valores, igual que las funcionesde Python.
2 Cada objeto ParameterInfo contiene nombre (name) y tipo (type). Esta función devuelve un solo valor, de nombre return, que es de coma flotante.

Juntémoslo todo y llamemos a un servicio web SOAP mediante un proxy WSDL.

Ejemplo 12.11. Invocación de un servicio web mediante un proxy WSDL

>>> from SOAPpy import WSDL
>>> wsdlFile = 'http://www.xmethods.net/sd/2001/TemperatureService.wsdl')
>>> server = WSDL.Proxy(wsdlFile)               1
>>> server.getTemp('90210')                     2
66.0
>>> server.soapproxy.config.dumpSOAPOut = 1     3
>>> server.soapproxy.config.dumpSOAPIn = 1
>>> temperature = server.getTemp('90210')
*** Outgoing SOAP ******************************************************
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
  xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
  xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"
  xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
  xmlns:xsd="http://www.w3.org/1999/XMLSchema">
<SOAP-ENV:Body>
<ns1:getTemp xmlns:ns1="urn:xmethods-Temperature" SOAP-ENC:root="1">
<v1 xsi:type="xsd:string">90210</v1>
</ns1:getTemp>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
************************************************************************
*** Incoming SOAP ******************************************************
<?xml version='1.0' encoding='UTF-8'?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<SOAP-ENV:Body>
<ns1:getTempResponse xmlns:ns1="urn:xmethods-Temperature"
  SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<return xsi:type="xsd:float">66.0</return>
</ns1:getTempResponse>

</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
************************************************************************

>>> temperature
66.0
1 La configuración es más sencilla que al llamar directamente al servicio SOAP, ya que el fichero WSDL contiene tanto la URL como el espacio de nombres que necesitamos para llamar al servicio. La creación del objeto WSDL.Proxy descarga el fichero WSDL, lo analiza y configura un objeto SOAPProxy que usa para invocar al servicio web SOAP.
2 Una vez se crea el objeto WSDL.Proxy invocamos a la función tan fácilmente como lo haríamos con el objeto SOAPProxy. Esto no es sorprendente; el WSDL.Proxy es un simple envoltorio alrededor de SOAPProxy con algunos métodos de introspección añadidos, así que la sintaxis de llamada de funciones es la misma.
3 Podemos acceder al SOAPProxy de WSDL.Proxy con server.soapproxy. Esto es útil para activar la depuración, para que cuando invoquemos funciones mediante el proxy WSDL su SOAPProxy vuelque los documentos XML de salida y entrada.