Mostrando entradas con la etiqueta python. Mostrar todas las entradas
Mostrando entradas con la etiqueta python. Mostrar todas las entradas

domingo, 10 de marzo de 2013

Django: Optimistic Locking

At the Córdoba (Argentina) Sprint, on February 23rd, we achieved to implement the minimum necessary code to have a working prototype of Optimistic Locking.

Why is needed?

To avoid the loss of information when 2 users update the same object, or even when one user has the same object open in multiples browsers or tabs (database locks nor 'select for update' help here).

Ej: the object with version 'N' is in the DB; the object is shown in the browser by two users (let sey user A and user B). The user A modifies it and save it in the database. The user B modifies the same object, and save it. The changes entered by user A are silently lost, and neither user A nor user B acknowledges the situation.

How it works

It works the same way as in Hibernate. The instance has a version field, which is sent to the user as a hidden field. Instead of using a simple SQL "UPDATE ...", we use "UPDATE ... WHERE VERSION = YYY", and check how many rows where updated. If no row was updated, it means the version has changed and we report this as an optimistic locking error.

Ej: the current implementation raises an OptimisticLockingError when user B tries to save the changes. The developer has to catch this exception and make whatever he or she wants. 

Alternatives to Optimistic Locking

  • maybe versioning of attributes (ej: instead of having a "descripcion" field, have a set of "descriptions" and a "current description" foreign key)

TODO

  1. remove requeriment of hard-coded 'version' field, and instead look for any field of type VersionField,
  2. decide and implement how to manage deletes,
  3. evaluate alternatives how to enable optimistic locking on models (see 'Alternatives for enabling O.L. on a model'),
  4. implement graceful handling of optimistic locking errors in admin,
  5. see how this relates the adoption of NOSQL support or other related topics, 

Alternatives for enabling O.L. on a model

We want to make optimistic locking totally optional and easy to use for existing models and forms. We explored different alternatives to 'mark' that we want optimistic locking semantics for a specific model. Also, we want to avoid introducing new mechanisms. The alternatives were:



1. Add a VersionField to models
This is how it's implemented right now: you add a VersionField to your model. The associated form field uses a HiddenInput widget, so ModelForms don't have to be modified to support optimistic locking, since the version is automatically sent to the browser as a hidden field.

PRO: is explicit (you see a new field being added)
CON: models should have only one VersionField, and the field is kind of magic: if it exists, the model has optimistic locking semantics. There is no comparable mechanism in Django (maybe could be comparable to AutoField?).

--- Example ---
----------


2. Use a Meta option
Allow the user set a Meta option, like 'optimistic_locking = True'.

PRO: it very clean
CON: hides the fact that a new field is added. There is no comparable mechanism in Django (a meta option that adds a field)

--- Example ---
----------


Git


References

Optimistic Locking

Related Django tickets


Authors

  • Ramiro Morales <cramm0@gmail.com>
  • Francisco Capdevila <fjcapdevila@gmail.com>
  • Carlos Ilcobich <cilcobich@gmail.com>
  • Fernando Flores <fef.flores@gmail.com>
  • Matías Bordese <mbordese@gmail.com>
  • Martín Gaitan <gaitan@gmail.com>
  • Horacio G. de Oro <hgdeoro@gmail.com>

jueves, 15 de noviembre de 2012

Nueva versión: PyArduinoProxy v0.0.3

Esta vez para arreglar un  bug, ya que digitalRead() habia dejado de funcionar.

Les recuerdo, para instalar PyArduinoProxy:

    $ pip install pyarduinoproxy

y para actualizarlo:

    $ pip install --upgrade pyarduinoproxy

miércoles, 14 de noviembre de 2012

Nueva versión: PyArduinoProxy v0.0.2

Luego de mucho tiempo, he lanzado PyArduinoProxy v0.0.2. Les cuento brevemente lo que ha cambiado:

  • instalación simplificada con pip/virtualenv,
  • eliminé fuentes de librerías (CherryPy, Jinja2, SimpleJSON), ahora son instaladas automaticamente via pip/virtualenv,
  • agregué soporte para sensores de temperatura/humedad DHT11
  • arreglé BUG de JS (expresiones regulares ahora no son 'callables').
