phương pháp lặp qua các cột xác định của mô hình sqlalchemy?


95

Tôi đang cố gắng tìm ra cách lặp lại danh sách các cột được xác định trong mô hình SQLAlchemy. Tôi muốn nó để viết một số phương pháp tuần tự hóa và sao chép vào một vài mô hình. Tôi không thể chỉ lặp lại obj.__dict__vì nó chứa rất nhiều mục SA cụ thể.

Có ai biết cách lấy tên iddesctừ những thứ sau không?

class JobStatus(Base):
    __tablename__ = 'jobstatus'

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

Trong trường hợp nhỏ này, tôi có thể dễ dàng tạo:

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

nhưng tôi muốn thứ gì đó tự động tạo dict(cho các đối tượng lớn hơn).

Câu trả lời:


83

Bạn có thể sử dụng chức năng sau:

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

Nó sẽ loại trừ các thuộc tính phép thuật SA , nhưng sẽ không loại trừ các mối quan hệ. Vì vậy, về cơ bản nó có thể tải các phụ thuộc, cha mẹ, con cái, v.v., điều này chắc chắn không mong muốn.

Nhưng nó thực sự dễ dàng hơn nhiều vì nếu bạn kế thừa từ Base, bạn có một __table__thuộc tính, do đó bạn có thể làm:

for c in JobStatus.__table__.columns:
    print c

for c in JobStatus.__table__.foreign_keys:
    print c

Xem Cách khám phá các thuộc tính bảng từ đối tượng được ánh xạ SQLAlchemy - câu hỏi tương tự.

Chỉnh sửa bởi Mike: Vui lòng xem các chức năng như Mapper.cMapper.mapped_table . Nếu sử dụng 0.8 và cao hơn, hãy xem Mapper.attrs và các chức năng liên quan.

Ví dụ cho Mapper.attrs :

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

21
Lưu ý rằng điều đó __table__.columnssẽ cung cấp cho bạn tên trường SQL, không phải tên thuộc tính mà bạn đã sử dụng trong định nghĩa ORM của mình (nếu hai tên khác nhau).
Josh Kelley

11
Tôi có thể khuyên bạn nên thay đổi '_sa_' != k[:4]thành not k.startswith('_sa_')?
Mu Mind

11
Không cần phải vòng lặp với kiểm tra:inspect(JobStatus).columns.keys()
kirpit

63

Bạn có thể lấy danh sách các thuộc tính đã xác định từ trình ánh xạ. Đối với trường hợp của bạn, bạn chỉ quan tâm đến các đối tượng 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
Cảm ơn, điều này cho phép tôi tạo một phương thức todict trên Base mà sau đó tôi sử dụng để 'kết xuất' một thể hiện ra một câu lệnh mà sau đó tôi có thể chuyển cho phản hồi trình trang trí jsonify của pylon. Tôi đã cố gắng đưa ra một ghi chú chi tiết hơn với ví dụ về mã trong câu hỏi ban đầu của mình nhưng nó khiến stackoverflow bị lỗi khi gửi.
Rick,

4
btw, class_mappercần được nhập từsqlalchemy.orm
Priestc 13/12/12

3
Mặc dù đây là một câu trả lời hợp lệ, nhưng sau phiên bản 0.8, nó được đề xuất sử dụng inspect(), trả về cùng một đối tượng ánh xạ chính xác như class_mapper(). docs.sqlalchemy.org/en/latest/core/inspection.html
kirpit

1
Điều này đã giúp tôi rất nhiều để ánh xạ tên thuộc tính mô hình SQLAlchemy với tên cột bên dưới.
FearlessFuture

29

Tôi nhận ra rằng đây là một câu hỏi cũ, nhưng tôi vừa gặp phải yêu cầu tương tự và muốn đưa ra một giải pháp thay thế cho những độc giả trong tương lai.

Như Josh lưu ý, các tên trường SQL đầy đủ sẽ được trả về JobStatus.__table__.columns, vì vậy thay vì id tên trường ban đầu , bạn sẽ nhận được jobstatus.id . Không hữu ích như nó có thể được.

