Python y redireccionar utf-8 a un fichero

Un problema recurrente en python son los problemas con el encoding y concretamente uno de ellos es el problema de redireccionar la salida a un fichero.

Es bastante habitual escribir a la salida estándar información  y cuando esta es demasiada para verla o analizarla a simple vista, es muy normal redirigir la salida a un fichero, y entonces BOOM, todo explota. Veamos un ejemplo:

Creo un fichero, que vamos a llamar utf-8.py y que contiene:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
print u"¡Hola mundo!"

Al ejecutarlo normalmente “python utf-8.py”, obtenemos como resultado, lo esperado ¡Hola mundo!, pero si lo redirigimos a un fichero “python utf-8.py > /dev/null” casca nuestra aplicación.

Traceback (most recent call last):
  File "utf-8.py", line 3, in <module>
    print u"¡Hola mundo!"
UnicodeEncodeError: 'ascii' codec can't encode character u'\xa1' in position 0: ordinal not in range(128)

Esto se debe a que python por defecto abre sus ficheros por defecto con codificación ascii, y por tanto, al abrir el fichero al que intentamos redirigir lo abre con codificación ascii, que no entiende los caracteres utf-8 que le hemos pasado (en este caso el carácter ¡).

Bien, una vez entendido cual es el problema, la solución es fácil, hay que decirle a python con que encoding por defecto se tienen que abrir los ficheros, y esto se hace a través de la variable de entorno PYTHONIOENCODING.

export PYTHONIOENCODING=utf-8
python utf-8.py > /dev/null

Y todo va perfectamente.

Desplegar Django bajo Apache con WSGI en un virtualenv, *y que funcione*

El despliegue de Django es una de las pocas cosas claramente mejorables de este estupendo framework. Suele dar más de un dolor de cabeza porque es un poco lío establecer correctamente las variables de entorno. Un caso concreto que me ha dado bastantes problemas es desplegarlo con mod_wsgi de Apache, pero estando el Django dentro de un python con virtualenv.

En la documentación de Django explican el caso general de usar Django con Apache y mod_wsgi, incluyendo enlaces a otros documentos más extensos. Pero no explican el caso del virtualenv. Buscando en la web he encontrado muchos tutoriales pero ninguno me terminaba de aclarar. El problema es cómo decirle a mod_wsgi que coja el python del virtualenv en vez del global del sistema. Este tutorial es el que parecía más prometedor. Básicamente dice que hay que añadir el path del site-packages de nuestro virtualenv con una llamada site.addsitedir() y definir el path de python añadiendo WSGIPythonHome al fichero de configuración de Apache.

Sin embargo, la parte del site.addsitedir() sí funciona, pero el WSGPythonHome no. Después de mucho probar, descubrí que mod_wsgi necesita que el python del virtualenv esté en el path del sistema, si no, no lo encuentra aunque pongamos el WSGIPythonHome apuntando a él. Así que la solución es añadir /mi/path/virtualenv/bin al path, por ejemplo en el script  que lanza apache (normalmente será /etc/init.d/apache o /etc/init.d/httpd). Ojo con esto, que puede afectar a otros proyectos que usen python en el mismo apache.

Y observar una cosa importante: WSGI usa el mismo python para todos los virtual hosts, con lo cual se pierde una de las razones principales para usar virtualenv. Ahora esto sólo servirá si queremos aislar nuestro django de otros proyectos python que haya en el sistema, pero que no usen mod_wsgi.

Y otra cosa más: el mod_wsgi lanza el servidor en un directorio cualquiera, que no tiene por qué ser la raíz de Django. Para que encuentre las cosas, hay dos opciones: una es poner todos los paths absolutos en el settings.py, y la otra es añadir al principio del script django.wsgi las siguientes líneas:

import os
os.chdir(‘/mi/path/virtualenv/mi_proyecto/src’)

En este otro documento explica con bastante más detalle el comportamiento del wsgi script con Django.

Utilizando webservices de Salesforce desde python con salesforce-python-toolkit y ficheros wsdl extra

