Automágica: durante 2017 estoy trabajando bastante en Automágica, mi software para editar libros: Más información - Posts relacionados

Python: guardar imagenes en una base de datos Sqlite

Hace un par de días Walter de Nicaragua me consultaba:

Estoy trabajando en una aplicacion para niños en las xo´s , estoy usando sqlite3 para guardar la informacion, y tambien necesito guardar fotos dentro de la base de datos.

Como la fotos seran tomadas con la camara de las propias XO's, asumo no son tan grande en resolucion y tamaño.

Tambien, estuve revizando la lista de correo, pero no hay nada concreto, y a decir verdad recomiendan que guarde una referencia de la imagen en la base de datos, y luego haga una consulta, para recuperarla, pero tambien he visto que tiene sus pro y sus contras.

Esta es la respuesta. Primero necesitamos una tabla con un campo de tipo BLOB donde guardar la imagen:

>>> import sqlite3

>>> conn = sqlite3.connect('/tmp/example')

>>> c = conn.cursor()

>>> c.execute('''create table imagenes (imagen BLOB);''')

Abro una imagen que tengo en disco, mismo directorio que donde ejecute

el intérprete de Python, la cargo en memoria y la guardo en la base.

>>> imgdata = open('tomyNarnia.jpg', 'r').read()

>>> len(imgdata)

1613949

>>> buff = sqlite3.Binary(imgdata)

>>> c.execute('insert into imagenes values(?);', (buff,))

>>> conn.commit()

>>> conn.close()

Luego podemos cerrar el intérprete. Con sqlite3 importado, recuperamos

la imagen.

>>> conn = sqlite3.connect('/tmp/example')

>>> c = conn.cursor()

>>> a = c.execute('select imagen from imagenes')

>>> img = a.fetchone()

>>> len(img)

1

>>> img

(<read-write buffer ptr 0x1c75d60, size 1613949 at 0x1c75d20>,)

El resultado es una tupla, pero como seleccionamos un solo campo de la

tabla, hay un solo elemento.

>>> img = img[0]

>>> len(img)

1613949

Finalmente lo escribimos en una imagen.

>>> f = open('newimage.jpg', 'w')

>>> f.write(img)

>>> f.close()


Un servidor web con pocas líneas de Python

Hoy un amigo necesitaba un servidor web para engañar a un programa. Cada vez que el programa iniciaba, se conectaba con un servidor web para verificar si había actualizaciones disponibles.

El nombre del host a dónde se hacía la petición era leído de un archivo de configuración, por lo que lo podíamos cambiar. El resto solo era levantar un servidor web que responda con la información apropiada.

En la librería estándar de Python tenemos todos los elementos necesarios para realizar la tarea. Luego de probar un poco, el resultado final fue algo como esto:

PORT = 8090

from BaseHTTPServer import BaseHTTPRequestHandler

import SocketServer

import cgi

class MyHandler(BaseHTTPRequestHandler):

def do_GET(self):

    self.send_response(200)

    self.end_headers()

        self.wfile.write('1.9.1')



def do_POST(self):

    form = cgi.FieldStorage(

        fp=self.rfile,

        headers=self.headers,

        environ={'REQUEST_METHOD':'POST',

        'CONTENT_TYPE':self.headers['Content-Type'],

        })

    print form

    self.send_response(200)

    self.end_headers()

    self.wfile.write('1.9.1')

httpd = SocketServer.TCPServer(("", PORT), MyHandler)

print "serving at port", PORT

httpd.serve_forever()

Levanta en localhost y responde la cadena '1.9.1' al ser consultado tanto por GET como por POST y en el caso de POST, también imprime los valores recibidos.


argparse - Command line option and argument parsing

Hoy recibí la última edición de Python Module of the Week, un semanal de Doug Hellmann sobre módulos de Python. Leo:

The argparse module was added to Python 2.7 as a replacement

for optparse.

Nunca había usando optparse hasta hace un par de meses y estaba contento de haberlo empezado a usar, ordenaba mis programas de línea de comando con bonitos parámetros. ¿Tengo que aprender otro sistema totalmente nuevo? Veamos.

Esta es una parte de mi programa original:

from optparse import OptionParser

parser = OptionParser(usage="rpcping ip [-p port] [-r rep] [-s size]", version="rpcping 1.0")



parser.add_option("-p", type="int", dest="port", default=PORT)

parser.add_option("-r", type="int", dest="rep", default=REP)

parser.add_option("-s", type="int", dest="size", default=SIZE)

(options, args) = parser.parse_args()



if not args:

    parser.error("se necesita la direccion ip del servidor como argumento posicional.")

else:

    ip = args[0]



