supervivencia en python usando xml con namespaces

En python hay dos librerías que se usan extensamente xml que viene en el core de python y lxml una librería externa que provee de algunas cosas que xml no.

Vamos a manejar tres clases o conceptons ElementTree, Element y ElemenPath y vamos a jugar siempre con que nuestro xml tiene namespaces ya que tooooodo el uso actual de xml de servicios soap se hace con namespaces, excepto los ficheros de configuración de java

<tag attributo=»value»>texto</tag>

Elment, es un tag, y tiene nombre ( tag, tagname o como quieras llamarlo ), attributos y texto

En estos ejemplos se realiza una acción y se dumpea la salida para comprobar lo que estamos haciendo

import xml.etree.ElementTree as ET

# crear nodos <root><body></body></root>
>>> root = ET.Element('root')
>>> body = ET.SubElement(a, 'body')
>>> ET.dump(root)

# asignar atributos a un tag
# <root><body env=»devel»></body></root>
>>> root = ET.Element('root')
>>> body = ET.SubElement(root, 'body', {'env':'devel'})
>>> ET.dump(body)
<root><body env="devel" /></root>

# otras formas de manejar los attributos
>>> body.attrib['env']
'devel'
>>> body.attrib['env'] = 'foo'
>>> body.attrib['env']
'foo'

# texto dentro de un Element
>>> body.text = 'this is the text'
>>> ET.dump(body)
<body env="foo">this is the text</body>

# importar de fichero
>>> import xml.etree.ElementTree as ET
>>> tree = ET.parse('country_data.xml')
>>> root = tree.getroot()

# importar de string
>>> xml_string = "<root><body></body></root>"
>>> root = ET.fromstring(xml_string)
>>> ET.dump(root)
<root><body /></root>

# namespaces

source = """<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:cwmp="urn:dslforum-org:cwmp-1-2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soap-enc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Header>
<cwmp:ID soap:mustUnderstand="1">irrelevant_request_id</cwmp:ID>
<cwmp:HoldRequests soap:mustUnderstand="1">0</cwmp:HoldRequests>
</soap:Header>
<soap:Body>
</soap:Body>
</soap:Envelope>"""