Para utilizar el API de Salesforce desde python una de las alternativas que tenemos es usar Salesforce Python Toolkit.
Como prerequisito es necesario tener instalado suds, tenemos dos opciones:

1.- Con easy install.

  easy_install "suds==0.3.9"

2.- Desde los fuentes:

  sudo apt-get install python-setuptools
  wget https://fedorahosted.org/releases/s/u/suds/python-suds-0.3.9.tar.gz
  tar zxvf python-suds-0.3.9.tar.gz
  cd python-suds-0.3.9/
  sudo python setup.py install

Una vez hecho esto descargamos el Salesforce Python Toolkit desde aquí y lo descomprimos.

Esto no pretende ser una guía completa de utilización del API, en esta url podéis consultar un montón de ejemplos. El caso más básico de autenticación y ejecución sería algo así:

from sforce.enterprise import SforceEnterpriseClient

#Login
h = SforceEnterpriseClient('/path/para/encontrar/enterprise.wsdl')
h.login(super@l.ex', '*passwordhere*', '*securitytokenhere*')

#Query de ejemplo
print h.query('SELECT FirstName, LastName FROM User')

Lo que no queda claro en la documentación es cómo utilizar un fichero de wsdl diferente al enterprise.wsdl o al partner.wsdl pero conservando la autenticación en SalesForce.
Por ejemplo, si tenemos el fichero otro.wsdl donde se especifica la llamada test() podríamos hacer algo así:

from suds.client import Client
c1 = Client('file:///path/para/encontrar/otro.wsdl')

#Añadimos la cabecera de autenticación de Salesforce y la pasamos al nuevo Client de Suds
c1.set_options(soapheaders=(h._sessionHeader))

#Hacemos la llamada a test
print c1.service.test()

Happy coding! 🙂

Conversión de ficheros “unoconv.py” lenta (seis minutos)

El proceso de conversión entre formatos OpenOffice.org y MSOffice no debe sobrepasar algunos segundos, así que en el caso de emplear seis minutos existe algún tipo de problema.

La solución que hemos encontrado pasa por desinstalar del sistema el paquete el “displayconfig-gtk”. Al parecer debe tener conflictos con el emulador de las X’s utilizado para OpenOffice.org.

Desinstalándolo, el proceso unoconv.py termina de manera correcta, en los mismos segundos que debía utilizar.

Una hipótesis del problema, podría ser la explicada a continuación.

El proceso de transformación “unoconv.py”, hace uso del bridge UNO que OpenOffice.org tiene para python. Por defecto, el proceso principal  de OpenOffice, “soffice.bin”, requiere que esté cargado un entorno gráfico en la máquina. De ahí la necesidad de emular las X’s con un servidor “falso” mediante el paquete “Xvfb”.

Por otro lado, la finalidad del paquete en conflicto, “DisplayConfigGTK”, es la de permitir cambiar la configuración de la tarjeta gráfica y la pantalla. Digamos que si se utiliza el paquete emulador de entorno gráfico, no puede, o no debería tener detrás un servidor de X real. Del mismo modo, si se dispone de un entorno gráfico real, no hay necesidad de utilizar el emulador.

Internamente podría estar dándose un posible conflicto al intentar fijar una determinada resolución (DisplayConfigGTK), para una pantalla ficticia montada por “xvfs”, que también la haya definido.

Python: ejecutar un subproceso desde un script

En un script de python necesitamos, ante unas condiciones, lanzar un programa externo (puede estar escrito en python o no, es irrelevante). Queremos monitorizarlo y para ello nos gustaría saber su PID.

Lo habitual es hacer un import os y empezar a jugar con los spawn*, los waitpid, los exec*, los popen* e incluso algún fork(). El resultado suele ser procesos zombie (Z) que no se inician bien y se quedan en defunct. Con bastante tiempo, podríamos tener algún éxito pero lo recomendable eshacer uso del módulo subprocess y, en concreto, de Popen().

Así que para lanzar un proceso independiente y recoger su PID escribid:

PID = subprocess.Popen([cmdline], shell=True).pid