Como siempre, cualquier tipo de comentario, sugerencia, etc. es bienvenida!


viernes, 20 de julio de 2012

South: 'assertsynced' para asegurar que todas las migraciones están aplicadas

El uso de South con aplicaciones Django es un estándar en los desarrollos en los que participo. Pero hace tiempo me surgió la necesidad de saber si hay migraciones sin aplicar.

Es fácil conocer 'visualmente' la situación, pero el problema es cuando hace falta saberlo desde un script. Por ejemplo: al momento de realizar deploys, y en los scripts de arranque de los sistemas.

Sobre todo en este último caso, la aplicación no debería iniciarse si hay migraciones pendientes de aplicar. Finalmente me tomé un tiempo y cree assertsynced. Este es un comando de Django, basado en la implementación de migrate de South.

Está disponible aquí.

viernes, 8 de junio de 2012

PIL + Virtualenv en Ubuntu Precise Pangolin

Al instalar PIL en un virtualenv, el proceso terminó con:


*** TKINTER support not available
*** JPEG support not available
*** ZLIB (PNG/ZIP) support not available
*** FREETYPE2 support not available
*** LITTLECMS support not available

lo que indica que PIL fue compilado SIN soporte para JPG, aunque todas las librerías necesarias (libz, libjpeg, etc) estaban instaladas, incluyendo los correspondientes paquetes de desarrollo (*-dev).

En busca de soluciones, encontré algunos posts que sugerían crear links de las librerias (libxxxxx.so) en /usr/lib, solución que me pareció poco "elegante". Hasta que encontré una solución diferente, donde tambien hace falta crear links, pero esta vez en el directorio "lib" de virtualenv.

Básicamente, hay que ejecutar:

$ cd virtualenv/lib
$ ln -s /usr/lib/x86_64-linux-gnu/libfreetype.* .
$ ln -s /usr/lib/x86_64-linux-gnu/libjpeg.* .
$ ln -s /usr/lib/x86_64-linux-gnu/libz.* .

lunes, 28 de mayo de 2012

Actualización de mi fork de fabric

Actualicé mi humilde fork de fabric :-D Hace tiempo salió la versión 1.4 de Fabric, pero la facultad y el trabajo no me dieron tiempo de ponerme al día.

Para instalarlo usando pip, simplemente hay que ejecutar:

pip install -e git+https://github.com/hgdeoro/fabric@1.4#egg=fabric

domingo, 27 de mayo de 2012

Caché para pip

Al trabajar con varios proyectos Python usando virtualenv, es comun bajar una y otra vez las mismas librerías/dependencias (Django, South, PIL, psycopg2, etc).

Para optimizar la instalación de estos paquetes, se puede configurar pip para que utilice cierto directorio como cache. Para lograrlo, hay que crear el archivo ~/.pip/pip.conf, con el siguiente contenido:


[install]
download-cache = /var/cache/pip-cache



Obviamente necesitamos permisos de escritura en ese directorio. Una forma de crearlo seria:

$ sudo mkdir /var/cache/pip-cache
$ sudo chown USUARIO /var/cache/pip-cache


jueves, 16 de febrero de 2012

Un primer acercamiento a ZeroMQ (con Python)

Estoy aprendiendo a usar ZeroMQ (con Python + pyzqm)y encontré que emular el funcionamiento normal de sockets TCP/IP puede ser algo complicado para un primer acercamiento a la librería.

La implementación del servidor es bastante intuitiva:

  • hacemos bind,
  • esperamos recibir peticiones,
  • para cada petición devolvemos una respuesta.
-

-

La implementación del cliente es la que cambia bastante. Si intentamos enviar un mensaje usando sockets TCP/IP y no se puede establecer conexión con el servidor, del lado del cliente detectamos este problema... Por lo tanto, el envío se realiza correctamente, o se produce algún error de conexión (incluyendo la posibilidad de utilizar timeouts).

Pero con ZeroMQ, si el servidor NO está ejecutándose y el cliente envía un mensaje, el cliente se bloquea hasta que se pueda contactar el servidor y realizar el envío.
-

-

Para evitar que el cliente se bloquee, no hay posibilidad de usar un "timeout", pero podemos implementar algo parecido usando Poll().

-

-

