IdiginBPEL

abril 19, 2010

Trabajando efectivamente con svn y git

Filed under: git — Etiquetas: — Javier Santacruz López-Cepero @ 9:49 pm

Umm me he dado cuenta de que mi entrada anterior es bastante común. Hay muchos otros lugares en inglés y castellano donde puede verse como hacer un simple git svn clone y que explican muy por encima como funciona. De manera que voy a poner un ejemplo real de uso práctico.

¿Cómo estaba usando el repositorio?

Tal y como comenté al final de la entrada anterior, no podemos mandar desde git al svn commits resultado de un merge entre branches. De manera que para poder hacer merges y operar con git habitualmente, decidí tener un branche llamado ‘svn’ que mantendría un historial lineal, actualizado, pero libre de merges. Lo haría haciendo rebases a partir de un master actualizado.

Espera, no me entero de nada ¿Cuál era el problema con git svn dcommit?

Bueno… en git se puede trabajar en varios branches (ramas) diferentes, y luego unir el resultado, fusionarlos, merge. Esto lo que hace es seguir el historial de las dos ramas hasta que tienen un ancestro común, y las une en un commit nuevo, que es lo que llamamos un merge commit.

Pues bien, el problema viene en el momento en que svn maneja los conflictos y los merges de manera diferente, así que enviar un merge commit a un repositorio svn, es buscarse problemas. La solución alternativa a merge en git, es rebase. Rebase lo que hace es, fusionar dos branches, de manera que al final no es que se unan mediante un commit que resuelve las diferencias, sino que a cada commit anterior de uno de los branches, se le aplican todas las diferencias que tiene con la otra, reescribiendo su historia. Al final de un rebase, acabas con el branche desde la que lo hiciste con sus commits reescritos añadiendo lo que tenía el otro branche, es como si lo hubieses escrito así desde siempre.

Esto de andar reescribiendo la historia, es decir aplicando parches hacia atrás, es peligroso cuando se trabaja también con un repositorio remoto, ya que si se modifican commits que ya han sido enviados, y alguien cambia la historia, va a odiarte mucho porque tendrá problemas y muchos conflictos en commits antiguos. Por eso en definitiva me parece mejor ir haciendo merge en master y otras ramas, y separar los rebases en una rama aparte específica para svn.

Hala, seguro que si te manejabas con git, esto ya lo sabías, y probablemente mejor (yo aún soy novatillo).

Trabajando de esta guisa: 3 branches

En el repositorio estoy trabajando con 3 branches:

  • master: El normal. Solo lo actualizo con versiones estables.
  • work: Lo uso para ir trabajando y haciendo modificaciones.
  • svn: Se usará para sincronizar con el svn

El ‘workflow’ será trabajar en work hasta que tenga algo que funcione, hacer un merge con master para actualizarlo. Y una vez que tengo algo decente en master, hacer un rebase desde svn y git svn dcommit para sincronizarlo con el servidor svn.

Un ejemplo real

Partimos de un master, work y svn limpios, sincronizados, todos iguales en su último commit (svn tiene historial diferente).

En este contexto, entramos en nuestro branch de trabajo, work y añadimos una pequeña feature a idiginBPEL.

$ git checkout work
$ vim idgui/proyecto.py

Modificamos un par de funciones

$ git diff 
(.. 20 o 30 líneas de cambios ..)

$ git status
# On branch work
# Changed but not updated:
#   (use "git add ..." to update what will be committed)
#   (use "git checkout -- ..." to discard changes in working directory)
#
#	modified:   idgui/proyecto.py

Ahora hacemos commit del cambio

$ git commit -am "Añadido contador de casos seleccionados a la pantalla de casos"
[work f70e216] Añadido contador de casos seleccionados a la pantalla de casos
 1 files changed, 10 insertions(+), 5 deletions(-)

Y estamos 1 commit por delante de master. Vamos a sincronizarlos. Para ello nos llegamos a master, y pedimos mezclarlo con work

$ git checkout master
Switched to branch 'master'
$ git merge work
Updating 6832ef9..f70e216
Fast forward
 trunk/idgui/proyecto.py |   15 ++++++++++-----
 1 files changed, 10 insertions(+), 5 deletions(-)

Ha sido fácil, pues ha sido fast-forward, solo tenido que mover el HEAD. Ahora tendríamos código estable en master, vamos a hacer commit también desde svn. Para esto, necesitamos sincronizar también la rama svn, pero como no podemos hacer merge, haremos rebase:

$ git checkout svn
Switched to branch 'svn'
$ git rebase master
First, rewinding head to replay your work on top of it...
Fast-forwarded svn to master.

Bien, ya tenemos work, master y svn con HEADS iguales y svn con código actualizado. Vamos a mandar un buen commit al svn

git svn dcommit
Committing to https://forja.rediris.es/svn/cusl4-idigin ...
Authentication realm:  Subversion User Authentication
Password for 'frajasal': 
	M	trunk/idgui/proyecto.py
Committed r245
	M	trunk/idgui/proyecto.py
r245 = 1ded21df0c8c764605d9b505c9ca6738609aaf53 (git-svn)

¡Listo! Ya tenemos todo bien sincronizado

En teoría, si mantengo estrictamente el workflow y siempre trabajo en un branch aparte para después hacer merge con master e inmediatamente rebase con svn, no debería haber problemas de reescritura de commits anteriores en el svn, ya que master y svn irían avanzando al mismo tiempo.

Espero que le sirva a alguien

Actualización: Como me comenta Antonio García en el comentario al post anterior, existe git merge –squash que lo que hace es crear un commit enorme con los cambios necesarios para unir dos ramas, sin información de merge. Esto sería menos peligroso que ir haciendo svn rebase, ya que no hay posibilidad de reescribir commits ya enviados en la rama svn, pero significa abandonar definitivamente la posibilidad de mantener historiales similares en svn y git, ya que git merge –squash no replica de manera parecida los commits de otras ramas, sino que crea un solo commit gordo y hermoso, que habría que mandar con un buen mensaje al svn.

De svn a git

Filed under: Takuan — Etiquetas: — Javier Santacruz López-Cepero @ 7:05 pm

Hola de nuevo 😀

