21/9/2014

Utilizando mongodb por medio de python.

En artículo anterior explique como iniciar un servidor mongodb y unos ejemplos de uso.

Ahora explicaré el uso de mongodb por medio de python con pymongo.

Para instalar pymongo se tiene dos vías, una a lo distribuciones basadas en Debian:
Para Python 2:
apt-get install python-pymongo python-pymongo-doc python-pymongo-ext
Para Python 3:
apt-get install python3-pymongo python3-pymongo-ext

O con pip:
pip install pymongo

La idea es mostrar como se conecta python a mongodb, como se crea la base de datos, como se insertan documentos, se listan y se borran.

A continuación el código del script:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from pymongo import Connection
#conexion a mongodb
con = Connection('localhost')
#Listas las base de datos
print(con.database_names())
#Conectarse a la base de datos pruebas.
db = con.pruebasdb
#Se crea una coleccion que se llama estados
estados = db.estados
#Lista de estados.
listaestados = [{'nombre': 'Carabobo', 'region':'centro','ciudades': 10},
{'nombre': 'Lara', 'region':'centro occidente','ciudades': 8},
{'nombre':'Merida','region':'andes','ciudades':6},
{'nombre':'Aragua','region':'centro','ciudades':13}]
#Insertar los datos en el documento
for lista in listaestados:
estados.insert(lista)
#Se lista un simple documento
print("Se lista un simple documento")
print("---------------------------")
print(estados.find_one({'nombre':'Carabobo'}))
#Se remueve el documento del estado Carabobo
estados.remove({"nombre" : "Carabobo"})
print("Se lista todos los documentos")
print("-----------------------------")
#Listar los datos
for i in estados.find():
print(i)
#
print("Se cuenta la cantidad de documentos")
print("-----------------------------------")
print(estados.count())
#Borrar todos los datos
estados.drop()
con.close()
El resultado de la ejecución se muestra a continuación:

['otradb', 'pruebas', 'pruebasdb', 'nuevadb', 'local']

Se lista un simple documento

---------------------------

{'_id': ObjectId('541f0ce123d1e1604504e0e6'), 'region': 'centro', 'nombre': 'Carabobo', 'ciudades': 10}

Se lista todos los documentos

-----------------------------

{'_id': ObjectId('541f0ce123d1e1604504e0e7'), 'region': 'centro occidente', 'nombre': 'Lara', 'ciudades': 8}

{'_id': ObjectId('541f0ce123d1e1604504e0e8'), 'region': 'andes', 'nombre': 'Merida', 'ciudades': 6}

{'_id': ObjectId('541f0ce123d1e1604504e0e9'), 'region': 'centro', 'nombre': 'Aragua', 'ciudades': 13}

Se cuenta la cantidad de documentos

-----------------------------------

3





Participación en el Primer Encuentro sobre Conocimiento Libre, Comunidad y Democracia

El día 18 y 19 de este mes se realizó el Primer Encuentro sobre Conocimiento Libre, Comunidad y Democracia.

Participe en el foro de Infogobierno.

A continuación les dejo la presentación que hice en el evento.


13/9/2014

Instalar un servidor mongodb en Debian


Mongodb es una base de datos no relacional (nosql) que se está usando mucho en este momento y es software libre.


Para instalar el servidor y el cliente en Debian se ejecuta:
apt-get install mongodb mongodb-clients mongodb-server

Configuración de mongodb.
La configuración del servidor mongodb se encuentra en /etc/mongodb.conf:
# donde se almacena los datos
dbpath=/var/lib/mongodb
#El log se guarda en el archivo:
logpath=/var/log/mongodb/mongodb.log
#Se define agregar info al log.
logappend=true
#Se define la ip y el puerto donde escuchará mongodb
bind_ip = 127.0.0.1
port = 27017
# Se habilita el journaling, http://www.mongodb.org/display/DOCS/Journaling
journal=true
# Se habilita o deshabilita la autenticación, por defecto está deshabilitado
#noauth = true
#auth = true

