web-dev-qa-db-fra.com

méthode d'itération sur les colonnes définies du modèle sqlalchemy?

J'ai essayé de comprendre comment parcourir 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 sur quelques modèles. Je ne peux pas simplement parcourir le obj.__dict__ car il contient beaucoup SA éléments spécifiques.

Quelqu'un connaît-il un moyen d'obtenir simplement les noms id et desc parmi les suivants?

class JobStatus(Base):
    __table= '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ère automatiquement le dict (pour les objets plus gros).

89
Rick

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 les attributs SA magic, mais n'exclura pas les relations. Donc, fondamentalement, cela pourrait charger les dépendances, les parents, les enfants, etc., ce qui n'est certainement pas souhaitable.

Mais c'est en fait beaucoup plus facile car si vous héritez de Base, vous avez un __table__ attribut, pour que vous puissiez faire:

for c in JobStatus.__table__.columns:
    print c

for c in JobStatus.__table__.foreign_keys:
    print c

Voir Comment découvrir les propriétés d'une table à partir d'un objet mappé SQLAlchemy - question similaire.

Edit by Mike: Veuillez voir les fonctions telles que Mapper.c et Mapper.mapped_table . Si vous utilisez 0.8 et plus, consultez également Mapper.attrs et les fonctions associées.

Exemple pour Mapper.attrs :

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

Vous pouvez obtenir la liste des propriétés définies à partir du mappeur. Pour votre cas, seuls les objets ColumnProperty vous intéressent.

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)]
61
Ants Aasma

Je me rends compte que c'est une vieille question, mais je viens de rencontrer la même exigence et je voudrais offrir une solution alternative aux futurs lecteurs.

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

La solution pour obtenir une liste de noms de champs tels qu'ils ont été définis à l'origine est de rechercher l'attribut _data Sur l'objet colonne, qui contient les données complètes. Si nous regardons JobStatus.__table__.columns._data, Cela 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 liste agréable et propre:

['id', 'desc']
28
morphics

self.__table__.columns vous donnera "uniquement" les colonnes définies dans cette classe particulière, c'est-à-dire sans celles héritées. si vous avez besoin de tout, utilisez self.__mapper__.columns. dans votre exemple, j'utiliserais probablement 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)
12
Andi Zeidler

Pour obtenir un as_dict méthode sur toutes mes classes J'ai utilisé 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 utilisez-le comme ça dans vos cours

class MyClass(BaseMixin, Base):
    pass

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

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

J'espère que cela t'aides.


J'ai joué un peu plus loin avec cela, j'avais en fait besoin de rendre mes instances sous la forme dict sous la forme d'un objet HAL avec ses liens vers des objets liés. J'ai donc ajouté cette petite magie ici, qui explorera toutes les propriétés de la classe comme ci-dessus, à la différence que j'explorerai plus profondément dans les propriétés Relaionship et générer links pour ces automatiquement.

Veuillez noter que cela ne fonctionnera que pour les relations avec une seule clé primaire

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
7
flazzarini

En supposant que vous utilisez le mappage déclaratif de SQLAlchemy, vous pouvez utiliser l'attribut __mapper__ Pour accéder au mappeur de classe. Pour obtenir tous les attributs mappés (y compris les relations):

obj.__mapper__.attrs.keys()

Si vous voulez des noms de colonnes strictement, utilisez obj.__mapper__.column_attrs.keys(). Voir la documentation pour d'autres vues.

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

4
jrc
self.__dict__

renvoie un dict où les clés sont des noms d'attribut et valorise les valeurs de l'objet.

/!\il y a un attribut supplémentaire: '_sa_instance_state' mais vous pouvez le gérer :)

0
Valentin Fabianski