Desde hace un par de semanas vengo preparando cuidadosamente ciertos cambios de cierto calado en el proyecto. Es más una reorganización general de código que una reescritura de la aplicación, pero al final las cosas se harán muy diferentes.

Como la aplicación debe poder seguir estando disponible e incluso es muy probable que tenga que añadir alguna funcionalidad o corregir algún bug sobre la versión que tengo ahora mismo, el sistema de control de versiones que uso ahora, el svn de la forja de rediris se me queda algo corto, de manera que pensé seriamente en cambiarme a git.

Cuando supe de la existencia del comando git-svn me decidí del todo, y esta tarde mismo he realizado el cambio. El repositorio svn seguirá disponible, y de vez en cuando actualizaré el código alojado allí, pero operaré en git, que es más cómodo para crear una rama aparte y realizar cambios faraónicos pudiendo volver rápidamente a master para arreglar cualquier cosilla.

Por ahora he creado un repositorio en gitorious a la espera de acabar en algún sitio algo más serio.

Una pequeña receta sobre como lo he hecho:

¿Cómo pasar un repositorio bajo svn a git en dos cómodos pasos?

  1. Instala git-svn
    $ sudo apt-get install git-svn
  2. Clona el repositorio svn creando uno git
    $ git svn clone <url-repo-svn>

Esto nos habrá creado un repositorio git con todos los commits realizados en el repositorio svn. Si nos fijamos en el fichero de configuración en .git/config pone lo siguiente:

[svn-remote "svn"]
	url = <url-repo-svn>
	fetch = :refs/remotes/git-svn

¿Cómo trabajar con él?

  • svn update ahora es git svn rebase
    git svn rebase

    Si sabes algo de git, te verás extrañado de que no se haga merge. Esto es porque se sigue usando la lógica de svn. Cuando haces svn-update lo que se hace es aplicar parches a tu trabajo actual. Bien pues rebase hace algo parecido, aplica a todos tus commits locales, los cambios que ha habido en el servidor

  • svn commit ahora es git svn dcommit
    git svn dcommit

    Este tipo de commit tiene un inconveniente enorme, y es que NO se manejan commits de merge. Es decir, no puedes mandar al repositorio svn commits resultado de mezclar dos ramas, tienes que hacer rebase siempre.

Eso último de no poder hacer merges en el historial que se envie a svn le quita bastante gracia a trabajar con git, de manera que me parece que no lo usaré directamente.

Supongo que más de uno ya estará pensando que lo que puedo hacer es mantener un branche llamado svn y hacer rebase allí de todo lo que hago en las demas, manteniendo un master habitual y teniendo una rama especial para sincronizar. No parece ser lo más agradable del mundo, y a final acabaré con un historial en svn algo falseado con respecto al que vaya creando en git, pero creo que es lo que haré, y bueno menos da una piedra

Espero que le sea útil a alguien.

Un saludo.

abril 18, 2010

Videodemo

Filed under: Takuan — Javier Santacruz López-Cepero @ 3:18 am

Hola, he grabado una pequeña demo de la beta del programa en funcionamiento, para quién no tenga el entorno adecuado para instalar todo lo necesario o solo desee dar un rápido vistazo a la aplicación.

marzo 31, 2010

Instalación

Filed under: Uncategorized — Javier Santacruz López-Cepero @ 2:16 pm

Para facilitar la instalación de la aplicación así como de todo el entorno de IdiginBPEL y Takuan, he escrito un pequeño script que facilita el proceso (por otra parte sencillo) de descargar la aplicación, configurarla y establecer el entorno de Takuan.

El script está en el svn y puede obtenerse su última versión siempre desde la forja
Lo que hace es lo siguiente:

  • Checkout del repositorio
  • Creación del directorio de usuario y configuración
  • Descarga el script de instalación Takuan y lo ejecuta

También se ha actualizado la página del blog ‘sobre el proyecto’ en la que se indicaba como instalar la aplicación.

Para instalarlo entonces solo se necesita ejecutar lo siguiente

./install.py

El script también sirve para actualizar una versión existente y admite argumentos para instalar solo uno de los componentes.

./install.py [idg|takuan|both]

marzo 11, 2010

Doxygen + Python

Filed under: python — Etiquetas: , , , — Javier Santacruz López-Cepero @ 12:12 am

A la hora de elegir una herramienta para manejar la documentación, lo primero que hice fué revisitar viejos conocidos, en este caso Doxygen.
Doxygen como todos sabeis permite obtener documentación a partir del código. A través de comentarios que tienen una forma especial, se documenta directamente sobre el código que escribimos, y Doxygen saca toda la información posteriormente y la podemos obtener en diversos formatos, aunque por defecto genera html y pdf.

Tiene muchos detalles. Referencias cruzadas, índices, páginas aparte, código de ejemplo, diagramas con graphviz… Lo cierto es que sin apenas esfuerzo se obtiene una documentación muy amigable y si se programa teniendo en cuenta la documentación, el código es mucho más legible.

Doxygen por defecto no se lleva demasiado bien con Python. Primero, porque está programado para lenguajes que utilizan bloques cerrados entre llaves, y siendo Python un lenguaje sin ese azuquitar sintáctico, a veces se lía.
Otro gran defecto, es que los comentarios Doxygen se colocan justo encima de la declaración de la variable o función que se quiera documentar, mientras que Python provee su propio sistema de documentación integrado en el lenguaje mediante docstrings… que se ponen justo debajo de la declaración.

Llegados a ese punto, ¿no es un poco ridículo llevarle la contraria al lenguaje?. Si ya te provee un sistema para que pongas la documentación en docstrings accesibles en tiempo de ejecución e integradas, ¿debemos dejar de usarlas y pasarnos al bloque de comentarios sobre las funciones habitual de Java, Perl o C++?.
La respuesta es NO, y la ayuda necesaria la encontré en forma de un pequeño script llamado doxypy, que consiste en un filtro que se invoca desde Doxygen para que modifique el código python, extraiga comentarios y realice varios arreglos de manera que Doxygen lo trate de manera parecida a código Java.

