[SOLVED] Añadir claves únicas en Google App Engine en 3 lineas

El problema:

Google App Engine mola, y la verdad es que programar en Python se me está empezando a hacer agradable, aunque me sigue gustando más la indentación con llaves…

En fin, la base de datos que usa Google es superpotente y supersencilla, si bien tiene algunas limitaciones con las que hay que aprender a vivir. Con el SDK que Google da, puedes indicar fácilmente qué atributos indexar (indexed=True), puedes forzar a que un atributo sea definido cuando vas a añadir un elemento a la base de datos (required=True), pero no puedes forzar a que un atributo o conjunto de atributos sean únicos (no hay un unique=True).

Por poner un ejemplo, es el típico campo de nick o e-mail en una base de datos de usuario SQL de toda la vida. No quieres que dos usuarios puedan tener el mismo e-mail o el mismo nick porque quieres identificar univocamente a un usuario por su nick o su e-mail. Es de cajón :p

¿Putada? El SDK de Google App Engine no te permite hacer esto… sin embargo la solución es trivial ;)

La solución:

Lo único que hay que hacer es redefinir la función put dentro de nuestro modelo y lanzar una excepción cuando se cree un elemento cuyas restricciones no se cumplan.

Por simplificar, creemos un modelo User con los atributos name, email, language y creation_date:

class User (db.Model): 
   creation_date = db.DateTimeProperty (auto_now_add=True)
   name          = db.StringProperty (required = True)
   email         = db.EmailProperty (required = True)
   language      = db.StringProperty (default="en")

Tal y como está definida la clase, si ejecutáramos el siguiente código, Google App Engine crearía dos registros distintos:

User (name="Mark Johnson", email="test@codigomanso.com").put()
User (name="JohnMarkinson", email="test@codigomanso.com", language="es").put()

No queremos dos usuarios compartiendo el mismo e-mail!! El segundo put debería fallar. ¿Cómo lo arreglamos? Tan simple como parece, redefinimos put para que cuando se vaya a crear un elemento con un atributo duplicado en la base de datos lance una excepción. Por lo tanto, la nueva clase una vez añadido el método put quedaría:

class User (db.Model): 
   creation_date = db.DateTimeProperty (auto_now_add=True)
   name          = db.StringProperty (required = True)
   email         = db.EmailProperty (required = True)
   language      = db.StringProperty (default="en")
 
   def put (self):
     # Make sure e-mails are unique for each user
     if (not self.is_saved()) and (User.gql ('WHERE email = :1', self.email).count() > 0):
       raise DuplicatedInstanceError ('User.email', self.email)
 
     # call the parent method
     db.Model.put (self)

Si te das cuenta, son sólo 3 lineas de código, 4 contando la linea del def… si es que más fácil no se puede.

Problemas varios

Después de meditarlo un poco, esta implementación no es perfecta. Yo le veo principalmente dos inconvenientes:

  • Hay que realizar un query extra cuando se pretende insertar un elemento (si actualizas un elemento la función se comporta como siempre).
  • Aún con esta solución puede ocurrir que acabes con dos usuarios con el mismo nick en la base de datos. ¿Cómo puede ser? La operación count-put no es atómica, por lo tanto, pueden llegar dos usuarios al mismo tiempo, realizar un count, la función devolver cero en ambos casos, y luego se insertan los dos.

El primer inconveniente, no lo es tanto, pero es una cosa a tener en cuenta.

El segundo inconveniente puede ser una putada. Para solucionarlo se me ocurre que se podría tratar de ejecutar el count-put dentro de una transacción, o bien crear un mutex (se puede?) y que el count-put se realicen dentro del mútex. En fin, a mi ese caso de momento no me preocupa, pero está bien saber que existe.

Apéndice: DuplicatedInstanceError

Si eres avispado te habrás dado cuenta que he lanzado una excepción de tipo DuplicatedInstanceError. Esta clase no existe. La he definido yo para poder detectar cuando ocurre un caso de este tipo.

