sqlalchemyモデルの定義された列を反復する方法?


95

SQLAlchemyモデルで定義された列のリストを反復処理する方法を理解しようとしています。いくつかのシリアル化およびコピーメソッドをいくつかのモデルに書き込むために必要です。obj.__dict__SA固有のアイテムが多数含まれているため、繰り返し処理することはできません。

iddesc名前を以下から取得する方法を知っている人はいますか?

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(より大きなオブジェクトに対して)自動生成するものを好みます。

回答:


83

次の関数を使用できます。

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.cMapper.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は、ORM定義で使用した属性名ではなく、SQLフィールド名を提供することに注意してください(2つが異なる場合)。
ジョシュケリー

11
に変更'_sa_' != k[:4]することをお勧めしnot k.startswith('_sa_')ますか?
Mu Mind

11
検査でループする必要はありません: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
おかげで、これでBaseにtodictメソッドを作成できるようになりました。これを使用して、インスタンスをdictに「ダンプ」し、pylonのjsonifyデコレータレスポンスに渡すことができます。元の質問にコード例を含むより詳細なメモを書き込もうとしましたが、送信時にスタックオーバーフローでエラーが発生しています。
Rick

4
ところで、以下class_mapperからインポートする必要がありますsqlalchemy.orm
priestc

3
これは正当な回答ですが、バージョン0.8以降inspect()では、とまったく同じマッパーオブジェクトを返すを使用することをお勧めしますclass_mapper()docs.sqlalchemy.org/en/latest/core/inspection.html
kirpit

1
これは、SQLAlchemyモデルのプロパティ名を基になる列名にマップするのに大いに役立ちました。
FearlessFuture

29

これは古い質問だと思いますが、同じ要件に出くわしたので、将来の読者に別の解決策を提供したいと思います。

Joshが指摘するように、完全な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']

2
いいね!この方法で関係を取得する方法はありますか?
シュラウド

3
_data attrは必要ありません。..columns.keys()でトリックを実行できます
Humoyun Ahmad

1
はい、プライベート_data属性の使用は可能な限り避けてください。@ Humoyunの方が正しいです。
Ng Oon-Ee 2017

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すべてのクラスのメソッドを取得するために、Ants AasmaMixinによって記述された技術を使用するクラスを使用しました

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 2018

コメントありがとうございます!不足しているインポートを追加しました
flazzarini、2018

それSQLAlchemyの宣言型のベースは、ここでその上で、より読みだdocs.sqlalchemy.org/en/13/orm/extensions/declarative/...
flazzarini


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

文字列に対してisinstance(col、Column)を実行することはできません
Muposat

table .columnsとmapper .columnsは、ホイールを再発明することなくこのデータを提供するため、反対票を投じました。
David Watson
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.