¿Como usarlo?

  1. Bajamos Doxypy de su página
  2. Lo descomprimimos en el directorio donde tenemos el Doxyfile
  3. Modificamos la siguiente opción del Doxyfile que nos permite especificar el filtro:
    FILTER_SOURCE_FILES    = YES
    INPUT_FILTER           = "python doxypy/doxypy.py"

    Ojo que la ruta es relativa al fichero Doxyfile. Yo he renombrado el directorio doxypy-1.4 a doxypy a secas.

Se puede instalar en el sistema, pero yo prefiero tenerlo junto con el proyecto para poder ejecutarlo desde cualquier máquina cuando me lo baje del repositorio.

Adicionalmente podemos activar opciones específicas para Java, que se llevan bien con el código en Python, como son:

OPTIMIZE_OUTPUT_JAVA   = YES

Esto último lo que hace es cambiar ciertas cosas de su representación típica en C++ a Java, que es más similar a Python, como por ejemplo la jerarquía de clases que pasaría de Padre::Hijo a Padre.Hijo.
También se avisa que para las docstrings de los módulos, que no se pillan por defecto, hay que introducir un @namespace en ellas con el nombre del módulo, y esta declaración hará el truco.

Espero que os sirva 😀

febrero 11, 2010

Problemas de Namespaces (y van 2)

Filed under: Takuan — Etiquetas: , , — Javier Santacruz López-Cepero @ 9:35 pm

Hace no demasiado escribí un larguísimo post hablando sobre un montón de problemas que me habia encontrado al tratar los namespaces con las bibliotecas dom disponibles en python: ElementTree y Minidom.
Ahora traigo unos mucho menos graves y que pudieron ser solucionados en media horita sin grandes dramas.

1º Problema: Los namespaces no se declaran solos…

El problema esta vez era que en mi aplicación compongo un fichero bpts grande con muchos casos de uso tomados de muchos otros ficheros bpts. Esto implica ‘copiar y pegar’ trozos de un xml en otro xml.
Por ejemplo:


<?xml version=»1.0″ encoding=»UTF-8″?>
<!– Fichero base para agrupar todos los casos de prueba –>
<tes:testSuite
    xmlns:tes=»http://www.bpelunit.org/schema/testSuite»>
        <!–Nombredelproyecto–>
  <tes:name></tes:name>

 <!– Servidor y puerto (valores por defecto) –>
  <tes:baseURL>http://localhost:7777/ws</tes:baseURL&gt;

     <!–Ficherobpr–>
  <tes:deployment>
      <!–Nombre del proyecto –>
        <tes:putname=»bpel_original»type=»activebpel»>

       <!– wsdl test –>
      <tes:wsdl></tes:wsdl>

       <tes:propertyname=»BPRFile»>bpr_file.bpr</tes:property>
  </tes:put>

        <!–<tes:partnername=»assessor»wsdl=»AssessorService.wsdl»/>–>
  </tes:deployment>

  <tes:testCases>
  </tes:testCases>
</tes:testSuite>

 Este es el esqueleto de un fichero test.bpts general, donde se pegan trocitos como estos, que son los casos de prueba:


<tes:testCase name=»LargeAmount» basedOn=»» abstract=»false» vary=»false»>
      <tes:clientTrack>
        <tes:sendReceive
           service=»sp:LoanServiceService»
            port=»LoanServicePort»
   operation=»grantLoan»>

           <tes:sendfault=»false»>
    <tes:data>
              <esq:ApprovalRequest>
                     <esq:amount>150000</esq:amount>
              </esq:ApprovalRequest>
            </tes:data>
          </tes:send>

           <tes:receivefault=»false»>
            <tes:condition>
              <tes:expression>esq:ApprovalResponseesq:accept</tes:expression>
              <tes:value>’true'</tes:value>
            </tes:condition>
          </tes:receive>
        </tes:sendReceive>
      </tes:clientTrack>

      (…)

    </tes:testCase>

Si os fijais, algunos elementos del caso de prueba tienen un prefijo diferente… porque tienen un namespace diferente, si los copio y pego añadiendo el elemento directamente así:


testCases.appendChild( testCase.cloneNode(true) )

El resultado es lo siguiente:


<?xml version=»1.0″ encoding=»UTF-8″?>
<!– Fichero base para agrupar todos los casos de prueba –>
<tes:testSuite
     xmlns:tes=»http://www.bpelunit.org/schema/testSuite»>

    (…)

  <tes:testCases>
     <tes:testCasename=»LargeAmount»basedOn=»»abstract=»false»vary=»false»>
    <tes:clientTrack>
                <tes:sendReceive
                    service=»sp:LoanServiceService»
                    port=»LoanServicePort»
                    operation=»grantLoan»>

                     <tes:send fault=»false»>
                        <tes:data>
                         <esq:ApprovalRequest>
                                <esq:amount>150000</esq:amount>
                         </esq:ApprovalRequest>
                        </tes:data>
                     </tes:send>

                <tes:receivefault=»false»>
                    <tes:condition>
                     <tes:expression>esq:ApprovalResponse/esq:accept</tes:expression>
                      <tes:value>’true'</tes:value>
                    </tes:condition>
                </tes:receive>
            </tes:sendReceive>
            </tes:clientTrack>

     (…)

  </tes:testCases>
</tes:testSuite>

¿Notais que falte algo? Si no lo haceis vosotros el parser sí que lo hará. Falta la declaración del namespace con prefijo ‘esq’. Con el prefijo solo NO nos sirve de nada. Para que el documento esté bien formado necesitamos la uri declarada en un atributo xmlns:prefix=»uri», y minidom no es lo suficientemente inteligente como para mirar los atributos documentURI de los elementos a la hora de serializarlos y tomarse la molestia de declarar los namespaces no declarados. En general, minidom no es inteligente. Pero al menos (a diferencia de ElementTree, que se pasa de listo) de puro tonto nos sirve, ya que nos permite acceder a las uri y prefijos de los namespaces de un documento, y es un buenazo: no elimina los atributos de las declaraciones de namespaces cuando parsea un xml como si que hace ElementTree.

El arreglo más vago existente, es simplemente, preparar el testCase para que contenga las declaraciones de los namespaces ‘inline’ es decir, que cada elemento con namespace, tenga su declaración de namespace, para ahorrarnos disgustos más tarde.
Por ejemplo, el testCase anterior se convertiría en este otro:


<tes:testCase abstract=»false» basedOn=»»
              name=»LoanApprovalProcess.bpts:LargeAmount»
              vary=»false»xmlns:tes=»http://www.bpelunit.org/schema/testSuite»>

      <tes:clientTrackxmlns:tes=»http://www.bpelunit.org/schema/testSuite»>
        <tes:sendReceiveoperation=»grantLoan» port=»LoanServicePort» service=»sp:LoanServiceService» xmlns:tes=»http://www.bpelunit.org/schema/testSuite»>

          <tes:sendfault=»false» xmlns:tes=»http://www.bpelunit.org/schema/testSuite»>
            <tes:data xmlns:tes=»http://www.bpelunit.org/schema/testSuite»>
              <esq:ApprovalRequest xmlns:esq=»http://xml.netbeans.org/schema/Loans»>
                <esq:amount xmlns:esq=»http://xml.netbeans.org/schema/Loans»>150000</esq:amount>
              </esq:ApprovalRequest>
           </tes:data>
          </tes:send>

          <tes:receivefault=»false» xmlns:tes=»http://www.bpelunit.org/schema/testSuite»>
             <tes:condition xmlns:tes=»http://www.bpelunit.org/schema/testSuite»>
             <tes:expression xmlns:tes=»http://www.bpelunit.org/schema/testSuite»>esq:ApprovalResponse/esq:accept</tes:expression>
               <tes:value xmlns:tes=»http://www.bpelunit.org/schema/testSuite»>’true'</tes:value>
            </tes:condition>
          </tes:receive>
        </tes:sendReceive>
      </tes:clientTrack>
      (…)
</tes:testCase>

¿Como podríamos conseguir esto? Simplemente recorriendo el árbol o la rama del testCase que queremos incluir y añadiéndo las declaraciones ‘a pelo’:


def minidom_namespaces(elto):
    «»»@briefDeclara inline namespaces no declarados
       @paramelto Elemento padre»»»

      #Utilizamosunacola y procesamos los elementos en orden de documento
    eltos= [elto]
    while eltos :
        e= eltos.pop(0)
        #Les añadimos la declaración del namespace si tienen
         if e.namespaceURI:
            e.setAttribute(‘xmlns:’+ e.prefix, e.namespaceURI)
        #Metemos sus hijos en la cola
        eltos.extend(e.childNodes)

    returnelto

Esta pequeña función toma un elemento y lo recorre junto a sus hijos empleando una cola.
Para cada nodo, si tiene namespace, le añadimos un atributo con la declaración del mismo.
El código xml al final es BASTANTE feo, por ser demasiado prolijo y redundante, pero total, a quien le importa, es código temporal que utiliza el proyecto y que el usuario no va a ver.

2º Problema: Aún quedan namespaces sin declarar… pero están escondios

Pero el anterior no fué el único problema que me encontré.
Echadle un vistazo otra vez el caso y mirad otros namespaces que no son tes:


<tes:testCase name=»LargeAmount» basedOn=»» abstract=»false» vary=»false»>
       <tes:clientTrack>
        <tes:sendReceive
            service=»sp:LoanServiceService»
            port=»LoanServicePort»
            operation=»grantLoan»>

           <tes:sendfault=»false»>
            <tes:data>
              <esq:ApprovalRequest>
                <esq:amount>150000</esq:amount>
              </esq:ApprovalRequest>
            </tes:data>
          </tes:send>

           <tes:receivefault=»false»>
            <tes:condition>
              <tes:expression>esq:ApprovalResponse/esq:accept</tes:expression>
              <tes:value>’true'</tes:value>
            </tes:condition>
          </tes:receive>
        </tes:sendReceive>
      </tes:clientTrack>

      (…)

</tes:testCase>

¿Veis a lo que me refiero?
Hay un namespace DENTRO de un campo de contenido, de texto… ¡otra vez! suspiro.
Es el mismo problema que habia encontrado al tratar con los wsdl, pero ahora mucho más complicado, ya que los namespaces del caso de uso son completamente impredecibles, puede haber muchos y además… ¡puede hasta que se pisen unos a otros con los prefijos!

Aquí la solución ha pasado por una decisión podríamos llamarla ‘de diseño’.

«»»Todas las declaraciones de namespaces que se usen dentro de un caso de uso, pero que no tengan ningún elemento real con ese namespace dentro del caso, deben estar declarados en el elemento testSuite, que es el primero del bpts»»»»

Esto no es demasiado drástico, ya que al ser testSuite el primer elemento del documento, lo normal es que los namespaces estén declarados ahí, pero ya es poner ciertas limitaciones y dejar la puerta abierta a posibles errores cometidos por el usuario. Una tarea que tengo pendiente sería testar todo esto con los namespaces mal puestos y manejar los errores de manera que advierta de todo esto al usuario en la interfaz gráfica.

La solución la implementé de la siguiente manera:

# Añadimos al testSuite de test.bpts las declaraciones
# de espacios de nombres del .bpts nuevo
for prefix, uri in testSuite.attributes.items() :
       ifnotttestSuite.hasAttribute(prefix):
         ttestSuite.setAttribute(prefix,uri)

Este trozo de código se encuentra en la función idg.proyecto.add_bpts_info, que es la encargada de establecer la configuración del test.bpts general, y se llama cada vez que se añade un nuevo bpts al proyecto. Lo que hace es copiar las declaraciones de namespaces del nuevo, al test.bpts general.

minidom mantiene los atributos en un tipo propio de diccionario llamado NameNodeMap. Recorremos así los atributos que tiene el testSuite del bpts a importar y los ponemos en ttestSuite que es el elemento del test.bpts general.

3º Problema: Vamos a ver, Minidom, ¡ya te he dicho que lleva namespace!

Ooootro pequeño (diminuto pero puñetero) problema que me he encontrado relacionado con este tema es la creación de nuevos elementos en un documento empleando un namespace existente.
Uno esperaría que si tiene este sencillo ejemplo:


import xml.dom.minidom as md
foo = md.parseString(‘<foo:first xmlns:foo=»www.foo.com»/>’)
new = md.createElementNS(‘www.foo.com’, ‘new’)
foo.firstChild.appendChild(new)
print foo.toxml()

El resultado fuese este:


<foo:first xmlns:foo=»www.foo.com»>
    <foo:new/>
</foo:first>

Pero la triste realidad es que el código de arriba genera:


<foo:first xmlns:foo=»www.foo.com»>
    <new/>
</foo:first>

¿WTF? ¿No lo he creado expresamente con createElementNS? Una pequeña exploración en el código anterior te diría que sí, que el elemento new, creado con createElementNS, tiene el atributo new.namespaceURI… pero no se serializa el prefijo correspondiente… simplemente porque no se lo has puesto. Como ya he dicho antes, minidom es bastante poco inteligente a la hora de tratar con namespaces, su único mérito es dejarnos la información con libre acceso para que al menos podamos nosotros manejarla.
En este caso lo único que deberíamos hacer es ponerle el prefijo manualmente al elemento:


new = md.createElementNS(‘www.foo.com’, ‘foo:new’)

Esto es, a mi modo de ver, una chapuza. Porque imaginemos (como es el caso) que estoy editando un fichero que tiene ya puesto el namespace http://www.foo.com&#8230; ¡pero que yo no sé su prefijo! ¡El prefijo podría ser cualquier cadena aleatoria! En la situación en la que resolví el problema, es que sabia que iba a ser siempre cierta cadena, porque está hardcodeada en xpaths de otro componente de la aplicación, pero en otra situación el no conocerlo me obligaría a repasar el documento, o a buscar la declaración del namespace y sacar el prefijo, manualmente… dolorosamente.

Y bueno, así, lidiando con los namespaces, se me van las horas. Vamos, que estoy entretenido.

febrero 9, 2010

Logging

Filed under: Takuan — Etiquetas: — Javier Santacruz López-Cepero @ 12:36 am

Aunque la aplicación consiste en una interfaz gráfica, hay mucha información de errores, debug, o simplemente de notificación que debe logearse bien a la salida estandar, bien a un fichero. Sirviendo para detectar errores y analizar comportamientos inesperados del sistema. Bueno, que voy a ponerme a contar ahora que no sepan.

Hasta ahora este tipo de información la mostraba sencillamente haciendo prints
El método era un poco pobre ya que permitía muy poco control sobre que mensajes deben imprimirse o no, el formato es a la buena de dios, no queda claro en cada momento a qué parte del programa pertenece cada mensaje y si en algún momento decidía redirigirlo todo a un fichero las iba a pasar canutas.

Resumiendo, lo que buscaba era:

  • Mostrar/Ocultar la información de debug sin inflarme a borrar prints ni añadir lógica manualmente.
  • Mantener un formato unificado en toda la aplicación a la hora de imprimir mensajes.
  • Especificar sin procedimientos tediosos desde que parte de la aplicación se muestra cada mensaje.
  • Posibilidad de redirigirlo todo a un fichero sin grandes problemas.

Felizmente, Python incluye en su biblioteca estandar un pequeño módulo de logging, que incluye más funcionalidades de las que necesito incluyendo no ya sólo ficheros, sino logs por red (HTTP, SMTP…), rotación de logs y más cosas, ya saben, batteries included.

A pesar de lo completo que es el módulo, su funcionamiento básico no puede ser más obvio:

  1. Se inicializa un objeto log con el nombre del módulo.
  2. Imprimir con log.debug() , log.info(), log.warning()…

Un pequeño ejemplo:

import logging
log = logging.getLogger('modulo')

log.error("No se pudo leer el fichero de configuración %s " % self.config)
log.info("Proyectos disponibles: %s" % str(self.lista_proyectos))

Los niveles de logging son debug,info,warning,error y critical.
La diferencia entre ellos es el nivel de prioridad que tienen, siendo debug el más bajo y critical el más alto. Puede fácilmente seleccionarse el nivel, esto es, la prioridad mínima que deben tener los mensajes para que se impriman.
Mediante:

logging.setLevel( logging.DEBUG )

Por ejemplo, estableceríamos la mínima prioridad, de manera que se mostrarían todos los mensajes, desde los de debug a los crítical.
Sin embargo realizando

logging.setLevel( logging.WARNING )

Se ignorarían los mensajes realizados con log.debug y log.info que tienen menor nivel.

El formato también puede ser ajustado mediante una cadena de formato. En mi caso empleo la siguiente:

FORMAT = "%(levelname)s    \t%(name)s:  %(message)s"
logging.basicConfig(format=FORMAT)

La cadena imprime el nivel (DEBUG, INFO…), el nombre del módulo desde donde se llama y el mensaje en si.

La redirección a un fichero puede hacerse mediante un FileHandler añadiéndolo al log (esto no lo he probado aún).

h = FileHandler('fichero.log')
logging.getlogger('modulo').addHandler(h)

Todo esto, en lugar de realizarlo en cada módulo, se concentra en una única función en un módulo aparte de utilidades que importan todos los demás con una sencilla función llamada getlog que devuelve un log bien configurado y listo para usar:

def getlog(name, level = ""):
"""@brief Devuelve un objeto logger estandar bien configurado
@param name Nombre del módulo que hace logging.
@param level Nivel de logging."""

# DEBUG por defecto si no se especifica, en otro caso, INFO
LEVEL = logging.DEBUG if not level else logging.INFO

# Formato para el log
FORMAT = "%(levelname)s    \t%(name)s:  %(message)s"

logging.basicConfig(format=FORMAT, level=LEVEL)
return logging.getLogger(name)

Que se usaría así:

import util.logger
log = util.logger.getlog('modulo')
log.debug('esto huele a chamusquina...')

La salida que obtengo es de esta guisa:

WARNING    	idg.main:  Usando locale en directorio local. ./locale No instaladas las locales.
DEBUG    	idiginBPEL:  Ejecutable en /home/arl/Dropbox/soft/takuan/cusl4-idigin/trunk
DEBUG    	idiginBPEL:  Buscando config en: /home/arl/.idiginbpel/config.xml
INFO    	idg.main:  Usando fichero de configuración: /home/arl/.idiginbpel/config.xml
ERROR    	idg.main:  No se encuentra el directorio: /home/arl/dbx/takuan/idiginBPEL/trunk/share
WARNING    	idg.main:  Se usará el valor por defecto para: share
INFO    	idg.main:  Home: /home/arl/.idiginbpel
INFO    	idg.main:  Share: ./share
INFO    	idg.main:  Takuan: /home/arl/takuan
INFO    	idg.main:  Proyectos disponibles: [u'p1', u'p2']