Con esta segunda implementación, el cliente no bloquea si el servidor está caído.

martes, 7 de febrero de 2012

Mejora a Logging por default en Django

La configuración por default de LOGGING de Django hace que no se muestren por consola los mensajes de logging generados, sólo muestra los requests, y hacer que se vean nuestros mensajes es bastante fácil, sólo hay que agregar a LOGGING (en settings.py) las líneas en negritas:

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'mail_admins': {
            'level': 'ERROR',
            'class': 'django.utils.log.AdminEmailHandler'
        },
        'console': {
            'level': 'INFO',
            'class': 'logging.StreamHandler'
        },
    },
    'loggers': {
        '': {
            'handlers': ['console'],
            'level': 'INFO',
            'propagate': True,
        },
        'django.request': {
            'handlers': ['mail_admins'],
            'level': 'ERROR',
            'propagate': True,
        },
    }
}

(esto funcionará en Django >= 1.3)

domingo, 5 de febrero de 2012

Mejorando listado de task en Fabric

Cuando al usar Fabric listamos los comandos disponibles con --list, este listado está restringido a un ancho de 75 caracteres. Pero al usar una terminal de 160 caracteres de ancho, es mucho el espacio desperdiciado, y la documentación "ocultada".

Hice unos cambios en Fabric para que, al listar los comandos, utilice todo el ancho disponible. Antes, con una columna de 160 caracteres, el listado se mostraba:


Available commands:


    FabricTest    Nose-oriented test runner which wipes state.env and provid...
    TestParallel
    eq_           Shadow of the Nose builtin which presents easier to read m...
    server        Returns a decorator that runs an SSH server during functio...


Ahora:


Available commands:

    FabricTest    Nose-oriented test runner which wipes state.env and provides file helpers.
    TestParallel
    eq_           Shadow of the Nose builtin which presents easier to read multiline output.
    server        Returns a decorator that runs an SSH server during function execution.


Estos cambios están disponibles en mi fork de Fabric y puede ser instalado ejecutando "pip install -e git+https://github.com/hgdeoro/fabric@1.3-hgdeoro#egg=fabric"


Fabric: unsupported operand type(s) for &: 'str' and 'int'

Me ocurrió que upload_template() de Fabric fallaba con el siguiente mensaje de error:

Fatal error: put() encountered an exception while uploading '<StringIO.StringIO instance at 0x7fb4b6e0e368>'


Underlying exception message:
    unsupported operand type(s) for &: 'str' and 'int'


Aborting.

Lo que estaba haciendo era bastante simple:

    upload_template(
        os.path.join('deploy-files', 'nginx', 'nginx-template.conf'),
        nginx_config_file_to,
        context=context,
        use_sudo=True, backup=False, mode='0644')


Pero claro, resulta que el modo debe ser un valor en OCTAL, y no un STRING! Cambiando el modo de string a octal solucionó el problema:

    upload_template(
        os.path.join('deploy-files', 'nginx', 'nginx-template.conf'),
        nginx_config_file_to,
        context=context,
        use_sudo=True, backup=False, mode=0644)


Es algo simple, pero lo comparto porque no encontré mucha información al buscar dicho mensaje de error en internet.



sábado, 4 de febrero de 2012

Nuevo "list format" para Fabric

He implementado un nuevo "list format" para Fabric. Este nuevo formateador hace que se muestren las distintas "tasks" declaradas, junto a la documentacion completa de las mismas.

