2010-03-29 10 views
69

He estado tratando de averiguar cómo iterar sobre la lista de columnas definidas en un modelo SqlAlchemy. Lo quiero para escribir algunos métodos de serialización y copia en un par de modelos. No puedo simplemente iterar sobre el obj. dict ya que contiene muchos artículos específicos de SA.método de iteración sobre las columnas definidas del modelo sqlalchemy?

¿Alguien sabe de una manera de obtener los nombres id, y desc de los siguientes?

class JobStatus(Base): 
    __tablename__ = 'jobstatus' 

    id = Column(Integer, primary_key=True) 
    desc = Column(Unicode(20)) 

En este caso pequeño que podía crear fácilmente una:

def logme(self): 
    return {'id': self.id, 'desc': self.desc} 

pero preferiría algo que era de generación automática de objetos de mayor tamaño.

Gracias por la ayuda.

Respuesta

53

podría utilizar la siguiente función:

def __unicode__(self): 
    return "[%s(%s)]" % (self.__class__.__name__, ', '.join('%s=%s' % (k, self.__dict__[k]) for k in sorted(self.__dict__) if '_sa_' != k[:4])) 

Se excluirá SA magia atributos, pero no excluirá las relaciones. Así que, básicamente, podría cargar las dependencias, los padres, los niños, etc., lo que definitivamente no es deseable.

pero en realidad es mucho más fácil porque si se hereda de Base, tiene un atributo __table__, por lo que se puede hacer:

for c in JobStatus.__table__.columns: 
    print c 

for c in JobStatus.__table__.foreign_keys: 
    print c 

Ver How to discover table properties from SQLAlchemy mapped object - pregunta similar.

Editar por Mike: Consulte funciones como Mapper.c y Mapper.mapped_table. Si usa 0.8 y más, también vea Mapper.attrs y funciones relacionadas.

Ejemplo para Mapper.attrs:

from sqlalchemy import inspect 
mapper = inspect(JobStatus) 
for column in mapper.attrs: 
    print column.key 
+13

Tenga en cuenta que '__table__.columns' le dará los nombres de los campos de SQL, no los nombres de atributos que ha usado en sus definiciones de ORM (si los dos son diferentes). –

+8

¿Podría recomendar cambiar ''_sa_'!! = K [: 4]' a 'no k.iniciar con ('_ sa _')'? –

+4

No es necesario bucle con inspeccionar: 'inspeccionar (JobStatus) .columns.keys()' – kirpit

53

Puede obtener la lista de propiedades definidas del asignador. Para su caso, solo le interesan los objetos ColumnProperty.

from sqlalchemy.orm import class_mapper 
import sqlalchemy 

def attribute_names(cls): 
    return [prop.key for prop in class_mapper(cls).iterate_properties 
     if isinstance(prop, sqlalchemy.orm.ColumnProperty)] 
+4

Gracias, esto me deja crear un método __todict__ en la base que luego utilizo para 'volcado' una instancia a una Entonces puedo pasar por la respuesta del decorador jsonify del pilón. Intenté poner una nota más detallada con el ejemplo de código en mi pregunta original, pero está causando que stackoverflow tenga un error al enviarlo. – Rick

+4

btw, 'class_mapper' necesita ser importado de' sqlalchemy.orm' – priestc

+1

Si bien esta es una respuesta legítima, después de la versión 0.8 se sugiere usar 'inspeccionar()', que devuelve el mismo objeto mapper exacto que 'class_mapper () '. http://docs.sqlalchemy.org/en/latest/core/inspection.html – kirpit

22

que se dan cuenta de que esta es una vieja pregunta, pero yo sólo he encontrado con el mismo requisito y me gustaría ofrecer una solución alternativa a los futuros lectores.

Como señala Josh, nombres completos de campo SQL serán devueltas por JobStatus.__table__.columns, por lo que en lugar del nombre de campo original Identificación del , obtendrá jobstatus.id. No es tan útil como podría ser.

La solución para obtener una lista de nombres de campo como se definieron originalmente es buscar el atributo _data en el objeto de columna, que contiene los datos completos.Si nos fijamos en JobStatus.__table__.columns._data, que se ve así:

{'desc': Column('desc', Unicode(length=20), table=<jobstatus>), 
'id': Column('id', Integer(), table=<jobstatus>, primary_key=True, nullable=False)} 