Así se consigue de una manera bastante sencilla solucionar todos los puntos que buscaba.

  • Puedo ocultar la info de debug usando los niveles.
  • Pongo una sola vez de donde provienen los mensajes
  • Mantengo un formato unificado.
  • Puedo redirigirlo a un fichero añadiendo solo algo más de código.

Rico rico y sobre todo facilísimo.

enero 24, 2010

Estructura de directorios

Filed under: Takuan — Etiquetas: — Javier Santacruz López-Cepero @ 9:35 pm

Hubo un momento en el que caí en la cuenta de que la aplicación no tenía por que estar instalada de la misma manera en la que la estaba desarrollando. Es decir que los datos que usaba y generaba la aplicación no podían ser empleados a través de rutas puestas directamente en el código, ‘hardcodeadas’. Ya que si usaba rutas relativas, estas se romperían cuando se realizase una instalación de la aplicación.

Por ejemplo, si desde el código cargaba los ficheros de proyecto abriendo «Dos directorios hacia arriba la primera a la izquierda» después al instalar el programa, el directorio de trabajo de la aplicación dios sabe cual sería, y por ejemplo donde estaría el código python sería algo así como /usr/lib/idiginbpel, y no funcionaría.

La aplicación hasta ese momento de parón, se distribuía en varias carpetas, con el código, la interfaz de usuario, datos fijos que usaba y los que se generaban para el usuario, de esta manera:

idiginBPEL
   |-base         # Estructura básica a copiar al crear un proyecto nuevo
   |-idg          # Lógica de la aplicación (código)
   |-idgui        # Lógica de la interfaz de usuario (código)
   |-ui           # Ficheros glade con la interfaz de usuario
   |-data         # Proyectos creados por el usuario
   `-lang         # Internacionalización (traducciones)

Pero sin embargo, en una aplicación real, instalada en un sistema que emplean varios usuarios, los datos comunes (base, lang, ui) van en un lugar distinto de los que genera el usuario (que irán en su home) y del código de la aplicación (que al ser en python irá en /lib o similar)…
Vaya, un problema.

Para solucionar esto decidí separarlo todo y hacer que la aplicación localizase cada parte en tiempo de ejecución, leyendo las localizaciones a poder ser de un fichero de configuración.

Primero, separé la aplicación en tres partes:

  1. Datos de usuario (proyectos creados y configuración de la aplicación)
  2. Runtime (datos fijos que emplea la aplicación, los ficheros glade de interfaz, la internacionalización…)
  3. Código (ejem…)

Quedando distribuida de la siguiente manera:

# Datos comunes (solo lectura)
share
 |-ui
 |-lang
 |-skel      (Antes base)
 `config.xml (Fichero de config general por defecto)

