วิธีการวนซ้ำคอลัมน์ที่กำหนดของโมเดล sqlalchemy?


98

ฉันพยายามหาวิธีทำซ้ำรายการคอลัมน์ที่กำหนดในโมเดล SQLAlchemy ฉันต้องการให้เขียนวิธีการทำให้เป็นอนุกรมและคัดลอกไปยังสองสามรุ่น ฉันไม่สามารถทำซ้ำได้obj.__dict__เนื่องจากมีรายการเฉพาะของ SA จำนวนมาก

ใครทราบวิธีรับidและdescชื่อจากต่อไปนี้?

class JobStatus(Base):
    __tablename__ = 'jobstatus'

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

ในกรณีเล็ก ๆ นี้ฉันสามารถสร้าง:

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

แต่ฉันต้องการสิ่งที่สร้างขึ้นโดยอัตโนมัติdict(สำหรับวัตถุขนาดใหญ่)

คำตอบ:


85

คุณสามารถใช้ฟังก์ชันต่อไปนี้:

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 แต่จะไม่รวมความสัมพันธ์ โดยพื้นฐานแล้วมันอาจโหลดการอ้างอิงผู้ปกครองเด็ก ฯลฯ ซึ่งไม่เป็นที่ต้องการอย่างแน่นอน

แต่จริงๆแล้วมันง่ายกว่ามากเพราะถ้าคุณสืบทอดมาBaseคุณจะมี__table__แอตทริบิวต์เพื่อให้คุณสามารถทำ:

for c in JobStatus.__table__.columns:
    print c

for c in JobStatus.__table__.foreign_keys:
    print c

ดูวิธีการค้นหาคุณสมบัติของตารางจากวัตถุที่แมป SQLAlchemy - คำถามที่คล้ายกัน

แก้ไขโดยไมค์:โปรดดูฟังก์ชั่นเช่นMapper.cและMapper.mapped_table หากใช้ 0.8 ขึ้นไปให้ดูMapper.attrsและฟังก์ชันที่เกี่ยวข้อง

ตัวอย่างสำหรับMapper.attrs :

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

21
โปรดทราบว่า__table__.columnsจะให้ชื่อฟิลด์ SQL แก่คุณไม่ใช่ชื่อแอ็ตทริบิวต์ที่คุณใช้ในนิยาม ORM ของคุณ (หากทั้งสองต่างกัน)
Josh Kelley

11
ฉันขอแนะนำให้เปลี่ยน '_sa_' != k[:4]เป็นnot k.startswith('_sa_')?
Mu Mind

13
ไม่จำเป็นต้องวนซ้ำกับการตรวจสอบ:inspect(JobStatus).columns.keys()
kirpit

หากคุณตั้งค่าด้วย model.__dict__[column] sqlalchemy ตรวจไม่พบการเปลี่ยนแปลง
Sebi2020

63

คุณสามารถรับรายการคุณสมบัติที่กำหนดได้จากผู้ทำแผนที่ สำหรับกรณีของคุณคุณสนใจเฉพาะวัตถุ 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
ขอบคุณสิ่งนี้ให้ฉันสร้างเมธอดtodictบน Base ซึ่งฉันใช้เพื่อ 'ถ่ายโอนข้อมูล' อินสแตนซ์ออกไปยัง dict ฉันสามารถส่งผ่านสำหรับการตอบสนองของมัณฑนากร jsonify ของเสา ฉันพยายามใส่บันทึกรายละเอียดเพิ่มเติมพร้อมตัวอย่างโค้ดในคำถามเดิมของฉัน แต่มันทำให้ stackoverflow เกิดข้อผิดพลาดในการส่ง
ริก

4
btw class_mapperต้องนำเข้าจากsqlalchemy.orm
บาทหลวง

3
ขณะนี้เป็นคำตอบที่ถูกต้องตามกฎหมายหลังจากรุ่น 0.8 ก็จะแนะนำให้ใช้inspect()ซึ่งจะส่งกลับวัตถุ mapper class_mapper()เดียวกันกับ docs.sqlalchemy.org/en/latest/core/inspection.html
kirpit

1
สิ่งนี้ช่วยฉันได้มากในการแมปชื่อคุณสมบัติโมเดล SQLAlchemy กับชื่อคอลัมน์ที่อยู่ข้างใต้
FearlessFuture

30

ฉันตระหนักดีว่านี่เป็นคำถามเก่า แต่ฉันเพิ่งเจอข้อกำหนดเดียวกันนี้และต้องการเสนอทางเลือกให้กับผู้อ่านในอนาคต

ในฐานะที่เป็น Josh บันทึกชื่อฟิลด์ SQL แบบเต็มจะถูกส่งกลับJobStatus.__table__.columnsดังนั้นแทนที่จะเป็นidชื่อฟิลด์เดิม คุณจะได้รับjobstatus.id jobstatus.idไม่มีประโยชน์เท่าที่ควร

วิธีแก้ปัญหาในการรับรายชื่อเขตข้อมูลตามที่กำหนดไว้ในตอนแรกคือการดู_dataแอตทริบิวต์บนวัตถุคอลัมน์ซึ่งมีข้อมูลทั้งหมด หากเราดูJobStatus.__table__.columns._dataจะมีลักษณะดังนี้:

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

