blog del equipo de tecnología de 11870.com

¿Cuál es el valor de nada?

Me comentaba hoy peralta que había encontrado algo sorprendente sobre Python, observando una tabla que compara el comportamiento del valor nulo en distintos lenguajes de programación. El tema se resume en cinco segundos de consola Python:

>>> None > 10
False
>>> None < 10
True

Como yo tampoco sabía por qué salen estos resultados (y además me pico con facilidad con estos temas, todo hay que decirlo), me he puesto a indagar un poco. En la documentación oficial de Python sobre None hay más bien poco, así que he tenido que arremangarme y hurgar un poco por mi cuenta.

En Python, las comparaciones entre objetos se pueden sobrecargar implementando métodos con nombres especiales, como __lt__, __ge__ o __cmp__. No entraré en detalles aburridos sobre ellos, principalmente porque he visto que el objeto None no tiene ninguno de ellos implementado (amo la función dir(), no puedo vivir sin ella).

Sin documentación, y sin pistas en el propio objeto acerca de dónde o cómo se hace la comparación, sólo quedaba un sitio donde mirar: ¡el código fuente! Después de un rato de desorientación y tirando de grep he conseguido llegar al quid de la cuestión, en el fichero Objects/object.c.

static int
default_3way_compare(PyObject *v, PyObject *w)
{
    int c;
    const char *vname, *wname;

    if (v->ob_type == w->ob_type) {
        /* Edito este trozo que no nos interesa ahora */
    }
    /* None is smaller than anything */
    if (v == Py_None)
        return -1;
    if (w == Py_None)
        return 1;
   /* Lo que queda tampoco nos interesa */
}

¡Por fin! Parece ser que cuando el intérprete no encuentra sobrecargas en los métodos, acaba llamando a una función de comparación genérica que intenta hacer algo más o menos sensato en función de los tipos de los objetos a comparar. Es una función al estilo de strcmp(), que devuelve -1 si el primer elemento es menor, 1 si es mayor y 0 si son iguales. Y ahí, claro como el agua, encontramos la respuesta que estábamos buscando: «None is smaller than anything».

Tags: ,

11870.com API 101 (con Python): autenticación

Si bien tenemos la documentación de nuestra API bastante limpia y al día, algo de certeza hay en eso de que tiene una curva de aprendizaje un tanto pronunciada dependiendo de lo que queramos hacer. En este primer post vamos a dar una vuelta rápida por alguna de las funcionalidades que se ofrecen usando el lenguaje de programación Python (con las librerías httplib2 y feedparser), aunque en realidad lo que aquí se explica es fácilmente portable a otros lenguajes de programación.

Esta entrada forma parte de una serie (11870.com API uanou uan) que intenta dar una visión más práctica de nuestra API. La documentación relevante a seguir es API authentication.

Obteniendo una API key

Lo primero que querremos hacer será generar una nueva API key. Para ello nos vamos a 11870.com/usuario/apps/new con nuestro navegador y rellenamos el formulario (importante la sección de notas, que nos gusta saber para qué se nos utiliza). Como resultado obtendremos dos cosas:

  • una clave de aplicación (API key) o appToken
  • un secreto asociado a esa clave de aplicación
Obtención de la API key

Tenemos dos formas de autenticar nuestras peticiones dependiendo de si sólo vamos a usar sólo las funcionalidades de búsqueda o todas.

Las peticiones de búsqueda requieren que se añadan dos parámetros adicionales para ser autenticadas: appToken y authSign. Los valores de estos parámetros son estáticos (cosa que cambiará en una futura revisión de la API) y en el caso del appToken es la misma clave de aplicación obtenida anteriormente mientras que authSign es un hash que se calcula haciendo el md5 de la clave de aplicación seguido del secreto concatenado. Para calcularlo:

>>> from hashlib import md5
>>> md5(appToken + secret).hexdigest()
'f688ae26e9cfa3ba6235477831d5122e'
>>>

Si preferimos hacerlo en shell:

$ echo -n ${APPTOKEN}${SECRET}|md5sum
f688ae26e9cfa3ba6235477831d5122e  -
$

Con esto ya podemos lanzar la primera query de búsqueda:

>>> import httplib2
>>> h = httplib2.Http()
>>> request, content = h.request("http://11870.com/api/v1/search?q=mudanzas&appToken=" + appToken + "&authSign=" + authSign)

Con las consultas de búsqueda, es muy cómodo trabajar con feedparser, que trata nuestras respuestas perfectamente:

>>> import feedparser
>>> feed = feedparser.parse(content)

A partir de ahora, la navegación del objeto feed es coser y cantar. Por otro lado, si nos sigue gustando el shell, hay para todos los gustos:

$ curl "http://11870.com/api/v1/search?q=mudanzas&appToken=${APPTOKEN}&authSign=${AUTHSIGN}"

En cuyo caso obtendremos el XML de resultado a pelo.

Hasta aquí digamos que nuestra API no ha mostrado lo áspera que puede llegar a ser (eso sí, sólo las primeras veces ;). Como pronto vamos a empezar a realizar ya peticiones de manejo de agenda, necesitaremos autorizar a nuestra aplicación para que la gestione. Para hacerlo a mano tenemos que ir a la siguiente URL:

http://11870.com/manage-api/create-token?appToken=APPTOKEN&authSign=AUTHSIGN&privilege=WRITE&returnUrl=http://www.google.es”

estando ya autenticados en 11870.com. Veremos que se nos solicita conceder autorización a la aplicación para manejar nuestros sitios (y también contactos e información de perfil). Al hacerlo, nos iremos a una página de resultados de google y de ahí tendremos que coger el authToken.

atb

Esto último suena un poco raro, lo que ocurre por debajo es que 11870.com redirige a la URL que hayamos puesto en en el parámetro returnUrl añadiendo un parámetro adicional que es authToken con el valor que nos interesa. Este parámetro es parecido a una contraseña, ya que está vinculada a nuestro usuario y aplicación.

A partir de ahora podremos autenticar las peticiones que hagamos usando el esquema de autenticación WSSE. Como usuario utilizaremos el email del usuario y como contraseña el authToken que acabamos de obtener:

>>> h.add_credentials("luis@11870.com", authToken)
>>> request, content = h.request("http://11870.com/api/v1/search?q=bares")
>>>

Conclusiones

Tres cosas deberían quedar claras después de leer este artículo:

  • cómo generamos una nueva clave de aplicación y somos capaces de obtener el valor de authSign
  • que hay dos formas de realizar peticiones contra la API: o añadiendo parámetros a la query (si sólo usamos la búsqueda) o usando WSSE (si queremos poder gestionar datos del usuario)
  • cómo autorizar a nuestra aplicación para que acceda a la agenda de un usuario

En próximos capítulos de la serie (dejadme que lo vuelva a decir: 11870.com API uanou uan) veremos cómo gestionar la agenda de un usuario, sus contactos, sus datos de perfil y acceder a su actividad social.

Tags: , , , , ,

Importaciones masivas de datos

La reciente importación de los sitios de la web de Turismo Castilla-La Mancha parece que ha generado cierto interés por saber cómo lo hemos hecho. Como imaginaréis, nadie se ha sentado a meter uno por uno los sitios; no somos tan malas personas, y además sabemos XML ;). Lo que hicimos fue crear un XML Schema que nos sirviese de formato de datos intermedio, de manera que los datos a importar tenían una forma predecible y podían ser importados sin más en nuestra base de datos.

¿Sin más? Bueno, en realidad la complejidad no está en obtener los datos, sino en evitar introducir duplicados en nuestra base de datos, es decir, en no crear de nuevo un sitio que ya tenemos. Identificar un duplicado es ya de por sí bastante difícil cuando los datos no siguen un formato estricto, pero además nos encontramos con otro problema, al que hemos bautizado «los multiduplicados». Estos aparecen cuando varios sitios de la fuente externa son identificados como duplicados de un mismo sitio de 11870.com. Suele pasar con sitios que son, por ejemplo, hostal y restaurante, y que según qué criterio se tome se pueden considerar como dos sitios diferentes o como uno. Nosotros hemos intentado arreglar esto, buscando minimizar los errores, pero si veis algo raro raro, hacédnoslo saber :)