class DuplicatedInstanceError (Exception):
  def __init__(self, attrpath, value = None):
    '''
    Accept a dict of attribute and value pairs, or just one attribute and a value:
 
    Example:
       # user unique constraint has been violated (User.nick to be unique)
       DuplicatedInstanceError ('User.nick', 'jsmith')
 
       # student unique constraint has been violated (student nick's are fin for different schools)
       DuplicatedInstanceError ({
         'Student.nick'   : 'jsmith',
         'Student.school' : 'demo-primary-school'
       })
    '''
    self.attrpath = ''
 
    # accept a dict as the only parameter
    if isinstance (attrpath, dict):
      for (k, v) in attrpath.items():
        self.attrpath += ', ' + str (k) + ' = ' + str(v)
      self.attrpath = self.attrpath[2:]
    # accept a couple of strings
    else:
      self.attrpath = str(attrpath) + ' = ' + str(value) 
 
    self.attrpath = '(' + self.attrpath + ')'
    return
 
  def __str__ (self):
    return str (self.attrpath)

Ahora a traducir el texto a inglés… Ale, otro día más!

, , , ,

[SOLVED] El teclado numérico no funciona en Ubuntu

Es la segunda vez que me toca corregir el problema de que el teclado numérico no funcione en Ubuntu (y por lo que he visto en foros, también pasa en otras distribuciones de linux).

Solución en 4 simples pasos:
– Abrir Sistema -> Preferencias -> Teclado
– Click en la Pestaña de Teclas del Ratón
– Deseleccionar "Permitir controlar el puntero usando el teclado numérico"
– Y finalmente, hacer click en Cerrar

, ,

Truco Manso: eliminar tags HTML en Python

La idea es eliminar todos los tags HTML de una determinada cadena.

El siguiente no es el mejor código del mundo, pero hace el papel.

def stripHTMLTags (html):
  """
    Strip HTML tags from any string and transfrom special entities
  """
  import re
  text = html
 
  # apply rules in given order!
  rules = [
    { r'>\s+' : u'>'},                  # remove spaces after a tag opens or closes
    { r'\s+' : u' '},                   # replace consecutive spaces
    { r'\s*<br\s*/?>\s*' : u'\n'},      # newline after a <br>
    { r'</(div)\s*>\s*' : u'\n'},       # newline after </p> and </div> and <h1/>...
    { r'</(p|h\d)\s*>\s*' : u'\n\n'},   # newline after </p> and </div> and <h1/>...
    { r'<head>.*<\s*(/head|body)[^>]*>' : u'' },     # remove <head> to </head>
    { r'<a\s+href="([^"]+)"[^>]*>.*</a>' : r'\1' },  # show links instead of texts
    { r'[ \t]*<[^<]*?/?>' : u'' },            # remove remaining tags
    { r'^\s+' : u'' }                   # remove spaces at the beginning
  ]
 
  for rule in rules:
    for (k,v) in rule.items():
      regex = re.compile (k)
      text  = regex.sub (v, text)
 
  # replace special strings
  special = {
    '&nbsp;' : ' ', '&amp;' : '&', '&quot;' : '"',
    '&lt;'   : '<', '&gt;'  : '>'
  }
 
  for (k,v) in special.items():
    text = text.replace (k, v)
 
  return text

Ejemplo de texto en HTML con algo de javascript, y varios tipos de tags:

print stripHTMLTags ('''
<html>
  <head>
  <title> Whatever </title>
  <script language="javascript">
  var j = 0;
  function asdf() {
    if (j < 0)
      alert ("Whatever");
  }
  </script>
  </head>
  <body style="background: red;">
    <div class="container">
      <h1>This is the title</h1>
      <p>
        This is the first paragraph with little text.
        <br/>
        This is the second line of the first paragraph (note the BR tag).
      </p>
      <p>This is the second paragraph with some extra text.</p>     
    </div>
    <div>
      <p>
        <span>Follow this link:</span>
        &nbsp;&nbsp;<a href="http://www.codigomanso.com/">Codigo Manso</a>
      </p>
 
      <p>Do you like it?</p>
    </div>
  </body>
</html>''')

Salida de la función stripHTMLTags:

This is the title
 
This is the first paragraph with little text.
This is the second line of the first paragraph (note the BR tag). 
 
This is the second paragraph with some extra text.
 
