метод итерации по определенным столбцам модели sqlalchemy?

Я пытался выяснить, как перебирать список столбцов, определенных в модели 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 (для более крупных объектов).


person Rick    schedule 29.03.2010    source источник


Ответы (9)


Вы можете использовать следующую функцию:

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
person van    schedule 29.03.2010
comment
Обратите внимание, что __table__.columns предоставит вам имена полей SQL, а не имена атрибутов, которые вы использовали в определениях ORM (если они отличаются). - person Josh Kelley; 20.12.2010
comment
Могу я порекомендовать изменить '_sa_' != k[:4] на not k.startswith('_sa_')? - person Mu Mind; 28.09.2012
comment
Нет необходимости в цикле с проверкой: inspect(JobStatus).columns.keys() - person kirpit; 10.12.2015
comment
если вы устанавливаете значения с model.__dict__[column], sqlalchemy не обнаруживает изменений. - person Sebi2020; 07.10.2020

Вы можете получить список определенных свойств в картографе. В вашем случае вас интересуют только объекты 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)]
person Ants Aasma    schedule 29.03.2010
comment
Спасибо, это позволило мне создать метод todict на Base, который я затем использую для «выгрузки» экземпляра в dict, который я затем могу передать для ответа декоратора pylon jsonify. Я попытался добавить более подробную информацию с примером кода в свой исходный вопрос, но это вызывает ошибку stackoverflow при отправке. - person Rick; 30.03.2010
comment
кстати, class_mapper нужно импортировать из sqlalchemy.orm - person priestc; 13.12.2012
comment
Хотя это законный ответ, после версии 0.8 предлагается использовать inspect(), который возвращает тот же объект сопоставления, что и class_mapper(). docs.sqlalchemy.org/en/latest/core/inspection.html - person kirpit; 10.12.2015
comment
Это очень помогло мне сопоставить имена свойств модели SQLAlchemy с именами базовых столбцов. - person FearlessFuture; 07.06.2016

Я понимаю, что это старый вопрос, но я только что столкнулся с тем же требованием и хотел бы предложить альтернативное решение для будущих читателей.

Как отмечает Джош, полные имена полей SQL будут возвращены JobStatus.__table__.columns, поэтому вместо исходного имени поля 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']
person morphics    schedule 28.09.2012
comment
Хороший! Есть ли способ с помощью этого метода также налаживать отношения? - person shroud; 15.01.2014
comment
нет необходимости в _data attr, просто ..columns.keys () сделает трюк - person Humoyun Ahmad; 18.02.2017
comment
Да, по возможности следует избегать использования частного атрибута _data, правильнее использовать @Humoyun. - person Ng Oon-Ee; 27.02.2017
comment
AttributeError: __data - person ; 08.03.2018
comment
Я также хотел бы восстановить отношения - person Konrad; 05.02.2021

Предполагая, что вы используете декларативное сопоставление SQLAlchemy, вы можете использовать атрибут __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

person jrc    schedule 29.09.2018
comment
Это простой ответ. - person stgrmks; 03.07.2020

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)
person Andi Zeidler    schedule 06.12.2012

Чтобы получить метод 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
person flazzarini    schedule 20.08.2015
comment
Добавьте from sqlalchemy.orm import class_mapper, ColumnProperty поверх фрагмента кода - person JVK; 26.02.2018
comment
Спасибо за ваш комментарий! Я добавил недостающий импорт - person flazzarini; 27.02.2018
comment
Это декларативная база sqlalchemy, подробнее об этом читайте здесь docs.sqlalchemy .org / ru / 13 / orm / extensions / declarative / - person flazzarini; 24.01.2020

self.__dict__

возвращает dict, где ключи - это имена атрибутов, а значения - значения объекта.

/! \ есть дополнительный атрибут: '_sa_instance_state', но вы справитесь :)

person Valentin Fabianski    schedule 23.04.2019
comment
Только когда установлены атрибуты. - person stgrmks; 03.07.2020

Я хочу динамически получать данные о конкретном экземпляре модели. Я использовал этот код.

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
person Sheikh Abdul Wahid    schedule 10.11.2020

Я знаю, что это старый вопрос, но как насчет:

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))
person vktec    schedule 28.10.2015
comment
вы не можете сделать isinstance (столбец, столбец) на строке - person Muposat; 14.10.2016
comment
Проголосовали против, потому что table .columns и mapper .columns предоставляют вам эти данные без повторного изобретения колеса. - person David Watson; 15.11.2016