29 abr. 2014

Manejo de colas de RabbitMQ en Django con Django-Celery

Hace un tiempo escribí unos 3 artículos sobre Django, ahora se tocará un tema algo más complejo pero útil cuando se requiere rendimiento en procesos.

A continuación paso a explicar un proceso sencillo de manejo de colas de RabbitMQ por medio de Django-Celery.

La idea es realizar una instalación y configuración básica, nada de tener RabbitMQ en varias máquinas virtuales y que se maneje la autenticación de las diferentes colas de las diferentes máquinas.

La documentación la pueden encontrar en el siguiente enlace.

Lo primero que se necesita es tener rabbitMQ instalado en Debian Wheezy.
#aptitude install rabbitmq-server

Luego de la instalación se verifica que se inicie el servicio:

root@pruebas:~# invoke-rc.d rabbitmq-server status 
Status of node rabbit@pruebas ...
[{pid,3595},
 {running_applications,[{rabbit,"RabbitMQ","2.8.4"},
                        {mnesia,"MNESIA  CXC 138 12","4.7"},
                        {os_mon,"CPO  CXC 138 46","2.2.9"},
                        {sasl,"SASL  CXC 138 11","2.2.1"},
                        {stdlib,"ERTS  CXC 138 10","1.18.1"},
                        {kernel,"ERTS  CXC 138 10","2.15.1"}]},
 {os,{unix,linux}},
 {erlang_version,"Erlang R15B01 (erts-5.9.1) [source] [64-bit] [async-threads:30] [kernel-poll:true]\n"},
 {memory,[{total,26213352},
          {processes,10386936},
          {processes_used,10386922},
          {system,15826416},
          {atom,504409},
          {atom_used,473948},
          {binary,263704},
          {code,11874771},
          {ets,771120}]},
 {vm_memory_high_watermark,0.39999999862767177},
 {vm_memory_limit,233180364},
 {disk_free_limit,1000000000},
 {disk_free,6221991936},
 {file_descriptors,[{total_limit,924},
                    {total_used,8},
                    {sockets_limit,829},
                    {sockets_used,5}]},
 {processes,[{limit,1048576},{used,167}]},
 {run_queue,0},
 {uptime,5808}]
...done.


También se puede verificar desde el comando rabbitctl: 
root@pruebas:~# rabbitmqctl status 
Status of node rabbit@pruebas ...
[{pid,3595},
 {running_applications,[{rabbit,"RabbitMQ","2.8.4"},
                        {mnesia,"MNESIA  CXC 138 12","4.7"},
                        {os_mon,"CPO  CXC 138 46","2.2.9"},
                        {sasl,"SASL  CXC 138 11","2.2.1"},
                        {stdlib,"ERTS  CXC 138 10","1.18.1"},
                        {kernel,"ERTS  CXC 138 10","2.15.1"}]},
 {os,{unix,linux}},
 {erlang_version,"Erlang R15B01 (erts-5.9.1) [source] [64-bit] [async-threads:30] [kernel-poll:true]\n"},
 {memory,[{total,26211600},
          {processes,10386952},
          {processes_used,10386938},
          {system,15824648},
          {atom,504409},
          {atom_used,473972},
          {binary,262176},
          {code,11874771},
          {ets,771120}]},
 {vm_memory_high_watermark,0.39999999862767177},
 {vm_memory_limit,233180364},
 {disk_free_limit,1000000000},
 {disk_free,6221991936},
 {file_descriptors,[{total_limit,924},
                    {total_used,8},
                    {sockets_limit,829},
                    {sockets_used,5}]},
 {processes,[{limit,1048576},{used,167}]},
 {run_queue,0},
 {uptime,5848}]
...done.

Como la conexión de Django es con una sóla máquina no se requiere autenticación del rabbitMQ así que ese paso lo pueden ver en el siguiente enlace.

Ahora se instalará python, celery y django-celery: 
#aptitude install python2.7 python2.7-dev python-django python-django-celery python-celery


Al terminar de instalar la aplicación ahora se va a crear un proyecto django:
Se crea el directorio django en /srv/
#mkdir -p /srv/django 

Luego se crea el proyecto pasarela con el comando django-admin:
root@pruebas:/srv/django# django-admin startproject pasarela

Dentro del directorio pasarela se encontrará la estructura de archivos que maneja django:
-rwxr-xr-x 1 pasarela pasarela   251 abr 29 15:32 manage.py
drwxr-xr-x 3 pasarela pasarela  4096 abr 29 16:49 pasarela
-rw-rw-rw- 1 pasarela pasarela 69632 abr 29 16:59 pasarela.db
  

