метод итерации по определенным столбцам модели sqlalchemy? - PullRequest
87 голосов
/ 29 марта 2010

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

Спасибо за помощь.

Ответы [ 8 ]

67 голосов
/ 29 марта 2010

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

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 magic , но не исключит отношения. Таким образом, в основном это может загрузить зависимости, родителей, детей и т. Д., Что определенно нежелательно.

Но на самом деле это намного проще, потому что если вы наследуете от 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
59 голосов
/ 29 марта 2010

Вы можете получить список определенных свойств из картографа. Для вашего случая вас интересуют только объекты 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)]
27 голосов
/ 28 сентября 2012

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

Как отмечает Джош, полные имена полей 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']
12 голосов
/ 07 декабря 2012

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)
7 голосов
/ 20 августа 2015

Чтобы получить метод 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
3 голосов
/ 30 сентября 2018

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

0 голосов
/ 23 апреля 2019
self.__dict__

возвращает слово, в котором ключи являются именами атрибутов и значениями объекта.

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

0 голосов
/ 28 октября 2015

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

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))
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...