26 abril 2015

Google Code anuncia su cierre

No es una noticia fresca, pero el mes pasado Google anunció que van a cerrar Google Code tras 9 años de existencia.

Ese servicio comenzó en 2006 con el objetivo de facilitar una manera segura y escalable de alojar proyectos open source. En ese tiempo, millones de personas contribuyeron con proyectos open source alojados en esa página. Sin embargo, el tiempo ha pasado y otras opciones como GitHub o BitBuket han superado en popularidad a Google Code.

Nadie puede negar que a Google le gusta probar nuevas tendencias y tecnologías, pero el hecho es que tienen que mantener contentos a sus accionistas por lo que acaban cerrando aquellos servicios que no se mantienen altos en popularidad. Muchos servicios de alta calidad han sido cerrados antes que Google Code: Notes o Wave son los primeros que me vienen a la cabeza. El problema es que Google cierra servicios porque no son populares, pero algunos empiezan a pensar que no son tan populares porque la gente y las compañías empiezan a tener miedo de que Google cierre esos servicios a la primera de cambio, por lo que no los utilizan. Quién sabe...

Lo cierto es que desde Marzo no se ha podido añadir ningún nuevo proyecto a Google Code y aquellos que ya existen en él podrán mantener su funcionalidad completa sólo hasta Agosto de este año. A partir de ahí, los datos de los proyectos serán sólo de lectura y el sitio se cerrará el año próximo aunque los datos de los proyectos seguirán disponibles para descarga en formato fichero.

Aquellos que como yo tengan proyectos en Google Code cuentan con el exportador de Google Code para migrar su proyecto a GitHub y documentación para migrar a otros servicios manualmente.

Muchos de mis proyectos están bastante desactualizados pero supongo que los importaré a mis repositorios privados en Bitbucket para refactorizarlos a fondo y después hacerlos disponibles a través de GitHub.

17 abril 2015

Empaquetando programas Python - Paquetes para PyPI


