metodo di iterazione sulle colonne definite del modello sqlalchemy?


95

Ho cercato di capire come scorrere l'elenco di colonne definite in un modello SQLAlchemy. Lo voglio per scrivere alcuni metodi di serializzazione e copia su un paio di modelli. Non posso semplicemente iterare su obj.__dict__poiché contiene molti elementi specifici di SA.

Qualcuno sa di un modo per ottenere solo i nomi ide descdai seguenti?

class JobStatus(Base):
    __tablename__ = 'jobstatus'

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

In questo piccolo caso potrei facilmente creare un:

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

ma preferirei qualcosa che generi automaticamente il dict(per oggetti più grandi).

Risposte:


83

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

Escluderà gli attributi magici SA , ma non escluderà le relazioni. Quindi fondamentalmente potrebbe caricare le dipendenze, i genitori, i figli ecc., Il che non è assolutamente desiderabile.

Ma in realtà è molto più semplice perché se erediti da Base, hai un __table__attributo, quindi puoi fare:

for c in JobStatus.__table__.columns:
    print c

for c in JobStatus.__table__.foreign_keys:
    print c

Vedere Come scoprire le proprietà della tabella dall'oggetto mappato SQLAlchemy - domanda simile.

Modifica di Mike: vedere funzioni come Mapper.c e Mapper.mapped_table . Se si utilizza 0.8 e versioni successive, 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

21
Nota che __table__.columnsti fornirà i nomi dei campi SQL, non i nomi degli attributi che hai usato nelle definizioni ORM (se i due differiscono).
Josh Kelley

11
Potrei consigliare di passare '_sa_' != k[:4]a not k.startswith('_sa_')?
Mu Mind,

11
Non è necessario eseguire il loop con ispezione:inspect(JobStatus).columns.keys()
kirpit

63

È possibile ottenere l'elenco delle proprietà definite dal mapper. Per il tuo caso ti interessano solo 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 ha permesso di creare un metodo todict su Base che poi uso per 'scaricare' un'istanza su un dict 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 al momento dell'invio.
Rick il

4
btw, class_mapperdeve essere importato dasqlalchemy.orm
priestc

3
Sebbene questa sia una risposta legittima, dopo la versione 0.8 si consiglia di utilizzare inspect(), che restituisce lo stesso identico oggetto mapper di class_mapper(). docs.sqlalchemy.org/en/latest/core/inspection.html
kirpit

1
Questo mi ha aiutato molto a mappare i nomi delle proprietà del modello SQLAlchemy ai nomi delle colonne sottostanti.
FearlessFuture

29

Mi rendo conto che questa è una vecchia domanda, ma ho appena incontrato la stessa esigenza e vorrei offrire una soluzione alternativa ai futuri lettori.

Come osserva Josh, i nomi dei campi SQL completi verranno restituiti da JobStatus.__table__.columns, quindi invece dell'id del nome del campo originale , otterrai jobstatus.id . Non così utile come potrebbe essere.

La soluzione per ottenere un elenco di nomi di campo così come sono stati originariamente definiti è guardare l' _dataattributo sull'oggetto colonna, che contiene i dati completi. Se guardiamo JobStatus.__table__.columns._data, assomiglia a questo:

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

Da qui puoi semplicemente chiamare JobStatus.__table__.columns._data.keys()che ti dà un elenco bello e pulito:

['id', 'desc']

2
Bello! C'è un modo con questo metodo per ottenere anche relazioni?
sudario

3
non è necessario _data attr, solo ..columns.keys () fa il trucco
Humoyun Ahmad

1
Sì, l'uso dell'attributo privato _data dovrebbe essere evitato ove possibile, @Humoyun è più corretto.
Ng Oon-Ee

AttributeError: __data

13

self.__table__.columnsti darà "solo" le colonne definite in quella particolare classe, cioè senza quelle ereditate. se hai bisogno di tutto, usa self.__mapper__.columns. nel tuo esempio probabilmente userei qualcosa del genere:

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)

10

Supponendo che tu stia usando la mappatura dichiarativa di SQLAlchemy, puoi usare l' __mapper__attributo per ottenere il class mapper. Per ottenere tutti gli attributi mappati (comprese le relazioni):

obj.__mapper__.attrs.keys()

Se vuoi rigorosamente nomi di colonna, usa obj.__mapper__.column_attrs.keys(). Vedere la documentazione per altre visualizzazioni.

https://docs.sqlalchemy.org/en/latest/orm/mapping_api.html#sqlalchemy.orm.mapper.Mapper.attrs


Questa è la risposta semplice.
stgrmks

7

Per ottenere un as_dictmetodo su tutte le mie classi ho utilizzato una Mixinclasse 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 poi usalo in questo modo nelle tue classi

class MyClass(BaseMixin, Base):
    pass

In questo modo puoi richiamare quanto segue su un'istanza di MyClass.

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

Spero che questo ti aiuti.


Ho giocato un po 'più a fondo con questo, in realtà avevo bisogno di rendere le mie istanze dictcome la forma di un oggetto HAL con i suoi collegamenti agli oggetti correlati. Quindi ho aggiunto questa piccola magia quaggiù, che scansionerà tutte le proprietà della classe come sopra, con la differenza che scansionerò più in profondità nelle Relaionshipproprietà e genererò linksautomaticamente per queste.

Tieni presente che questo funzionerà solo per le relazioni che hanno un'unica chiave 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

Aggiungi from sqlalchemy.orm import class_mapper, ColumnPropertyin cima allo snippet di codice
JVK

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

È la Base dichiarativa di sqlalchemy leggi di più su questo qui docs.sqlalchemy.org/en/13/orm/extensions/declarative/…
flazzarini

1
self.__dict__

restituisce un dict dove le chiavi sono i nomi degli attributi e i valori i valori dell'oggetto.

/! \ c'è un attributo supplementare: '_sa_instance_state' ma puoi gestirlo :)


Solo quando gli attributi sono impostati.
stgrmks

-1

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

class JobStatus(Base):

    ...

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

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

Sarebbe tornato ['id', 'desc']

Quindi puoi eseguire il ciclo e fare cose con le colonne e i valori:

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

non puoi fare isinstance (col, Column) su una stringa
Muposat

Downvoted perché table .columns e mapper .columns ti danno questi dati senza reinventare la ruota.
David Watson
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.