ORM Python

"Héctor S. Ponce" hectorsponce en gmail.com
Sab Mayo 23 15:53:33 CEST 2009


bueno, gracias por las respuestas y a decir verdad es mi primer programa
y he quedado encantado con el lenguaje.
es la primera vez que posteo y veo que es de mucha ayuda.
estoy con todo de acuerdo (id en la tabla, lo del loader de la db, lo
del fichero de configuración u otro metodo, etc.) son cosas que ya las
había pensado y hasta probado, pero el objetivo del ejercicio era solo
cargar la db.

experimente que mapeando la clase al estilo de:

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

es exageradamente lerdo en comparación a la opción de "saturar la
memoria !!! (proceso con 250 MB en ram)" con:

bulk=[dict(zip(*[l for l in (data, unicode(props))])) for props in lista]

o

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

(aparte que queria generar las variables en una linea de codigo !!! increible!!!)


tengo otras 3 consultas:

1) no entiemdo bien el tema del "yield"
2) no le puse a la tabla un  campo id por no saber que valor pasarle de
parametro al insertar una fila !!! como lo hago?
3) aca va una clase que hice para parsear un archivo de texto con campos
fijos en el que pueden venir varios tipos de registro (en el mismo
archivo diferenciados por una cabecera, o diferentes archivos... como en
el ejemplo). quisiera escuchar cualquier comentario.


################

import sqlite3

class fixed_text_record():
    """
    Clase para proceso de registros de texto fijo
    Modo de uso:
        r=record()
        r.add_field_definition('motos','razon_social',0,69)
        r.add_field_definition('motos','tipo_documento',70,74)
        r.add_field_definition('autos','tipo_documento',70,74)
        for line in file('mi_archivo'):
            row = r.get_fields(line, 'motos')
    """
    def __init__(self):
        self.types={}

    def add_field_definition(self, type, field, begin, end):
        f={}
        if not self.types.has_key(type):
            self.types[type]=f
        else:
            f=self.types[type]
        f[field]={'begin':begin, 'end':end}

    def get_fields(self, record, type):
        values={}
        limits={}
        fields=self.types[type]
        for field in fields.keys():
            limits=fields[field]
            for limit in limits.keys():
               
values[field]=record[limits['begin']:limits['end']].strip().replace("'","")
        return values




database_name = "registro.db"
db_connection = sqlite3.connect(database_name)
db_cursor = db_connection.cursor()

cuantos=0
r=fixed_text_record()

""" Definicion del registro archivo de motos"""
r.add_field_definition('motos','razon_social',0,69)
r.add_field_definition('motos','tipo_documento',70,74)
r.add_field_definition('motos','numero_documento',75,89)
r.add_field_definition('motos','cuit',90,104)
r.add_field_definition('motos','nueva_numero',105,109)
r.add_field_definition('motos','nueva_letra',110,114)
r.add_field_definition('motos','fabricacion',115,120)
r.add_field_definition('motos','marca',121,180)
r.add_field_definition('motos','cilindrada',181,185)
r.add_field_definition('motos','domicilio',187,441)
r.add_field_definition('motos','localidad',442,501)
r.add_field_definition('motos','provincia',502,531)
r.add_field_definition('motos','postal',532,541)
r.add_field_definition('motos','propietario',542,547)

""" Definicion del registro archivo de automotores"""
r.add_field_definition('autos','razon_social',0,69)
r.add_field_definition('autos','tipo_documento',70,74)
r.add_field_definition('autos','numero_documento',75,89)
r.add_field_definition('autos','cuit',90,104)
r.add_field_definition('autos','nueva_letra',105,109)
r.add_field_definition('autos','nueva_numero',110,114)
r.add_field_definition('autos','vieja_letra',115,117)
r.add_field_definition('autos','vieja_numero',118,127)
r.add_field_definition('autos','fabricacion',128,133)
r.add_field_definition('autos','marca',134,193)
r.add_field_definition('autos','cilindrada',194,253)
r.add_field_definition('autos','domicilio',254,508)
r.add_field_definition('autos','localidad',509,568)
r.add_field_definition('autos','provincia',569,598)
r.add_field_definition('autos','postal',599,608)
r.add_field_definition('autos','propietario',609,613)

db_cursor.execute('delete from registro')
db_connection.commit()

""" Proceso Motos.txt"""
for line in file('Motos.txt'):
    cuantos+=1
    row = r.get_fields(line, 'motos')
    tipo_documento=''
    if row["tipo_documento"] == 'U': tipo_documento='DNI'
    elif row["tipo_documento"] == 'C': tipo_documento='LC'
    elif row["tipo_documento"] == 'E': tipo_documento='LE'
    elif row["tipo_documento"] == 'I': tipo_documento='CI'
    else: tipo_documento=row["tipo_documento"]

    propietario=''
    if row["propietario"] == 'R': propietario='RESP. FISCAL'
    elif row["propietario"] == 'T': propietario='TITULAR'
    else: propietario=row["propietario"]

    sql="insert into registro ("
    sql=sql+"razon_social, tipo_documento, numero_documento, "
    sql=sql+"cuit, nueva, fabricacion, marca, cilindrada, domicilio, "
    sql=sql+"localidad, provincia, postal, propietario, tipo) "
    sql=sql+"values ('"+row["razon_social"]+"', '"+tipo_documento+"', '"
    sql=sql+row["numero_documento"]+"', '"+row["cuit"]+"',
'"+row["nueva_numero"]+row["nueva_letra"]+"', '"
    sql=sql+row["fabricacion"]+"', '"+row["marca"]+"',
'"+row["cilindrada"]+"', '"
    sql=sql+row["domicilio"]+"', '"+row["localidad"]+"',
'"+row["provincia"]+"', '"
    sql=sql+row["postal"]+"', '"+propietario+"', 'MOTO')"

    db_cursor.execute(sql)

db_connection.commit()

print str(cuantos) + ' registros de motos insertados.'

""" Proceso Automotores.txt"""
cuantos=0
for line in file('Automotores.txt'):
    cuantos+=1
    row = r.get_fields(line, 'autos')

    documento=''
    if row["tipo_documento"] == 'U': documento='DNI'
    elif row["tipo_documento"] == 'C': documento='LC'
    elif row["tipo_documento"] == 'E': documento='LE'
    elif row["tipo_documento"] == 'I': documento='CI'
    else: documento=row["tipo_documento"]

    propietario=''
    if row["propietario"] == 'R': propietario='RESP. FISCAL'
    elif row["propietario"] == 'T': propietario='TITULAR'
    else: propietario=row["propietario"]

    sql="insert into registro ("
    sql=sql+"razon_social, tipo_documento, numero_documento, "
    sql=sql+"cuit, nueva, vieja, fabricacion, marca, cilindrada,
domicilio, "
    sql=sql+"localidad, provincia, postal, propietario, tipo) "
    sql=sql+"values ('"+row["razon_social"]+"', '"+tipo_documento+"', '"
    sql=sql+row["numero_documento"]+"', '"+row["cuit"]+"',
'"+row["nueva_numero"]+row["nueva_letra"]+"', '"
    sql=sql+row["vieja_numero"]+row["vieja_letra"]+"', '"
    sql=sql+row["fabricacion"]+"', '"+row["marca"]+"',
'"+row["cilindrada"]+"', '"
    sql=sql+row["domicilio"]+"', '"+row["localidad"]+"',
'"+row["provincia"]+"', '"
    sql=sql+row["postal"]+"', '"+propietario+"', 'AUTO')"

    db_cursor.execute(sql)

db_connection.commit()

print str(cuantos) + ' registros de motos insertados.'



################


lasizoillo escribió:
> 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
>
>   

_______________________________________________
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