จากที่นี่คุณสามารถโทรหารายการJobStatus.__table__.columns._data.keys()ที่ดีและสะอาด:

['id', 'desc']

2
ดี! มีวิธีนี้ในการเข้าถึงความสัมพันธ์ด้วยหรือไม่?
ผ้าห่อศพ

3
ไม่จำเป็นต้องมี _data attr เพียงแค่ ..columns.keys () ทำเคล็ดลับ
Humoyun Ahmad

1
ใช่ควรหลีกเลี่ยงการใช้แอตทริบิวต์ _data ส่วนตัวหากเป็นไปได้ @Humoyun จะถูกต้องกว่า
อึ้งอุ่นเอ๋อ

AttributeError: __data

13

self.__table__.columnsจะ "เท่านั้น" ให้คอลัมน์ที่กำหนดไว้ในคลาสนั้น ๆ กล่าวคือไม่มีคอลัมน์ที่สืบทอดมา self.__mapper__.columnsถ้าคุณต้องการทุกการใช้งาน ในตัวอย่างของคุณฉันอาจใช้สิ่งนี้:

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)

13

สมมติว่าคุณใช้การแมปแบบประกาศของ SQLAlchemy คุณสามารถใช้__mapper__แอตทริบิวต์เพื่อรับที่ class mapper ในการรับแอตทริบิวต์ที่แมปทั้งหมด (รวมถึงความสัมพันธ์):

obj.__mapper__.attrs.keys()

หากคุณต้องการชื่อคอลัมน์อย่างเคร่งครัดให้ใช้obj.__mapper__.column_attrs.keys(). ดูเอกสารสำหรับมุมมองอื่น ๆ

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


นี่คือคำตอบง่ายๆ
stgrmks

7

เพื่อให้ได้as_dictเมธอดในคลาสทั้งหมดของฉันฉันใช้Mixinคลาสที่ใช้เทคนิคที่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

แล้วใช้แบบนี้ในชั้นเรียนของคุณ

class MyClass(BaseMixin, Base):
    pass

ด้วยวิธีนี้คุณสามารถเรียกสิ่งต่อไปนี้ในอินสแตนซ์ของMyClass.

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

หวังว่านี่จะช่วยได้


ฉันได้เล่นกับสิ่งนี้ต่อไปอีกเล็กน้อยจริงๆแล้วฉันต้องแสดงผลอินสแตนซ์ของฉันdictในรูปแบบของวัตถุ HALโดยมีลิงก์ไปยังวัตถุที่เกี่ยวข้อง ดังนั้นฉันจึงได้เพิ่มเวทย์มนตร์เล็ก ๆ นี้ลงไปที่นี่ซึ่งจะรวบรวมข้อมูลคุณสมบัติทั้งหมดของคลาสเดียวกันกับด้านบนด้วยความแตกต่างที่ฉันจะรวบรวมข้อมูลลึกลงไปในRelaionshipคุณสมบัติและสร้างขึ้นlinksสำหรับคุณสมบัติเหล่านี้โดยอัตโนมัติ

โปรดทราบว่าสิ่งนี้ใช้ได้เฉพาะกับความสัมพันธ์ที่มีคีย์หลักเดียว

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

โปรดเพิ่มfrom sqlalchemy.orm import class_mapper, ColumnPropertyข้อมูลโค้ดของคุณ
JVK

ขอบคุณสำหรับความคิดเห็น! ฉันได้เพิ่มการนำเข้าที่ขาดหายไปแล้ว
flazzarini

เป็นฐานที่เปิดเผยของ sqlalchemy อ่านเพิ่มเติมได้ที่นี่docs.sqlalchemy.org/en/13/orm/extensions/declarative/…
flazzarini

1
self.__dict__

ส่งคืนคำสั่งโดยที่คีย์เป็นชื่อแอ็ตทริบิวต์และกำหนดค่าของอ็อบเจ็กต์

/! \ มีแอตทริบิวต์เสริม: '_sa_instance_state' แต่คุณสามารถจัดการได้ :)


เมื่อมีการตั้งค่าแอตทริบิวต์เท่านั้น
stgrmks

0

ฉันต้องการรับข้อมูลของอินสแตนซ์เฉพาะของ Model แบบไดนามิก ฉันใช้รหัสนี้

def to_json(instance):
    # get columns data
    data = {}
    columns = list(instance.__table__.columns)
    for column in columns:
        data[column.name] = instance.__dict__[column.name]
    return data

-1

ฉันรู้ว่านี่เป็นคำถามเก่า แต่สิ่งที่เกี่ยวกับ:

class JobStatus(Base):

    ...

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

จากนั้นเพื่อรับชื่อคอลัมน์: jobStatus.columns()

ที่จะกลับมา ['id', 'desc']

จากนั้นคุณสามารถวนซ้ำและทำสิ่งต่างๆกับคอลัมน์และค่า:

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

คุณไม่สามารถทำ isinstance (col, Column) บนสตริงได้
Muposat

โหวตลงเนื่องจาก ตาราง.คอลัมน์และตัวทำแผนที่.คอลัมน์ให้ข้อมูลนี้แก่คุณโดยไม่ต้องสร้างวงล้อขึ้นมาใหม่
David Watson
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.