>>> xml = ET.fromstring(source)
>>> xml
<Element ‘{http://schemas.xmlsoap.org/soap/envelope/}Envelope’ at 0x7f8ddb53d950>

Dos cosas a tener en cuenta, NO puede haber un salto de linea de en el «»»<?xml…» genera una excepción «XML or text declaration not at start of entity»

source = """
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:cwmp="urn:dslforum-org:cwmp-1-2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soap-enc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Header>
<cwmp:ID soap:mustUnderstand="1">irrelevant_request_id</cwmp:ID>
<cwmp:HoldRequests soap:mustUnderstand="1">0</cwmp:HoldRequests>
</soap:Header>
<soap:Body>
</soap:Body>
</soap:Envelope>"""

>>> import xml.etree.ElementTree as ET
>>> xml = ET.fromstring(source)
>>> xml = ET.fromstring(source)
Traceback (most recent call last):
File «<stdin>», line 1, in <module>
File «/usr/lib/python2.7/xml/etree/ElementTree.py», line 1311, in XML
parser.feed(text)
File «/usr/lib/python2.7/xml/etree/ElementTree.py», line 1653, in feed
self._raiseerror(v)
File «/usr/lib/python2.7/xml/etree/ElementTree.py», line 1517, in _raiseerror
raise err
xml.etree.ElementTree.ParseError: XML or text declaration not at start of entity: line 2, column 0
#Usando namespaces los tags se nombran así
{url}TagName

Con lo que las busquedas xpath se hace buscando por {url}TagName. Para buscar <cwmp:ID> lo tendremos que hacer de la siguiente manera, pero antes hay que registar las urls

Podemos usar find o findall para buscar uno o todos los nodos que cumplan un pattern xpath

source = """<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:cwmp="urn:dslforum-org:cwmp-1-2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soap-enc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Header>
<cwmp:ID soap:mustUnderstand="1">irrelevant_request_id</cwmp:ID>
<cwmp:HoldRequests soap:mustUnderstand="1">0</cwmp:HoldRequests>
</soap:Header>
<soap:Body>
</soap:Body>
</soap:Envelope>"""

>>> xml = ET.fromstring(source)
# como tenemos namespaces esta consulta no funcionará
>>> xml.find('ID')
# esta debería funcionar, pero tampoco funciona por que find busca desde la raiz y no desde cualquier nodo
>>> xml.find('{urn:dslforum-org:cwmp-1-2}ID')
# buscamos con find desde cualquier nodo con una busqueda al estilo xpath
>>> xml.findall('.//{urn:dslforum-org:cwmp-1-2}ID')
<Element '{urn:dslforum-org:cwmp-1-2}ID' at 0x7f234c675c50>

# es posible usar etiquetas para los namespaces
>>> ns = {'cwmp': 'urn:dslforum-org:cwmp-1-2',
'soap': 'http://schemas.xmlsoap.org/soap/envelope/'}
>>> xml.find('.//{urn:dslforum-org:cwmp-1-2}ID', ns)
<Element '{urn:dslforum-org:cwmp-1-2}ID' at 0x7f234c675c50>

# findall devolerá una lista con los resultados, o una lista vacía si no hay resultado
>>> xml.findall('.//{urn:dslforum-org:cwmp-1-2}ID', ns)
[<Element '{urn:dslforum-org:cwmp-1-2}ID' at 0x7f234c675c50>]

# pip install lxml
# from lxml import etree

# crear nodos <root><body></body></root>
>>> root = etree.Element('root')
>>> root.append(etree.Element('body'))
>>> etree.tostring(root)
'<root><body/></root>'
>>> etree.tostring(root, pretty_print=True)
'<root>\n <body/>\n</root>\n'

# asignar atributos a un tag

>>> root = etree.Element('root')
>>> root.append(etree.Element('body', env='devel'))
>>> etree.tostring(root)
'<root><body/><body env="devel"/></root>'
>>> etree.tostring(root, pretty_print=True)
'<root>\n <body/>\n <body env="devel"/>\n</root>\n'

# manejar attributes de forma comoda
>>> root = etree.Element('root')
>>> root.append(etree.Element('body', my_attribute='my_value'))
>>> attributes = root[0].attrib
>>> attributes['new_attr'] = '12345'
>>> etree.tostring(root)
'<root><body my_attribute="my_value" new_attr="12345"/></root>'

# manejar texto
>>> from lxml import etree
>>> root = etree.Element('root')
>>> root.append(etree.Element('body', my_attribute='my_value'))
>>> root.text = 'this is the text'
>>> etree.tostring(root)
'<root>this is the text<body my_attribute="my_value"/></root>'

# importar de fichero

>>> from lxml import etree
>>> tree = ET.parse('country_data.xml')
>>> root = tree.getroot()

# importar de string
>>> xml_string = "<root><body></body></root>"
>>> root = ET.fromstring(xml_string)
>>> ET.dump(root)
<root><body /></root>

# namespaces

>>> source = """<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:cwmp="urn:dslforum-org:cwmp-1-2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soap-enc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Header>
<cwmp:ID soap:mustUnderstand="1">irrelevant_request_id</cwmp:ID>
<cwmp:HoldRequests soap:mustUnderstand="1">0</cwmp:HoldRequests>
</soap:Header>
<soap:Body>
</soap:Body>
</soap:Envelope>"""
>>> root = ET.fromstring(source)

#Buscando nodos, como el xml hay que poner {<namespace}Tag

# nótese que el {namespace} debe ser exacto al encabezado o no funcionará la búsqueda
>>> print(root.findall('{http://schemas.xmlsoap.org/soap/envelope}Header'))
[]
>>> print(root.findall('{http://schemas.xmlsoap.org/soap/envelope/}Header'))
[<Element '{http://schemas.xmlsoap.org/soap/envelope/}Header' at 0x7f234c675e90>]

# para poder obtener los nsmap de un xml debemos usar objectify, hay dos Element

http://lxml.de/api/xml.etree.ElementTree.Element-class.html
http://lxml.de/api/lxml.etree.ElementBase-class.html

>>> root = objectify.fromstring(source)
>>> root.nsmap
{'cwmp': 'urn:dslforum-org:cwmp-1-2', 'soap-enc': 'http://schemas.xmlsoap.org/soap/encoding/', 'xsi': 'http://www.w3.org/2001/XMLSchema-instance', 'soap': 'http://schemas.xmlsoap.org/soap/envelope/', 'xsd': 'http://www.w3.org/2001/XMLSchema'}

# printea con cabecera xml usando xml_declaration=True
root = etree.Element('root')
etree.tostring(root, encoding="utf-8" , xml_declaration=True)
"<?xml version='1.0' encoding='utf-8'?>\n<root/>"

# Se pueden crear a mano elmentos de namespaces con ElementMarker pero el trabajo es muy extenso y el código no es muy legible, mi recomendación es usar templates de string importarlos y usarlos en vez de generarlos a mano.

from lxml import etree
from lxml.builder import ElementMaker
soap = ElementMaker(namespace=»http://schemas.xmlsoap.org/soap/envelope/», nsmap={‘soap’ : «http://schemas.xmlsoap.org/soap/envelope/»})

# Extras : xmlwitch
xmlwitch sólo lee y no escribe, pero lo hace de manera muy cómodo

entornos virtuales en python

Las aplicaciones cada vez son más complejas y por suerte más estructuradas, por ello los pequeños bloques de código reutilizable se extraen y se paquetizan, con uno u otro nombre dependiendo de cada lenguaje. El siguiente paso necesario es una herramienta que gestione los paquetes, instalar, listar, eliminar, instalar desde una fuente remota, etc… Bien pues aquí es donde llega la necesidad de usar un entorno virutal. ¿Por qué? Por varios motivos, uno es que cada aplicación puede requerir una serie de paquetes con versiones concretas que no son necesarias con otras aplicaciones o que incluso que sean incopatibles entre sí.
Usando un entorno virtual podemos instalar estas dependencias en una ruta exclusiva para nuestra aplicación y no tenemos que instalarlas en el sistema. Esto es genial ya que no necesitaremos permisos elevados para instalar los paquetes ni tendremos que andar instalando o desinstalando paquetes para poder desarrollar o instalar una aplicación. Además podemos deshacernos fácilmente de estos paquetes eliminando la ruta donde se almacenan y volver a instalarlos para probar el despliegue o simplemente por si sospechamos que hay ‘algo’ que no está correcto en nuestras dependencias.

En el caso de python hay dos entornos virtuales que se usan extensivamente: virtualenvwrapper y virtuaenv.

La primera vez que empecé a leer sobre esto me resultó muy confuso por que parecía que se hablaba de un sólo software para crear el entorno virutal y no de dos. El uso de estos dos nombres muy parecidos favorece la confusión, pero son dos proyectos que al fin y al cabo hacen lo mismo: proveernos de un entorno virtual.

A grandes rasgos las características principales de virtualenvwrapper vs virtualenv en Debian son:
* virtualenvwrapper sólo soporta python2.x ( ojo, en paquete Debian, ver READE.debian) y virutalenv soporta 2.x y 3.x
* virtualenvwrapper centraliza el almacenamiento de dependencias en $WORKON_HOME Y virutalenv no, debes especificar el directorio cada vez que creas el entorno.

Ahora, como comenzar a usar un entorno virtual en python:

Virtualenvwrapper


sudo apt-get install virtualenvwrapper
mkvirtualenv my_project
workon my_project
...
deactivate

Se crea por defecto en `~/.virtualenvs`

 Virtualenv

Puedes preparar el entorno para la version de python que quieras siempre que la tengas instalada en el systema ( -p <ruta_a_bin_python> )

sudo apt-get install python-virtualenv
virtualenv -p /usr/bin/python2 venv
source venv/bin/activate
...
deactivate

instalar dependencias: pip

El gestor de dependencias por defecto en python es pip, y con el puedes instalar, desintalar, instalar desde remoto, etc…
Con estos entornos ya puedes instalar tus ficheros requirements.txt o requirements-dev.txt de pip sin tener que usar poderes de supervaca. La manera ‘standard’ para instalar las dependencias de un proyecto:


pip install pip --upgrade
pip install -r acs/requirements-dev.txt --upgrade
pip install -r acs/requirements.txt --upgrade

Finalmente podrías configurar cada paquete que disponga de setup.py

for package in $(ls -d */); do pushd $package; if [ -e setup.py ]; then python setup.py develop; fi; popd; done