метод ітерації над визначеними стовпцями моделі sqlalchemy?


95

Я намагався зрозуміти, як переглядати список стовпців, визначений у моделі 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

12
Не потрібно зациклюватися з inspect(JobStatus).columns.keys()
оглядом

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's jsonify decorator. Я спробував докласти докладнішу примітку із прикладом коду у своєму початковому запитанні, але це спричиняє помилку stackoverflow під час надсилання.
Рік

4
До речі, class_mapperпотрібно імпортувати зsqlalchemy.orm
священика

3
Хоча це законна відповідь, після версії 0.8 пропонується використовувати inspect(), яка повертає той самий об'єкт відображення, що і class_mapper(). docs.sqlalchemy.org/en/latest/core/inspection.html
kirpit

1
Це мені дуже допомогло зіставити імена властивостей моделі SQLAlchemy з основними іменами стовпців.
FearlessFuture

29

Я розумію, що це давнє запитання, але я щойно натрапив на ту саму вимогу і хотів би запропонувати альтернативне рішення для майбутніх читачів.

Як зазначає Джош, повні імена полів 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
Приємно! Чи є спосіб за допомогою цього методу також дістати стосунки?
саван

3
немає потреби в _data attr, просто ..columns.keys () виконайте фокус
Хумоюн Ахмад

1
Так, використання атрибута private _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)

10

Якщо припустити, що ви використовуєте декларативне відображення 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


Це проста відповідь.
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

Будь ласка, додайте from sqlalchemy.orm import class_mapper, ColumnPropertyповерх коду фрагмент
JVK

Дякуємо за ваш коментар! Я додав відсутні
імпорти

Це декларативна база sqlalchemy. Докладніше про це читайте тут docs.sqlalchemy.org/en/13/orm/extensions/declarative/…
flazzarini

1
self.__dict__

повертає дикт, де ключі є іменами атрибутів, а значення значення об'єкта.

/! \ є додатковий атрибут: '_sa_instance_state', але ви можете з цим впоратися :)


Тільки тоді, коли встановлені атрибути.
stgrmks

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

ви не можете зробити це обставиною (col, Column) на струні
Muposat

Проголосовано проти, тому що таблиці .columns і mapper .columns дають вам ці дані без перевизначення колеса.
Девід Уотсон,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.