Luego dentro del directorio pasarela se tiene lo siguiente: 
root@pruebas:/srv/django/pasarela/pasarela# ls -l
total 32
-rw-r--r-- 1 pasarela pasarela    0 abr 29 15:32 __init__.py
-rw-r--r-- 1 pasarela pasarela 5464 abr 29 16:46 settings.py
-rw-r--r-- 1 pasarela pasarela  562 abr 29 15:32 urls.py
-rw-r--r-- 1 pasarela pasarela 1138 abr 29 15:32 wsgi.py

Se crea el directorio apps:
root@pruebas:/srv/django/pasarela/pasarela#mkdir -p apps

Dentro del directorio se crea el archivo __init__.py:
root@pruebas:/srv/django/pasarela/pasarela/apps# touch __init__,py

Ahora se crea la aplicación sms: 
root@pruebas:/srv/django/pasarela/pasarela/apps# django-admin startapp sms

El directorio sms contiene lo siguiente: 
root@pruebas:/srv/django/pasarela/pasarela/apps/sms# ls -l
total 24
-rw-r--r-- 1 pasarela pasarela   0 abr 29 15:46 __init__.py
-rw-r--r-- 1 pasarela pasarela  57 abr 29 15:46 models.py
-rw-r--r-- 1 pasarela pasarela 383 abr 29 15:46 tests.py
-rw-r--r-- 1 pasarela pasarela  26 abr 29 15:46 views.py

En este directorio se crea el archivo tasks.py con el siguiente contenido: 
#Se importa Celery
from celery import Celery
#Se instancia Celery importando tasks, se define el broker (quien recibe los procesos en este 
#caso rabbitMQ con el protocolo amgp) y se define el backend (quien recibe los resultado
#del proceso encolado en este caso con rabbitMQ y el protocolo amgp) 
app = Celery('tasks', broker='amqp://',backend='amqp')

#Se usa el decorador task de app y se define la suma.
@app.task
def add(x, y):
    return x + y

Lo que se va a hacer es manejar la suma por medio de la cola de rabbitMQ.

Ahora se revisa el contenido del archivo settings.py (lo más relevante): 

Se va a trabajar por los momentos con la base de datos sqlite:
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
        'NAME': 'pasarela.db',                      # Or path to database file if using sqlite3.
        'USER': '',                      # Not used with sqlite3.
        'PASSWORD': '',                  # Not used with sqlite3.
        'HOST': '',                      # Set to empty string for localhost. Not used with sqlite3.
        'PORT': '',                      # Set to empty string for default. Not used with sqlite3.
    }
}

Se define el uso horario de la aplicación: 
TIME_ZONE = 'America/Caracas'

Se define el idioma: 
LANGUAGE_CODE = 'es-ve'

Se agrega la aplicación djcelery y sms: 
INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # Uncomment the next line to enable the admin:
    'django.contrib.admin',
    'djcelery',
    'pasarela.apps.sms',
    # Uncomment the next line to enable admin documentation:
    # 'django.contrib.admindocs',
)

Ahora queda sincronizar la base de datos (acá se crea la base de datos para django, lo necesario para django celery y la cuenta del administrador de la aplicación): 
root@pruebas:/srv/django/pasarela# python manage.py syncdb