Follow this link:  http://www.codigomanso.com/
 
Do you like it?

Pues eso, la función es un tanto churrasco, pero es muy sencilla y hace el papel (al menos para mis propositos). El que quiera algo mejor, que se busque un buen parser de HTML (BeautifulSoup es un gran parser HTML para Python).

, , , ,

Truco Manso: formatear con ceros al principio de un número en javascript

Hasta ayer echaba en falta un método sencillo y legible para rellenar con ceros cualquier número o cadena en javascript.

Un ejemplo típico es cuando quieres mostrar la hora y quieres que el formato sea hh:mm. No hay ningún problema cuando son las 12:40, pero si es la una y cinco, se muestra 1:5 en vez de 01:05. Lo mismo pasa con las fechas y en innumerables situaciones.

Ayer me he encontré una solución que me parece la mejor que he visto hasta la fecha.

Normalmente, tratándose de un cero, uno haría algo como:

h = (h < 10) ? ("0" + h) : h;

Esto no es feo del todo, pero tampoco es elegante. Y que pasa si queremos 3 ceros? Podríamos hacer algo como:

h = (h < 100) ? ( (h >= 10) ? ("0" + h) : ("00" + h) ) : h;

Este código en un if/else seguro que queda más claro, pero aún así esto es una guarrada. Lo normal sería hacerse una función que rellene con ceros (o con cualquier otro caracter), y usar esa función.

Una vez vista la solución más típica y feucha, es el momento de ver la solución mágica:

("0" + h).slice (-2);  // devolverá "01" si h=1; "12" si h=12
("00" + h).slice (-3); // "001" si h=1; "012" si h=12;"123" si h = 123

Lo que hace el código de arriba es concatenar “00″ con el número (por ejemplo “00″ + 12 = “0012″) y luego cogemos los 3 últimos caracteres empezando desde el final (de ahí el -3). Y los 3 últimos caracteres son: “012″

No se a vosotros, pero yo cuando vi esta solución se me iluminó el cielo. ¡¡Que fácil!!

Por cierto, también se puede usar para rellenar con espacios o con lo que se quiera.

Fuentes:

Update: Milan Adamovsky ha creado una página web en http://jsperf.com/zero-padding donde se puede ver el rendimiento de distintas funciones para hacer padding

, , ,

Obtener el user-agent en Google App Engine con Python

Básicamente el User-Agent nos indica cual es el cliente que está realizando la petición. Si se trata de un navegador, de un robot, …  En principio esta cadena se envía en todas las cabeceras HTTP, sea el cliente que sea (aunque también podría omitirse).

Para obtener el “user-agent” en Google App Engine símplemente, desde cualquier método “get” o “post” que derive de webapp.RequestHandler podemos hacer:

self.request.headers['User-Agent']

Otra cosa bastante útil es averiguar la IP del cliente que nos está realizando la petición. Esto es más fácil aún:

self.request.remote_addr

La IP se suele utilizar para localizar geográficamente a la persona que está solicitando la página web.

Links relacionados:

, ,

Longitud máxima de una URL (o GET) en Google App Engine

Quería realizar experimentos acerca de la longitud máxima con la que se puede hacer una petición con GET en los distintos navegadores, y al final el más restrictivo ha resultado ser el servidor.

Según las pruebas que he estado realizando, resulta que Google App Engine sólo hacepta URLs de hasta 2048 caracteres.

Por lo tanto, si vas a realizar una petición GET a tu aplicación en Google App Engine, asegurate de que tanto la dirección de la página, junto con los datos GET no supere los 2048 bytes.

, , ,

GeoIP en Google App Engine

Me resulta sorprendente que Google no ofrezca ningún servicio o método en Google App Engine para obtener la localización geográfica a partir de la IP. Es algo que ellos tienen más que resuelto, y no veo motivo por el cual no lo ofrezcan a los desarrolladores en la parte del servidor  (en la parte del cliente se puede usar google.loader.ClientLocation en javascript)

Por suerte, este es un problema que se puede resolver fácilmente de dos maneras distintas.

Solución 1: haciendo peticiones a otro servidor

