Methode zum Iterieren über die definierten Spalten des SQL-Chemiemodells?

95

Ich habe versucht herauszufinden, wie die Liste der in einem SQLAlchemy-Modell definierten Spalten durchlaufen werden kann. Ich möchte, dass einige Serialisierungs- und Kopiermethoden in einige Modelle geschrieben werden. Ich kann nicht einfach darüber iterieren, obj.__dict__da es viele SA-spezifische Elemente enthält.

Kennt jemand einen Weg, um nur die idund descNamen von den folgenden zu bekommen?

class JobStatus(Base):
    __tablename__ = 'jobstatus'

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

In diesem kleinen Fall könnte ich leicht ein:

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

aber ich würde etwas bevorzugen, das das automatisch generiert dict(für größere Objekte).

Rick
quelle

Antworten:

82

Sie können die folgende Funktion verwenden:

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

SA- magische Attribute werden ausgeschlossen, die Beziehungen jedoch nicht. Grundsätzlich kann es also zu Abhängigkeiten, Eltern, Kindern usw. kommen, was definitiv nicht wünschenswert ist.

Aber es ist tatsächlich viel einfacher, denn wenn Sie von erben Base, haben Sie ein __table__Attribut, so dass Sie Folgendes tun können:

for c in JobStatus.__table__.columns:
    print c

for c in JobStatus.__table__.foreign_keys:
    print c

Siehe Ermitteln von Tabelleneigenschaften aus SQLAlchemy-zugeordneten Objekten - ähnliche Frage.

Bearbeiten von Mike: Siehe Funktionen wie Mapper.c und Mapper.mapped_table . Bei Verwendung von 0,8 und höher siehe auch Mapper.attrs und verwandte Funktionen.

Beispiel für Mapper.attrs :

from sqlalchemy import inspect
mapper = inspect(JobStatus)
for column in mapper.attrs:
    print column.key
van
quelle
21
Beachten Sie, __table__.columnsdass Sie die SQL-Feldnamen erhalten, nicht die Attributnamen, die Sie in Ihren ORM-Definitionen verwendet haben (falls sich die beiden unterscheiden).
Josh Kelley
11
Darf ich empfehlen Wechsel '_sa_' != k[:4]zu not k.startswith('_sa_')?
Mu Mind
11
Keine Notwendigkeit, mit Inspektion zu schleifen:inspect(JobStatus).columns.keys()
Kirpit
63

Sie können die Liste der definierten Eigenschaften aus dem Mapper abrufen. Für Ihren Fall interessieren Sie sich nur für ColumnProperty-Objekte.

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)]
Ameisen Aasma
quelle
4
Dank dieser Option kann ich eine todict- Methode auf Base erstellen , mit der ich eine Instanz an ein Diktat weitergebe, das ich dann für die Antwort von pylon auf jsonify decorator durchlaufen kann. Ich habe versucht, in meiner ursprünglichen Frage eine weitere Detailnotiz mit Codebeispiel einzufügen, aber dies führt bei der Übermittlung zu einem Fehler beim Stapeln des Überlaufs.
Rick
4
Übrigens, class_mappermuss importiert werden vonsqlalchemy.orm
Priester
3
Obwohl dies eine legitime Antwort ist, wird nach Version 0.8 die Verwendung empfohlen inspect(), die genau das gleiche Mapper-Objekt wie zurückgibt class_mapper(). docs.sqlalchemy.org/en/latest/core/inspection.html
Kirpit
1
Dies hat mir sehr geholfen, die Eigenschaftsnamen des SQLAlchemy-Modells den zugrunde liegenden Spaltennamen zuzuordnen.
FearlessFuture
29

Mir ist klar, dass dies eine alte Frage ist, aber ich bin gerade auf dieselbe Anforderung gestoßen und möchte zukünftigen Lesern eine alternative Lösung anbieten.

Als Josh Notizen, werden die vollen SQL - Feldnamen durch zurückgegeben werden JobStatus.__table__.columns, also eher als der ursprünglichen Feldnamen ID , erhalten Sie jobstatus.id . Nicht so nützlich wie es sein könnte.