Total, que entre cargar el XML, detectar duplicados, arreglar multiduplicados y finalmente realizar la importación propiamente dicha, hay un montón de cosas que hay que hacer. Para poder explicar mejor todo este baile, nos vamos a ayudar de un dibujillo.

Cadena de procesos de la importación

En el dibujo se ven las diferentes fases del proceso, que os contamos a continuación.

Carga del XML

Posiblemente la más sencilla de todas. Se lee el XML que vamos a importar, se transforma a un formato de datos un poco más manejable desde dentro de las aplicaciones y se almacena en una base de datos intermedia. Y ya tenemos los datos listos para la siguiente fase.

Detección de duplicados

En esta fase intentamos, para cada registro que se va a importar, detectar si ya tenemos guardado en 11870.com un sitio equivalente. Identificar un duplicado es bastante más difícil de lo que parece: ¿son el mismo sitio el Restaurante Casa Paco y el Hostal Rte. Casa Paco? ¿«avenida de Chiquitistán nº 7» es la misma dirección que «Avda. de Chiquitistan num. 7»? A un ordenador le cuesta mucho responder este tipo de preguntas.

Para no enrollarnos mucho, sólo diremos que la búsqueda de duplicados se basa en un sistema de «votación» en el que un conjunto de funciones comparan los datos que tenemos de los registros que pensamos que pueden ser duplicados (tras una búsqueda inicial muy laxa de «registros sospechosos»). Por cada función de comparación que se aplica, se obtienen dos valores, que vienen a significar «cuánto creo que son duplicados» y «cuánto creo que no lo son». Aunque parezca mentira, no son valores complementarios, porque cierto tipo de comparación puede tener mucho peso para la detección (teléfonos iguales) y poco para el descarte (direcciones no exactamente iguales letra por letra, pero parecidas en algo).

Cuando se tienen los valores de la votación de esas funciones, se combinan con diferentes pesos, y finalmente se decide si el registro es duplicado o nuevo. En algunos casos esto no es posible determinar de manera automática, por lo que esos registros pasan a una validación manual.

Detección de multiduplicados

En esta fase se buscan los registros que se han asignado como duplicados del mismo sitio de 11870.com, y se intenta resolver el conflicto, generalmente eligiendo al que más se parece al sitio. En esta fase todavía estamos profundizando, porque nos gustaría tomar automáticamente decisiones más «inteligentes». Esperemos poder aplicar ideas interesantes en breve :)

Importación de los datos

Por último, se importan los datos propiamente dichos, creándose los sitios nuevos o fusionándose con los existentes según el caso. Además, se añaden a las secciones apropiadas del usuario, y se categorizan si se dispone de suficiente información.

Epílogo: hacer que parezca fácil

Y todo este lío es precisamente para que los contenidos importados encajen con el resto, que os sean útiles y que parezca que todo ha sido fácil. Creednos, no lo ha sido, pero merece la pena :)


cómo crear botones de formulario más atractivos

La investigación en la atención, persuasión, elección, felicidad, aprendizaje, y otros temas similares nos demuestran que un diseño más atractivo es también más usable para la mayoría de la gente, por lo que decidimos que deberíamos mejorar el aspecto de nuestros formularios.

Uno de los elementos de formulario que nos ha dado algún quebradero de cabeza ha sido el botón de submit. Si queríamos mejorar su aspecto visual añadiéndole un toque más dospuntocerista necesitábamos hacer alguna artimaña, por lo que pensamos que la mejor solución era sustituir el elemento input type=”submit” por un button y ya que su función dentro del formulario va a ser enviar los datos qué mejor que el type=”submit”, la razón queda muy clara en la especificación del W3C sobre el elemento button.

