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

96

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

Рик
источник

Ответы:

84

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

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 (если они отличаются).
Джош Келли
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, который я затем могу передать для ответа декоратора pylon jsonify. Я попытался добавить более подробную информацию с примером кода в свой исходный вопрос, но это вызывает ошибку stackoverflow при отправке.
Рик
4
кстати, class_mapperнеобходимо импортировать изsqlalchemy.orm
priestc
3
Хотя это законный ответ, после версии 0.8 предлагается использовать inspect(), который возвращает тот же объект сопоставления, что и class_mapper(). docs.sqlalchemy.org/en/latest/core/inspection.html
kirpit
1
Это очень помогло мне сопоставить имена свойств модели SQLAlchemy с именами базовых столбцов.
FearlessFuture
30

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

Как отмечает Джош, полные имена полей SQL будут возвращены JobStatus.__table__.columns, поэтому вместо исходного идентификатора имени поля вы получите 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
Ницца! Есть ли способ с помощью этого метода также налаживать отношения?
shroud
3
_data attr не нужен, просто ..columns.keys () сделают трюк
Хумоюн Ахмад
1
Да, по возможности следует избегать использования частного атрибута _data, правильнее использовать @Humoyun.
Ng Oon-Ee
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)
Энди Зейдлер
источник
12

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

jrc
источник
Это простой ответ.
stgrmks
7

Чтобы получить as_dictметод для всех моих классов, я использовал Mixinкласс, который использует технику, описанную Антсом Аасмой .

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
Flazzarini
источник
Добавьте from sqlalchemy.orm import class_mapper, ColumnPropertyповерх фрагмента кода
JVK,
Спасибо за ваш комментарий! Я добавил недостающий импорт
flazzarini
Это декларативная база sqlalchemy, подробнее об этом читайте
flazzarini
1
self.__dict__

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

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

Валентин Фабиански
источник
Только когда установлены атрибуты.
stgrmks
0

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

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))
vktec
источник
вы не можете сделать isinstance (столбец, столбец) на строке
Muposat
Проголосовали против, потому что .columns таблицы и mapper .columns предоставляют вам эти данные без повторного изобретения колеса.
Дэвид Уотсон