Carlos Ble

Carlos Ble

I am a professional software developer, I solve problems.

I also teach and mentor developers to build better software.

Developing software since 2001.

Can I help you?

  • Do you need high quality tailor-made software?
  • Need training on TDD, clean code or refactoring?
  • Do you need a technical consultant?
  • May I pair with you to write better code?

Events

Upcoming training courses:

  1. TDD - [en Español] - 6 y 7 Octubre
    Gran Canaria
  2. TDD - [in English] - October 20, 21 & 22
    London, UK
  3. TDD - [en Español] - 29, 30 y 31 de Octubre.
    Madrid, Spain

Conferences:

  1. I'll be at the Agile Testing Days 2014
  2. I'll be at the London Test Gathering Workshops.

Archive for the ‘Google App Engine’ Category



Mapreduce API is great. We've got a tool now that can process tasks taking more than 30 seconds. Yeaaaahhh! This is a huge improvement. I wish we have had this tools months ago. All the examples in the documentation use the webapp framework, there aren't many examples using the Django helper in the internet. This post is about that.

mapreduce.yaml:

  1.  
  2. mapreduce:
  3.  
  4. - name: Delete SearchableTowns
  5. mapper:
  6. input_reader: mapreduce.input_readers.DatastoreInputReader
  7. handler: main_map_reduce.delete_searchable_towns
  8. params:
  9. - name: entity_kind
  10. default: mapreduce_models.SearchableTown
  11.  
  12. - name: Create SearchableTown from Town
  13. mapper:
  14. input_reader: mapreduce.input_readers.DatastoreInputReader
  15. handler: main_map_reduce.town_to_searchable
  16. params:
  17. - name: entity_kind
  18. default: mapreduce_models.Town
  19.  
  20. - name: Create Town and SearchableTown from csv for USA
  21. mapper:
  22. input_reader: mapreduce.input_readers.BlobstoreLineInputReader
  23. handler: main_map_reduce.csv_to_towns
  24. params:
  25. - name: blob_keys
  26. default: AMIfv97g-x4G9-KM24YXQi6dSyBddAb97p0n98NgJlCL68jJA9jcvwETojEcF7MGGlZsDLEFVcJeeLHGgwxo9Nlay9GR33LniA06Obw3C781Te9yAn9Dk1EkwxjrFqHEBo4-WbZ7GUS9nKa3NOpDGdbxBBkD2sTYUg
  27.  

The file contain 3 tasks. 2 of them are intended to create or modify datastore entities. The other one is going to read a big csv from the blogstore, creating a datastore entity for every line in the file. This is the Python version of this blog post (which uses Java).

Now, main_map_reduce is a python file that I keep in the same location than mapreduce.yaml. Just a regular python file. The imports in that file might cause exceptions, specially if they try to load Django stuff. In order to avoid problems we had to copy our models.py into mapreduce_models.py removing almost all the imports. As mapreduce_models.py is placed at same level than mapreduce.yaml, we had to hack also the file appengine_django/models.py, replacing this line:

  1.  
  2. self.app_label = model_module.name.split('.')[-2]
  3.  

With this block:

  1.  
  2. self.app_label = 'my_app_name'
  3. try:
  4. self.app_label = model_module.__name__.split('.')[-2]
  5. except IndexError:
  6. pass
  7.  

main_map_reduce.py:

  1.  
  2. def delete_searchable_towns(town_entity):
  3. yield op.db.Delete(town_entity)
  4.  
  5. def town_to_searchable(town_entity):
  6. searchable = models.SearchableTown()
  7. searchable.code = town_entity.code
  8. searchable.lower_name = town_entity.name.lower()
  9. yield op.db.Put(searchable)
  10.  
  11. def csv_to_towns(input_tuple):
  12. line = input_tuple[1]
  13. offset = input_tuple[0]
  14. # process the line ...
  15. yield op.db.Put(town_entity)
  16.  

In the first two methods, the mapreducer passes in an entity. In the last one, it passes a tuple, where its second item is the line read from the blog, which is a big csv file.
This way, we can now upload a huge csv and then create entities from it. This tasks was really painful before, as we had to make a ton of dirty hacks in order to avoid the 30 seconds restriction.

GAE Blobstore is a great way to manage big files. This post is about using the Blogstore with Python and the Django GAE Helper (appengine_django).

