2010-03-29 3 views
69

J'ai essayé de comprendre comment itérer sur la liste des colonnes définies dans un modèle SqlAlchemy. Je le veux pour écrire des méthodes de sérialisation et de copie pour quelques modèles. Je ne peux pas simplement itérer sur l'obj. dict car il contient beaucoup d'éléments spécifiques SA.méthode d'itération sur les colonnes définies du modèle sqlalchemy?

Quelqu'un sait-il comment obtenir l'identifiant et les noms suivants?

class JobStatus(Base): 
    __tablename__ = 'jobstatus' 

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

Dans ce petit cas, je pourrais facilement créer un:

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

mais je préfère quelque chose qui générait automatique pour des objets plus grands.

Merci pour l'aide.

Répondre

53

Vous pouvez utiliser la fonction suivante:

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

Il exclura SA magie attributs, mais ne sera pas exclure les relations. Donc, fondamentalement, il pourrait charger les dépendances, les parents, les enfants, etc., ce qui n'est certainement pas souhaitable.

Mais il est en fait beaucoup plus facile parce que si vous héritez de Base, vous avez un attribut __table__, de sorte que vous pouvez faire:

for c in JobStatus.__table__.columns: 
    print c 

for c in JobStatus.__table__.foreign_keys: 
    print c 

Voir How to discover table properties from SQLAlchemy mapped object - question similaire.

Modifier Mike: S'il vous plaît voir des fonctions telles que Mapper.c et Mapper.mapped_table. Si vous utilisez 0,8 et plus, voir également Mapper.attrs et les fonctions connexes.

Exemple Mapper.attrs:

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

Notez que '__table__.columns' vous donnera les noms de champs SQL, pas les noms d'attributs que vous avez utilisés dans vos définitions ORM (si les deux sont différents). –

+8

Puis-je recommander de changer ''sa_ '! = K [: 4]' à 'pas k.startswith (' _ sa _ ')'? –

+4

Pas besoin de boucler avec inspecter: 'inspecter (JobStatus) .columns.keys()' – kirpit

53

Vous pouvez obtenir la liste des propriétés définies à partir du mappeur. Pour votre cas, vous êtes intéressé uniquement par les objets 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

Merci, cela me laisse créer une méthode __todict__ sur la base que je l'utilise ensuite « décharge » une instance sur un Dict je peux alors passer à travers pour Jsonify réponse du décorateur de pylône. J'ai essayé de mettre une note de plus de détails avec l'exemple de code dans ma question originale mais cela provoque une erreur de stackoverflow lors de la soumission. – Rick

+4

btw, 'class_mapper' doit être importé de' sqlalchemy.orm' – priestc

+1

Bien que ce soit une réponse légitime, après la version 0.8, il est suggéré d'utiliser 'inspect()', qui renvoie exactement le même objet mapper que 'class_mapper () '. http://docs.sqlalchemy.org/en/latest/core/inspection.html – kirpit

22

Je me rends compte que c'est une vieille question, mais je viens tout juste dans la même exigence et voudrait proposer une solution alternative aux futurs lecteurs.

Comme Josh note, les noms complets de champ SQL seront retournés par JobStatus.__table__.columns, donc plutôt que le nom du champ d'origine id, vous obtiendrez jobstatus.id. Pas aussi utile qu'elle pourrait l'être.

La solution pour obtenir une liste de noms de champs tels qu'ils ont été définis à l'origine consiste à rechercher l'attribut _data sur l'objet de colonne, qui contient les données complètes.Si nous regardons JobStatus.__table__.columns._data, il ressemble à ceci:

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

De là, vous pouvez simplement appeler JobStatus.__table__.columns._data.keys() qui vous donne une belle liste propre:

['id', 'desc'] 
+1

Nice! Y a-t-il un moyen avec cette méthode d'avoir aussi des relations? – shroud

+2

il n'y a pas besoin de _data attr, juste ..columns.keys() fais l'affaire – Humoyun

+0

Oui, en utilisant l'attribut private _data devrait être évité autant que possible, @Humoyun est plus correct. –

10

self.__table__.columns sera « seulement » vous donner les colonnes défini dans cette classe particulière, c'est-à-dire sans hérités. Si vous avez besoin de tout, utilisez self.__mapper__.columns. dans votre exemple, je serais probablement utiliser quelque chose comme ceci:

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

Pour obtenir une méthode as_dict sur toutes mes classes je une classe Mixin qui utilise les techniques décrites par 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 

Et puis l'utiliser comme ceci dans vos classes

class MyClass(BaseMixin, Base): 
    pass 

De cette façon, vous pouvez invoquer ce qui suit sur une instance de MyClass.

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

Espérons que cela aide.


J'ai joué arround avec un peu plus loin, en fait je besoin de rendre mes instances comme dict que la forme d'un HAL object avec ses liens vers des objets connexes. J'ai donc ajouté cette petite magie ici, qui va explorer toutes les propriétés de la classe comme ci-dessus, à la différence que je vais explorer plus en profondeur les propriétés Relaionship et générer links automatiquement.

S'il vous plaît noter que cela ne fonctionne que pour les relations ont une clé primaire unique

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

Merci d'ajouter 'from sqlalchemy.orm import class_mapper, ColumnProperty' en haut de votre extrait de code – JVK

+0

Merci pour votre commentaire! J'ai ajouté les importations manquantes – flazzarini

-1

Je sais que c'est une vieille question, mais qu'en est-:

class JobStatus(Base): 

    ... 

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

Ensuite, pour obtenir les noms des colonnes: jobStatus.columns()

Cela retournerait ['id', 'desc']

Ensuite, vous pouvez parcourir et faire des choses avec les colonnes et les valeurs:

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

vous ne pouvez pas faire isinstance (col, Colonne) sur une chaîne – Muposat

+0

Downvoted car __table __. Columns et __mapper __. Columns vous donnent ces données sans réinventer la roue. –

Questions connexes