ORM Python

lasizoillo lasizoillo en gmail.com
Sab Mayo 23 03:16:21 CEST 2009


El día 23 de mayo de 2009 0:10, "Héctor S. Ponce"
<hectorsponce en gmail.com> escribió:
> Hola, soy nuevo con python y quiero adoptar un ORM para trabajar con
> bases de datos. Estoy probando con SQLAlchemy y he hecho un pequeño
> ejercicio de insertar a partir de un archivo delimitado casi 300000
> filas, donde cada fila de texto es:
>
> %18514257%|%1967%|%ABACA%|%GUSTAVO ANDRES%|%EMPLEAD%|%Bº EVA PERON MZ: X
> C:482%|%A%|%DNID%|%    1%|%Capital%|%1001%|%San Luis (1001)%|0|%M%|0
>
> Quiero preguntar:
>
> 1) de los ORM que experiencia tienen en general y que valoración pueden
> hacer de SQL ALchemy.

* Un ORM es la unica manera de no casarte con ningun fabricante
concreto de base de datos. Te permite hacer un codigo más pythonico
cuando accedes a las bases de datos. Y algunos (como sqlalchemy) te
permiten hacer verdadera magia. Por supuesto suelen incluir algunas
limitaciones, pero creo que a pesar de eso merecen la pena.
* SQLAlchemy esta basado en el patrón unidad de trabajo. Muchos ORM
implementan el active record (que es más sencillo de implementar) y
además ponen muchas limitaciones. Los basados en active record suelen
estar bien para hacer wikis en 10 minutos, si necesitas heredar codigo
o conectarte a bases de datos ya creadas ve directamente a sqlalchemy.

> 2) que mejoras se le podría hacer al programa
>

A partir de ahora me voy a poner en modo quisquilloso. Alguna de las
cosas que comentaré estoy seguro que ya las tuviste en cuenta. Otras
supongo que no, es en ellas donde más constructivo voy a ser.


> Gracias
>
>
> Este es el programa:
>
> ##############################
> import time
> from sqlalchemy import *
>
> db = create_engine('sqlite:////home/hectorsponce/sis/python/padron2.sqlite')
>

La cadena de conexión deberias cogerla de un fichero de configuración.
Cambiando la configuración cambias el atacar a tu base de desarrollo
sqlite a la de pre y producción (mysql, postgresql, ... o cualquiera
de las soportadas).

http://docs.python.org/library/configparser.html#module-ConfigParser

> db.echo = False
>
> metadata = MetaData(db)
>
> padron = Table('padron', metadata,
>    Column('matricula', String(40)),
>    Column('clase', String(4)),
>    Column('apellidos', String(40)),

Me apellido "peña". Eso requiere UnicodeString.

>    Column('nombres', String(40)),
>    Column('profesion', String(7)),
>    Column('domicilio', String(34)),
>    Column('analf', String(1)),
>    Column('tipodocumento', String(7)),
>    Column('seccion_nro', String(5)),
>    Column('seccion', String(30)),
>    Column('circuito_nro', String(5)),
>    Column('circuito', String(30)),
>    Column('mesa', String(4)),
>    Column('sexo', String(1)),
>    Column('partido', String(3)))
>

Esa tabla no tiene clave primaria. ¿Como sabes que dos registros son distintos?
La tabla no tiene índices. Con 300.000 registros le vendría bien.
¿Seguro que todos los campos son strings que pueden contener nulos?

> metadata.drop_all()
> padron.create()
>
Supongo que esto lo separaras en un script de inicialización de la
base de datos.
Hay algunas cosas como miruku que ayudan a hacer los cambios que vayas
realizando al esquema, aunque no cubre todas las necesidades y tendras
que hacerte scripts propios.
http://trac.ollix.org/miruku/

> t0=time.clock()
> lista=[[field[1:len(field)-1] for field in line.split('|')] \
>    for line in file('/home/hectorsponce/sis/python/padron/padron.txt')]
>

field[1:len(field)-1] y field[1:-1] son equivalentes. Python permite
indices negativos que empiezan a contar desde el final.

Me acabas de generar en memoria una lista con 300.000 elementos.
Busca información sobre generadores, iteradores, ...
http://www.python.org/dev/peps/pep-0289/

Te pongo una función sin probar ni nada por dar una idea diferente
(convertir lo tuyo en generadores es inmediato):

def parser(file):
  for line in file:
    yield map(lambda field: field[1:-1], line.split('|'))

> data=['matricula',
>     'clase',
>     'apellidos',
>     'nombres',
>     'profesion',
>     'domicilio',
>     'analf',
>     'tipodocumento',
>     'seccion_nro',
>     'seccion',
>     'circuito_nro',
>     'circuito',
>     'mesa',
>     'sexo',
>     'partido']
>
> i=padron.insert()

Estas infrautilizando sqlalchemy. Para hacer ir por ese camino no
necesitas semejantes alforjas. Lo suyo es "complicarse" usando el
mapeo de clases a tablas:
http://www.rmunn.com/sqlalchemy-tutorial/tutorial.html

class Padron(object): pass

Es más, a mi me gusta hacer sobre esa clase un constructor con las
variables obligatorias de la tabla y llenarlo de la lógica de negocio
de la aplicación.

class Padron(object):
  listCols = ['matricula', ... 'partido']
  @classmethod
  def valuesFromList(class_, list):
     newReg = class_()
     for i in range(listCols):
       setattr(newReg, class_.listCols[i], list[i]
     return newReg

mapper(Padron, padron)

Realmente no daría entidad de negocio a la funcion valuesFromList,
pero es un ejemplo didactico de lo que te comento. Jamás volvere a
implementar un importador de datos si puedo usar el del motor de base
de datos que uso.

>
> t1 = time.clock()
> print 'Tiempo de lista:' , t1-t0
> print 'Filas en la lista:' , len(lista)
> print '---------------------------------'
> bulk=[dict(zip(*[l for l in (data, unicode(props))])) for props in lista]

Otra estructura gigantesca en memoria

> t2 = time.clock()
> print 'Tiempo de bulk:' , t2-t1
> print 'Filas en la lista:' , len(bulk)
> print '---------------------------------'
> i.execute(bulk)

for i in parser:
  p = Padron.valuesFromList(i)
  session.add(p)
session.commit()

Puedes hacer commits cada menos tiempo si quieres evitar llenar el
redo log de la base de datos que uses (consultar con su DBA antes de
implementar).

Por supuesto, si el objetivo es hacer una carga masiva de datos me
olvido de toda esta parafernalia y hago los siguientes pasos:
 * Parsear fichero origen a un TSV, CSV, ... que interprete el loader
de mi base de datos (todas tienen al menos uno que va como un tiro)
 * Crear la tabla sin los indices o plantearme el desactivarlos
mientras hago la carga (relentizan las inserciones y suelen crearse
degenerados)
 * Uso el loader de mi base de datos
 * (Re)Creo los indices de la base de datos.

Es lo más rápido, directo y fácil de implementar.

SQLAlchemy se empieza a notar conforme va engordando la cosa. La
gracia esta en meter la logica de negocio (la inteligencia de manejar
los datos) con las funciones que manejan los datos. Así tienes el core
de la aplicación en un sitio y dejas el resto para controlar eventos
de interfaz de usuario y generar la vista. Luego será facil hacer
scripts que usen el modelo para hacer todo tipo de cosas que no
requieran de interfaz.

Por supuesto, todo lo anterior son un monton de opiniones subjetivas.
Aplica tu propio criterio que no pintaba mal lo que habías hecho, ya
me hubiera gustado programar asi en python cuando era nuevo ;-)
_______________________________________________
Lista de correo Python-es 
http://listas.aditel.org/listinfo/python-es
FAQ: http://listas.aditel.org/faqpyes





Más información sobre la lista de distribución Python-es