Iniciar el servicio (como me encuentro usando systemd se usa el comando systemctl):
Iniciar el servicio:
systemctl start mongodb.service
Estatus del servicio:
systemctl status mongodb.service 
mongodb.service - An object/document-oriented database
   Loaded: loaded (/lib/systemd/system/mongodb.service; disabled)
   Active: active (running) since sáb 2014-09-13 21:08:10 VET; 2s ago
     Docs: man:mongod(1)
 Main PID: 5542 (mongod)
   CGroup: /system.slice/mongodb.service
           └─5542 /usr/bin/mongod --config /etc/mongodb.conf

sep 13 21:08:10 grievous systemd[1]: Starting An object/document-oriented database...
sep 13 21:08:10 grievous systemd[1]: Started An object/document-oriented database.
sep 13 21:08:10 grievous mongod[5542]: all output going to: /var/log/mongodb/mongodb.log


Revisar que el proceso esté arriba:
ps aux | grep mongo
mongodb   5542  0.4  0.9 385176 38512 ?        Ssl  21:08   0:01 /usr/bin/mongod --config /etc/mongodb.conf

Revisar que los puertos estén abiertos:
netstat -anp  | grep mongo
tcp        0      0 127.0.0.1:27017         0.0.0.0:*               LISTEN      5542/mongod     
tcp        0      0 127.0.0.1:28017         0.0.0.0:*               LISTEN      5542/mongod     
unix  2      [ ACC ]     STREAM     LISTENING     138512   5542/mongod         /tmp/mongodb-27017.soc

Conectarse al servidor mongodb:
#mongo
MongoDB shell version: 2.4.10
connecting to: test
>

Seleccionar una base de datos:
> db
test

Muestra la lista de nombres de la base de datos: 
> show dbs 
local 0.078125GB

Cambiarse a una nueva base de datos pruebas
> use pruebas
switched to db pruebas

Confirmar que la sesión se cambio a la base de datos pruebas:
> db
pruebas

Mostrar la ayuda de mongo:
> help
db.help()                    help on db methods
db.mycoll.help()             help on collection methods
sh.help()                    sharding helpers
rs.help()                    replica set helpers
help admin                   administrative help
help connect                 connecting to a db help
help keys                    key shortcuts
help misc                    misc things to know
help mr                      mapreduce

show dbs                     show database names
show collections             show collections in current database
show users                   show users in current database
show profile                 show most recent system.profile entries with time >= 1ms
show logs                    show the accessible logger names
show log [name]              prints out the last segment of log in memory, 'global' is default
use <db_name>                set current database
db.foo.find()                list objects in collection foo
db.foo.find( { a : 1 } )     list objects in foo where a == 1
it                           result of the last line evaluated; use to further iterate
DBQuery.shellBatchSize = x   set default number of items to display on shell
exit                         quit the mongo shell

Crear 2 documentos j y k usando la sintaxis de javascript: 
> j = {name: 'mongo'}
{ "name" : "mongo" }
> k = {x:3}
{ "x" : 3 }

Insertar los documentos j y k en la colección testData:
> db.testData.insert( j )
> db.testData.insert( k )

Confirmar que la colección testData existe:
> show collections
system.indexes
testData

Confirmar que los documentos existen en la colección testData:
> db.testData.find()
{ "_id" : ObjectId("5414fdec21061053051d13e8"), "name" : "mongo" }
{ "_id" : ObjectId("5414fdf721061053051d13e9"), "x" : 3 }

Imprimir el json de j y k:
> printjson(j)
{ "name" : "mongo" }
> printjson(k)
{ "x" : 3 }


En próximo artículo explicaré como trabajar con mongo desde python.


17/8/2014

Urwid librería ncurses para hacer aplicaciones de texto para la consola (parte 1).

Este artículo es el inicio de una serie de artículos sobre desarrollo de aplicaciones de texto para la consola.

Existe en python la librería ncurses pero si se quiere una librería más completa se tiene a urwid.

 Para instalarla en Debian por apt-get:
apt-get install python-urwid python3-urwid

O con pip:
pip install urwid

En este primer artículo simplemente se hará el típico hola mundo.

#!/usr/bin/env python



#Se importa la libreria urwid

import urwid



#Se define el texto a publicar