Está disponibles en un branch de master (https://github.com/hgdeoro/fabric/tree/new_list_format_fulldoc) en un un branch de 1.3 (https://github.com/hgdeoro/fabric/tree/1.3-hgdeoro).

Por ejemplo, al ejecutar Fabric sobre "tests/test_parallel.py" con los argumentos "--list --list-format=fulldoc", se genera el siguiente listado:

Available commands:

    FabricTest
    ----------
        Nose-oriented test runner which wipes state.env and provides file helpers.

    TestParallel
    ------------

    eq_
    ---
        Shadow of the Nose builtin which presents easier to read multiline output.

    server
    ------
        Returns a decorator that runs an SSH server during function execution.
        Direct passthrough to ``serve_responses``.



Para instalarlo via pip:
$ pip install -e git+https://github.com/hgdeoro/fabric@1.3-hgdeoro#egg=fabric



Corriendo tests con instancia de PostgreSql en ram

Otra manera de optimizar la ejecución de tests de Django, pero SIN modificar la instalación de PostgreSql del sistema operativo es crear una nueva instancia, corriendo totalmente en ram.

Para crear una nueva instancia usamos initdb:

$ /usr/lib/postgresql/9.1/bin/initdb \
    --pgdata=/dev/shm/pgtesting \
    -U horacio

Y para lanzar la instancia en segundo plano:


$ /usr/lib/postgresql/9.1/bin/postgres \
    -F -i -p 5444 -S $((1024*64)) \
    -D /dev/shm/pgtesting \
    --unix_socket_directory=/dev/shm/pgtesting \
    -r /dev/shm/pgtesting/postgresql.log &


Luego creamos el usuario y BD para la aplicación Django:

psql -h 127.0.0.1 \
    -c "create user vindler with password 'x' superuser" postgres
psql -h 127.0.0.1 \
    -c "create database vindler owner vindler" postgres


Mejora en tiempos de ejecución de test: 28%

La ejecución de los tests con PostgreSql en disco, con las optimizaciones de mi artículo anterior tardan alrededor de 7 segundos:


Ran 18 tests in 7.071s
5.44user 0.58system 0:10.40elapsed 57%CPU (0avgtext+0avgdata 122880maxresident)k
0inputs+1184outputs (0major+16964minor)pagefaults 0swaps


Ran 18 tests in 6.843s
5.28user 0.65system 0:10.27elapsed 57%CPU (0avgtext+0avgdata 122976maxresident)k
0inputs+1176outputs (0major+16956minor)pagefaults 0swaps


Ran 18 tests in 7.032s
5.42user 0.64system 0:10.42elapsed 58%CPU (0avgtext+0avgdata 124304maxresident)k
0inputs+1168outputs (0major+17122minor)pagefaults 0swaps


Los mismos tests ejecutandose en la nueva instancia de PostgreSql totalmente en ram, tardan alrededor de 5 segundos:


Ran 18 tests in 5.220s
4.41user 0.65system 0:08.38elapsed 60%CPU (0avgtext+0avgdata 117904maxresident)k
8inputs+1176outputs (0major+16524minor)pagefaults 0swaps


Ran 18 tests in 5.047s
4.32user 0.53system 0:08.04elapsed 60%CPU (0avgtext+0avgdata 117888maxresident)k
0inputs+1168outputs (0major+16504minor)pagefaults 0swaps


Ran 18 tests in 4.665s
3.95user 0.64system 0:07.79elapsed 58%CPU (0avgtext+0avgdata 117856maxresident)k
0inputs+1176outputs (0major+16502minor)pagefaults 0swaps



Al ejecutar los tests de stress: 33%

Con PostgreSql en disco:


Ran 1 test in 24.601s
15.21user 2.72system 0:27.98elapsed 64%CPU (0avgtext+0avgdata 130944maxresident)k
0inputs+9616outputs (0major+18298minor)pagefaults 0swaps


Ran 1 test in 24.458s
15.16user 2.51system 0:27.57elapsed 64%CPU (0avgtext+0avgdata 131488maxresident)k
472inputs+9608outputs (0major+18457minor)pagefaults 0swaps


Ran 1 test in 24.552s
15.23user 2.63system 0:27.95elapsed 63%CPU (0avgtext+0avgdata 131088maxresident)k
232inputs+9616outputs (0major+18310minor)pagefaults 0swaps

Con PostgreSql en ram:

Ran 1 test in 16.082s
11.30user 2.46system 0:19.22elapsed 71%CPU (0avgtext+0avgdata 126080maxresident)k
0inputs+9616outputs (0major+17675minor)pagefaults 0swaps

Ran 1 test in 15.736s
11.25user 2.20system 0:18.82elapsed 71%CPU (0avgtext+0avgdata 126192maxresident)k
0inputs+9608outputs (0major+17684minor)pagefaults 0swaps

Ran 1 test in 15.408s
10.78user 2.27system 0:18.44elapsed 70%CPU (0avgtext+0avgdata 126384maxresident)k
0inputs+9608outputs (0major+17693minor)pagefaults 0swaps



Acelerando tests de Django con PostgreSql

La ejecucion inicial de los tests toma aproximadamente 15 segundos:

4.54user 0.64system 0:15.25elapsed 34%CPU (0avgtext+0avgdata 123600maxresident)k
4.70user 0.54system 0:14.70elapsed 35%CPU (0avgtext+0avgdata 122992maxresident)k
4.46user 0.65system 0:14.85elapsed 34%CPU (0avgtext+0avgdata 122912maxresident)k

Luego de ajustar los siguientes parametros:
- fsync = off
- synchronous_commit = off
- wal_sync_method = fsync

la ejecución toma aproximadamente 8.5 segundos:

4.22user 0.56system 0:08.62elapsed 55%CPU (0avgtext+0avgdata 123024maxresident)k
4.32user 0.58system 0:08.97elapsed 54%CPU (0avgtext+0avgdata 123008maxresident)k
4.29user 0.50system 0:08.74elapsed 54%CPU (0avgtext+0avgdata 123040maxresident)k

El tiempo se redujo casi a la mitad! Aunque esta configuración no es para nada recomendable para un equipo de producción, creo que vale la pena para ejecutar tests.

miércoles, 25 de enero de 2012

Instalando django_openid_auth usando PIP

Hay algunos problemas con la version 0.4 de django_openid_auth (ej: no se puede instalar via pip). El proyecto es open source, por lo tanto es muy facil subirlo a GitHub y compartir estos arreglos :-)