Die Lösung zum Abrufen einer Liste von Feldnamen, wie sie ursprünglich definiert wurden, besteht darin, das _dataAttribut auf dem Spaltenobjekt zu suchen , das die vollständigen Daten enthält. Wenn wir uns das ansehen JobStatus.__table__.columns._data, sieht es so aus:

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

Von hier aus können Sie einfach anrufen JobStatus.__table__.columns._data.keys(), um eine schöne, saubere Liste zu erhalten:

['id', 'desc']
Morphics
quelle
1
Nett! Gibt es mit dieser Methode eine Möglichkeit, auch zu Beziehungen zu gelangen?
Leichentuch
3
_data attr ist nicht erforderlich, nur ..columns.keys () machen den Trick
Humoyun Ahmad
1
Ja, die Verwendung des Attributs private _data sollte nach Möglichkeit vermieden werden. @Humoyun ist korrekter.
Ng Oon-Ee
AttributeError: __data
13

self.__table__.columnsgibt Ihnen "nur" die in dieser bestimmten Klasse definierten Spalten, dh ohne geerbte. Wenn Sie alle brauchen, verwenden Sie self.__mapper__.columns. In Ihrem Beispiel würde ich wahrscheinlich so etwas verwenden:

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)
Andi Zeidler
quelle
10

Angenommen, Sie verwenden die deklarative Zuordnung von SQLAlchemy, können Sie das __mapper__Attribut verwenden, um zum Klassen-Mapper zu gelangen. So erhalten Sie alle zugeordneten Attribute (einschließlich Beziehungen):

obj.__mapper__.attrs.keys()

Wenn Sie ausschließlich Spaltennamen möchten, verwenden Sie obj.__mapper__.column_attrs.keys(). Weitere Ansichten finden Sie in der Dokumentation.

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

jrc
quelle
Dies ist die einfache Antwort.
stgrmks
7

Um eine as_dictMethode für alle meine Klassen zu erhalten, habe ich eine MixinKlasse verwendet, die die von Ants Aasma beschriebenen Techniken verwendet .

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

Und dann benutze es so in deinen Klassen

class MyClass(BaseMixin, Base):
    pass

Auf diese Weise können Sie für eine Instanz von Folgendes aufrufen MyClass.

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

Hoffe das hilft.


Ich habe ein bisschen weiter damit herumgespielt, ich musste meine Instanzen tatsächlich dictals die Form eines HAL-Objekts mit seinen Links zu verwandten Objekten rendern . Also habe ich hier unten diese kleine Magie hinzugefügt, die über alle Eigenschaften der Klasse wie oben kriecht, mit dem Unterschied, dass ich tiefer in RelaionshipEigenschaften krieche und linksdiese automatisch generiere .

Bitte beachten Sie, dass dies nur für Beziehungen mit einem einzigen Primärschlüssel funktioniert

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
Flazzarini
quelle
Bitte fügen Sie from sqlalchemy.orm import class_mapper, ColumnPropertyoben auf Ihrem Code-Snippet
JVK
Vielen Dank für Ihren Kommentar! Ich habe die fehlenden Importe hinzugefügt
Flazzarini
Es ist die deklarative Basis von sqlalchemy. Lesen Sie mehr dazu hier docs.sqlalchemy.org/en/13/orm/extensions/declarative/…
flazzarini
1
self.__dict__

Gibt ein Diktat zurück, bei dem Schlüssel Attributnamen sind und die Werte des Objekts.

/! \ es gibt ein zusätzliches Attribut: '_sa_instance_state', aber du kannst damit umgehen :)

Valentin Fabianski
quelle
Nur wenn Attribute gesetzt sind.
stgrmks
-1

Ich weiß, dass dies eine alte Frage ist, aber was ist mit:

class JobStatus(Base):

    ...

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

Um dann Spaltennamen zu erhalten: jobStatus.columns()

Das würde zurückkehren ['id', 'desc']

Dann können Sie eine Schleife durchlaufen und Dinge mit den Spalten und Werten tun:

for col in jobStatus.colums():
    doStuff(getattr(jobStatus, col))
vktec
quelle
Sie können keine Instanz (Spalte, Spalte) für eine Zeichenfolge
ausführen
Downvoted, da die Tabellen .columns und Mapper .columns diese Daten liefern, ohne das Rad neu zu erfinden.
David Watson