Ahora lo que queda es iniciar celery: 
Se inicia manage.py de djanco diciendole que va a iniciar celery en nivel de información y con dos procesos concurrentes.
ernesto@pruebas:/srv/django/pasarela$ python manage.py celeryd -E -l info -c 2
/usr/lib/python2.7/dist-packages/djcelery/loaders.py:108: UserWarning: Using settings.DEBUG leads to a memory leak, never use this setting in production environments!
  warnings.warn("Using settings.DEBUG leads to a memory leak, never "
[2014-04-29 17:22:37,272: WARNING/MainProcess]  

 -------------- celery@pruebas v2.5.3
---- **** -----
--- * ***  * -- [Configuration]
-- * - **** ---   . broker:      amqp://guest@localhost:5672//
- ** ----------   . loader:      djcelery.loaders.DjangoLoader
- ** ----------   . logfile:     [stderr]@INFO
- ** ----------   . concurrency: 2
- ** ----------   . events:      ON
- *** --- * ---   . beat:        OFF
-- ******* ----
--- ***** ----- [Queues]
 --------------   . celery:      exchange:celery (direct) binding:celery
                  

[Tasks]
  . pasarela.apps.sms.tasks.add

[2014-04-29 17:22:37,293: INFO/PoolWorker-1] child process calling self.run()
[2014-04-29 17:22:37,297: INFO/PoolWorker-2] child process calling self.run()
[2014-04-29 17:22:37,307: WARNING/MainProcess] celery@pruebas has started.


En otra consola se inicia el shell del manage.py de django: 
Se importa la función add que se definió en el archivo tasks.py.

ernesto@pruebas:/srv/django/pasarela$ python manage.py shell
Python 2.7.3 (default, Mar 13 2014, 11:03:55) 
[GCC 4.7.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)

>>> from pasarela.apps.sms.tasks import add
#Se realiza la suma de 11+19
>>> result = add.delay(11, 19)
#Se obtiene el resultado:
>>> result.get()
30
#Se verifica si ya celery está listo para recibir más procesos:
>>> result.ready()
True
#Se verifica si ya el proceso termino.
>>> result.successful()
True

En la salida de celery se tiene lo siguiente: 
[2014-04-29 17:25:36,200: INFO/MainProcess] Got task from broker: pasarela.apps.sms.tasks.add[64275874-545b-4ab9-bfc8-ea38904d2320]
[2014-04-29 17:25:36,228: INFO/MainProcess] Task pasarela.apps.sms.tasks.add[64275874-545b-4ab9-bfc8-ea38904d2320] succeeded in 0.0155239105225s: 30


Como se muestra el celery recibe y devuelve el resultado de la suma con su tiempo de ejecución, si se tiene varios equipos con rabbitMQ se puede mejorar significativamente los procesos de ejecución de funciones en django.

Pueden revisar la siguiente presentación.


19 abr. 2014

Reproductor de vídeo con python y kivy

De los dos artículos anteriores, el introductorio con manejo de etiquetas; y el de botón y scatter ahora explicaré algo más completo, la reproducción de un vídeo.

Este artículo se basa en inglés sobre un reproductor de vídeo y scatter con kivy.

Ahora se mostrará como asociar un evento (una función) al botón al darle clip.

A continuación el código del reproductor:
#Se importa kivy y se valida que es la versión 1.8.0

import kivy

kivy.require('1.8.0')



#Se importa la clase App

from kivy.app import App

#Se importa Button, Widget y VideoPlayer

from kivy.uix.button import Button

from kivy.uix.widget import Widget

from kivy.uix.videoplayer import VideoPlayer



#Se instancia Widget y Button.

parent= Widget()

button= Button()



#Se crea la clase MyApp que hereda de App

class MyApp(App):

    #Se define el método build.

    def build(self):

         #Se instancia Button con su texto y tamaño del  mismo.

         button = Button(text='Reproductor Video', font_size=14)

         #Se asocia al boton la función on_button_press al argumento on_press

         button.bind(on_press=on_button_press)  

         #Se agrega button a la instancia de parent

         parent.add_widget(button) #agrega el boton

         #Retorna parent 

         return parent



#Se define la función on_button_press

def on_button_press(self):

        #Se crea la instancia de VideoPlayer donde se le pasa como argumento la fuente de vídeo,

        #el estado y la opción allow_stretch True.

        video= VideoPlayer(source='Tribus-SethGodin.webm', state='play',options={'allow_stretch': True})

        #Se agrega el vídeo a la instancia parent

        parent.add_widget(video) #add videoplayer

        #Se retorna parent

        return parent

     

if __name__ == '__main__':

    MyApp().run()



El resultado de reproducir el script se muestra en el siguiente vídeo:
Nota: El vídeo del reproductor no tiene problemas con el audio, el problema generador de screencast no he logrado hacer que grabe audio :-/ ...


Si desea conocer más sobre el widget VideoPlayer puede ver el siguiente enlace.

Widget button y Scatter (widget de comportamiento) en kivy.

El artículo anterior se mostró una pequeña introducción de kivy y como tener una ventana con una etiqueta.

También se mostró como separar el layaout de la programación.
A continuación se mostrará dos ejemplos, uno utilizando el widget button y otro con una etiqueta dentro de un Scatter (widget de comportamiento que permite drag, rotar y escalar un widget).
El artículo se basa del vídeo tutorial No.1 del equipo de kivy:

El código python del ejemplo del botón se muestra a continuación:
#!/usr/bin/env python

#Se importa kivy

import kivy

#Se valida que se tiene la versión 1.8.0 de kivy

kivy.require('1.8.0')



#Se importa la clase App y el widget Button

from kivy.app import App

from kivy.uix.button import Button



#Se crea la clase Hola3App que hereda de App

class Hola3App(App):

    #Se define el método build que devuelve el widget Button sin argumentos.

    def build(self):

        return Button()





if __name__ == "__main__":

    #Se crea la instancia de la clase y se ejecuta.

    Hola3App().run()

El archivo hola3.kv con el layaout de hola3.py se muestra a continuación:
Se define el texto del botón, el color será azul y su tamaño.
# File name: hello2.kv

#:kivy 1.8.0

<Button>: 

  text: 'Hola mundo v3!'

  font_size:100

  background_color:(0,0,1,1)

El siguiente vídeo muestra la ejecución del script:


El siguiente ejemplo es del uso del widget de comportamiento Scatter, este widget permite realizar drag, cambiar de escala de lo que contenga el widget y rotar, aparte es un wiget que soporta multitouch.

El código de hola4.py se muestra a continuación:

#!/usr/bin/env python

#se importa kivy

import kivy

#Se válida que la versión de kivy sea la 1.8.0

kivy.require('1.8.0')



#Se importa la clase App

from kivy.app import App

#Se importa Scatter, Label y FloatLayout

from kivy.uix.scatter import Scatter

from kivy.uix.label import Label

from kivy.uix.floatlayout import FloatLayout





#Se crea la clase Hola4App que hereda de App

class Hola4App(App):

    #Se define el método build

    def build(self):

        #Se instancia al objeto FloatLayout

        f = FloatLayout()

        #Se instancia al objeto Scatter

        s = Scatter()

        #Se instancia al objeto Label con el texto y el tamaño.

        l = Label(text="Hola v4",font_size=100)

        #Se agrega a FloatLayout la instancia del widget scatter

        f.add_widget(s)

        #Se agrega el widget Label a Scatter

        s.add_widget(l)

        #Se retorna la instancia de FloatLayout

        return f





if __name__ == "__main__":

    #Se ejecuta run de la instancia del objeto Hola4App 

    Hola4App().run()



El siguiente vídeo muestra el resultado de la ejecución del script:



18 abr. 2014

Desarrollo de aplicaciones multiplataforma con python y kivy

Kivy es una librería que permite usar python para crear aplicaciones para el escritorio y dispositivos móviles con soporte multitouch.

Es multiplataforma:
Soporta:

  • Linux (Debian, Ubuntu, Fedora, ArchLinux, etc). 
  • Raspberry Pi
  • IOS de Apple
  • OSX de Apple
  • Windows
  • Android


Para descargarlo pueden visitar la página del proyecto.

En el caso de Debian en la versión estable (wheezy) toca instalarlo vía pip o bajar la fuente del enlace anterior.
pip install kivy (aplica para cualquier versión de Debian o distro basada en Debian).

Para el caso de la versión de pruebas (Jessie) ya viene en los repositorios de Debian:
apt-get install python-kivy python3-kivy python-kivy-examples


Para más información del proyecto pueden visitar su wiki en github.

El ejemplo que se explicará es el de una ventana con una etiqueta que diga "Hola Mundo!", la primera versión contendrá todo el código en un script en python y la siguiente versión se separa el script python de las características de los widgets.

Código del hola mundo versión 1:
#!/usr/bin/env python

#Se importa kivy

import kivy

#Se válida que se esté usando la versión 1.8.0 de kivy

kivy.require('1.8.0')



#Se importa la clase App y el widget Label

from kivy.app import App

from kivy.uix.label import Label



#Se crea la clase HolaApp que hereda de App

class HolaApp(App):

    #Se crea la función build

    def build(self):

        #Se retorna el widget Label con un texto y el tamaño del mismo

        return Label(text='Hola Mundo!!!',

                     font_size=100)







if __name__ == "__main__":

    #Se instancia la clase y se ejecuta

    HolaApp().run()

El script se llamará hola.py.

A continuación se muestra la figura del resultado de la ejecución del script:



La versión 2 del hola mundo se diferencia del primero por que ahora se manejará dos archivos, hola2.py y hola2.kv, su nombre tiene relación con el nombre de la clase que se crea y es la forma como python y kivy reconoce los archivos que necesita ejecutar.

A continuación el código de hola2.py:
#!/usr/bin/env python

#Se imposta kivy

import kivy

#Se valida que se use la versión 1.8.0 de kivy

kivy.require('1.8.0')



#Se importa la case App y el widget Label

from kivy.app import App

from kivy.uix.label import Label



#Se crea la Clase Hola2App que hereda de App

class Hola2App(App):

    #Se define la función build que retorna el widget Label

    #Esta vez sin ningún argumento ya que será manejado por el archivo hola2.kv

    def build(self):

        return Label()





if __name__ == "__main__":

    #Se instancia la clase Hola2App y se ejecuta.

    Hola2App().run()



Código del archivo hola2.kv:
# File name: hola2.kv

#:kivy 1.8.0

<Label>:

  text: 'Hola mundo v 2!'

  font_size:100

Este archivo tiene la descripción de la etiqueta, con el parámetro text y font_size, esto facilita tener ordenada y cambiar los parámetros de los widgets sin tener que modificar código.

El resultado de la ejecución del script hola2.py se muestra en la siguiente figura:


En siguientes artículos se seguirá explicando el uso de los widgets y como instalar la librería en Android y crear aplicaciones para Android.


AddThis