Básicamente bajé el proyecto original (revisión 88) y lo subi a GitHub. Para instalarlo usando PIP hay que ejecutar:

./pip install -e git+http://github.com/hgdeoro/hgdeoro_fork_of_django_openid_auth#egg=django_openid_auth

Actualización

Mucho mejor, instalamos usando PIP, pero un commit en particular (ec68dfc046d1a75b50080ea98c56eda71956ed7a):

pip install -e git+http://github.com/hgdeoro/hgdeoro_fork_of_django_openid_auth@ec68dfc046d1a75b50080ea98c56eda71956ed7a#egg=django_openid_auth

O también un tag en particular (bzr_revno_88):

pip install -e git+http://github.com/hgdeoro/hgdeoro_fork_of_django_openid_auth@bzr_revno_88#egg=django_openid_auth


miércoles, 19 de octubre de 2011

Cómo resetear South

Podemos resetear South desinstalándolo y volviéndolo a instalar. Atención: esto hará que se pierdan todas las migraciones existentes, y los datos de las BD de las aplicaciones que se quieran resetear. Los pasos a seguir son:

1) eliminar de la BD las tablas de South: south_migrationhistory.
2) eliminar de la BD las tablas de las aplicaciones administradas por south.
3) eliminar el paquete migrations de las aplicaciones que usen South.
4) ejecutar syncdb.
5) volver a marcar las aplicaciones que deben ser administradas usando South, ejecutando: "manage.py convert_to_south APP"


sábado, 20 de agosto de 2011

Benchmarks de port forwarding con Python+Eventlet

Les comparto algunos benchmarks que hice para verificar la velocidad del "tcp port forwarder" implementado usando Python+Eventlet: http://blog.eventlet.net/2010/02/28/beautiful-er-eventlet/

No pude hacer los tests usando 'ab', ya que parece haber algun problema por el cual la aplicación no funciona correctamente.

Copia de ISO usando scp, puerto 22: OK
  • ubuntu.iso 100%  698MB  63.4MB/s   00:11
  • ubuntu.iso 100%  698MB  69.8MB/s   00:10
  • ubuntu.iso 100%  698MB  69.8MB/s   00:10

Copia de ISO usando scp, puerto 7000 (eventlet): OK
  • ubuntu.iso 100%  698MB  41.0MB/s   00:17
  • ubuntu.iso 100%  698MB  41.0MB/s   00:17
  • ubuntu.iso 100%  698MB  43.6MB/s   00:16

Copia de ISO, usando wget, bajada directamente de Apache, puerto 80: OK
  • 731,453,440  524M/s   in 1.3s    
  • 731,453,440  539M/s   in 1.3s    
  • 731,453,440  523M/s   in 1.3s    


