2010-03-29 14 views
69

Ho cercato di capire come iterare sull'elenco di colonne definito in un modello SqlAlchemy. Lo voglio per scrivere alcuni metodi di serializzazione e copia su un paio di modelli. Non posso solo iterare sopra l'obiettivo. dict poiché contiene molti articoli specifici di SA.metodo di iterazione su colonne definite del modello sqlalchemy?

Qualcuno sa di un modo per ottenere semplicemente l'id e i nomi di desc di quanto segue?

class JobStatus(Base): 
    __tablename__ = 'jobstatus' 

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

In questo piccolo caso ho potuto facilmente creare un:

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

ma preferirei qualcosa che era di generazione automatica per gli oggetti di grandi dimensioni.

Grazie per l'assistenza.

risposta

53

è possibile utilizzare la seguente funzione:

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])) 

Si escluderà SA magia attributi, ma non escluderà i rapporti. Quindi fondamentalmente potrebbe caricare dipendenze, genitori, figli ecc., Che non è assolutamente desiderabile.

ma in realtà è molto più facile, perché se si eredita dalla Base, si dispone di un attributo __table__, in modo che si può fare:

for c in JobStatus.__table__.columns: 
    print c 

for c in JobStatus.__table__.foreign_keys: 
    print c 

Vedi How to discover table properties from SQLAlchemy mapped object - domanda simile.

Modifica da Mike: Vedere funzioni come Mapper.c e Mapper.mapped_table. Se si utilizza 0.8 e superiore, vedere anche Mapper.attrs e funzioni correlate.

Esempio per Mapper.attrs:

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

Si noti che '__table__.columns' vi darà i nomi dei campi SQL, non i nomi degli attributi che avete usato nelle vostre definizioni ORM (se i due differiscono). –

+8

Potrei raccomandare di cambiare ''_sa_'! = K [: 4]' a 'non k.startswith ('_ sa _')'? –

+4

Non è necessario eseguire il ciclo con inspect: 'inspect (JobStatus) .columns.keys()' – kirpit

53

È possibile ottenere l'elenco delle proprietà definite dal programma di mappatura. Per il tuo caso sei interessato solo agli oggetti 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

Grazie, questo mi permette di creare un metodo __todict__ sulla base che ho poi usato per 'scaricare' un'istanza fuori ad un Detto che posso quindi passare per la risposta del decoratore jsonify del pilone. Ho provato a inserire una nota più dettagliata con un esempio di codice nella mia domanda originale ma sta causando errori di stackoverflow all'invio. – Rick

+4

btw, 'class_mapper' deve essere importato da' sqlalchemy.orm' – priestc

+1

Mentre questa è una risposta legittima, dopo la versione 0.8 si suggerisce di usare 'inspect()', che restituisce lo stesso oggetto mapper come 'class_mapper () '. http://docs.sqlalchemy.org/en/latest/core/inspection.html – kirpit

22

mi rendo conto che questa è una vecchia questione, ma ho solo venire attraverso la stessa esigenza e vorrebbe offrire una soluzione alternativa ai futuri lettori.

Come osserva Josh, pieni i nomi dei campi SQL saranno restituiti da JobStatus.__table__.columns, quindi piuttosto che il nome del campo originale id, si otterrà jobstatus.id. Non è utile come potrebbe essere.

La soluzione per ottenere un elenco di nomi campo come erano originariamente definiti è di cercare l'attributo _data sull'oggetto colonna, che contiene i dati completi.Se guardiamo JobStatus.__table__.columns._data, sembra che questo:

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

Da qui si può semplicemente chiamare JobStatus.__table__.columns._data.keys() che vi dà una bella, lista pulita:

['id', 'desc'] 
+1

Nice! C'è un modo con questo metodo per ottenere relazioni anche? – shroud

+2

non c'è bisogno di _data attr, solo ..columns.keys() fai il trucco – Humoyun

+0

Sì, utilizzando l'attributo _data privato dovrebbe essere evitato dove possibile, @Humoyun è più corretto. –

10

self.__table__.columns sarà "solo" dare le colonne definito in quella particolare classe, cioè senza quelle ereditate. se hai bisogno di tutti, usa self.__mapper__.columns. nel tuo esempio mi piacerebbe probabilmente usare qualcosa di simile:

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

per ottenere un metodo di as_dict su tutti i miei corsi ho usato una classe Mixin che utilizza le tecniche descritte da 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 

e quindi utilizzarlo come questo nelle classi

class MyClass(BaseMixin, Base): 
    pass 

In questo modo è possibile richiamare le seguenti su un'istanza di MyClass.

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

Spero che questo aiuti.


Ho giocato arround con questo un po 'più lontano, in realtà ho bisogno di rendere le mie istanze come dict come la forma di un HAL object con i suoi collegamenti a oggetti correlati. Così ho aggiunto questa piccola magia qui, che gattonerà su tutte le proprietà della classe come sopra, con la differenza che cercherò più in profondità nelle proprietà Relaionship e genererò automaticamente links.

prega di notare che questo funziona solo per le relazioni hanno un unico primario chiave

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

Aggiungi 'da sqlalchemy.orm import class_mapper, ColumnProperty' sopra lo snippet di codice – JVK

+0

Grazie per il tuo commento! Ho aggiunto le importazioni mancanti – flazzarini

-1

So che questa è una vecchia questione, ma per quanto riguarda:

class JobStatus(Base): 

    ... 

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

Poi, per ottenere i nomi delle colonne: jobStatus.columns()

Che restituirebbe ['id', 'desc']

Poi è possibile scorrere, e fare cose con le colonne e valori:

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

non è possibile eseguire isinstance (col, Column) su una stringa – Muposat

+0

Downvoted perché colonne __table __. E colonne __mapper __. Forniscono questi dati senza re-inventare la ruota. –

Problemi correlati