txt = urwid.Text(u"Hola Mundo!!! ", align='center')

#Se define el lugar donde se publica el texto

fill = urwid.Filler(txt, 'top')





#Se define una funcion donde si se presiona la tecla q o Q se sale del programa.

def exit(input):

    if input in ('q', 'Q'):

        raise urwid.ExitMainLoop()



#Se define un lazo donde se le pasa fill y la funcion de salida

loop = urwid.MainLoop(fill, unhandled_input=exit)

#Se ejecuta el lazo

loop.run()



Al ejecutar el script se tiene lo que muestra la siguiente figura:

Al presionar la tecla q se sale del programa.

27/7/2014

Tarea de Django-Celery desde un URL.

En el artículo anterior (Restful API con Django-tastypie y Django-Celery ) se mostró como pasarle datos a django desde su interprete de comandos, en este arículo se explicará como pasarlo desde el navegador o un script en python que lo haga por nosotros.

Recapitulando la parte del código que captura el url se maneja en el archivo urls.py que tiene lo siguiente:
url(r'^enviar/', celery_views.task_view(tasks.RecNums)),

Al abrir el url http://127.0.0.1:8080/ se tiene la siguiente imagen de una captura de pantalla:


Como django está en modo debug nos muestra esa página, la cual dice los urls tienen que manejar el orden que se muestra (admin,api, enviar y los otros dos con identificación de procesos).

Django devuelve mensaje 404, como se muestra a continuación:


En el artículo anterior se uso el interprete de comandos de Django con el siguiente código:
>>>import json
>>>from pasarela.apps.sms.tasks import RecNums
>>>datos = '{"numeros": ["34225673531", "34265673531", "34365673531", "34245673531", "34345673531"], "mensaje": "xyzw", "cantnumeros": 5,"evento":6}'
>>>resultado = RecNums.delay(datos)
>>>resultado.get()
'Se enviaron: 5'

Como se ve, datos es un string que contiene entre llaves un conjunto de datos, lo que está entre las comillas es un diccionario de python, por esa razón cuando la función RecNums recibe datos convierte el string que en sí es un json, el cual pasa de ser un tipo str a un tipo dict:
>>>type(datos)
str
>>>diccionario = json.loads(datos)
>>>type(diccionario)
dict

Ahora bien, se tiene que el url a usar debería ser algo como:
http://127.0.0.1/enviar/?datos=
Donde lo que va luego del igual debería ser el jason que se está pasando a la función ( '{"numeros": ["34225673531", "34265673531", "34365673531", "34245673531", "34345673531"], "mensaje": "xyzw", "cantnumeros": 5,"evento":6}' ). Este es el json, el problema que se tiene es que el url no está bien construído, para ello se mostrará en un script python como se abre el url pasandole el json (se muestra en la siguiente figura):


A continuación se muestra el log de celery donde se ve que procesa las tareas del envío de cada mensaje a su número celular respectivo:

Y para terminar se muestra el log de Django al recibir el URL que se paso en el script donde devuelve el código 200:

Así que para poder pasar por medio de un url sus parámetros se tiene que realizar una conversión a formato url de los datos que se tienen en el json.

19/7/2014

Restful API con Django-tastypie y Django-Celery

Continuando con los artículos sobre django, en este caso se usará django-celery para envío de mensajes a un servidor kannel (servidor sms) y django-tastypie para mostrar el resultado por medio de API rest full con json.
En los dos artículos anteriores Manejo de colas de RabbitMQ en django con django-celery y Restfult API con django tastypie se tiene la base de este artículo. Este artículo se basa en el ejemplo de la mensajería sms del artículo de Restfult API con django tastypie (todo el proceso de creación del proyecto y de la aplicación sms fue explicada en ese artículo).

La idea ahora es poder mostrar los sms listados por evento, no es necesario estar creando una tabla que herede de las otras dos, esto será posible gracias a tastypie en una consulta del json.

Se usará como base de datos sqlite3. A continuación se muestra el archivo settings.py:
#Archivo settings.py:
#Configuración de la base de datos sqlite3
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
        'NAME': 'sms.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.
    }
}





#Zona horaria y localización