Hacer peticiones HTTP a http://geoip.wtanaka.com/ y éste nos devolverá el código de país:

Por ejemplo para saber el país de la dirección 72.14.235.121 sólo habría que hacer una petición a http://geoip.wtanaka.com/cc/72.14.235.121

Para código sobre cómo hacer este tipo de peticiones, podeis echarle un vistazo a http://code.google.com/p/geo-ip-location/wiki/GoogleAppEngine

Solución 2: implementarlo en el propio servidor

A mi esta otra solución me gusta más. Se trata símplemente de bajarse la última versión de GeoIP.dat y usarla con la librería pygeoip.py

Luego es tan fácil como hacer:

def getCountryByIP (remote_addr):
  GEOIP = pygeoip.Database('GeoIP.dat')
  info = GEOIP.lookup(remote_addr)
  return info.country

Nótese que la librería no está pensada para hacer esto así, ya que cada vez que se llama a pygeoip.Database se carga el archivo entero a una cadena en memoria, y, en principio, a nosotros nos interesaría cargarlo sólo una única vez.

Por otra parte, en mi caso sólo me interesaba hacer un único lookup, así que cargar el archivo a memoria no tenía mucho sentido.

He actualizado la librería para permitir dos modos. El modo cargarlo a memoria (mejor si vas a hacer cientos de lookups), y el modo acceder directamente al archivo en disco (mejor si vas a hacer pocos lookups, ya que consume menos memoria y tarda igual o incluso menos).

Aquí os dejo el archivo pygeoip.py con mis cambios. Para usarlo he añadido la función disk_lookup:

pygeoip.disk_lookup (remote_addr)

Para hacer un único lookup va más rápido y consume infinitamente menos memoria.

Desde aquí quiero felicitar a David Wilson, el autor de esta librería, porque ha sido muy fácil implementar estos cambios (por supuesto le he mandado un e-mail, ahora es cosa suya incluir mis cambios o no hacerlo).

, ,

Una de python: force_unicode

Las codificaciones de caracteres (character encoding) son la mayor patraña jamás inventada, gracias a dios se inventó Unicode, con sus variantes UTF-8 y UTF-32.

Para mi, UTF-8 es, y debería ser el estándar  para guardar o enviar cadenas de texto.

Los lenguajes de programación, python incluido, no deberían soportar otra cosa que no fuera UTF-8 o cadenas de bytes.  La verdad es que lo voy a dejar aquí, porque cada vez que pienso en este tema, me enciendo.

El caso es que el soporte de codificaciones de texto en Python me parece lo peor. ¿Por qué? Porque no es suficientemente inteligente para mezclar cosas, y lanza excepciones a la mínima.

Hasta hoy, usaba str.decode(‘utf-8′, ‘ignore’) donde str es la cadena que quiero convertir a UTF-8, pero ni siquiera esto me libraba de excepciones.

Hoy, buscando otra vez una solución, he encontrado la función force_unicode del frámework django. De momento, con las pruebas que he hecho, se lo come todo. Es perfecto.

Símplemente quería compartir esto:

def force_unicode(s, encoding='utf-8', errors='ignore'):
    """
    Returns a unicode object representing 's'. Treats bytestrings using the
    'encoding' codec.
    """
    import codecs
    if s is None:
      return ''
 
    try:
        if not isinstance(s, basestring,):
            if hasattr(s, '__unicode__'):
                s = unicode(s)
            else:
                try:
                    s = unicode(str(s), encoding, errors)
                except UnicodeEncodeError:
                    if not isinstance(s, Exception):
                        raise
                    # If we get to here, the caller has passed in an Exception
                    # subclass populated with non-ASCII data without special
                    # handling to display as a string. We need to handle this
                    # without raising a further exception. We do an
                    # approximation to what the Exception's standard str()
                    # output should be.
                    s = ' '.join([force_unicode(arg, encoding, errors) for arg in s])
        elif not isinstance(s, unicode):
            # Note: We use .decode() here, instead of unicode(s, encoding,
            # errors), so that if s is a SafeString, it ends up being a
            # SafeUnicode at the end.
            s = s.decode(encoding, errors)
    except UnicodeDecodeError, e:
        if not isinstance(s, Exception):
            raise UnicodeDecodeError (s, *e.args)
        else:
            # If we get to here, the caller has passed in an Exception
            # subclass populated with non-ASCII bytestring data without a
            # working unicode method. Try to handle this without raising a
            # further exception by individually forcing the exception args
            # to unicode.
            s = ' '.join([force_unicode(arg, encoding, errors) for arg in s])
    return s

