2017-03-20 13 views
1

social-app-flask-sqlalchemyの問題を診断しようとしているときに、予期しない動作やバグがあるかどうかわからないところで、やや直感的な振る舞いを見つけました。mixinの変更可能な列が突然変異を追跡しない

は、次のコードを考えてみましょう:

from sqlalchemy import create_engine, Column, Integer, PickleType 
from sqlalchemy.ext.declarative import declarative_base 
from sqlalchemy.ext.mutable import MutableDict 
from sqlalchemy.orm import sessionmaker 

Base = declarative_base() 

class A(Base): 
    __abstract__ = True 

class B(Base): 
    id = Column(Integer, primary_key=True) 
    __tablename__ = 'some_table' 
    my_data = Column(MutableDict.as_mutable(PickleType)) 

class C(A, B): 
    pass 

engine = create_engine('sqlite://') 
session_factory = sessionmaker(bind=engine) 
db_session = session_factory() 

Base.metadata.create_all(engine) 

assert B.my_data.type.__class__ is PickleType 

c_instance = C(my_data={'foo': 'bar'}) 
db_session.add(c_instance) 
db_session.commit() 
loaded_instance = db_session.query(C).first() 
loaded_instance.my_data.update(baz=1) 

assert loaded_instance.my_data['baz'] == 1 

assert loaded_instance in db_session.dirty 

これは問題なく動作します。ここでBのスーパークラスをobjectに変更すると、最後のアサーションエラーが出力されます。そこまで、すべてがうまくいく。

それはBサブクラスを持っていないことにより、直接、my_dataの種類がもはやMutableDictことを強制されdeclarative_baseことが判明していないが、オブジェクト(この場合はdict)をインスタンス化するときに我々が与えるどんなタイプ。明らかに、これはmy_dataへの変更がもはや追跡されないことを意味する。ただし、my_dataはデータ型としてPickleTypeを使用しているため、すぐには表示されません。

extra_dataへの変更がsocial-app-flask-sqlalchemyにDBに書き込まれていないと、もともと私はこれに遭遇しました。 social-app-flask-sqlalchemyは、列を保持するSQLAlchemyUserMixinクラスがサブクラス化されていないdeclarative_baseクラスですが、そのサブクラスUserSocialAuthは、_AppSessionを経由しています。

今、問題として報告する場所がわかりません。sqlalchemyまたはsocial-app-flask-sqlalchemyです。何かご意見は?

+0

、[Iは '社会ストレージsqlalchemy'が固定されるべきであると信じて(HTTPS失敗単純な宣言とdeclared_attr両方の例://github.com/python-social-auth/social-storage-sqlalchemy/blob/master/social_sqlalchemy/storage.py#L90)、ひどい名前のモデルではない場合。彼らには[commit-flush](https://github.com/python-social-auth/social-storage-sqlalchemy/blob/master/social_sqlalchemy/storage.py#L64)などの他の面白いパターンもあります。これは冗長でなければなりません[とにかく最初にフラッシュします](http://docs.sqlalchemy.org/en/latest/orm/session_api.html#sqlalchemy.orm.session.Session.commit)場合。 –

答えて

4

declare complex mixin columns such as yours using the declared_attr decoratorです。単純な列宣言はミックスインからコピーされます。これを達成するために

、宣言型の拡張はミックスインとして検出されたクラスに遭遇する各Columnオブジェクトのコピーを作成します。

そしてこれは明らかにmutation trackingでうまくいきません。ドキュメンテーションは、どのコンストラクトがデコレータを使用すべきかについて多少曖昧です。

ForeignKeyには、このレベルで正しく再作成できない列への参照が含まれているため、このコピーメカニズムは外部キーを持たない単純な列に限定されています。 、ならびに多くのクラスに共通のパターンは次のように定義することができるようにdeclared_attrデコレータが設けられている先の明示的なコンテキストを必要マッパレベルの構築物の様々な外部キーを持っている列に対して呼び出し可能

強調しておきますが、この文脈では「宛先明示的コンテキスト」が何を意味するのか誤解しているかもしれません。だから、次の操作を行います。

代わり
class B(object): 
    ... 
    @declared_attr 
    def my_data(cls): 
     return Column(MutableDict.as_mutable(PickleType)) 

Bはミックスインクラスにする場合。

In [2]: from sqlalchemy.ext.mutable import MutableDict 

In [3]: class MixinA: 
    ...:  extra = Column(MutableDict.as_mutable(PickleType)) 
    ...: 

In [4]: from sqlalchemy.ext.declarative import declared_attr 

In [5]: class MixinB: 
    ...:  @declared_attr 
    ...:  def extra(cls): 
    ...:   return Column(MutableDict.as_mutable(PickleType)) 
    ...: 

In [6]: class A(MixinA, Base): 
    ...:  __tablename__ = 'a' 
    ...:  id = Column(Integer, primary_key=True, autoincrement=True) 
    ...: 

In [7]: class B(MixinB, Base): 
    ...:  __tablename__ = 'b' 
    ...:  id = Column(Integer, primary_key=True, autoincrement=True) 
    ...: 

In [8]: metadata.create_all() 

とアクションで:責任を通過するように

In [9]: a = A(extra={}) 

In [10]: b = B(extra={}) 

In [11]: session.add(a) 

In [12]: session.add(b) 

In [13]: session.commit() 

In [14]: session.query(A.extra).first() 
Out[14]: ({}) 

In [15]: session.query(B.extra).first() 
Out[15]: ({}) 

In [16]: b.extra['b'] = 1 

In [17]: session.commit() 

In [18]: session.query(B.extra).first() 
Out[18]: ({'b': 1}) 

In [19]: a.extra['a'] = 1 

In [20]: session.commit() 

In [21]: session.query(A.extra).first() 
Out[21]: ({}) 

In [22]: b.extra['bb'] = 2 

In [23]: assert b in session.dirty 
+0

優秀な回答、ありがとう! 1つのフォローアップの質問で、クラスBで '@ declared_attr'を使用すると、* SAWarning:マッピングされていないクラスB *から宣言的属性my_dataのアンマネージドアクセスを取得します。この警告も出ますか? – dorian

+0

この警告は、mixinを持つ宣言型モデルクラスではなく、mixinクラスから直接属性にアクセスしていることを意味します。上記の例では、 'MixinB.extra'はその例外を送出します。なぜなら' MixinB'はマップされた/管理されたモデルクラスではなく単純なクラスなのですから。出力例は次のとおりです:https://paste.ofcode.org/6YenqspiMNs5vH4wfLhDye。ミックスインの背後にあるアイデアは、それを使って共通のビヘイビアを含むことですが、実際にそのビヘイビアを実装するためには、実際のモデルクラスを作成する必要があります。 –

+0

["Mixin and Custom Base Classes"](http://docs.sqlalchemy.org/en/latest/orm/extensions/declarative/mixins.html)を読んでいない場合は、そうする必要があります。それは多くのmixinパターンを説明します。 –

関連する問題