Desde aquí se puede llamar simplemente JobStatus.__table__.columns._data.keys() que le da un agradable, lista limpia:

['id', 'desc'] 
+1

¡Agradable! ¿Hay alguna manera con este método para llegar a las relaciones también? – shroud

+2

no hay necesidad de _data attr, solo ..columns.keys() haga el truco – Humoyun

+0

Sí, utilizando el atributo _data privado se debe evitar siempre que sea posible, @Humoyun es más correcto. –

10

self.__table__.columns será "sólo" le dará las columnas definido en esa clase particular, es decir, sin heredar. si necesita todo, use self.__mapper__.columns. en su ejemplo lo que probablemente usar algo como esto:

class JobStatus(Base): 

    ... 

    def __iter__(self): 
     values = vars(self) 
     for attr in self.__mapper__.columns.keys(): 
      if attr in values: 
       yield attr, values[attr] 

    def logme(self): 
     return dict(self) 
7

Para obtener un método as_dict en todas mis clases he utilizado una clase Mixin que utiliza las técnicas descritas por Ants Aasma.

class BaseMixin(object):                                            
    def as_dict(self):                                            
     result = {}                                             
     for prop in class_mapper(self.__class__).iterate_properties:                                 
      if isinstance(prop, ColumnProperty):                                      
       result[prop.key] = getattr(self, prop.key)                                   
     return result 

y luego usarlo como este en sus clases

class MyClass(BaseMixin, Base): 
    pass 

De este modo puede invocar la siguiente en una instancia de MyClass.

> myclass = MyClass() 
> myclass.as_dict() 

Espero que esto ayude.


He jugado arround con esto un poco más lejos, lo que realmente se necesita para hacer mis casos como dict como la forma de un HAL object con sus enlaces a objetos relacionados. Así que he agregado esta pequeña magia aquí, que se rastreará sobre todas las propiedades de la clase como la anterior, con la diferencia de que me arrastraré más profundamente en las propiedades Relaionship y generaré links automáticamente.

Tenga en cuenta que esto sólo funcionará para las relaciones tienen una única clave primaria

from sqlalchemy.orm import class_mapper, ColumnProperty 
from functools import reduce 


def deepgetattr(obj, attr): 
    """Recurses through an attribute chain to get the ultimate value.""" 
    return reduce(getattr, attr.split('.'), obj) 


class BaseMixin(object): 
    def as_dict(self): 
     IgnoreInstrumented = (
      InstrumentedList, InstrumentedDict, InstrumentedSet 
     ) 
     result = {} 
     for prop in class_mapper(self.__class__).iterate_properties: 
      if isinstance(getattr(self, prop.key), IgnoreInstrumented): 
       # All reverse relations are assigned to each related instances 
       # we don't need to link these, so we skip 
       continue 
      if isinstance(prop, ColumnProperty): 
       # Add simple property to the dictionary with its value 
       result[prop.key] = getattr(self, prop.key) 
      if isinstance(prop, RelationshipProperty): 
       # Construct links relaions 
       if 'links' not in result: 
        result['links'] = {} 

       # Get value using nested class keys 
       value = (
        deepgetattr(
         self, prop.key + "." + prop.mapper.primary_key[0].key 
        ) 
       ) 
       result['links'][prop.key] = {} 
       result['links'][prop.key]['href'] = (
        "/{}/{}".format(prop.key, value) 
       ) 
     return result 
+0

Agregue 'from sqlalchemy.orm import class_mapper, ColumnProperty' en la parte superior de su fragmento de código – JVK

+0

¡Gracias por su comentario! He agregado las importaciones faltantes – flazzarini

-1

Sé que esto es una vieja pregunta, pero ¿qué pasa:

class JobStatus(Base): 

    ... 

    def columns(self): 
     return [col for col in dir(self) if isinstance(col, db.Column)] 

Entonces, para obtener los nombres de columna: jobStatus.columns()

Eso devolvería ['id', 'desc']

Entonces puede recorrer y hacer cosas con las columnas y valores:

for col in jobStatus.colums(): 
    doStuff(getattr(jobStatus, col)) 
+0

que no se puede hacer esinstance (col, Columna) en una cadena – Muposat

+0

Downvoted porque __table __. Columns y __mapper __. Columnas le dan esta información sin reinventar la rueda. –

Cuestiones relacionadas