# Datos del usuario
home
 |-proy
 `config.xml  (fichero de config general para el usuario)

 y el código que va a parte.

El problema ahora es. ¿Como se donde está cada cosa en el sistema?
Cuando el programa arranca, solo puedo saber donde está el ejecutable y cual es el directorio de trabajo. Solo puedo suponer donde están los proyectos del usuario, la instalacioń de takuan, los ficheros fijos que utiliza la aplicación…

Esto se solucionó con la creación de un archivo config.xml donde se indican las rutas de cada cosa.
Al arrancar se busca el config.xml en varios lugares preestablecidos, según un orden de preferencia y vualá. Del config.xml lee la ubicación de los directorios home, share y takuan y listo.

Un ejemplo del config:

$cat config.xml
<!-- Fichero de configuración por defecto

     Orden de búsqueda para la configuración:
     1. ~/.idiginbpel/config.xml
     2. ./home/config.xml
     3. /usr/share/idiginbpel/config.xml -->
<config>
    <!-- Directorio del usuario -->
    <home src="~/.idiginbpel"/>
    <!-- Directorio de instalación -->
    <share src="/usr/share/idiginbpel"/>
    <!-- Directorio de takuan -->
    <takuan src="~/takuan"/>
</config>

enero 7, 2010

ElementTree, Minidom y el olor del napalm por la mañana.

Filed under: Uncategorized — Javier Santacruz López-Cepero @ 9:11 pm

Este es un post sobre los dolores de cabeza y el amargo dolor del tiempo perdido.

Como comenté en el post anterior, se decidió tomar el bpel que nos daba un usuario e importarlo, buscando sus dependencias, dentro del proyecto. Para así tratar con el de la manera que más nos apeteciese.

Este proceso es el de búsqueda recursiva de dependencias (.bpel, .wsdl, .xsd) de un bpel dado y encuentra todos los imports que tenga, y todos los imports del fichero que importa…

El procedimiento para hacer esto sería el siguiente:

1. Abrir el fichero F con un parser xml
2. Buscar todos los import a ficheros externos (f1, f2, ..)
4. Copiar el fichero F al proyecto pero modificando la ruta que ponia en el import a la que tendrá dentro
del proyecto (típicamente ./dependencias/fichero)
5. Volver al paso 1 con todos los nuevos ficheros encontrados (f1, f2, ..) y buscar imports…

Una simplificación de un caso a tratar sería este bpel, que importa dos wsdl y estos un xsd:

Importaciones del bpel

Importación

El código para hacer el grafo dot seria algo así:

digraph F {
FicheroFuente_bpel -> ApprovalService_wsdl -> Loans_xsd
FicheroFuente_bpel -> AssessorService_wsdl -> Loans_xsd
}

Y aquí el código para que podais ver la relación.

$cat fichero_bpel.bpel
<process name=’”LoanApprovalProcess”’ xmlns=’”http://docs.oasis-open.org/wsbpel/2.0/process/executable”’ (muuuuchos namespaces)>
<import namespace=’”http://j2ee.netbeans.org/wsdl/ApprovalService”’ location=’”ApprovalService.wsdl”’ importtype=’”http://schemas.xmlsoap.org/wsdl/”/’>
<import namespace=’”http://j2ee.netbeans.org/wsdl/AssessorService”’ location=’”AssessorService.wsdl”’ importtype=’”http://schemas.xmlsoap.org/wsdl/”/’>
</process>

$cat ApprovalServide.wsdl

<definitions name=»ApprovalService» xmlns=»http://schemas.xmlsoap.org/wsdl/» (muuuuchos namespaces)>
<types>
<xsd:schema targetNamespace=»http://j2ee.netbeans.org/wsdl/ApprovalService»>
<xsd:import namespace=»http://xml.netbeans.org/schema/Loans»schemaLocation=»Loans.xsd»/>
</xsd:schema>
</types>
</definitions>

$cat AssessorService.wsdl
(…)

$cat Loans.xsd
(Loans no importa nada)

Bien, en realidad el problema no está en parsear todo esto y buscar las dependencias, que es realmente sencillo. Se toma el fichero, se parsea con un módulo de python para xml… ¿Pero cual?  Tras una primera documentación, ElementTree me pareció el más completo y sobre todo el más eficiente. Además es muy ‘pythónico’ siendo muy agradable de utilizar.

Básicamente mediante ElementTree podíamos tomar un fichero, cargarlo en memoria representado mediante una estructura en árbol, modificarlo tranquilamente y volverlo a escribir en xml (serializarlo que se dice).  Todo muy sencillo, y sin embargo… ¿Habeis visto la presencia de namespaces en el código anterior? Fijaos bien.

Los namespaces se utilizan para resolver entre otros, problemas de colisiones de nombres, son una medida que puede dar quebraderos de cabeza y más de una vez he leido a gente que los evita siempre que puede.  El por qué me dan miedo los namespaces lo explico a hora.

Todo esto viene debido a que el estandar XML dice que un namespace es básicamente una url que se asocia a cada elemento, pero como no podemos añadir un chorizo enorme delante de cada elemento en el xml por motivos de espacio y legibilidad, se usará un prefijo. Y ese prefijo, según el estandar, puede ser cualquier cadena aleatoria, no tiene significado ninguno.

Vamos a tomar un fragmento omitido del bpel anterior como referencia:

$cat fichero_bpel.bpel
<process xmlns=»http://docs.oasis-open.org/wsbpel/2.0/process/executable» xmlns:ns0=»http://xml.netbeans.org/schema/Loans» (…) >
<if name=»If1″>
<condition> ( number(string($processInput.input/ns0:amount)) &lt;= 10000 ) </condition>
<sequence name=»SmallAmount»>
<assign name=»copyLoanInfoToAssessorInput»>
<copy>
<from>$processInput.input/ns0:amount</from>
<to>$assessorInput.input/ns0:amount</to>
</copy>
(…)

Este sería el código original y como puede verse tenemos elementos ns0:amount… dentro del texto de un elemento.

A lo que me enfrenté fué que ElementTree, como buena seguidora del estandar, asigna prefijos inventados a los namespaces al serializar un documento, esto es, al escribirlo en xml genera los prefijos como le da la gana. Si por ejemplo parseamos el xml de arriba y lo serializamos acto seguido, lo que nos queda es lo siguiente:

$cat fichero_bpel_serializado.bpel
<ns0:process xmlns:ns0=»http://docs.oasis-open.org/wsbpel/2.0/process/executable» xmlns:ns1=»http://xml.netbeans.org/schema/Loans» (…)>
<ns0:if name=»If1″>
<ns0:condition> ( number(string($processInput.input/ns0:amount)) &lt;= 10000 ) </condition>
<ns0:sequence name=»SmallAmount»>
<ns0:assign name=»copyLoanInfoToAssessorInput»>
<ns0:copy>
<ns0:from>$processInput.input/ns0:amount</from>
<ns0:to>$assessorInput.input/ns0:amount</to>
</copy>
(…)

Como se puede ver, los prefijos de los namespaces los reinventa. Esto provoca que cualquier operación con el bpel posterior en la que intervengan xpath con prefijo ‘fijo’ provoque errores por todas partes y genere auténticos quebraderos de cabeza.

El problema viene cuando se aplica a un xml una consulta xpath las cuales se basan en el prefijo para manejar namespaces. Esto es importante, ya que, si siguiendo el estandar, ponemos prefijos aleatorios, al aplicar el instrumentador, las xpath con prefijos ya puestos… no van a funcionar. De ahí que mucha gente le coja cariño a sus namespaces y pretenda mantenerlos.

Pero sin embargo, hay otro problema más. ¡Mirad dentro del texto de los elementos!, hay variables que emplean el namespace y que están dentro del texto de los elementos. Esto lo que hace es mezclar la estructura del documento, con el contenido, xml no está pensado para esto y no se espera en ningún momento que aparezca un namespace en un campo de texto. Al serializarse y cambiar todos los namespaces de sitio, el ns0 pasa a ser ns1 pero dentro del texto sigue siendo ns0 y significa otra cosa, entonces obviamente ya nada de esto funciona.

En ese momento me embarqué en una búsqueda sobre como mantener los prefijos de los namespaces durante la serialización con ElementTree, y comencé a hacer pequeños intentos de chapuza, como a recorrer todo el documento cambiando tags, a acceder directamente a la implementación del módulo, a un diccionario que mantiene una tabla de los prefijos y los namespaces y modificarlos por los que yo quisiese…

Podía hacer esto último de manera medianamente satisfactoria, sin embargo no podia hacerlo dinámicamente, no podía abrir un documento con ElementTree y leer sus prefijos originales, ya que estos se perdían al cargarse en memoria, todo lo que podia hacer era modificar prefijos de namespaces conocidos, y por supuesto esto no me servía, porque a saber que namespaces metía el usuario en su bpel.

Cosas poco portables y que darían confictos entre versiones, que oscurecian el código y complicaban una operación sencilla.

La solución, después de dar más vueltas que una peonza, de ensuciarme las manos, de errores que desde mi punto de vista no tenian sentido (¡el xml estaba bien!) fué emplear el módulo minidom, más antiguo, feucho y menos eficiente que ElementTree, con un soporte de namespaces discutible, pero que sin embargo respeta los namespaces del documento original y permite al usuario especificar los suyos.

La diferencia radica en que ElementTree añade al tag de los elementos, la uri directamente, y se olvida de los prefijos (como debe ser según el estandar), y sin embargo, Minidom mantiene los prefijos.
Por ejemplo:

$cat test.xml:
<ns0:test xmlns:ns0=»http://wwww.pepe-perez.com/»/>

Se carga en memoria mediante ElementTree como:


>> from xml.etree import ElementTree as et
>> tree = et.ElementTree()
>> tree.parse(‘test.xml’)
<Element {http://www.pepe-perez.com/}test at 7ff6c2da7128>

Se carga en memoria mediante minidom como:


>> from xml.dom import minidom as md
>> tree = md.parse(‘test.xml’)
>> tree.firstChild
<DOM Element: ns0:test at 0x1f4e680>

Utilizo minidom entonces, únicamente para tratar con estos ficheros en los que los prefijos de los namespaces importan, para el resto empleo ElementTree, la cual es todo un placer usar.

Espero que quizás esto sirva a alguien, ya sea a modo de aviso o si lo encuentra por casualidad buscando ayuda relacionada, yo he aprendido por la via dura. Durante el proceso me hizo bastante gracia leer en las listas de correo de desarrolladores de Python al mantenedor de ElementTree negandose a implementar los prefijos personalizables para los espacios de nombres ya que su parser seguia íntegramente el estandar xml…

Aunque pueda parecer una solución muy obvia, en un inicio para mi no lo era tanto ya que minidom, debido a su consumo de memoria, inicialmente no era una opción y no me sería agradable emplearlo a lo largo de todo el proyecto.
Pero bueno este tipo de cosas son las que le hacen sentirse a uno santisfecho cuando se solucionan…  huele a victoria 😀

enero 2, 2010

El sistema de proyecto

Filed under: Takuan — Javier Santacruz López-Cepero @ 8:14 pm

Bien, una vez diseñada la intefaz, y aproximadamente el sistema de clases y estructura de la aplicación en sí, lo primero a implementar fué la creación del proyecto y la importación del bpel.

Aquí vino la primera ‘decisión’. ¿Debo quedarme el bpel ‘para mi’? ¿O simplemente tomar la ruta que me ofrezca el usuario al fichero fuente con el código y usarlo directamente? Ambas opciones tenian sus más y sus menos.

Por una parte, seguir el bpel del usuario allá donde lo tenga tenia las ventajas de:

  1. Estará en el entorno de ejecución original y no tengo que preocuparme por ficheros importados y dependencias. El usuario lo controla y se encargará de poner las dependencias en su sitio para ejecutarlo.
  2. Si el usuario modifica el bpel, modifica el marcado etc… puedo detectar la modificación y ‘avisar’ para volver a ejecutar las trazas, que se han quedado anticuadas y descartar los invariantes antiguos.

Sin embargo:

  1. Si el usuario movia el bpel de sitio o lo borraba, o este se encontraba en una unidad extraible, cambiaban los permisos… el ‘proyecto’ ya no funcionaria adecuadamente y es un engorro controlar que el usuario no se dispare en el pie.
  2. En caso de querer realizar el estudio del bpel en otra máquina, sería difícil realizar un ‘paquetito’ con el proyecto de manera que se pudiese ejecutar en cualquier otra máquina tal y como está. Esto es, exportar el proyecto.
  3. Si se necesita modificar ficheros (para modificar configuraciones de conexión al servidor, nombres etc…) estaría modificando los ficheros originales, quizás, causando un perjuicio al propietario.

Por otro lado, importando el bpel a mi propio directorio de la aplicación tenía las ventajas de:

  1. Todo está en un lugar conocido. Permisos adecuados, ruta cercana, dependencias localizadas…
  2. Puede exportarse tranquilamente. Una vez tengo el bpel y todas sus dependencias, el proyecto, en el estado en que esté, puede comprimirse en un paquete e importarse/exportarse sin problemas.
  3. Los ficheros son copias, de manera que puedo modificarlos a mi antojo con toda la tranquilidad que quiera.

Los contras son:

  1. No se puede actualizar el bpel. Una vez se realiza el proyecto, tenemos el código fuente ‘congelado’ tal y como está y sin posibilidad de cambio.
  2. Tenemos que importar también las dependencias. De manera que hay que bucear recursivamente en todos los ficheros fuente, buscando sentencias import, obteniendo las rutas y copiando los ficheros a la carpeta del proyecto. Eso sí, modificando la ruta del import para hacerla relativa a su nueva ubicación.

Después de darle algunas vueltas, decidí copiar los ficheros fuente junto a todas sus dependencias y montar el entorno de ejecución del bpel en la carpeta local de la aplicación. Me animaba la idea de que todo sería más robusto y estaría más controlado. La pega es que perdía la opción de controlar la modificación del bpel, pero siempre puedo añadir más adelante la opción de ‘monitorizar’ o ‘vigilar’ el fichero fuente para detectar cambios y realizar todo el proceso de importación otra vez en ese caso.

Bueno una vez resuelta mi duda, el sistema de proyectos se materializó en forma de una carpeta llamada ‘proy’ donde estarían todos los proyectos dentro, cada proyecto un directorio propio con su nombre ‘unix’.  La estructura es la siguiente:

proy
`– proyecto
|-dependencias            # Directorio con las dependencias
|-casos                           # Directorio con los casos de prueba
|-invariantes                 # Directorio con los invariantes
|-bpel_original.bpel     # Bpel fuente original
`-proyecto.xml              # Configuración del proyecto

En el proyecto estaría todo lo necesario para ejecutar el bpel, así como todas las trazas e invariantes generados por el mismo etc…  En proyecto.xml se guardaría la configuración del proyecto. Ruta al original, timestamps, casos de prueba disponibles, configuración de conexión al servidor…

Con esta estructura se podría crear un paquete comprimido con el proyecto y emplearlo en cualquier otra máquina de manera muy sencilla.

Older Posts »

Crea un blog o un sitio web gratuitos con WordPress.com.