Views.py:

  1.  
  2.  
  3. from google.appengine.ext import blobstore
  4. # import other django stuff
  5.  
  6. def show_upload_form(request):
  7. template = "admin_upload_blob.html"
  8. dict = {}
  9. dict['action'] = blobstore.create_upload_url('/admin/upload_blob/')
  10. return render_to_response(template,
  11. context_instance= context.get_context(
  12. dict, request))
  13.  
  14. def upload_blob(request):
  15. try:
  16. uploads = get_uploads(request, 'file')
  17. for upload in uploads:
  18. file = BlobFile(blob=upload)
  19. file.save()
  20. except Exception, e:
  21. return HttpResponseRedirect("/admin")
  22. return HttpResponse("Ok!")
  23.  
  24.  
  25. def get_uploads(request, field_name=None, populate_post=False):
  26. """
  27.  
  28. http://appengine-cookbook.appspot.com/recipe/blobstore-get_uploads-helper-function-for-django-request/
  29.  
  30. Get uploads sent to this handler.
  31.  
  32. Args:
  33. field_name: Only select uploads that were sent as a specific field.
  34. populate_post: Add the non blob fields to request.POST
  35.  
  36. Returns:
  37. A list of BlobInfo records corresponding to each upload.
  38. Empty list if there are no blob-info records for field_name.
  39. """
  40.  
  41. logging.info(request)
  42. if hasattr(request,'__uploads') == False:
  43. request.META['wsgi.input'].seek(0)
  44. fields = cgi.FieldStorage(request.META['wsgi.input'], environ=request.META)
  45.  
  46. request.__uploads = {}
  47. if populate_post:
  48. request.POST = {}
  49. for key in fields.keys():
  50. field = fields[key]
  51. if isinstance(field, cgi.FieldStorage) and 'blob-key' in field.type_options:
  52. request.__uploads.setdefault(key, []).append(blobstore.parse_blob_info(field))
  53. elif populate_post:
  54. request.POST[key] = field.value
  55.  
  56. if field_name:
  57. try:
  58. return list(request.__uploads[field_name])
  59. except KeyError:
  60. return []
  61. else:
  62. results = []
  63. for uploads in request.__uploads.itervalues():
  64. results += uploads
  65. return results
  66.  

admin_upload_blob.html:

  1.  
  2. {% extends "base.html" %}
  3. {% load i18n %}
  4.  
  5. {% block content %}
  6.  
  7. Upload a big file:
  8.  
  9. <form action="{{ action }}" method="post" enctype="multipart/form-data">
  10. Upload File: <input type="file" name="my_file"><br>
  11. <input type="submit" name="submit" value="Submit"> </form>
  12. {% endblock content %}
  13.  

Make sure urls.py contains the methods in views.py as usual.
Just access show_upload_form with your browser and upload a file. It works!

Ha llegado MavenCharts.com

Git dice que hice el primer commit en el repositorio el dia 2 de diciembre de 2009. Sin embargo la idea se me ocurrió hace 3 años. Aquello de que el día a día no te deja, retrasó el proyecto hasta hace poco. La unica manera de poderlo llevar hacia adelante ha sido dedicarme a ello exclusivamente, primero en solitario y luego con un equipo de desarrolladores vocacionales como el que somos ahora. Oscar se unió al proyecto hace unos 3 meses y Dani todavía no ha hecho el mes. Curso a curso he ido pagando el alquiler y las facturas para ponerlo todo en este desarrollo. Se ha llevado a cabo usando Test Driven Development al 100% en el backend. Para el frontend, no hemos hecho tests automaticos del javascript. Realmente es el mejor código que he escrito en mi vida. Y estamos tranquilos de que podemos hacer cambios con velocidad sin perder estabilidad. Menos mal, porque Google App Engine lo pone muy dificil.

Cuando empecé a escribir código en diciembre, planeaba tener lista la version que ha salido ahora, en abril. O sea que nos hemos retrasado 3 meses, practicamente el doble de la previsión. Calculo que trabajando sobre un sistema SQL tradicional y un servidor web como Apache, hubiesemos de verdad terminado en abril o mayo a más tardar. Realmente desarrollar sobre GAE ha sido un dolor. En primer lugar porque es NoSQL y eso significa que tienes que aprender a acceder a datos de otra manera. Tienes que pensar diferente. En segundo lugar, porque el Datastore (la base de datos) tiene tal cantidad de restricciones que hacer consultas medio complejas es muy muy complejo. Las consultas del buscador de MavenCharts son complicadas sin duda. Esto ha hecho que haya más tests de integración de los que hubiesen sido deseables.
Por otro lado, cargar la base de datos con las poblaciones de España tambien ha sido complejo. Primero habia que buscar los datos, tratarlos y subirlos. Los tenemos georeferenciados vía google maps para las nuevas funciones en las que estamos trabajando. Eso también llevó su trabajo.
GAE no te permite hacer ningun request que dure mas de 30 segundos, lo interrumpe con un error 500. Entonces te las tienes que ingeniar para dividir la tarea. Menos mal que hemos usado python y los scripts se hacen rápido porque hemos automatizado mil cosas de ese tipo. Para subir datos no puedes hacer un dump de SQL, todo lo que tienes es CSVs, que puedes subir si te programas las clases que los saben interpretar. Te lo tienes que hacer todo tu.
Otro problema importante es que el entorno de desarrollo de GAE en el pc local no es igual que el de la nube, y los tests de integracion que te van bien en local, luego arriba petan por falta de indices en la BD y cosas asi. Gracias a WebDriver, el cual hemos manejado desde Java, hemos podido automatizar las pruebas de integracion mas intensivas y complicadas.