TIME_ZONE = 'America/Caracas'




LANGUAGE_CODE = 'es-ve'


#Aplicaciones instaladas (djcelery,pasarela.apps.sms,tastypie y south)
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',
    'tastypie',
    'south',
    # Uncomment the next line to enable admin documentation:
    # 'django.contrib.admindocs',
)


#Conexión al servidor kannel 
direccion = "127.0.0.1"
portsms = "13013"
portadmin  = "13000"
usuario = "kannel"
clave = "kannel"
----------------------

El archivo pasarela/apps/sms/models.py:
#Archivo pasarela/apps/sms/models.py
from django.db import models

#Se crea la tabla Evento que contiene los campos evento (número del evento), el estatus del mismo (si se #termino el evento o no y si no termino fue por falla o no.
class Evento(models.Model):
    evento = models.IntegerField(primary_key=True)
    estatus = models.BooleanField(default=False)

    def __unicode__(self):
       evento = "Evento: %s, Estatus: %s , Falla: %s" %(self.evento,self.estatus,self.falla)
       return evento

#Se crea la tabla HistoricoSMS donde se tiene un campo
#foreignkey de la tablla evento, el mensaje, el número de celular, el estatus si se envío o no o si fallo.
class HistoricoSMS(models.Model):
    evento = models.ForeignKey(Evento)
    mensaje = models.CharField(max_length=150)
    numcel = models.CharField(max_length=11)
    estatus = models.BooleanField(default=False)

    def __unicode__(self):
        mensaje = "%s, %s, %s, %s, %s"  %(self.evento.evento,self.numcel,self.estatus, self.mensaje,self.falla)
        return mensaje
--------------------


Archivo pasarela/apps/sms/admin.py:
#Archivo pasarela/apps/sms/admin.py
#En este archivo se define que las tablas Evento e HistoricoSMS se puedan visualizar desde la #administración de django.
from django.contrib  import admin
from pasarela.apps.sms.models import HistoricoSMS,Evento


admin.site.register(HistoricoSMS)
admin.site.register(Evento)


Archivo pasarela/apps/sms/api.py (archivo para crear el api restful de django-tastypie), en este caso tiene varias modificaciones con respecto al artículo anterior sobre tastypie, ahora se agrega la variable filtering(más información sobre filtering en el siguiente enlace) que es un diccionario en cada recurso:

#Se importa de tastypie.resources ModelResource, ALL y ALL_WITH_RELATIONS
from tastypie.resources import ModelResource, ALL, ALL_WITH_RELATIONS
#Se importa del modelo HistoricoSMS y Evento
from .models import HistoricoSMS,Evento
#Se importa fields de tastypie
from tastypie import fields


class EventoResource(ModelResource):
    class Meta:
        queryset =Evento.objects.all()
        resource_name = 'evento'
#Se muestra todos los eventos
        filtering = {
            'evento': ALL,
        }


class SMSResource(ModelResource):
    evento = fields.ForeignKey(EventoResource, 'evento')
    class Meta:
        queryset = HistoricoSMS.objects.all()
        resource_name = 'sms'
#De evento se muestra todo con relación a el. de estatus se muestra exactamente lo que se necesita,
#acá también se puede usar para las consultas: ['exact', 'range', 'gt', 'gte', 'lt', 'lte'].
        filtering = {
            'evento': ALL_WITH_RELATIONS,
            'estatus':['exact'],
        }
------------------------


El archivo que permite manejar las tareas de celery pasarela/apps/sms/tasks.py :
#Archivo pasarela/apps/sms/tasks.py
from celery import Celery
app = Celery('tasks', broker='amqp://',backend='amqp')
from urllib2 import urlopen
from urllib import urlencode
import urllib2
import json
from time import sleep
from django.conf import settings
from pasarela.apps.sms.models import HistoricoSMS,Evento



#Conexión al servidor kannel (está configuración se puede pasar al archivo settings.py y usarla desde allí
direccion = settings.direccion
portsms = settings.portsms
portadmin  = settings.portadmin
usuario = settings.usuario
clave = settings.clave