Giải pháp để có được danh sách các tên trường như chúng được xác định ban đầu là tìm _datathuộc tính trên đối tượng cột, chứa đầy đủ dữ liệu. Nếu chúng ta nhìn vào JobStatus.__table__.columns._data, nó trông như thế này:

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

Từ đây, bạn có thể chỉ cần gọi JobStatus.__table__.columns._data.keys()mà cung cấp cho bạn một danh sách sạch đẹp:

['id', 'desc']

2
Đẹp! Có cách nào với phương pháp này để có được các mối quan hệ không?
khâm liệm

3
không cần _data attr, chỉ cần ..columns.keys () thực hiện thủ thuật
Humoyun Ahmad

1
Có, nên tránh sử dụng thuộc tính _data private nếu có thể, @Humoyun thì đúng hơn.
Ng Oon-Ee

AttributeError: __data

13

self.__table__.columnssẽ "chỉ" cung cấp cho bạn các cột được xác định trong lớp cụ thể đó, tức là không có các cột được kế thừa. nếu bạn cần tất cả, hãy sử dụng self.__mapper__.columns. trong ví dụ của bạn, tôi có thể sử dụng một cái gì đó như thế này:

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

Giả sử bạn đang sử dụng ánh xạ khai báo của SQLAlchemy, bạn có thể sử dụng __mapper__thuộc tính để lấy tại trình ánh xạ lớp. Để nhận tất cả các thuộc tính được ánh xạ (bao gồm cả các mối quan hệ):

obj.__mapper__.attrs.keys()

Nếu bạn muốn có tên cột chặt chẽ, hãy sử dụng obj.__mapper__.column_attrs.keys(). Xem tài liệu để biết các quan điểm khác.

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


Đây là câu trả lời đơn giản.
stgrmks

7

Để có được một as_dictphương thức trên tất cả các lớp của mình, tôi đã sử dụng một Mixinlớp sử dụng các kỹ thuật được mô tả bởi 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

Và sau đó sử dụng nó như thế này trong các lớp học của bạn

class MyClass(BaseMixin, Base):
    pass

Bằng cách đó, bạn có thể gọi như sau trên một phiên bản của MyClass.

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

Hi vọng điêu nay co ich.


Tôi đã chơi arround với điều này xa hơn một chút, tôi thực sự cần phải hiển thị các phiên bản của mình dictdưới dạng một đối tượng HAL với các liên kết của nó đến các đối tượng liên quan. Vì vậy, tôi đã thêm phép thuật nhỏ này ở đây, nó sẽ thu thập thông tin trên tất cả các thuộc tính của lớp giống như ở trên, với sự khác biệt là tôi sẽ thu thập dữ liệu sâu hơn vào Relaionshipcác thuộc tính và tạo ra linkscác thuộc tính này tự động.

Xin lưu ý rằng điều này sẽ chỉ hoạt động đối với các mối quan hệ có một khóa chính duy nhất

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

Vui lòng thêm from sqlalchemy.orm import class_mapper, ColumnPropertyvào đầu đoạn mã của bạn
JVK

Cám ơn bạn đã góp ý! Tôi đã thêm các mục nhập còn thiếu
flazzarini

Đó là Cơ sở khai báo của sqlalchemy đọc thêm về điều đó tại đây docs.sqlalchemy.org/en/13/orm/extensions/decl Compare/
flazzarini

1
self.__dict__

trả về một dict trong đó các khóa là tên thuộc tính và giá trị các giá trị của đối tượng.

/! \ có một thuộc tính bổ sung: '_sa_instance_state' nhưng bạn có thể xử lý nó :)


Chỉ khi các thuộc tính được thiết lập.
stgrmks

-1

Tôi biết đây là một câu hỏi cũ, nhưng còn:

class JobStatus(Base):

    ...

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

Sau đó, để lấy tên cột: jobStatus.columns()

Điều đó sẽ trở lại ['id', 'desc']

Sau đó, bạn có thể lặp lại và thực hiện mọi thứ với các cột và giá trị:

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

bạn không thể làm isinstance (col, Cột) trên một chuỗi
Muposat

Downvoted vì bảng .columns và mapper .columns cung cấp cho bạn dữ liệu này mà không tái phát minh ra bánh xe.
David Watson
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.