metode iterasi atas kolom yang ditentukan model sqlalchemy?


95

Saya telah mencoba mencari cara untuk mengulang daftar kolom yang ditentukan dalam model SQLAlchemy. Saya menginginkannya untuk menulis beberapa serialisasi dan metode penyalinan ke beberapa model. Saya tidak bisa hanya mengulang obj.__dict__karena mengandung banyak item spesifik SA.

Adakah yang tahu cara mendapatkan nama iddan descdari berikut ini?

class JobStatus(Base):
    __tablename__ = 'jobstatus'

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

Dalam kasus kecil ini saya dapat dengan mudah membuat:

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

tapi saya lebih suka sesuatu yang otomatis menghasilkan dict(untuk objek yang lebih besar).

Jawaban:


84

Anda bisa menggunakan fungsi berikut:

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

Ini akan mengecualikan atribut sihir SA , tetapi tidak akan mengecualikan relasi. Jadi pada dasarnya mungkin memuat dependensi, orang tua, anak-anak dll, yang pasti tidak diinginkan.

Tetapi sebenarnya jauh lebih mudah karena jika Anda mewarisi dari Base, Anda memiliki __table__atribut, sehingga Anda dapat melakukan:

for c in JobStatus.__table__.columns:
    print c

for c in JobStatus.__table__.foreign_keys:
    print c

Lihat Bagaimana menemukan properti tabel dari objek yang dipetakan SQLAlchemy - pertanyaan serupa.

Diedit oleh Mike: Silakan lihat fungsi seperti Mapper.c dan Mapper.mapped_table . Jika menggunakan 0.8 dan lebih tinggi juga lihat Mapper.attrs dan fungsi terkait.

Contoh untuk Mapper.attrs :

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

21
Perhatikan bahwa itu __table__.columnsakan memberi Anda nama kolom SQL, bukan nama atribut yang Anda gunakan dalam definisi ORM Anda (jika keduanya berbeda).
Josh Kelley

11
Bolehkah saya menyarankan '_sa_' != k[:4]untuk mengubah ke not k.startswith('_sa_')?
Mu Mind

12
Tidak perlu mengulang dengan memeriksa:inspect(JobStatus).columns.keys()
kirpit

63

Anda bisa mendapatkan daftar properti yang ditentukan dari pembuat peta. Untuk kasus Anda, Anda hanya tertarik pada objek 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
Terima kasih, ini biarkan saya membuat metode todict pada Base yang kemudian saya gunakan untuk 'membuang' sebuah instance ke dict saya kemudian dapat melewati untuk respon dekorator jsonify pylon. Saya mencoba memasukkan catatan yang lebih detail dengan contoh kode dalam pertanyaan asli saya tetapi itu menyebabkan stackoverflow mengalami kesalahan saat pengiriman.
Rick

4
btw, class_mapperperlu diimpor darisqlalchemy.orm
pendeta

3
Meskipun ini adalah jawaban yang sah, setelah versi 0.8 disarankan untuk digunakan inspect(), yang mengembalikan objek mapper yang sama persis dengan class_mapper(). docs.sqlalchemy.org/en/latest/core/inspection.html
kirpit

1
Ini sangat membantu saya untuk memetakan nama properti model SQLAlchemy ke nama kolom yang mendasarinya.
FearlessFuture

29

Saya menyadari bahwa ini adalah pertanyaan lama, tetapi saya baru saja menemukan persyaratan yang sama dan ingin menawarkan solusi alternatif bagi pembaca di masa mendatang.

Seperti yang Josh catat, nama field SQL lengkap akan dikembalikan JobStatus.__table__.columns, jadi daripada id nama field asli , Anda akan mendapatkan jobstatus.id . Tidak berguna seperti yang seharusnya.

Solusi untuk mendapatkan daftar nama bidang seperti yang awalnya ditentukan adalah dengan melihat _dataatribut pada objek kolom, yang berisi data lengkap. Jika kita lihat JobStatus.__table__.columns._data, tampilannya seperti ini:

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

Dari sini Anda cukup memanggil JobStatus.__table__.columns._data.keys()yang memberi Anda daftar yang bagus dan bersih:

['id', 'desc']

2
Bagus! Adakah cara dengan metode ini untuk menjalin hubungan juga?
kain kafan

3
tidak perlu _data attr, cukup ..columns.keys () lakukan trik
Humoyun Ahmad

1
Ya, penggunaan atribut _data pribadi harus dihindari jika memungkinkan, @Humoyun lebih tepat.
Ng Oon-Ee

AttributeError: __data

13

self.__table__.columnsakan "hanya" memberi Anda kolom yang ditentukan dalam kelas tertentu itu, yaitu tanpa kolom yang diwariskan. jika Anda membutuhkan semuanya, gunakan self.__mapper__.columns. dalam contoh Anda, saya mungkin akan menggunakan sesuatu seperti ini:

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

Dengan asumsi Anda menggunakan pemetaan deklaratif SQLAlchemy, Anda dapat menggunakan __mapper__atribut untuk mendapatkan mapper kelas. Untuk mendapatkan semua atribut yang dipetakan (termasuk hubungan):

obj.__mapper__.attrs.keys()

Jika Anda hanya menginginkan nama kolom, gunakan obj.__mapper__.column_attrs.keys(). Lihat dokumentasi untuk tampilan lain.

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


Inilah jawaban sederhananya.
stgrmks

7

Untuk mendapatkan as_dictmetode di semua kelas saya, saya menggunakan Mixinkelas yang menggunakan teknik yang dijelaskan oleh Semut 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

Dan kemudian gunakan seperti ini di kelas Anda

class MyClass(BaseMixin, Base):
    pass

Dengan cara itu Anda dapat memanggil yang berikut pada sebuah instance MyClass.

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

Semoga ini membantu.


Saya telah bermain-main dengan ini sedikit lebih jauh, saya sebenarnya perlu membuat instance saya dictsebagai bentuk objek HAL dengan tautannya ke objek terkait. Jadi saya telah menambahkan sihir kecil ini di sini, yang akan merayapi semua properti kelas yang sama seperti di atas, dengan perbedaan bahwa saya akan merayapi lebih dalam ke Relaionshipproperti dan membuatnya linkssecara otomatis.

Harap dicatat bahwa ini hanya akan berfungsi untuk hubungan yang memiliki kunci utama tunggal

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

Harap tambahkan from sqlalchemy.orm import class_mapper, ColumnPropertydi atas cuplikan kode Anda
JVK

Terima kasih atas komentar Anda! Saya telah menambahkan impor yang hilang
flazzarini

Ini adalah Basis deklaratif sqlalchemy, baca lebih lanjut di sini docs.sqlalchemy.org/en/13/orm/extensions/declarative/…
flazzarini

1
self.__dict__

mengembalikan sebuah dict di mana kunci adalah nama atribut dan nilai-nilai dari objek tersebut.

/! \ ada atribut tambahan: '_sa_instance_state' tetapi Anda dapat menanganinya :)


Hanya jika atribut ditetapkan.
stgrmks

-1

Saya tahu ini pertanyaan lama, tapi bagaimana dengan:

class JobStatus(Base):

    ...

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

Kemudian, untuk mendapatkan nama kolom: jobStatus.columns()

Itu akan kembali ['id', 'desc']

Kemudian Anda dapat mengulang, dan melakukan hal-hal dengan kolom dan nilai:

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

Anda tidak dapat melakukan isinstance (kolom, Kolom) pada string
Muposat

Diturunkan karena tabel .columns dan mapper .columns memberi Anda data ini tanpa menemukan kembali roda.
David Watson
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.