#Se define una tarea para celery por medio del decorador @task, se recibe un json para luego sea
# procesado.
@app.task
def RecNums(datos):
    #Se toma el json y se convierte en un diccionario
    diccionario = json.loads(datos)
    #Se instancia la clave Evento(tabla Evento).
    evento = Evento()
    #Se asigna cada variable del diccionario para trabajarlos directamente
    for clave in diccionario.keys():
        if clave == 'mensaje':
            mensaje = diccionario[clave]
        elif clave == 'numeros':
            numeros = diccionario[clave]
        elif clave == 'cantnumeros':
            cantnum = int(diccionario[clave])
        elif clave == 'evento':
            eventoid = int(diccionario[clave])
    #Se crea una lista para agregar todos los números de celular a dicha lista
    lista = []
    for num in numeros: lista.append(str(num))
    #A evento.evento se le asigna el id del evento.
    evento.evento = eventoid
    #Si la lista es distinta a la variable cantnum se envía un mensaje de error, si no se procesa la lista
    if len(lista) == cantnum:
        #Se envía al proceso Enviar (de celery) cada mensaje con su número celular, esperando un segundo 
        #para procesar el siguiente.
        for i in range(len(lista)):
            #Se Envía el mensaje pasando el evento, el número celular de la lista y el mensaje
            resultado = Enviar.delay(evento,lista[i],mensaje)
            sleep(1)
        #Se asigna True al estatus del evento al terminar de procesar los mensajes.
        evento.estatus = True
        #Se salva los valores en la tabla Evento.
        evento.save()
        return "Se enviaron: %s" %cantnum
    else:
        evento.estatus = False
        evento.save()
        return "Error en la recepcion de los datos"


#Se crea la tarea de celery Enviar donde recibe el número del evento, el número de celular y la cantidad de 
#intentos para enviar el sms el valor por defecto es 5.
@app.task
def Enviar(evento,numcel,mensaje,intentos=5):
    #Se instancia la clase HistoricoSMS que maneja dicha table de models.py
    historico = HistoricoSMS()
    #Se le da forma de códificación url al mensaje para eliminar los espacios en blanco del mismo.
    form = urlencode({'text': mensaje})
    #se define la variable Url donde se tiene el url del servidor kannel donde se le pasa al texto
    #el usuario, la clave, el número celular y el mensaje a enviar.
    Url = "http://%s:%s/cgi-bin/sendsms?username=%s&password=%s&to=%s&text=%s" % (direccion,portsms,usuario,clave,numcel,form)
    #Se maneja una excepción si hay un error de comunicación http.
    try
        #Se abre el Url
        f = urlopen(Url,timeout=10)
        #Se asigna los valores numcel, mensaje y evento al objeto historico.
        historico.numcel = numcel
        historico.mensaje = mensaje
        historico.evento = evento
        #Se lee el resultado de abrir el url, si se tiene el mensaje  de encolado para enviar más tarde
        #se asigna el estatus False y se salva devolviendo el mensaje de no enviado 
        if f.read() <> '3: Queued for later delivery':
            historico.estatus = False
            historico.save()
            return 'Mensaje no enviado a: %s' %numcel
        else:
            #Se envío el mensaje se coloca el estatus en True y se salva en la tabla
            historico.estatus = True
            historico.save()
            #Se devuelve el mensaje de mensaje enviado.
            return 'Mensaje enviado a: %s' %numcel
    except (urllib2.URLError,urllib2.HTTPError),exc:
    #Si hay una excepción de error http se reintenta el envío llamando de forma 
    #concurrente a esta misma función reduciendo el valor de los intentos, 
    #cuando llegue a cero el número de intentos se devuelve un mensaje de no enviado 
        if intentos <> 0:
            Enviar(evento,numcel,mensaje,intentos-1)
        else:
            #Se salva los valores en la tabla y devuelve el mensaje de sms no enviado
            historico.numcel = numcel
            historico.mensaje = mensaje
            historico.evento = evento
            historico.estatus = False
            historico.save()
            return'No hay conexion al kannel por puerto o IP, el numero que no se procesaron es: %s' %numcel
----------------------------