Copia de ISO, usando wget, bajada directamente de Apache, puerto 7080 (eventlet): OK
    • 731,453,440  319M/s   in 2.2s
    • 731,453,440  318M/s   in 2.2s
    • 731,453,440  323M/s   in 2.2s


    Usando ApacheBench, al puerto 80:  OK
    • ab -n 400 -c 5 http://localhost:80/index.html 
      • Finished 400 requests
      • Concurrency Level:      5
      • Time taken for tests:   0.083 seconds
      • Complete requests:      400
      • Failed requests:        0


    Usando ApacheBench, al puerto 7080 (eventlet): ERROR!!!
      • ab -n 400 -c 5 http://localhost:7080/index.html 
        • apr_poll: The timeout specified has expired (70007)
      • ab -n 40 -c 5 http://localhost:7080/index.html 
        • apr_poll: The timeout specified has expired (70007)
      • ab -n 5 -c 1 http://localhost:7080/index.html 
        • apr_poll: The timeout specified has expired (70007)

      miércoles, 10 de agosto de 2011

      psycopg2 (2.4.2) + Django (1.3) = problemas!


      Si usas psycopg2 versión 2.4.2 con Django 1.3 surge el siguiente problema:

      $ ./virtualenv27/bin/python src/dynamicware/manage.py test myprojects
      Creating test database for alias 'default'...
      (...)
      psycopg2.ProgrammingError: autocommit cannot be used inside a transaction



      Una solución temporal es instalar la versión anterior de psycopg2:

      $ ./virtualenv27/bin/pip install psycopg2==2.4.1

      La solución definitiva ya existe: https://code.djangoproject.com/ticket/16250

      PIL / pillow + virtualenv

      Instalé PIL usando virtualenv, y así termina la instalción:


          --------------------------------------------------------------------
          PIL 1.1.7 SETUP SUMMARY
          --------------------------------------------------------------------
          version       1.1.7
          platform      linux2 2.7.1+ (r271:86832, Apr 11 2011, 18:13:53)
                        [GCC 4.5.2]
          --------------------------------------------------------------------
          *** TKINTER support not available (Tcl/Tk 8.5 libraries needed)
          *** JPEG support not available
          *** ZLIB (PNG/ZIP) support not available
          *** FREETYPE2 support not available
          --- LITTLECMS support available
          --------------------------------------------------------------------



      PIL se instaló correctamente, pero sin soporte para JPEG y PNG. Este es el funcionamiento "esperado" al instalar PIL con virtualenv.

      La solución: usar pillow (fork PIL, amigable con virtualenv)

      Luego de ejecutar "./virtualenv27/bin/pip install pillow":


          --------------------------------------------------------------------
          Pillow 1.7.4 ( PIL fork based on PIL 1.1.7 ) SETUP SUMMARY
          --------------------------------------------------------------------
          platform  linux2 2.7.1+ (r271:86832, Apr 11 2011, 18:13:53)
            [GCC 4.5.2]
          --------------------------------------------------------------------
          *** TKINTER support not available (Tcl/Tk 8.5 libraries needed)
          --- JPEG support available
          --- ZLIB (PNG/ZIP) support available
          --- FREETYPE2 support available
          --- LITTLECMS support available
          --------------------------------------------------------------------

      sábado, 18 de junio de 2011

      AppEngine: cómo evitar subir contenidos de un directorio

      Al trabajar con Google App Engine, podemos crear un directorio en la base del proyecto, y luego configurar app.yaml para que ignore estos archivos. Por ejemlpo, si el directorio se llama local, hay que agregar a las siguientes líneas al archivo app.yaml:

      skip_files:
      - ^(.*/)?app\.yaml
      - ^(.*/)?app\.yml
      - ^(.*/)?index\.yaml
      - ^(.*/)?index\.yml
      - ^(.*/)?#.*#
      - ^(.*/)?.*~
      - ^(.*/)?.*\.py[co]
      - ^(.*/)?.*/RCS/.*
      - ^(.*/)?\..*
      - ^local/.*
      

      Todas las líneas excepto la última, corresponden a la configuración por default de skip_files. La última línea hace que el directorio local en la raiz del proyecto sea ignorado al ejecutar appcfg.py.