Estoy convencido de que si tienes problemas con los strings en python, sabrás apreciar este código.

, , , ,

mansofk: el super mega ultra lightweight js framework

Me hacía falta un framework de javascript que fuese capaz de cambiar el CSS de los elementos, que fuese capaz de hacer peticiones AJAX,  capaz de cargar javascripts o CSS externos, capaz de añadir o cambiar HTML sobre la marcha, capaz de capturar eventos, capaz de realizar animaciones y capaz de evitar colisiones con otros frameworks o incluso con sigo mismo, y además, que fuera superligero y funcionara en IE6+, FF, Safari, Chrome y Opera.

Al final, después de cansarme de buscar y buscar, lo he programado yo, y en honor al blog, he decidido llamarlo manso framework. mansofk para los amigos.

Al final he conseguido meter toda esta funcionalidad en tan sólo 1.5KB.

Las funcionalidades principales son:

  • Fácil renombrar el framework para evitar colisiones
  • Soporta encadenamiento de llamadas
  • Soporta la carga de componentes externos de forma dinámica
    • Soporta cargar CSS externos dinámicamente
    • Soporta cargar javascript externos dinámicamente
  • Manipulaciones DOM sencillas
    • Seleccionar elementos por ID
    • Añadir elementos
    • Reemplazar elementos
  • Manipular el estilo CSS de los elementos
    • Obtener una propiedad del estilo actual de un elemento
    • Cambiar una propiedad del estilo de un elemento
    • Cambiar varias propiedades a la vez
  • Animaciones CSS sencillas
    • Soporta varios atributos a la vez
    • Varios parametros, entre ellos la duración e incluso los frames por segundo
    • Es posible seleccionar la función linear y la cúbica
  • Soporte de eventos
    • bind
    • unbind
  • Llamadas AJAX
    • Con POST
    • Con GET
    • Con soporte XML
    • Con soporte JSON
    • Con soporte texto plano
  • Superligero
    • 3.3 KB minified
    • 1.5 KB gzipped!

Sois libres de usar este framework para lo que os venga en gana, pero no me responsabilizo de nada.

A continuación teneis la versión sin comprimir, y la versión reducida con Google Closure Compiler:

Ale, pues ya está. Lo suyo sería hacer una demo, pero como ahora mismo tengo otras cosas que hacer, me lo dejo para otra ocasión.

, , , , , ,

Google App Engine en Ubuntu 10.4 Lucid Lynx

No es nuevo, siempre que actualizo a la siguiente versión de Ubuntu (en este caso la 10.4), me toca pasarme un par de días reconfigurando cosas o reinstalando paquetes.

En fin, que estoy ahora mismo desarrollando una aplicación con Google App Engine (GAE para los amigos), y para no tener problemas al hacer un deployment, lo suyo es tener instalado Python 2.5.

Como no, la gente de Canonical ha eliminado el paquete python2.5 de Ubuntu Lucid, así que ya la tenemos montada. No puedo lanzar el servidor local de Google App Engine.

Por suerte, después de estar indagando e indagando en launchpad.net he encontrado a alguien que se lo ha currado y ha creado los paquetes python2.4 y python2.5 para Ubuntu Lucid Lynx.

Lo único que tienes que hacer es añadir las siguientes lineas en /etc/apt/sources.list

deb http://ppa.launchpad.net/fkrull/deadsnakes/ubuntu lucid main
deb-src http://ppa.launchpad.net/fkrull/deadsnakes/ubuntu lucid main

Y finalmente ejecutar:

$ sudo apt-get update
$ sudo apt-get install python2.5

Y ya está, ya puedes ejecutar GoogleAppEngine en Ubuntu Lucid Lynx.

Enlaces de interés:

, ,

prev posts prev posts