A continuación se muestra el contenido del archivo pasarela/urls.py el cual contiene el acceso al API como se explico en el artículo anterior y ahora tiene el acceso a la función que permite recibir los datos para enviar (estos datos se pasan por un json):
#Archivo pasarela/urls.py
from django.conf.urls import patterns, include, url

from djcelery import views as celery_views
from pasarela.apps.sms import tasks

# Uncomment the next two lines to enable the admin:
from django.contrib import admin
admin.autodiscover()

#Se importa Api de tastypie.api
from tastypie.api import Api
from apps.sms.api import SMSResource,EventoResource

v1_api = Api(api_name='v1')
#Se registra los recursos en la instancia del api.
v1_api.register(SMSResource())
v1_api.register(EventoResource())

urlpatterns = patterns('',
    # Uncomment the next line to enable the admin:
    url(r'^admin/', include(admin.site.urls)),
    #Acceso al API
    url(r'^api/', include(v1_api.urls)),
    #se define enviar que usa la vista de tareas de celery para ejecutar tasks.RecNums
    url(r'^enviar/', celery_views.task_view(tasks.RecNums)),
    #Consulta a celery si la tarea se ejecuto sin problemas
    url(r'^(?P<task_id>[\w\d\-]+)/done/?$', celery_views.is_task_successful,
        name="celery-is_task_successful"),
    #Consulta de celery para ver el estatus de la tarea
    url(r'^(?P<task_id>[\w\d\-]+)/status/?$', celery_views.task_status,
        name="celery-task_status"),
)
-----------------------


La base de datos se sincroniza como se explico en los artículos anteriores, ahora toca ejecutar en modo pruba django-celery, django como tal y abrir un interprete de comandos para django.

Ejecución de django-celery con dos colas de rabbitMQ y en modo info:
python manage.py celeryd -E -l info -c 2
A continuación captura de pantalla de la ejecución:

Note que se tienen 2 tareas en el celery:
  • pasarela.apps.sms.tasks.Enviar
  • pasarela.apps.sms.tasks.RecNums
Ahora se ejecuta django en el 127.0.0.1 y puerto 8080:
python manage.py runserver 127.0.0.1:8080
A continuación una captura de pantalla de la ejecución del comando:

Y por último se ejecuta el comando para tener un shell interfactivo de django:
python manage.py shell
A continuación la captura de pantalla: 

Ahora se ejecutará en el shell una prueba que simula la recepción por parte de la tarea del json que recibe de una aplicación externa para enviar los sms:
Hay que acotar que los números de celular de la prueba no corresponden a números  reales o de proveedores de teléfonía celular.
Ahora para complementar la idea de consulta API restful por json del artículo anterior la idea es listar los números celulares que se enviaron del evento 6, en este caso se usará el programa curl:
curl http://127.0.0.1:8080/api/v1/sms/?evento=6
El resultado es:
{"meta": {"limit": 20, "next": null, "offset": 0, "previous": null, "total_count": 5}, "objects": [{"estatus":true, "evento": "/api/v1/evento/6/", "id": "21", "mensaje": "Esta es una prueba de sms", "numcel": "14225673531", "resource_uri": "/api/v1/sms/21/"}, {"estatus":true, "evento": "/api/v1/evento/6/","id": "22", "mensaje": "Esta es una prueba de sms", "numcel": "142165673531", "resource_uri": "/api/v1/sms/22/"}, {"estatus": true, "evento": "/api/v1/evento/6/","id": "23", "mensaje": "Esta es una prueba de sms", "numcel": "14265673531", "resource_uri": "/api/v1/sms/23/"}, {"estatus": true, "evento": "/api/v1/evento/6/", "id": "24", "mensaje": "Esta es una prueba de sms", "numcel": "14145673531", "resource_uri": "/api/v1/sms/24/"}, {"estatus": true, "evento": "/api/v1/evento/6/","id": "25", "mensaje": "Esta es una prueba de sms", "numcel": "14245673531", "resource_uri": "/api/v1/sms/25/"}]}

Por último se muestra la captura de pantalla de lo que se genera en el djcelery:

En el próximo artículo se mostrará el acceso a las tareas de celery desde el URL definido en urls.py.

AddThis