“Los botones creados con el elemento BUTTON funcionan exactamente como los botones creados con el elemento INPUT, pero ofrecen la posibilidad de un renderizado mejorado.”

Para empezar hicimos una sustitución directa:

<button type="submit" id="submit" name="submit">enviar</button>

y luego nos pusimos a añadirle degradado y bordes redondeados, para lo cual usamos la clásica técnica de tener dos imágenes: una más grande (en nuestro caso de 300px de ancho) en el lado izquierdo, y otra mucho más pequeña en el lado derecho, añadiendo el siguiente html:

<button type="submit" id="submit" name="submit">
<span class="overmarked">
<span>enviar</span>
</span>
</button>

y css:

button span.overmarked {display:block; background:url(/imgs/bck_submit_left.png) top left no-repeat}
button span.overmarked span {display:block; padding:5px 14px 6px; color:#FFF; font-size:16px; font-weight:bold; background:url(/imgs/bck_submit_right.png) top right no-repeat}

La razón para incluir bajo el elemento button dos span en lugar de los clásicos div’s es que los elementos de línea no deben contener elementos de bloque en su interior.

El primer problema a resolver sería eliminar el borde y el fondo del button que queda alrededor de nuestro maravilloso botón y recuperar el cursor con forma de mano que caracteriza a los elementos clickables, para lo que añadimos la siguiente regla:

button {border:none; padding:0; background:transparent; cursor:pointer; width:auto; overflow:visible}

Las propiedades width y overflow se han añadido para eliminar los espacios extra que añade IE6 y IE7 a los buttons. Está todo muy bien explicado en Button Width in IE.

Y para finalizar pasamos a definir las reglas CSS para soportar cada uno de los estados que debe tener un botón: hover, active y disabled.

button:hover span.overmarked {background:url(/imgs/bck_submit_hover_left.png) top left no-repeat}
button:hover span.overmarked span {background:url(/imgs/bck_submit_hover_right.png) top right no-repeat}
button:active span.overmarked {background:url(/imgs/bck_submit_active_left.png) top left no-repeat}
button:active span.overmarked span {background:url(/imgs/bck_submit_active_right.png) top right no-repeat} 
button.disabled span.overmarked {background:url(/imgs/bck_submit_disabled_left.png) top left no-repeat}
button.disabled span.overmarked span {background:url(/imgs/bck_submit_disabled_right.png) top right no-repeat}

La clase disabled se añade con javascript en el momento de realizar el submit del formulario y nos sirve además para evitar que se envíe el formulario más de una vez por error.

Finalmente, y para redondear la jugada eliminamos el outline extra al recibir el foco del button que añaden los navegadores basados en gecko:

button::-moz-focus-inner {border: 0}

El resultado debe ser algo así lo que se puede ver en nuestro formulario de pedidos.

actualización 18/05/09
Después de leer Do not remove the outline from links and form controls hemos decidido retractarnos de la eliminación del outline de nuestro botón para así permitir la navegación de la página a través del teclado y la página no deje de ser accesible.

referencias

Tags: , , , , ,

Buscamos programador java

El equipo técnico de 11870.com crece. Buscamos un programador con conocimientos avanzados de java. Necesitamos una persona con profundos conocimientos y experiencia en java en el lado servidor: Spring, Spring MVC, Hibernate, Jboss. Trabajamos sobre linux, tanto en nuestros servidores como en los desktops.

Buscamos a alguien con interés en la plataforma java en toda su extensión. De hecho valoraremos mucho el conocimiento de otros lenguajes sobre la plataforma java, como groovy o scala, y conocimientos en ruby y RoR.

Necesitamos una persona que no se case con una tecnología concreta, que sepa escoger la mejor herramienta para cada tarea y que sienta debilidad por el código bien escrito.

Si crees que encajas, envía tu CV a soyesapersona arroba 11870.com. En el subject pon java.

Tags: