2010-04-04 15 views
10

I tienen el siguiente problema:sqlalchemy mapeo dinámico

tengo la clase:


class Word(object): 

    def __init__(self): 
     self.id = None 
     self.columns = {} 

    def __str__(self): 
     return "(%s, %s)" % (str(self.id), str(self.columns)) 

self.columns es un dict que llevará a cabo: valores (columnName columnValue). El nombre de las columnas son conocidos en tiempo de ejecución y se cargan en una lista wordColumns, por ejemplo


wordColumns = ['english', 'korean', 'romanian'] 

wordTable = Table('word', metadata, 
        Column('id', Integer, primary_key = True) 
       ) 
for columnName in wordColumns: 
    wordTable.append_column(Column(columnName, String(255), nullable = False)) 

incluso creé una explícitos propiedades Mapper para "forzar" las columnas de las tablas que se asignan en la palabra. columnas [columnName], en lugar de word.columnName, no aparece ningún error en la asignación, pero parece que no funciona.


mapperProperties = {} 
for column in wordColumns: 
    mapperProperties['columns[\'%']' % column] = wordTable.columns[column] 

mapper(Word, wordTable, mapperProperties) 

Cuando cargo un objeto de palabra, SQLAlchemy crea un objeto que tiene las word.columns [ 'inglés'], word.columns [ 'coreano'] etc. propiedades en lugar de cargarlos en word.columns dict . Entonces, para cada columna, crea una nueva propiedad. Además, el diccionario word.columns ni siquiera existe.

De la misma manera, cuando trato de persistir una palabra, SQLAlchemy espera encontrar los valores de la columna en propiedades denominadas como word.columns [ 'Inglés'] (tipo de cadena) en lugar de los word.columns diccionario.

Tengo que decir que mi experiencia con Python y SQLAlchemy es bastante limitada, tal vez no sea posible hacer lo que estoy tratando de hacer.

Cualquier ayuda apreciada,

Gracias de antemano.

Respuesta

29

Parece que puede usar los atributos directamente en lugar de usar columnsdict.

Considérese la siguiente configuración:

from sqlalchemy import Table, Column, Integer, Unicode, MetaData, create_engine 
from sqlalchemy.orm import mapper, create_session 

class Word(object): 
    pass 

wordColumns = ['english', 'korean', 'romanian'] 
e = create_engine('sqlite://') 
metadata = MetaData(bind=e) 

t = Table('words', metadata, Column('id', Integer, primary_key=True), 
    *(Column(wordCol, Unicode(255)) for wordCol in wordColumns)) 
metadata.create_all() 
mapper(Word, t) 
session = create_session(bind=e, autocommit=False, autoflush=True) 

Con esa clase vacía que puede hacer:

w = Word() 
w.english = u'name' 
w.korean = u'이름' 
w.romanian = u'nume' 

session.add(w) 
session.commit() 

Y cuando se desea acceder a los datos:

w = session.query(Word).filter_by(english=u'name').one() 
print w.romanian 

Esa es la todo sqlalchemy punto de ORM, en lugar de utilizar a tuple o dict para acceder a los datos, utiliza atributo-como acceso en su propia clase.

Me preguntaba por qué desea utilizar un dict. Tal vez sea porque tiene cadenas con los nombres de los idiomas. Bueno, para que se puede utilizar de getattrsetattr pitón y en su lugar, como lo haría en cualquier objeto Python:

language = 'korean' 
print getattr(w, language) 

Eso debería resolver todos sus problemas.


Dicho esto, si aún desea utilizar dict -como el acceso a las columnas, también es posible.Solo tiene que implementar un objeto similar a dict. Ahora proporcionaré un código para hacer esto, aunque creo que es un desorden absolutamente innecesario, ya que el acceso a los atributos es muy limpio. Si su problema ya se resolvió utilizando el método anterior, no use el código que se encuentra debajo de este punto.

Se podía hacerlo en la clase Word:

class Word(object): 
    def __getitem__(self, item): 
     return getattr(self, item) 
    def __setitem__(self, item, value): 
     return setattr(self, item, value) 

El resto de la configuración funciona como antes. Posteriormente, se podría utilizar de esta manera:

w = Word() 
w['english'] = u'name' 

Si quieres un atributo columns entonces usted necesita un dict -como

class AttributeDict(DictMixin): 
    def __init__(self, obj): 
     self._obj = obj 
    def __getitem__(self, item): 
     return getattr(self._obj, item) 
    def __setitem__(self, item, value): 
     return setattr(self._obj, item, value) 

class Word(object): 
    def __init__(self): 
     self.columns = AttributeDict(self) 

entonces se podría usar como se pretendía:

w = Word() 
w.columns['english'] = u'name' 

Creo que estarás de acuerdo en que todo esto es innecesariamente complicado sin ningún beneficio adicional.

+2

Gracias por tomarse el tiempo para escribir una respuesta tan completa. Estoy de acuerdo en que la primera solución es más limpia y más elegante que la segunda. No tenía ninguna buena razón para usar un dict excepto el hecho de que esto era lo "normal" que me vino a la mente. Como dije antes, soy bastante nuevo en Python y aún no he aprendido a aprovechar los beneficios de los lenguajes dinámicos. Usaré tu primera solución. Gracias de nuevo :) –

+3

No puedo saber cuántas publicaciones como esta existen aquí, pero aún me asombra cuando veo una. Gracias por pensar tanto en esto. Sigue siendo útil, para gente nueva, más de un año después. Saludos a usted – Profane

+0

Ahora, si quisiera usar kwargs para iniciar Word, como en w = Word (inglés = 'nombre', coreano = 'Phoo', rumano = 'nume'), ¿cómo funcionaría? En mi caso, seguirá arrojando un error __init__. ¡Gracias! – Julius

3

Utilicé la solución de nosklo (¡gracias!) Pero ya tenía una clave principal (pasada como pk_col) dentro de la línea de la columna (primera línea de csv). Así que pensé en compartir mi modificación. Usé un ternario.

table = Table(tablename, metadata, 
    *((Column(pk_col, Integer, primary_key=True)) if rowname == pk_col else (Column(rowname, String())) for rowname in row.keys())) 
table.create()