El otro gran problema que nos supuso 3 semanas de retraso y unas 1500 lineas de código tiradas a la basura, fue el de la gestion de cache de datos. GAE te permita usar memcache. La gran pregunta es... ¿cómo gestionas la cache cuando tienes datos que actualizar? Despues de intentar mil cosas hemos adoptado la politica de Google, es decir, el buscador no refresca los datos inmediatamente sino que necesita unas horas. En nuestro caso unas 24 horas. Francamente es lo más optimo que se puede hacer. Todo lo demas, no escala. Realmente no vale con usar GAE para que tu aplicacion escale, sino que tienes que tener en cuenta mil cosas mas, y pensar que, cada peticion a la web, podria ser arrancada en una instancia distinta, en un servidor distinto, y si tenias un singleton por ahí, tienes un gran problema. Todo esto lo hemos ido aprendiendo a bofetadas. Menos mal que nuestras baterias de tests estaban ahí, porque sino, el caos nos hubiera ganado.

Finalmente, como todo el mundo sabe, la maquetación ha sido muy dura por aquello de que cada nequipo_mavenavegador (sobre todo Internet Explorer) pinta las cosas como quiere. Hemos cometido el error de dejar toda la maquetacion para el final pensando que seria sencilla, y no lo ha sido. En lo sucesivo iremos armando las pantallas a medida que el backend esté hecho. De hecho ahora hay que darle un gran refactoring a las css, pero al menos ya se ve bien en casi todos los sitios.

En la foto teneis al equipo de MavenCharts 1.0. En desarrollo, Oscar Moreno, Dani Latorre y un servidor. En diseño Lucas Carmona, Carlos Sosa y Pedro Gracia. En QA tenemos a Dácil a la cabeza de un grupo de amigos y colaboradores.

Ya tenemos la web internacionalizada y todo listo para soportar más paises e idiomas, asi que en las próximas semanas vamos a intentar salir en EEUU y en varios paises europeos, a la par que ir abriendo más funcionalidad. Hay ya cientos de lineas de codigo esperando a ser puestas en produccion :-)

Nos hacen falta 4 buenos desarrolladores pero por desgracia todavia no hay pelas para contratar. Ojala podamos seguir creciendo pronto.

Tenemos mucha ilusión y estamos poniendo mucho esfuerzo en este proyecto. Cualquier feedback es bienvenido. Gracias a todos los que lo habeis retuiteado y lo comentais en facebook.

Seguimos trabajando :-)

http://www.MavenCharts.com

The documentation on how to upload bulk data to Google App Engine's data store: http://code.google.com/intl/es/appengine/docs/python/tools/uploadingdata.html

How to express relationships between models? How to support utf-8 in the bulk load?
Here is my loader.py

  1.  
  2. from appengine_django import InstallAppengineHelperForDjango
  3. InstallAppengineHelperForDjango()
  4.  
  5. import datetime
  6. from google.appengine.ext import db
  7. from google.appengine.tools import bulkloader
  8.  
  9. from myapp.models import *
  10.  
  11. class MyLoader(bulkloader.Loader):
  12. def __init__(self):
  13. bulkloader.Loader.__init__(self, 'MyModel',
  14. [('country',
  15. lambda x: Country.get_by_key_name(x)),
  16. ('code',
  17. lambda x: unicode(x, 'utf-8')),
  18. ('name',
  19. lambda x: unicode(x, 'utf-8'))
  20. ])
  21.  
  22. loaders = [MyLoader]
  23.  

The cvs:
ES, someCode, someName

The command line to upload the data:
.google_appengine/appcfg.py upload_data --config_file=loaders.py --filename=bulkdata.csv --kind=MyModel .

Problems I had: http://groups.google.com/group/google-appengine-python/browse_thread/thread/b524b5ad2cbecb30