if not 1024 < options.port < 65535:

    parser.error("el puerto debe ser mayor a 1024 y menor a 65535.")

else:

    port = options.port



if not 0 < options.rep < 101:

    parser.error("las repeticiones deben ser mayores a 0 y menores a 101.")

else:

    rep = options.rep



if not 0 < options.size < 10001:

    parser.error("el tamaño deben ser mayores a 0 y menores a 10001.")

else:

    size = options.size

Qué tuve que cambiar para que funcione con argparse:

  • Cambiar nombres; optparse por argparse, OptionParser por ArgumentParser, add_option por add_argument.
  • Cambiar el valor del agumento type de string a un tipo de verdad. En mi caso type="int" por type=int.
  • Si tratamos de obtener el resultado del parsing así (options, args) = parser.parse_args(), vamos a tener un error TypeError: 'Namespace' object is not iterable. El nombre result en el nuevo código está asociado a un objeto del tipo Namespace.
  • optparse no trabaja con argumentos obligatorios, por lo que para el argumento ip había tenido que escribir código extra por mi cuenta. En argparse es más simple.

El resultado es:

from argparse import ArgumentParser

parser = ArgumentParser(description="Realiza ping via RPC contra un servidor", version="rpcping 1.0")



parser.add_argument("ip")

parser.add_argument("-p", type=int, dest="port", default=PORT)

parser.add_argument("-r", type=int, dest="rep", default=REP)

parser.add_argument("-s", type=int, dest="size", default=SIZE)

result = parser.parse_args()



ip = result.ip



if not 1024 < result.port < 65535:

    parser.error("el puerto debe ser mayor a 1024 y menor a 65535.")

else:

    port = result.port



if not 0 < result.rep < 101:

    result.error("las repeticiones deben ser mayores a 0 y menores a 101.")

else:

    rep = result.rep



if not 0 < result.size < 10001:

    parser.error("el tamaño deben ser mayores a 0 y menores a 10001.")

else:

    size = result.size

Parece que con poco esfuerzo se puede migrar de optparse a argparse y luego empezar a estudiar las nuevas funcionalidades que incorpora.

The implementation of argparse supports features that would not have been easy to add to optparse, and that would have required backwards-incompatible API changes, so a new module was brought into the library instead. optparse is still supported, but is not likely to receive new features.

Más en: PyMOTW: argparse - Command line option and argument parsing.


functools.update_wrapper

Este post se alinea con la serie Decoradores en Python (I, II, III) pero no es tan elaborado como para ser Decoradores en Python (IV) :)

Desde Python 2.5, al crear un decorador, se puede utilizar functools.update_wrapper para quela versión decorada de la función, tenga los atributos name, doc, module y dict de la función original.

>>> import functools

>>> def deco(f):

... def inner(a, *kw):

... print "Este decorador no hace nada"

... return f(a, *kw)

... return inner

...

>>> def saludo():

... print "hola"

...

>>> saludo2 = deco(saludo)

>>> saludo2()

Este decorador no hace nada

hola

>>> saludo2.name

'inner'

>>> def deco(f):

... def inner(a, *kw):

... print "Este decorador no hace nada"

... return f(a, *kw)

... return functools.update_wrapper(inner, f)

...

>>> saludo3 = deco(saludo)

>>> saludo3()

Este decorador no hace nada

hola

>>> saludo3.name

'saludo'

>>> saludo = saludo3


Pilas y colas en Python

La forma más directa de tener pilas y colas en Python es usando listas, una de las poderosas estructuras de datos que vienen con el lenguaje.

Una pila es una estructura de datos secuencial en la que el último elemento insertado es el primero en retirarse (LIFO) y puede implementarse directamente con los métodos append y pop.

Una operación común en las pilas es top, que devuelve el elemento en el tope de la pila, pero sin quitarlo; esta operación se puede emular indexando por -1.

>>> pila = [6,7,8]

>>> pila.append(9)

>>> pila

[6, 7, 8, 9]

>>> pila.pop()

9

>>> pila.pop()

8

>>> pila[-1]

7

Implementar una cola requiere algo más. En las colas, a diferencia de las pilas, el primer valor insertado es el primero en quitarse (FIFO).

Un primer intento es implementarla con append y pop(0) que obtiene el primer elemento (o con insert insertando al principio y pop). Pero las listas de Python, si bien están optimizadas para insertar al final y quitar del final, no lo están para insertar al principio o quitar del principio. La solución es utilizar collections.deque que si está implementada con estas operaciones optimizadas.

>>> from collections import deque

>>> cola = deque([1,2,3])

>>> cola.append(4)

>>> cola

deque([1, 2, 3, 4])

>>> cola.popleft()

1

>>> cola.popleft()

2

>>> cola

deque([3, 4])