Una vez que hayamos finalizado nuestro programa lo más probable es que queramos compartirlo. Hay varias maneras de compartir un programa Python:
  • Comprimir el programa y mandarlo por email: Sólo usaría este programa para scripts muy cortos que sólo utilizasen bibliotecas incluidas por defecto en Python.
  • Colgar el programa en un repositorio público como GitHub o BitBucket: Es una buena opción si queremos compartir nuestro programa con otros desarrolladores para dejarles hacer contribuciones al código. Revisamos esa opción en mi tutorial sobre Mercurial.
  • Ponerlo en el Python Package Index (PyPI): Es la opción obvia si queremos compartir nuestro programa con otros desarrolladores de Python no para que lo modifiquen sino para que lo usen como librería en sus propios desarrollos de Python. La parte buena de esta opción es que se pueden instalar dependencias de manera automática.
  • Empaquetarlo como un paquete nativo de una distribución de Linux: En otro capítulo explicaré maneras de empaquetar nuestro programa Python en un paquete RPM (para Fedora/RedHat/Suse) o un paquete DEB (para Ubuntu/Debian
En este artículo voy a explicar el tercer método: usar PyPI como repositorio.

PyPI es el repositorio oficial de paquetes de Python y está mantenido por la Python Software Foundation. Por eso es el repositorio central de referencia para practicamente todos los paquetes de Python públicamente disponibles. Pypi tiene una página web desde la que se puede descargar cualquier paquete manualmente pero generalmente lo mejor es usar pip o easy_install para instalar los paquetes desde la línea de comandos. Yo prefiero pip porque está incluido por defecto en las últimas versiones de Python 3.x y además es la herramienta recomendada por la Python Packaging Authority (PyPA), la principal fuente sobre buenas prácticas sobre empaquetamiento de aplicaciones python. Con pip se puede descargar e instalar cualquier paquete python desde los repositorios PyPI, junto con cualquier dependencia necesaria.

Se pueden subir dos tipos principales de paquetes python a PyPI:
  • Paquetes Wheel: Es un paquete de tipo binario que está pensado para reemplazar a los antiguos paquetes Egg. Es el formato recomendado para subir paquetes a PyPI.
  • Paquetes source: Es un tipo de paquete que tiene que ser compilado en el extremo del usuario. El formato wheel es más rápido y fácil de instalar porque ya viene precompilado desde el extremo del empaquetador. Una razón para utilizar paquetes source en vez de wheel sería cuando queramos incluir extensiones en C, dado que estas tienen que ser compiladas en el extremo del usuario.
En general los paquetes wheel son los más recomendables para ser distribuidos al común de los usuarios pero los paquetes source son útiles para hacer paquetes nativos para distintas distribuciones de linus (paquetes DEB para Debian/Ubuntu, paquetes RPM para Red Hat/Fedora/Suse, etc). En este capítulo vamos a ver cómo generar ambos tipos.

Lo primero que tenemos que hacer para asegurarnos de que nuestra aplicación está lista para ser empaquetada es organizarla en una estructura de carpetas estándar. Puedes organizar los ficheros de tu proyecto como quieras pero si le echas un ojo a cualquiera de los proyectos de python populares en Bitbucket o GitHub te darás cuenta de que siguen una estructura similar a la hora de organizar sus ficheros. Dicha estructura es una buena práctica que se ha ido generalizando con el tiempo. Para verlo más claro, se puede consultar el esqueleto de estructura de proyecto desarrollado por PyPA como ejemplo de esta buena práctica. Según la misma, lo habitual es poner el script de instalación y todos los ficheros que describan el proyecto en la carpeta raíz del mismo. Los ficheros de la aplicación (los que desarrollamos) deberían ir dentro de una carpeta llamada como el proyecto, pero colgando de la raíz. Otro ficheros relacionados con el desarrollo, pero que no formen parte del desarrollo mismo de la aplicación, deberían colocarse en sus propias carpetas al mismo nivel que la carpeta de desarrollo de la aplicación, pero no dentro de ella.


Veamos un ejemplo, el repositorio en GitHub de Geolocate. Se puede ver que puse los scripts de compilación, empaquetado e instalación en el nivel razi, junto con ficheros como el README.txt o el de REQUIREMENTS.txt que descirben la aplicación. Sin embargo los ficheros de desarrollo de la aplciación están dentro de una carpeta llamada "geolocate". Algunos prefieren meter los tests unitarios en su propia carpeta fuera de la de aplicación y otros prefieren meterlos dentro. Si no se pretende distribuir los tests a los usuarios finales, creo que es mejor mantenerlos fuera de la carpeta de aplicación. En el caso de geolocate están dentro porque tuve una serie de problemas con los imports y fue la primera manera que encontré de resolverlos, sin embargo ahora que creo que tengo claras las causas de los problemas creo que mantendrá los tests en su propia carpeta colgando directamente del nivel raíz.

Una vez organizado el proyecto en una estructura de carpetas estándar, suele ser buena idea crear un virtualenv para ejecutar la aplicación. Si no sabes lo que es un virtualenv, te recomiendo que le eches un ojo a este tutorial. Además, ese virtualenv nos permitirá definir la lista de paquetes de python que nuestra aplicación necesita como dependencias y exportar dicha lista a un fichero REQUIREMENTS.txt, tal y como se explica aquí. Ese fichero será realmente útil a la hora de escribir nuestro script setup.py, tal y como vamos a ver.

El script setup.py es el fichero más importante pra crear paquetes compatibles con PyPI. Tiene dos funciones principales:
  • Es el fichero donde se configuran los diferentes aspectos de nuestro proyecto. El script contiene una función global: setup(). Los argumentos que se le pasen a dicha función definirán determinados detalles de nuestra aplicación: autor, versión, descripción, etc.
  • Es el interfaz para ejecutar desde la línea de comandos diversas órdenes relacionadas con las tareas de empaquetamiento.
Se puede obtener una lista de los comando disponibles a través de setup.py haciendo:

dante@Camelot:~/project-directory$ python setup.py --help-commands

Setup.py depende del paquete de python setuptools, así que hay que asegurarse de tenerlo instalado.

Vamos a explicar como escribir un script setup.py funcional usando como guía
usando como guía el fichero setup.py de geolocate. Como se puede ver en ese ejemplo, el fichero en si es relativamente simple: se importa la función setup.py del paquete setuptools y se le llama. La configuración como tal viene con los parámetros que le pasamos a setup(). Veamos esos parámetros:
  • "name": Es el nombre del paquete tal y como va a ser identificado en PyPI. Lo mejor es comprobar que el nombre que deseamos ponerle a nuestra aplicación no esté siendo usado ya en PyPI. En mi caso desarrollé geolocate sólo para encontrarme con que el nombre ya estaba siendo usado por otro paquete, por lo que tuve que llamar al paquete glocate aunque el ejecutable de dentro aún se llamase geolocate. Una solución muy sucia pero la próxima vez lo haré mejor desde el principio.
  • "version": Versión del paquete. Intenta mantenerlo para facilitar que los usuarios puedan actualizar su copia descargándosela de PyPI.
  • "description": Es la descripción corta que se mostrará en la página del paquete en PyPI. Lo más recomendable es mantenerla corta y descriptiva.
  • "long_description": Esta es la versión larga de la descipción. Aquí se puede añadir más detalle. Dos o tres párrafos está bien.
  • "author": Tu nombre (real o nickname).
  • "author_email": Correo electrónico donde quieres que te contacten para cosas relativas a esta aplicación.
  • "license": Nombre de la licencia elegida para este programa.
  • "url": URL del sitio web de esta aplicación.
  • "download_url": Aquí suelo poner la URL desde donde se pueden descargar los paquetes de instalación para cada una de las distribuciones de linux.
  • "classifiers":Categorías en las que clasificar la aplicación. Es importante definirlas para que sirvan de ayuda a los usuarios a para buscar la aplicación que realmente necesitan dentro de la base de datos de PyPI. Se puede encontrar la lista completa de clasificadores aquí.
  • "keywords": Losta de palabras clave que describen tu aplicación.
  • "install_requires": Aquí es donde se ponen la lista dependencias que exportamos en su momento a REQUIREMENTS.txt.
  • "zip_safe": Una posible optimización que ofrecen los paquetes PyPI es la de poder ser instalados en formato comprimido de manera que consuman menos espacio en disco duro. El problema es que alguna aplicaciones no funcionan bien así, por lo que yo prefiero fijarlo siempre en "false".
  • "packages": Es necesario para enumerar la lista de packetes del proyecto a ser incluidos dentro del paquete instalable de tu proyecto. Aunque pueden ser especificados manualmente, yo prefiero usar la función setuptools.find_packages() para encontrarlos automáticamente.En esta función, la palabra clave "exclude" se supone que permite omitir paquetes que no necesitamos que sean incluidos en el instalable. el problema es que, en realidad, esa palabra clave no funciona debido a un bug. Más adelante veremos como solventar ese problema.
  • "entry_points" Aquí se define qué funciones de dentro de nuestros scripts serán llamadas directamente por el usuario. Yo lo utilizo para definir "console_scripts". Usando "console_scripts" setuptool compila el script fijado en un ejecutable de linux. Por ejemplo, si definimos la función main() dentro de nuestro script.py obtendremos un ejecutable sin extensión .py que podrá ser llamado ejecutado por el usuario directamente.
  • "package_data": Por defecto, setup.py sólo incluye ficheros  de python en el instalable. Si nuestra aplicación contiene otros tipos de ficheros que deben ser llamados por nuestros paquetes python, entonces deberíamos usar esta palabra clave para hacer que sean incluidos. Se define package_data con un diccionario. Cada clave es un paquete de la aplicación y su correspondiente valor es una lista de rutas relativas a los nombres de fichero que deberían incluirse dentro del instalable. Las rutas se consideran relativas a la carpeta que contiene el paquete. Setup.py no es capaz de crear carpetas vacióas para poner en ellas ficheros creados tras la instalación, por eso el truco es crear ficheros vacíos en dichas carpetas y acto seguido incluir dichos ficheros en el instalable usando esta palabra clave.
Algunos utilizan la palabra clave "data_files" para incluir ficheros que no están localizados en ninguno de los paquetes python de su aplicación. El problema que he encontrado con esta aproximación es que esos ficheros acaban instalados en rutas que dependen de la plataforma de instalación, por eso es realmente difícil hacer que tus scripts localicen esos ficheros cuando son ejecutados tras la instalación. Esa es la razón por la que prefiero poner mis ficheros dentro de mis paquetes y uso la palabra clave "package_data" en su lugar.

Una vez escrito el fichero setup.py ya se puede compilar el instalable, pero puede ser que queremos comprobar que setup.py está bien configurado. Pyroma es una aplicación que analiza la configuración de setup.py para comprobar que se cumplen las buenas prácticas reconocidas sobre empaquetado y alertándonos si no es así.

Una vez que estemos satisfechos con nuestra configuración de setup.py podemos crear un paquete source haciendo:

dante@Camelot:~/project-directory$ python setup.py sdist

Mientras que para crear un paquete wheel sólo hay que hacer:
dante@Camelot:~/project-directory$ python setup.py bdist_wheel

Cuando se usa setup.py para crear tus paquetes, se generará una carpeta dist (en la misma carpeta que setup.py) y se ubicarán ahí los instalables.

El problema surge cuando se intenta usar la función find_packages(), junto al argumento exclude y con la palabra clave packages. Me he dado cuenta de que en ese caso particular, el argumento exclude no funciona para omitir los ficheros no deseados de tu instalable, por lo que estos acaban siendo distribuidos. Según parece, este comportamiento es un bug y en tanto en cuanto lo resuelven la solución implica crear primero un paquete source y acto seguido crear un paquete whell del source con el siguiente comando:

dante@Camelot:~/project-directory$ pip wheel --no-index --no-deps --wheel-dir dist dist/*.tar.gz

El paquete generado se puede instalar localmente usando pip:
dante@Camelot:~/project-directory$ pip install dist/your_package.whl

Intentar instalar localmente tu paquete en un entorno virtualenv recién creado es una buena manera de probar si el instalable funciona como era de esperar.

Para compartir el paquete se puede enviar por email o hacerlo públicamente disponible desde un servidor web, pero la manera "pythonica" de hacerlo es subirlo a PyPI.

PyPI tiene dos sitios web.

  • PyPI test: Este sitio se limpia de una manera semi regular. Antes de subir tu paquete a al sitio principal de PyPI es mejor probarlo cacharreando con el sitio de tests. Se puede intentar instalar el paquete desde el sitio de tests de PyPI pero puede ocurrir que la instalación falle porque en dicho sitio no estén presentes todos los paquetes necesarios para solventar las dependencias. Eso se debe porque el sitio de test no cuenta con toda la base datos de paquetes sino con el subconjunto de paquetes que la gente haya subido para cacharrear.
  • PyPI main site: Este es el sitio web que tiene la base de datos de paquetes completa. Si alguna de nuestras dependencias no puede ser descargada de este lugar, entonces es que estamos haciendo algo mal.
Para usar cualquiera de estos sitios es necesario registrarse. Dado que la base de datos de usuario tampoco está compartida entre los sitios, habrá que registrarse en cada uno de ellos. En la página web de registro es donde se rellena el formulario dando los detalles de nuestro paquete. No hay que agobiarse, cualquier cosa que pongamos en dicho formulario puede modificarse después.

Tras el registro, se pueden subir los paquetes instaladores a través del interfaz web de PyPI pero lo más probable es que prefiramos mayor grado de automatización. Para enviar los ficheros desde nuestra consola (o desde un script), habrá que crear un fichero .pypirc en nuestra carpeta home (ojo al punto al comienzo del nombre) y darle este contenido:
[distutils]
index-servers=pypi 
[pypi]
repository = https://pypi.python.org/pypi
username =
password =

Después se puede ejecutar el comando twine para subir los paquetes a PyPI:
dante@Camelot:~/project-directory$ twine upload dist/*


Puede ser que tengamos que instalar twine ,con pip, antes de usarlo si se da el caso de que nuestro sistema no lo tenga instalado. Twine utilizará las credenciales que hayamos puesto en ~/.pypirc para conectarse a PyPI y subir nuestros paquetes. Twine usa cifrado SSL para proteger nuestras credenciales por lo que es una opción más segura que otras, como usar "python setup.py upload".

Tras lo anterior, nuestros paquetes estará disponibles a través de PyPI y cualquiera podrá instalárselos haciendo simplemente:
dante@Camelot:~/project-directory$ pip install your_package