lunes, 28 de diciembre de 2009

Tutorial: ¿Cómo migrar desde Subversion (SVN) hacia Mercurial con Hgsvn?

Mercurial SCM

Después de sumarme al desarrollo del plugin de Trac para RPC (gracias a Odd Simon Simonsen ;o) y trabajar en unas mejoras, he decidido migrar mis proyectos anteriores desde Subversion hacia Mercurial. Aquí describiré una alternativa para realizarla. Por el momento no soy un experto, por lo que el análisis de las ventajas y puntos débiles de esta variante quedará pospuesto hasta que pruebe otras alternativas (si es que eso ocurre ...). Sin embargo, Usted puede formar parte de la búsqueda de la solución más adecuada y opinar u ofrecer sugerencias. Si desea saber acerca de cómo compartir código y colaborar utilizando repositorios de Mercurial, le sugiero que consulte un artículo previo sobre el uso de MQ con Bitbucket. Es así cómo se desarrolla el plugin que les mencioné anteriormente, utilizando su repositorio de parches.

Actualizado 23/04/2010 13:43:09 con hgpullsvn

Estado inicial : Subversion

Subversion ha sido por mucho tiempo la opción más difundida para desarrollar proyectos de software libre. De hecho este es el sistema de control de versiones que fue ofrecido en primer lugar por Sourceforge, Google Code y casi todos los otros grandes patrocinadores del open source. Sin embargo el surgimiento primero de GitHub, luego de Bitbucket y otros sitios similares basados en Bazaar, han hecho que los sistemas distribuidos de control de versiones ganen una fuerza tremenda. En gran parte esto se debe a la inmensa gama de modelos de colaboración que son posibles cuando no se tiene un repositorio centralizado. De hecho, desde que comencé a utilizar SVN la sugerencia que nos hacia mi profesor, Alí, era que le echáramos un vistazo a SVK.

En este momento se gestó el proyecto FLiOOPS con una idea original de Kyrie (a.k.a. Luis Alberto Zarrabeitia) para concebir una implementación de la programación por contratos en Python. Lo que una vez fue una tarea de curso, con el tiempo fue creciendo y se incorporaron nuevas ideas. Entre todas ellas se destacó el módulo dutest, que se utilizó para escribir las pruebas de unidad. El aumento en la complejidad, implicó la existencia de varias ramas de desarrollo y otros elementos a los que Subversion no les da una solución. Por ejemplo acá les muestro la estructura actual del repositorio original :

/
    java
        trunk
        branches
        tags
    py
        trunk
        branches
            add_opts
            mdl_inv
            pep302
            test_pycontract
            guarded_synch
            ochk_arch
            pysimrec
        tags
            dutest
                0_1_0
                0_1_1
                0_1_2
                0_2_1
                0_2_2

La buena noticia es que Sourceforge permite también el uso de (múltiples) repositorios de Mercurial y Git. Inicialmente esto hace posible tener un repositorio para cada lenguaje de programación, o para cada librería que se desarrolle en el marco del proyecto. Sin embargo hay que trasladar todas las versiones ya existentes hacia su nuevo destino.

Opciones para migrar

Después de buscar información e indagar un poco, encontré varias herramientas, pero parece que tres se distinguen del resto:

  • HgSubversion es una librería externa cuyo objetivo es lograr una integración completa con Subversion.
  • El comando convert permite migrar repositorios de distintos tipos (incluso otros repositorios hechos con Mercurial) hacia un repositorio de Mercurial. Como permite filtrar y renombrar los contenidos que serán incorporados, también resulta útil para filtrar repositorios Mercurial completos y obtener un subconjunto de los cambios que resultan de interés. Entre los tipos de herramientas soportadas se encuentran :
  • Hgsvn es un projrcto que surgió a partir de la comunidad de desarollo de Python para dar una respuesta a la migración de los repositorios de la librería estándar. Varias de sus características son:
    • Los datos de las versiones se solicitan por lotes.
    • El proceso es incremental, por lo que es posible interrumpirlo y resumirlo posteriormente.
    • Preserva los meta-datos (e.g. nombres y fechas de los cambios, registros de copia y de cambio de nombres de ficheros).
    • Crea marcas (a.k.a. tags) locales para cada versión en el repositorio original.
    • Preserva los nombres y las relaciones entre las ramas (a.k.a. branches).

En todos los casos parece que el punto débil resulta ser la posibilidad de hacer commits en el repositorio SVN. Sin embargo, como ya les expliqué, este no era mi objetivo en este caso. Finalmente me decidí a utilizar Hgsvn.

Clonando el repositorio con Hgsvn

Hgsvn solo clona una rama a la vez. Considerando la estructura del repositorio original, el primer paso es clonar el trunk. Para ello se ejecuta el comando hgimportsvn de la siguiente manera:

$ pwd
/home/olemis/repos/
$ mkdir flioops
$ cd flioops
$ hgimportsvn https://flioops.svn.sourceforge.net/svnroot/flioops/py/trunk > ./trunk.log
$ cd trunk && hgpullsvn >> ../trunk.log

En el ejemplo se redirecciona la salida hacia el fichero ./trunk.log para su posterior análisis. Les recomiendo observarlo, puesto a que conocerán los cambios que se van incorporando y los datos que se van recuperando del repositorio original. La lista puede ser larga.

En este caso se crea un repositorio dentro de la carpeta trunk. En general el nombre se corresponde con el último tramo de la URL que se especifica como parámetro. Podemos comprobar que todo está en orden después de observar el log del nuevo repositorio.

$ pwd
/home/olemis/repos/flioops/trunk
$ hg log | more
changeset:   49:39cfb58b31b6
branch:      trunk
tag:         tip
tag:         svn.91
user:        olemis
date:        Wed Sep 17 10:34:32 2008 -0500
summary:     [svn r91] - Bug fixed... The class PackageTestLoader loaded the tests defined in

changeset:   48:5595c9f190ca
branch:      trunk
tag:         svn.88
user:        olemis
date:        Thu Sep 11 14:38:56 2008 -0500
summary:     [svn r88] Starting dutest release 0.2.1.

changeset:   47:ac470dd94852
branch:      trunk
tag:         svn.86
user:        olemis
date:        Thu Sep 11 12:10:25 2008 -0500
summary:     [svn r86] - PackageTestLoader class added to oop.utils.dutest module. It loads

$ hg branches
trunk                         49:39cfb58b31b6

$ hg tags -v | more
tip                               49:39cfb58b31b6
svn.91                            49:39cfb58b31b6 local
svn.88                            48:5595c9f190ca local
svn.86                            47:ac470dd94852 local
svn.83                            46:0f8df4313c98 local
svn.82                            45:287eef1166bf local
svn.80                            44:7accc993aad9 local
svn.78                            43:9ab9af3bbbfd local
svn.77                            42:4678ec09ef71 local

$ hg log -r svn.88 -v
changeset:   48:5595c9f190ca
branch:      trunk
tag:         svn.88
user:        olemis
date:        Thu Sep 11 14:38:56 2008 -0500
files:       utils/dutest.py
description:
[svn r88] Starting dutest release 0.2.1.

Como se puede observar solo obtenemos una sola rama llamada trunk. Su nombre se selecciona de manera análoga a partir de la URL. También se crea una marca local para cada revisión que se encuentra en el repositorio original. De esta forma es más fácil relacionar los cambios en un repositorio y otro. Por ejemplo la versión 88 en el repositorio central se corresponde con la versión 48 localmente, cuyo identificador global sería 5595c9f190ca. Además, si se nos olvidan estos detalles (de más está decir que yo me los sé de memoria :P ) pero conocemos la versión en el repositorio de Subversion, entonces es posible hacer referencia a su contra-parte en el repositorio de Mercurial utilizando el nombre (i.e. marca) svn.88.

Como se puede observar se mantiene el nombre del autor de los cambios y la fecha. Quizás le puedan quedar dudas ... pues ¡aclarémoslas!.

$ svn info
Path: .
URL: https://flioops.svn.sourceforge.net/svnroot/flioops/py/trunk
Repository Root: https://flioops.svn.sourceforge.net/svnroot/flioops
Repository UUID: 878b2ac5-cb47-0410-a52a-c42bf53788ce
Revision: 91
Node Kind: directory
Schedule: normal
Last Changed Author: olemis
Last Changed Rev: 91
Last Changed Date: 2008-09-17 10:34:32 -0500 (Wed, 17 Sep 2008)

Pues bien, en un mismo espacio coexisten ambos repositorios. Compare esta fecha con la que reporta Mercurial y verá que son idénticas. Después de satisfacer su curiosidad inicial, es hora entonces de clonar el resto de los cambios.

Clonando las ramas con Hgsvn

Como hemos visto antes, Hgsvn solo clona una rama a la vez. El proceso para recuperar los cambios restantes es análogo.

$ cd ..
$ pwd
/home/olemis/repos/flioops
$ hgimportsvn https://flioops.svn.sourceforge.net/svnroot/flioops/py/branches/add_opts > ./add_opts.log
$ cd add_opts && hgpullsvn >> ../add_opts.log && cd ..
$ hgimportsvn https://flioops.svn.sourceforge.net/svnroot/flioops/py/branches/mdl_inv > ./mdl_inv.log
$ cd mdl_inv && hgpullsvn >> ../mdl_inv.log && cd ..
$ hgimportsvn https://flioops.svn.sourceforge.net/svnroot/flioops/py/branches/pep302 > ./pep302.log
$ cd pep302 && hgpullsvn >> ../pep302.log && cd ..
$ hgimportsvn https://flioops.svn.sourceforge.net/svnroot/flioops/py/branches/test_pycontract > ./test_pycontract.log
$ cd test_pycontract && hgpullsvn >> ../test_pycontract.log && cd ..
$ hgimportsvn https://flioops.svn.sourceforge.net/svnroot/flioops/py/branches/guarded_synch > ./guarded_synch.log
$ cd guarded_synch && hgpullsvn >> ../guarded_synch.log && cd ..
$ hgimportsvn https://flioops.svn.sourceforge.net/svnroot/flioops/py/branches/ochk_arch > ./ochk_arch.log
$ cd ochk_arch && hgpullsvn >> ../ochk_arch.log && cd ..
$ hgimportsvn https://flioops.svn.sourceforge.net/svnroot/flioops/py/branches/pysimrec > ./pysimrec.log
$ cd pysimrec && hgpullsvn >> ../pysimrec.log && cd ..
$ ls
add_opts          mdl_inv          pep302           test_pycontract
guarded_synch     ochk_arch        pysimrec         trunk
add_opts.log      mdl_inv.log      pep302.log       test_pycontract.log
guarded_synch.log ochk_arch.log    pysimrec.log     trunk.log

Ahora todos esperamos efectos similares (i.e. marcas locales, una sola rama, fechas consistentes y dos repositorios en fraternal coexistencia).

$ cd add_opts
$ hg log | more
changeset:   36:660d2a6c5f05
branch:      add_opts
tag:         tip
tag:         svn.54
user:        olemis
date:        Thu Aug 07 09:42:48 2008 -0500
summary:     [svn r54] The error messages can be formatted now by specifying the templates

changeset:   35:0243dc200611
branch:      add_opts
tag:         svn.53
user:        olemis
date:        Thu Aug 07 09:36:52 2008 -0500
summary:     [svn r53] The whole assertion checking infrastructure is prepared for

changeset:   34:84a28bf08590
branch:      add_opts
tag:         svn.52
user:        olemis
date:        Thu Aug 07 09:22:20 2008 -0500
summary:     [svn r52] The function oop.utils.NormalizeException is prepared for assuming

$ svn info
Path: .
URL: https://flioops.svn.sourceforge.net/svnroot/flioops/py/branches/add_opts
Repository Root: https://flioops.svn.sourceforge.net/svnroot/flioops
Repository UUID: 878b2ac5-cb47-0410-a52a-c42bf53788ce
Revision: 54
Node Kind: directory
Schedule: normal
Last Changed Author: olemis
Last Changed Rev: 54
Last Changed Date: 2008-08-07 09:42:48 -0500 (Thu, 07 Aug 2008)

$ hg branches
add_opts                      36:660d2a6c5f05
trunk                         29:536184bf7e16 (inactive)

$ hg heads | grep changeset
changeset:   36:660d2a6c5f05

$ hg tags -v
tip                               36:660d2a6c5f05
svn.54                            36:660d2a6c5f05 local
svn.53                            35:0243dc200611 local
svn.52                            34:84a28bf08590 local
svn.50                            33:7e5b743efd37 local
svn.49                            32:901f61877293 local
svn.48                            31:51e7d78f26d8 local
svn.47                            30:336637e2a0f1 local

$ hg log -b trunk | grep changeset
changeset:   29:536184bf7e16
changeset:   28:ec176d410993
changeset:   27:d6ddd117926d
changeset:   26:1f89e8573172
changeset:   25:7b0847a67a80
changeset:   24:23571cc5686b
changeset:   23:cad71e6a4e34
changeset:   22:2d9e43109e87
changeset:   21:2a77c9c5e6ad
changeset:   20:c177d00b5a4e
changeset:   19:64eb1c7cdd91
changeset:   18:7c941992dbe5
changeset:   17:4dd286854034
changeset:   16:548c66a002c2
changeset:   15:7ad0521ce2b4
changeset:   14:f0087f96d466
changeset:   13:2540d610c198
changeset:   12:ee7e670e9b88
changeset:   11:8ba039171b0a
changeset:   10:af39f33a1776
changeset:   9:c95d926d676d
changeset:   8:28535deb9fc9
changeset:   7:1ddfa139931c
changeset:   6:84e23e55166e
changeset:   5:e1d36b91fb4a
changeset:   4:80257d1256a9
changeset:   3:6c81277c9392
changeset:   2:82714a5663d1
changeset:   1:89d45b024c67
changeset:   0:51750d40d056

$ cd ..\trunk
$ hg log -r 29:25 | grep changeset
changeset:   29:536184bf7e16
changeset:   28:ec176d410993
changeset:   27:d6ddd117926d
changeset:   26:1f89e8573172
changeset:   25:7b0847a67a80

Si bien hay varias semejanzas con respecto a los meta-datos, las diferencias son notables. Por lo visto hay menos revisiones y ... ¿dos ramas? ¿De dónde salió trunk si clonamos a add_opts? ¿Qué se puede deducir después de ejecutar los comandos anteriores? Primeramente, como las revisiones tienen el mismo identificador en ambos repositorios (trunk y add_opts) se puede deducir que se clonó el primer repositorio que teníamos localmente hasta la revisión 29:536184bf7e16. De ahi en adelante hgsvn detectó que en el repositorio SVN se había hecho una copia de los contenidos de la carpeta trunk y por tanto interpreta este hecho como la creación de una nueva rama y lo refleja localmente. En lo sucesivo solo se añaden las versiones en la rama add_opts. Es por esto que solo se crea una cabeza (a.k.a. head). La misma situación se repite para los otros repositorios.

Si Usted prefiere tener varias ramas en un solo repositorio (como yo ;o) solo resta unir todas las ramas.

$ pwd
/home/olemis/repos/flioops/trunk
$ find ../* -type d -print0  | xargs -0 -I repos hg pull repos
$ hg branches
ochk_arch                     76:6ac33679d5c3
guarded_synch                 75:386b8f866218
test_pycontract               73:d80a5ccc6153
mdl_inv                       69:a9b380084029
pep302                        62:00b7244f3642
add_opts                      60:660d2a6c5f05
pysimrec                      53:93e9562bfb41
trunk                         49:39cfb58b31b6

$ hg heads | grep changeset
changeset:   76:6ac33679d5c3
changeset:   75:386b8f866218
changeset:   73:d80a5ccc6153
changeset:   69:a9b380084029
changeset:   62:00b7244f3642
changeset:   60:660d2a6c5f05
changeset:   53:93e9562bfb41
changeset:   49:39cfb58b31b6

Ya tenemos el repositorio con todas las de la ley. Lo único que es de resaltar es que desaparecen las marcas que indicaban los números de revisión SVN para las ramas, pues son locales a los repositorios clonados.

Conclusiones

Hgsvn solo clona una rama a la vez. Subversion no tiene implementada una noción de ramas, por lo tanto el comando hgimportsvn considera como nombre el último tramo de la URL especificada. La herramienta se destaca por mantener la mayor fidelidad posible con respecto al repositorio SVN original y recuperar la mayor cantidad posible de meta-datos para incorporarlos en la copia local hecha con Mercurial. Hgsvn analiza las operaciones realizadas en el repositorio SVN original e infiere las relaciones entre las ramas para reflejarlas en el clon que se crea localmente.

viernes, 18 de diciembre de 2009

El servidor Apache pescó un resfriado

Malas noticias para todos aquellos que creían que el servidor web Apache estaba hecho a pruebas de balas. Hace pocos minutos consulté el sitio http://www.meteo.fr y en su página principal se mostraba el siguiente mensaje :

Compte tenu des conditions météorologiques, le site de Météo-France est temporairement saturé. Nous vous redirigeons vers l'information de Vigilance et vous prions de nous excuser pour la gêne occasionnée.

¿Querrá esto decir que no funciona si hay más de 3 cm de nieve? ¿Cómo es posible que los desarrolladores no hayan previsto esto? ¿Será un error en la especificación? ¿Por qué no han incluido un caso de prueba al respecto en su suite de pruebas de rendimiento y balance de carga? Sin embargo hay quienes dicen que este es un error conocido: no es nada nuevo que los indios no están habituados a la nieve .

:-D

Lo que sí parece es que se repite la historia de cazador cazado

Descubierto por Hervé Agnoux

miércoles, 16 de diciembre de 2009

Mejorando nuestro software libre con Bitbucket : Mercurial Queues

Bitbucket

Es un servicio de hospedaje de proyectos basado en Mercurial y enriquecido con características sociales. A continuación les describo una funcionalidad que puede ser muy útil en los proyectos de software libre para manejar los parches (patches) y propuestas de mejoras. Las potencialidades de Mercurial, de Python y la web 2.0, se combinan una vez más para salvarnos.

¿Qué es Bitbucket?

No está muy alejado de la verdad mencionar que Bitbucket está inspirado en Github, otro excelente sitio con características similares pero orientado al uso de Git (y este es nada más y nada menos que el sistema de control de versiones utilizado para desarrollar el kernel de Linux ;o). Entre las diferencias fundamentales entre ambos tenemos que Mercurial es más fácil de mantener, puede ser adaptado más fácilmente a entornos distribuidos, especialmente por estar hecho en Python (al igual que Bazaar) y posee un número de comandos más reducido y similar al de Subversion, por lo que la transición es menos traumática para los usuarios de este popular sistema. Sin embargo esto es solo una breve introducción. Hay comparaciones entre Mercurial y Bazaar y también otras entre Bazaar y Mercurial :P. Se pueden encontrar más detalles en el libro Distributed revision control with Mercurial escrito por Bryan O'Sullivan. Por cierto solo se utilizó software libre para confeccionarlo ;o).

Por su parte Bitbucket todavía está en su versión beta y están trabajando rápidamente para realizar majoras y eliminar defectos. Pero ya se han puesto en funcionamiento una buena cantidad de posibilidades maravillosas. Una de ellas es el soporte para la extensión Mercurial Queues.

Mercurial Queues

Mercurial Queues no es más que la evolución de quilt, un enfoque iniciado con Git. Muy brevemente MQ es una extensión que permite mantener una pila (i.e. LIFO) de parches que se aplican sobre un repositorio. Esto permite trabajar en unas cuantas mejoras que se construyen una sobre la otra en la forma de parches, en vez de mantener cambios más abultados y poco cohesionados. Por ejemplo si se trabaja en la interfaz de usuario y se descubre un error en capas inferiores de la aplicación entonces es posible congelar los cambios para la interfaz de usuarios, e ir liberando parches hasta llegar hasta el nivel dónde se encuentra el error. Luego se arregla el error y se vuelven a incluir en la pila los cambios que fueron extraídos anteriormente, para así proseguir con el desarrollo de la interfaz de usuarios. De este modo los parches pueden ser considerados como revisiones temporales o flotantes que no se reflejan en el repositorio hasta que no se confirma que todo marcha bien o que un experimento realmente dará resultados. Como ventaja resulta que la historia del repositorio se simplifica, y desaparecen versiones irrelevantes.

Similares beneficios se pueden obtener en proyectos en los que se mantienen parches sobre un código de base (por ejemplo, optimizaciones para dispositivos o arquitecturas específicas), al menos hasta que son revisados y aprobados por los mantenedores oficiales o hasta que se terminan. En estos casos esta extensión también permite mezclar los parches (rebasing) e incorporarlos en el repositorio.

Normalmente se trabaja localmente sobre un conjunto de parches, pero es posible establecer un control de versiones sobre los propios parches de forma tal que otros también puedan perfeccionar aspectos en pleno desarrollo.

¿Cómo funciona MQ con Bitbucket?

A continuación ilustro el camino más corto para configurar una pila de parches en Bitbucket:

  • Cree el repositorio principal. Este contiene los ficheros del proyecto.
  • Si ya se tiene parte del trabajo en un repositorio local, pues le hacemos push especificando la URL del proyecto (llamémosle project-repo).
  • Presione el botón patch queue en la página principal del repositorio.

Activando el repositorio de parches en Bitbucket

  • Teclee los datos que son solicitados y cree el nuevo repositorio (llamémosle por ejemplo project-repo-patches) para manejar versiones de los parches.
  • Asegúrese de marcar la opción Omit series si Usted desea añadir parches previos.
  • Active la extensión MQ.
  • Ejecute el comando hg qclone como se muestra a continuación (si el repositorio es de nuestra propiedad entonces owner=user)

$ hg qclone https://user@bitbucket.org/owner/project-repo-patches

Como resultado obtendrá una copia local del repositorio y la pila de parches preparada para incluir nuevas versiones. Eso es todo si no se van a incluir parches ya existentes. Luego se utiliza hg qnew para comenzar un nuevo parche y se continua con hg qref y hg qcommit para refrescarlo y para añadir una versión del parche. El repositorio de los parches se encuentra dentro de la carpeta `./.hg/patches. Para distribuir el nuevo parche a los demás se hace lo siguiente.

$ cd ./.hg/patches 
$ hg push 

Las nuevas versiones del parche deben aparecer en el repositorio de parches preparado por Bitbucket :o). La URL del repositorio remoto es configurada automáticamente.

Si se desea publicar varios parches que ya existen entonces solo hace falta copiarlos en ./.hg/patches junto con el fichero series, pero nunca copie el fichero status. Agréguelos al repositorio de parches. Todo sería más o menos así.

$ cp /path/to/old/repos/.hg/patches/*.diff ./.hg/patches 
$ cp /path/to/old/repos/.hg/patches/series ./.hg/patches 
$ cd ./.hg/patches 
$ hg add ./*.diff

¿A qué se deben las diferencias?

Lo primero que resulta diferente con respecto al comportamiento habitual es que hay que hacer hg qclone al repositorio de parches y como consecuencia se obtiene todo el repositorio del proyecto. Esto se debe a que Bitbucket brinda soporte para asociar varias pilas de parches con un único proyecto. Esto es algo muy útil. Es por esto que el servidor no almacena el repositorio de parches dentro del proyecto como ocurre normalmente. Sino, ¿cómo saber cuál pila de parches se desea clonar localmente?

Para descubrir los detalles de implementación observemos el fichero ./.hg/patches/.hg/hgrc. Allí encontraremos algo más o menos así

[paths]
default = https://bitbucket.org/owner/project-repo-patches/.hg/patches

Interesante ¿no es así? En vez de enviar los contenidos hacia el repositorio de parches (como se podría imaginar inicialmente ;o), estos son enviados hacia una pila dentro del repositorio de parches. ¿Una pila de parches dentro de una pila de parches? ¿Por qué? Si observamos el fichero ./.hg/hgrc veremos algo más o menos así.

[paths]
default = https://bitbucket.org/owner/project-repo-patches/

Por tanto los cambios deberían ser enviados a la raíz del repositorio de parches. Sin embargo si se modifican localmente los ficheros del proyecto y se publican los cambios, entonces estos se reflejan en el repositorio original (i.e. https://bitbucket.org/owner/project-repo). ¿Estarán redireccionando la raíz del repositorio de parches hacia el repositorio del proyecto? Todo parece indicar que sí.

¿Qué pasa con SSH?

Hasta ahora los ejemplos han utilizado el protocolo HTTP y esto es por una razón: la magia descrita anteriormente para clonar un repositorio de parches no está implementada para SSH. Este protocolo es útil puesto a que permite facilidades adicionales (e.g. compresión) que no permite el protocolo HTTP. Una posible solución sería :

$ hg clone ssh://hg@bitbucket.org/user/project-repo .
$ cd .hg
$ hg clone ssh://hg@bitbucket.org/user/project-repo-patches/.hg/patches .

Al menos de esta forma queda todo preparado de forma equivalente al caso de la versión HTTP, solo que se utiliza la URL del repositorio real para saltarse el paso de la redirección.

Conclusiones

Es mejor poner el parche antes que salga el hueco. Pero si hay demasiados o si queremos compartir nuestras mejoras con otras personas para colaborar y refinarlas o si se torna complicado tener todo bajo de control entonces Bitbucket está aquí para salvarnos.

:P

martes, 8 de diciembre de 2009

Depurando errores en aplicaciones web CGI con Python

Powered by Python

La tendencia de arrendar un servidor compartido para hospedar sitios web parece que va en ascenso. Las limitaciones (e.g. nada de acceso a los log de Apache, ni posibilidad de instalar módulos ni de modificar directamente el httpd.conf) resultan frecuentes en estos casos. ¿Cómo depurar entonces los errores que pueden aparecer al desplegar nuestra aplicación web? Les muestro cómo se haría con scripts de Python ejecutados como CGI. El ejemplo utilzado ilustra además la manera de convertir una aplicación WSGI en un script CGI. Este enfoque es tan sencillo que me he decidido a compartir mis descubrimientos con Ustedes. ¡Espero que les sea útil!

El problema

Sí señores, resulta que pueden ocurrir errores incluso aunque se haya ensayado todo localmente en un servidor de prueba. Tenga en cuenta que las medidas de control de acceso y protección tomadas por el proveedor más la configuración de los servicios en el ordenador remoto hacen que el contexto de ejecución pueda ser totalmente diferente.

En condiciones normales se estila revisar los logs del servidor web pues en ellos quedan registradas las fallas detectadas en tiempo de ejecución. De esta forma se pueden valorar los síntomas y (a veces con una cuota de suerte ;o) se logra tener una pista de las causas. Resulta ser que esto a veces no es posible en los contextos que mencionaba anteriormente. Por otra parte las posibles causas son muchísimas y pueden depender de las configuraciones de los diferentes servicios o de parches específicos aplicados a las aplicaciones subyacentes. Esto implica que a la hora de la verdad comenzamos con las ráfagas de parches a ciegas. ¡Por suerte tenemos a Python!

Una aplicación WSGI sencilla

Las razones anteriores pueden darnos un indicio de que la solución es tomar al toro por los cuernos y hacernos responsables de descubrir los defectos nosotros mismos (especialmente si estamos seguros de que el script se ejecuta pero no cómo esperábamos, e.g. al recibir un error HTTP 500 Internal Server Error en el navegador web). Pero ¿cómo hacer una aplicación sencilla?

La respuesta no es única. Por suerte hay muchos caminos para llegar a Roma ;o). La manera estándar para escribir aplicaciones web con Python es el modelo WSGI. ¡Echémosle un vistazo! Una aplicación WSGI muy sencilla luce más o menos así :

#! /usr/bin/env python

def application(environ, start_response):
    status = '200 OK'
    headers = [('Content-type', 'text/plain')]
    start_response(status, headers)
    return ['Hello world ! \n\n', 'The simplest web app ever seen']

Como se puede apreciar solo se necesita escribir una función. Se recomienda que su nombre sea application. Esto se debe a que es el nombre predeterminado que utiliza el módulo mod_wsgi (i.e. Apache) para identificar la aplicación web que se ejecutará al invocar un módulo. La función (o en general cualquier objeto invocable o callable en inglés) tiene que aceptar dos parámetros. El primero es un diccionario que contiene valores que describen el entorno de ejecución. Entre los más relevantes se encuentran el nombre del host, los parámetros de la URL solicitada, el método (e.g. POST, GET ...) y hay otros más.

El segundo argumento es una función que se encarga de iniciar el proceso de envío de la repuesta del servidor hacia el cliente. La razón para hacer esto es que cada servidor web realiza esta operación de una forma diferente, pero siempre es capaz de facilitar una implementación de dicha función para encapsular los detalles. Como se aprecia en el ejemplo, esta función acepta como parámetros el texto que describe el código HTTP y una lista de tuplas binarias. Los primeros están definidos en el estándar RFC 2616. Por otra parte cada tupla representa el nombre y el valor de un encabezamiento que será retornado hacia el cliente. El valor que retorna la función es utilizado para enviar una respuesta hacia el cliente. Todo el contenido no tiene que ser enviado de una sola vez. Si nos fijamos bien, lo que se devuelve es una secuencia con varios segmentos de la respuesta. Estas porciones de la respuesta se envían en el mismo orden y poco a poco hacia el cliente. Esto es importante desde el punto de vista de la eficiencia puesto a que, si se está generando el cuerpo de la respuesta, no hay que esperar a realizar todo el procesamiento para hacer llegar todos los datos al cliente. Es por esto que los servidores y frameworks hechos con Python tienen tiempos de respuesta bastante pequeños.

Para poner a funcionar esta aplicación con mod_wsgi solo hace falta la siguiente directiva en el fichero httpd.conf (o equivalente ;o)

WSGIScriptAlias /helloworld /path/to/script.py

Si se accede a http://example.com/helloworld entonces se obtendría el siguiente texto

Hello world ! 

The simplest web app ever seen

Reutilizando la aplicación WSGI en otros contextos

Sin embargo, una restricción frecuente es que no están instalados los módulos de Apache (que viene siendo el caso cada vez más frecuente :o) que son necesarios para ejecutar este tipo de aplicaciones y solo tenemos a mod_python. Otras muchas veces ni siquiera tenemos eso y solo queda la opción de CGI. En estos casos el estándar WSGI todavía resulta útil para hacer las cosas una sola vez y que todo funcione en varios contextos de ejecución. Solo hay que hacer las siguientes modificaciones

if __name__ == '__main__' :
  from wsgiref.handlers import CGIHandler
  CGIHandler().run(application)

Listo !

Depurando los scripts con el módulo cgitb

El módulo cgitb provee un tratamiento de errores muy simple y que funciona incluso bajo situaciones extremas. Lo único que hay que hacer es añadir las dos líneas siguientes

import cgitb
cgitb.enable()

Después de hacer esto, si ocurre un error en la aplicación entonces ya no obtenemos el lacónico mensaje 500 Internal Server Error, sino más bien un reporte HTML detallando el error (incluso más informativo que el que se mostraría en la consola ;o). La función cgitb.enable intercepta el mecanismo estándar de procesamiento de excepciones para hacer su trabajo. También acepta otros parámetros, por ejemplo :

Llamada Efecto
enable(display=0) No mostrar los tracebacks
enable(logdir="/path/to/dir") Los reportes se encriben en ficheros ubicados en /path/to/dir
enable(format="text") Imprime reportes en modo texto

Comprobando que el servidor ejecuta los scripts

Todo lo que hemos visto es estándar. Pero todavía hay más. A veces es necesario determinar si el error es del servidor o es provocado por la aplicación web. Con estos fines utilizo el script siguiente

#! /usr/bin/env python

from wsgiref.simple_server import demo_app as application

if __name__ == '__main__' :
  from wsgiref.handlers import CGIHandler
  CGIHandler().run(application)

La función wsgiref.simple_server.demo_app es una aplicación WSGI pequeña pero completa, que visualiza en el navegador una página en texto plano con el mensaje Hello world'' y los pares de valores que describen en el contexto de ejecución. Resulta muy útil para verificar que el servidor WSGI funciona correctamente.

Conclusiones

El modelo WSGI se ajusta muy bien a las necesidades de los servidores web y del protocolo HTTP, haciendo énfasis en la eficiencia. El mismo resulta ser muy completo y ofrece soporte para otras tecnologías web. Sin embargo no resuelve los problemas cuando se despliega una aplicación en un hosting compartido o en otros contextos con restricciones especiales. Es por esto que el módulo cgitb resulta tan importante cuando nos encontramos en un callejón sin salida al desarrollar nuestra aplicación web. Como por arte de magia podemos tener un análisis exhaustivo de las causas del error escribiendo solamente dos líneas de código adicionales. Es así de fácil. Espero que le sean útiles estos consejos. Les espero pronto aquí en el blog de Simelo.

viernes, 16 de octubre de 2009

Gasol-ina para España

Pau GasolImage by lubright via Flickr

Gasol, el grande, es un fenómeno que raya lo paranormal y es para preocupar. Tiene ventosas y sus oponentes saben que hipnotiza el balón y, como un imán, se encarga con eficiencia de interceptarlo antes de que llegue a las redes. ¿Así quién puede atreverse a encestar? Pero más vale que no lo hagas, porque sino, te hace un feo en tu propia cancha y te baja tus niveles de autoestima al dedicarte un sobervio alley-oop. ¡Se te caería la cara de vergüenza!

Por favor, escucha: ponerse violento no es la solución. ¿Te has puesto a pensar realmente de qué te sirve? De todas formas si no es él entonces todo el equipo se encargará de encestar desde más allá de la línea de tres puntos, o de efectuar los pases más asombrosos para luego anotar una de lujo. Lo más probable es que a medio camino ya estes lleno de faltas, y entonces es cuando descubrirás que has sido el mejor espectador que ha tenido el partido. Es por eso que España es el nuevo campeón europeo de basketball (por primera vez), mientras que a Serbia no le quedó otra que acostumbrarse a ser segundo. Para Pau Gasol, coronarse en la NBA fue solo una de las primeras partes.

jueves, 24 de septiembre de 2009

Sobrepasa las 300 descargas el módulo dutest

Descargar el módulo dutest

¿Alguna vez Usted se ha asombrado al ver lo rápido que crecen los niños? Seguro que sí, y para mí mis proyectos de software libre son como niños que han venido al mundo para llenar de felicidad a todos los usuarios. Por tanto aprovecho la ocasión para festejar las 300 descargas del módulo dutest. Sé que estoy en deuda con Ustedes, pues solo he podido redactar un artículo muy corto al respecto. En cuanto tenga un tiempo libre les hago una nota. A pesar de ser esta una entrada muy breve, para mí no implica que sea menos importante; solo significa que tengo muy poco tiempo :(.

Visite el sitio de administración de mis proyectos y el del proyecto FLiOOPS para conocer más acerca de mi trabajo. Espero que les guste, y estar pronto con Ustedes acá, en el blog de Simelo.

lunes, 14 de septiembre de 2009

Solo 12 minutos ... para volver a nacer

Chris Pond

Recientemente publiqué un artículo acerca de la posibilidad de hackear la terjeta de identidad británica en solo 12 minutos. Pero un comentario de un lector me llevó a profundizar más en este tema. ¿Cuáles son los antecedentes de la introducción de la tarjeta de identidad en territorio británico? Se sorprenderá al conocer los detalles. Recuerde que simelo pide Usted, también habrá un comentario para abordar sus inquietudes. Y si le gusta, pues Usted puede seguir este blog.

La situación actual

El tema de las tarjetas de identificación y su introducción en territorio británico ha sido muy controvertido. Por solo citar ejemplos, en una sesión del parlamento británico se debatieron ambos temas. El Señor Chris Pond (cuyo cargo no quiero traducir y es Secretary of State for Work and Pensions) respondió unas preguntas acerca de los usos que su departamentoi daría a las tarjetas de identidad, y alegó que (texto en inglés) :

In addition, use of an identity card will ensure that the process of establishing identity for benefit claims is both more secure and more convenient for the customer, and we also envisage that the national identity card and its supporting database will be a useful tool in countering identity fraud.

¿Una tarjeta de identidad que se puede clonar en solo 12 minutos puede realmente ser la solución al robo de identidad y ofrecer un proceso de verificación de identidad más seguro y conveniente para los ciudadanos ?

En otra sesión parlamentaria con fecha 9 de febrero del 2009 se puede constatar que se incrementa el presupuesto para combatir el terrorismo, mientras que se redujo el que se destina al combate de otros crímenes comunes. En el debate también surgió el tema de las tarjetas de identidad y la prueba a realizar en la región de Manchester con trabajadores del sector aeronaval. En este caso la pregunta fue :

Mr. Leech I thank the Minister for her reply. Does she accept that there is no evidence to suggest that identity cards will have any positive impact on dealing with crime on our streets, and that a much better way of using the money would be to put extra police officers on our streets to combat burglary, for instance, which is on a massive increase in south Manchester?

Un tema muy candente, ¿qué es más efectivo para combatir el crímen, las tarjetas de identidad o los policias?. La respuesta fue muy curiosa :

Meg Hillier (Parliamentary Under-Secretary of State for the Home Department): There are already more police on the streets than there have been for some time, but perhaps I can give the hon. Gentleman a little lesson in how the cost of identity cards will work. There is not a big pot of money sitting and waiting to be spent on identity cards; there will be money to spend on them only if the general population choose to take them up. It is clear from my conversations with the public and other stakeholders that there is demand for identity cards.

Es decir, según la opinión pública y otros interesados, en el fondo alguien realmente demanda el sistema de tarjetas de identidad. Para acabar de ponerle la tapa al pomo en la misma sesión surgió otra pregunta muy interesante :

Damian Green (Ashford) (Con): The Minister has just said that airport workers in Manchester will be one of the first groups to have compulsory ID cards. Labour Members may wish to know that those airport workers themselves proposed a motion that was passed overwhelmingly at the TUC conference last year, to oppose ID cards with all the means at their disposal. Does that not tell the Minister that when real people are told that they must have an ID card, they recognise the scheme as expensive, intrusive, pointless and a dangerous threat to our freedom? Why does she not save time and scrap it right now?

Tarjetas Briton

Es decir, los mismos trabajadores propusieron una moción para oponerse a este sistema con todos los medios a su alcance, y la misma obtuvo un amplio respaldo debido a que los ciudadanos reales lo consideran caro, intrusivo, sin sentido y peligroso para las libertades individuales. Lo más curioso es que la respuesta indica que se trabajaba fuertemente para asegurar que se brindan beneficios reales a los trabajadores de los aeropuertos, entre ellos la agilidad en los trámites, basada en la seguridad que ofrece un sistema ... que no es seguro.

La historia se repite

Pero la historia se repite. Segun un artículo publicado en politics.co.uk en el año 1952. el Reino Unido rechazó su primer experimento con tarjetas de identidad. En las circunstancias actuales los activistas defensores de los derechos cívicos se oponen a su aplicación, a pesar de que las fuentes presentan este intento como la fórmula mágica para atacarlo todo ...

Tarjeta de identidad -detalle-

En aquella ocasión todo comenzó en tiempos de la Guerra Mundial" las tarjetas Briton llevaban el nombre, sexo, edad, ocupación, residencia, estado conyugal y profesión del portador. Se cuenta que incluso bajo la amenaza nazi, el parlamento efectuó un amplio debate, durante el cual John Tinker MP expresó : no queremos ser detenidos en la calle por cualquier persona, en cualquier momento y ser forzados a mostrar una tarjeta. Si ese tipo de cosas comienza, viviremos con miedo a encontrarnos con las personas y que nos soliciten nuestras tarjetas. Si hay algo que valoramos en este país es nuestra libertad para no ser interceptados en las calles en cada momento para mostrar algo con el objetivo de probar que realmente somos personas.

Las tarjetas duraron hasta 1951, cuando parece que alguien decidió que ya era demasiado. Ese hombre fue Willcock v Muckle, un chofer que fue interceptado y requerido a mostrar su tarjeta. Petición que el conductor rechazó varias veces, por lo que su caso fue llevado a la corte. El caso fue llevado a apelación, y allí hubo intervenciones muy interesantes como la siguiente (Lord Chief Justice Goddard de la King's Bench Division) :

El hecho de que la policia tenga poderes, no significa que tengan que ponerlos en práctica en todo momento como un asunto de rutina. A partir de lo que he escuchado es obvio que la policia ahora, como una cuestión rutinaria, demanda la producción de una tarjeta de identidad nacional a mostrar cada vez que detengan o interroguen a un conductor por la causa que fuere. Este accionar fue aprobado por razones de seguridad, nunca para los propósitos para los cuales está siendo utilizado.

Tarjetas para los mayores de 16 años

Poco después el mismo orador se presentó dirigió al parlamento como estas palabras :

Es evidente que la policia ahora, como un procedimiento de rutina, solicita la tarjeta de identificación cada vez que detienen a un conductor por cualquier razón. Claro, si están buscando un auto robado o hay razones para creer que la persona está involucrada en un crímen, eso es una cosa, pero solicitarla por cualquier razón, por ejemplo, a una dama que pudo haber dejado su auto fuera de una tienda más tiempo que el que debía, o algún asunto menor de ese tipo, es completamente sin fundamento.

De todas formas, simelo preguntan en este caso, les digo que solo el tiempo dirá el desenlace definitivo.

Muchas gracias por su comentario, pues realmente había olvidado mencionar esta arista más social (menos tecnológica) del problema.

lunes, 7 de septiembre de 2009

¿Quiénes somos realmente?

http://img.neteco.com/photo/0096000001956984.jpg

Al parecer nadie escapa del flagelo del robo de identidad. Ni siquiera el presidente de la Reserva Federal : Ben Bernanke. Desde Inglaterra llegan también noticias acerca de delitos similares. ¿Cuáles son los límites de este fenómeno? Simelo pregunta les respondo con otra pregunta : ¿Qué es lo que resulta realmente seguro?

La nueva tarjeta de identidad británica : hackeada

A principios de agosto del 2009 sucedió algo que puede parecer insólito. Una edición limitada de la nueva tarjeta digital de identificación para el territorio británico ha sido pirateada en solo ... 12 minutos. El encargado de realizarlo fue Adam Laurie, anteriormente famoso por haber hackeado el pasaporte inglés en solo ... cuatro horas.

Tarjeta de identidad británica

Esta tarjeta es instituida por una ley llamada Identity Card Act, que abarca 50 categorias de datos personales, en especial : las características físicas, las huellas digitales, el statut de immigración, la dirección postal, el número de seguridad social y hasta la matrícula de un vehículo. Solamente quiénes residan más allá de los límites de la Unión Europea que desean vivir o trabajar en el Reino Unido serán obligados a adquirir esta tarjeta, bajo riesgo de multas por montos de hasta 1100 € en caso de no informar los cambios de dirección. Se estima que a finales del 2009 se hayan entregado alrededor de 75,000 tarjetas de este tipo. Todas contienen un chip RFID que facilita la obtención y consulta de estos datos en tiempo real utilizando canales inalámbricos.

¡Ah!, claro de más está decir que se afirma que la tarjeta es inviolable, y que la información solo puede ser extraída con dispositivos especiales. Es por esto que Adam Laurie y la revista Mail Online contactaron a un estudiante extranjero con el propósito de tener a mano uno de estos dispositivos. Solo se necesitaron 12 minutos para leer los datos almacenados en el dispositivo, y transferirlas hacia una tarjeta vírgen utilizada por una de las compañías londinenses de transporte público. Así de fácil resultó clonarla. ¡Claro!, olvidé mencionarles que para todo esto se utilizó nada más y nada menos que ... un teléfono celular Nokia con un software para comunicarse con dispositivos RFID. ¡Vaya dificultad! Según la revista Mail Online, también fue posible modificar las informaciones con el objetivo de asumir la identidad de otra persona. El señor Laurie explica que las víctimas se encuentran realmente en el centro del problema, pues será casi imposible probar su inocencia en el caso de pérdida de su tarjeta, y afirma que :

Si el gobierno desea realmente prevenir los robos de identidad, entonces se necesitará algo mejor que esto.

¿Cómo reaccionará el gobierno británico después de conocer esto?

¿El Banco Central de los Estados Unidos? ¿También?

http://img.neteco.com/photo/02386494.jpg

Al parecer nadie escapa del flagelo del robo de identidad. Ni siquiera el presidente de la Reserva Federal : Ben Bernanke. El jefe del banco central y su esposa, Anna, se encuentran entre las víctimas de una red acusada de haber robado durante el año 2008 una suma superior a $ 2 millones USD.

Según Newsweek, un desconocido intentó sustraer $900 USD de la cuenta común de la pareja. Poco después una investigación habría arrojado a varios sospechosos, que habían combinado métodos tradicionales y alta tecnología para retirar el efectivo de las cuentas bancarias de los afectados.

En un comunicado reciente Ben Bernanke indica que el robo de identidad es un delito grave que afecta a millones de ciudadanos cada año y luego agregó nuestra familia no ha sido más que una de las 500 (víctimas) atribuidas a esta red solamente.

Conclusiones

Después de escuchar la intervención de un colega argentino en Informática 2009 y conversar un poco con él, me llevé la idea de que la búsqueda de la seguridad es una carrera muy, muy larga. Así que yo pregunto : ¿Qué es lo que resulta realmente seguro? ¿Cuál es el compromiso de los estados nacionales con la seguridad de sus ciudadanos? Mi opinión, bueno simelo preguntan solo les invito a que próximamente lean un artículo que preparo acerca de la reacción de los ciudadanos ante los fenómenos y los nuevos desafíos que se derivan del uso de las nuevas tecnologías. ¡No se lo pierda!

viernes, 4 de septiembre de 2009

Introducción a la API de Google Analytics

Para ratificar el compromiso de Google con los desarrolladores, esta semana vió la luz una de las solicitudes más anheladas por la comunidad que gira alrededor de las APIs de Google. El gigante de Mountain View acerca la tecnología Google Analytics a los desarrolladores ofreciendo un protocolo REST similar al de sus parientes cercanos.

Google avanza comprometido con los desarrolladores

Hace un tiempo ya Google había demostrado su compromiso con los amantes de sus tecnologías y, atendiendo a las peticiones de los fieles desarrolladores, introdujo a Java como lenguaje en Google App Engine. Este paso también generó una especie de onda expansiva beneficiosa pues cualquier lenguaje que pueda ejecutarse sobre la máquina virtual de Java también podrá ser utilizado. Por tanto se habla de que ya hay aplicaciones que se encuentran en esa nube y que están implementadas en PHP, Ruby (¿recuerdan a Ruby on Rails?), Groovy y sería posible también que resurgieran otros como Scala, ¿quién sabe?.

Para ponerle la tapa al pomo de ahora en adelante la plataforma gratuita de análisis de visitas de los sitios web tiene también una API de programación que incluye primeramente a los lenguajes Java y JavaScript. Otras nueve librerías están a disposición para acceder a las estadísticasproveídas por este servicio. Entre los lenguajes se encuentra, especialmente Python (ya disponibles para su uso en Google App Engine), PHP y Ruby. Como el servicio esta en fase beta, se han establecido cuotas para su uso racional.

Esta decisión también influye en las sinergias que induce la trasnacional sobre la comunidad de sus usuarios, le añaden valor agregado al servicio, y contribuyen a su mejora continua aprovechando la imaginación de los programadores. Con el auge de los mashups y sumándo las posibilidades ya disponibles en Google Visualization API, Android, Google Maps API y otros, Google da pruebas de que la onda expansiva que acompaña a cada una de sus tecnologías es cada vez más fuerte. En este caso, ya desde el mismo lanzamiento existen ejemplos concretos que lo demuestran: la visualización de las estadísticas en dispositivos móviles, la inclusión de gráficos en presentaciones de Microsoft Power Point, la integración con aplicaciones para hojas de cálculo. ¡¡¡ Señores, lo de Google ya es demasiado !!!

Instalando el cliente de Google Data API

Para no dejarles con las ganas aquí les muestro un ejemplo básico para construir con Python una aplicación de consola que utilice esta nueva y útil interfaz de programación para obtener las estadísticas. Si no conoce este lenguaje le propongo que siga el curso de Python (una serie de artículos ;o) que vengo redactando desde hace un buen tiempo ya para la revista TuxInfo. Por brevedad voy a suponer que ya tienen instalado correctamente un intérprete de Python.

Antes de pensar en programar hay que instalar una versión de la librería posterior a la 1.3.2. Fue en este momento cuando la implementación de Sal Uryasev fue incluída en este paquete. Después de descargar una versión reciente hay que descompactar el fichero. Si se escoge el fichero .ZIP entonces puede ejecutar los siguientes comandos.

$ unzip -x  gdata-1.3.2.zip
Archive:  gdata-1.3.2.zip  
  inflating: gdata.py-1.3.2/README.txt
  inflating: gdata.py-1.3.2/RELEASE_NOTES.txt
  ...

$ ls 
gdata.py-1.3.2      gdata-1.3.2.zip

Si se escoge el fichero TAR.GZ entonces hay que ejecutar los siguientes comandos.

$ gzip -dc gdata-2.0.0.tar.gz > gdata-2.0.0.tar

$ tar -xf gdata-2.0.0.tar

$ ls
gdata-1.3.2                   gdata-1.3.2.tar
gdata-1.3.2.tar.gz

En cualquiera de los casos, el siguiente paso es ejecutar el script de instalación estándar. Para ello hay que colocarse en la carpeta donde se ha descompactado el código fuente y ejecutar los siguientes comandos :

$ cd gdata.py-1.3.2

$ ls
INSTALL.txt        README.txt         samples            tests
MANIFEST           RELEASE_NOTES.txt  setup.py
PKG-INFO           pydocs             src

$ python setup.py install
...

De esta forma se instala la librería de forma tal que todos los usuarios pueden utilizarla. Si Usted no desea que esto suceda, puede instalarla en su directorio personal de la siguiente manera.

$ cd gdata.py-1.3.2

$ ls
INSTALL.txt        README.txt         samples            tests
MANIFEST           RELEASE_NOTES.txt  setup.py
PKG-INFO           pydocs             src

$ python setup.py install  --home=/home/user_name
...

Después solo basta iniciar el intérprete de Python ...

$ python

... y comprobar que todo marcha bien.

>>> import gdata
>>>

¡Ya estamos listos!

Autentificación

Al igual que con todos los servicios de Google Data API, lo primero que es preciso hacer es autentificarse.

>>> from gdata.analytics.service import AnalyticsDataService as ADS
>>> s = ADS(email='murphy@gmail.com', password='can_hurt_your_eyes', \
...         source='aaa-aaa-v1')
...
>>> s.ProgrammaticLogin()
>>>

Para utilizar la API más cómodamente se utiliza la clase gdata.analytics.service.AnalyticsDataService. Sus instancias dan acceso al servicio Google Analytics. En el mismo paquete hay otras clases que se utilizan para acceder a otros servicios de Google. Sin embargo en todos los casos el procedimiento es el mismo, y se comienza por suministrar la cuenta del usuario, su password, y un parámetro llamado source, que se emplea para identificar a nuestra aplicación (pero que no es muy relevante para probar ;o). A continuación se llama al procedimiento de autentificación. Para todos los servicios hay tres modalidades disponibles: OAuth, AuthSub y ClientLogin. La última es la única que se puede utilizar para construir aplicaciones de escritorio, razón por la cual se invoca el método ProgrammaticLogin en el ejemplo.

Después de hacer todo esto el servicio es capaz de dar acceso solamente a la información sobre los sitios de nuestra propiedad.

Utilizando la API de Google Analytics

A continuación le muestro varios ejemplos que utilicé para probar. Para cada uno de ellos utilicé las estadísticas de mi sitio de administración de proyectos. Primeramente quería saber las principales fuentes de las visitas y los rechazos que acontecían.

>>> url = 'https://www.google.com/analytics/feeds/data/?ids=ga:12345678&dimensions=ga:source,ga:medium&metrics=ga:visits,ga:bounces&sort=-ga:visits&start-date=2009-06-01&end-date=2009-06-30&max-results=25'
>>> f = s.AnalyticsDataFeed(url)
>>> for e in f.entry:
...     for d in e.dimension:
...             print d.name, d.value,
...     for m in e.metric:
...             print m.name, m.value,
...     print
...
ga:source (direct) ga:medium (none) ga:visits 31 ga:bounces 12
ga:source trac-hacks.org ga:medium referral ga:visits 13 ga:bounces 5
ga:source pypi.python.org ga:medium referral ga:visits 12 ga:bounces 7
ga:source google ga:medium organic ga:visits 11 ga:bounces 10
ga:source groups.google.com ga:medium referral ga:visits 2 ga:bounces 2
ga:source trac.edgewall.org ga:medium referral ga:visits 2 ga:bounces 0
ga:source gossamer-threads.com ga:medium referral ga:visits 1 ga:bounces 0
ga:source groups.google.be ga:medium referral ga:visits 1 ga:bounces 1
ga:source mail.google.com ga:medium referral ga:visits 1 ga:bounces 0

Como se puede ver en el ejemplo lo primero que hay que hacer es construir la URL mediante la cual se recuperan los datos que deseamos. La base siempre es la misma https://www.google.com/analytics/feeds/data/. Para especificar qué datos deseamos obtener se utilizan diferentes parámetros, pero uno que no puede faltar nunca es el identificador de nuestra propiedad (i.e. el sitio para el cual queremos obtener las estadísticas). Para ello se utiliza el parámetro ids (en este caso ids=ga:12345678). Tenga en cuenta que siempre hay que utilizar el prefijo ga:.

Las estadísticas están organizadas en la forma de una tabla. Para cada dimensión se obtienen los valores de una o varias métricas. Por ejemplo, en este caso las dimensiones utilizadas son la fuente desde donde se accedió al sitio y el medio utilizado para llegar hasta las páginas. Para esto se necesita el parámetro dimensions=ga:source,ga:medium. Por otra parte las métricas solicitadas fueron los números de las visitas y de rechazos, por lo que se incluyeron el parámetro metrics=ga:visits,ga:bounces. Para presentar los valores de una forma más cómoda, he ordenado los resultados en orden descendente por la cantidad de visitas (i.e. sort=-ga:visits). Este parámetro realmente no es obligatorio. Otro parámetro opcional es max-results, el cual permite acotar el número de elementos a incluir en cada petición (e.g. max-results=25 se ha empleado para solicitar no más de 25 filas con datos). Por el contrario, es obligatorio especificar el intervalo de tiempo en el que se quieren acotar las mediciones. Para ello se emplean los parámetros start-date y end-date (e.g. en el ejemplo start-date=2009-06-01&end-date=2009-06-30 se utiliza para obtener las mediciones hechas durante el mes de junio del año 2009).

Después de haber conformado la URL, se pasa como parámetro al método AnalyticsDataFeed de la instancia del servicio mencionada en el epígrafe anterior. El valor retornado es un objeto que contiene los valores que estamos buscando. A través de la colección entry podemos acceder a las filas individuales. Cada fila es representada por un objeto que se coresponde con un valor diferente de las distintas dimensiones. Dichos valores pueden ser obtenidos recorriendo la coleccion dimension. Las métricas solicitadas se encuentran en la colección metric.

¡Eso es todo! Veamos otros casos ;o).

El último ejemplo ilustrará cómo obtener las páginas más visitadas del sitio.

>>> f = s.AnalyticsDataFeed('https://www.google.com/analytics/feeds/data/?ids=ga:12345678&dimensions=ga:pagePath&metrics=ga:pageviews,ga:uniquePageviews,ga:timeOnPage&sort=-ga:pageviews&start-date=2009-06-01&end-date=2009-06-30&max-results=25')
>>> for e in f.entry:
...     for d in e.dimension:
...             print d.name, d.value,
...     for m in e.metric:
...             print m.name, m.value,
...     print
...
ga:pagePath /traccgi/swlcu/wiki/En/Devel/TracGViz ga:pageviews 39 ga:uniquePageviews 32 ga:timeOnPage 5847.0
ga:pagePath /traccgi/swlcu ga:pageviews 17 ga:uniquePageviews 15 ga:timeOnPage 657.0
ga:pagePath /traccgi/swlcu/wiki/En/Devel/TracGViz/TracLinks ga:pageviews 13 ga:uniquePageviews 9 ga:timeOnPage 3929.0
ga:pagePath /external/opensvn.csie.org/traccgi/swlcu/timeline ga:pageviews 7 ga:uniquePageviews 6 ga:timeOnPage 91.0
ga:pagePath /traccgi/swlcu/roadmap ga:pageviews 7 ga:uniquePageviews 7 ga:timeOnPage 1118.0
ga:pagePath /traccgi/swlcu/wiki/TracPlugins ga:pageviews 7 ga:uniquePageviews 4 ga:timeOnPage 476.0
ga:pagePath /traccgi/swlcu/browser ga:pageviews 6 ga:uniquePageviews 4 ga:timeOnPage 120.0
ga:pagePath /traccgi/swlcu/wiki/En/Devel/TracGViz/TracLinks?action=edit ga:pageviews 6 ga:uniquePageviews 6 ga:timeOnPage 976.0
ga:pagePath /external/opensvn.csie.org/traccgi/swlcu/browser ga:pageviews 4 ga:uniquePageviews 4 ga:timeOnPage 98.0
ga:pagePath /traccgi/swlcu/timeline ga:pageviews 4 ga:uniquePageviews 4 ga:timeOnPage 358.0
ga:pagePath /traccgi/swlcu/wiki ga:pageviews 4 ga:uniquePageviews 3 ga:timeOnPage 180.0
ga:pagePath /traccgi/swlcu/wiki/En/Devel/TracGViz/EmbedGVizGadget ga:pageviews 4 ga:uniquePageviews 3 ga:timeOnPage 273.0
ga:pagePath /external/opensvn.csie.org/traccgi/swlcu/browser/trunk ga:pageviews 3 ga:uniquePageviews 3 ga:timeOnPage 102.0
ga:pagePath /traccgi/swlcu/browser/trunk ga:pageviews 3 ga:uniquePageviews 1 ga:timeOnPage 254.0
ga:pagePath /traccgi/swlcu/wiki/En/Devel/TracGViz/DataSources ga:pageviews 3 ga:uniquePageviews 2 ga:timeOnPage 24.0
ga:pagePath /traccgi/swlcu/wiki/En/Devel/TracGViz/TracLinks?version=5 ga:pageviews 3 ga:uniquePageviews 2 ga:timeOnPage 836.0
ga:pagePath /traccgi/swlcu/wiki/En/Devel/TracI18N ga:pageviews 3 ga:uniquePageviews 3 ga:timeOnPage 368.0
ga:pagePath /traccgi/swlcu/xmlrpc ga:pageviews 3 ga:uniquePageviews 3 ga:timeOnPage 0.0
ga:pagePath /external/opensvn.csie.org/traccgi/swlcu/roadmap ga:pageviews 2 ga:uniquePageviews 2 ga:timeOnPage 34.0
ga:pagePath /external/opensvn.csie.org/traccgi/swlcu/wiki/En/Devel/TracGViz/DataSources ga:pageviews 2 ga:uniquePageviews 2 ga:timeOnPage 34.0
ga:pagePath /traccgi/swlcu/attachment/wiki/En/Devel/TracGViz/ ga:pageviews 2 ga:uniquePageviews 1 ga:timeOnPage 58.0
ga:pagePath /traccgi/swlcu/attachment/wiki/En/Devel/TracGViz/EmbedGVizGadget/timeline.json ga:pageviews 2 ga:uniquePageviews 2 ga:timeOnPage 2697.0
ga:pagePath /traccgi/swlcu/attachment/wiki/En/Devel/TracGViz/TracGViz-1.2.1.tar.gz?action=delete ga:pageviews 2 ga:uniquePageviews 1 ga:timeOnPage 598.0
ga:pagePath /traccgi/swlcu/browser/trunk/trac-dev/i18n/README?rev=14 ga:pageviews 2 ga:uniquePageviews 2 ga:timeOnPage 17.0
ga:pagePath /traccgi/swlcu/browser/trunk/trac-dev/i18n/msgs/es/trac.po ga:pageviews 2 ga:uniquePageviews 2 ga:timeOnPage 8.0

Les explico los valores de los parámetros incluídos en la URL:

Parámetro Valor Descripción
ids ga:12345678 El identificador de la propiedad (sitio) web
dimensions ga:pagePath La ubicación de las páginas del sitio
metrics ga:pageviews
ga:uniquePageviews
ga:timeOnPage
Los datos solicitados son el número de consultas para cada página, el número de solicitudes individuales y el tiempo promedio que los usuarios permanecieron en esta página
sort -ga:pageviews Devuelve primero las páginas más vistas
start-date 2009-06-01 Descarta mediciones anteriores al 1 de julio del 2009
end-date 2009-06-30 Descarta mediciones posteriores al 30 de julio del 2009
max-results 25 Devolver no más de 25 resultados

Antes de finalizar quería aclarar que todas las métricas no son compatibles con todas las dimensiones, por lo que hay que tener cuidado al utilizarlas.

Conclusiones

Las estadísticas son muy importantes. La API de Google Analytics tiene una estructura relativamente sencilla (bueno ... después que llegas a conocerla un poquito ;o) y puede llegar a ser una herramienta muy útil. De hecho, me acabo de dar cuenta de que la página más vista del sitio es la del plugin TracGViz. ¿Nunca les he hablado de él? ¡Ostias! Por suerte tengo las estadísticas. Pronto les haré una nota para que lo conozcan. Pero bueno, simelo piden también abordaré cualquier tema que sea de su interés. ¡Hasta pronto! .