2017-06-27 23 views
0

SQLAlchemyを使用しています。ビジネスルールがあります。「バーのすべての子が準備が整ったら準備ができています。カスタムメソッドを実装してSQLAlchemyのクエリで使用する方法

Session.query(Foo).filter(Foo.is_ready()) 

しかし、私は例外を取得しています:

Traceback (most recent call last): 
    File "/usr/local/lib/python3.5/dist-packages/sqlalchemy/orm/attributes.py", line 185, in __getattr__ 
    return getattr(self.comparator, key) 
AttributeError: 'Comparator' object has no attribute 'all' 

モデル

ので、私は、次のクエリを実行しようとしている準備ができていること0

私は間違っていますか?ビジネスルールは将来複雑になるため、後で再利用するためにはその動作をカプセル化することが重要ですので、Foo.is_ready()メソッドを実装する必要があります。

答えて

1

classmethodselfは、すなわちFooクラス自体であるので、あなたのコードが動作しない理由があります。 (これは、従来、selfの代わりにclsと命名された理由です)。Foo.barsはを持たないから、Foo.barsは関係そのものであり、Queryオブジェクトではないからです。

これを書き込む正しい方法は何ですか?これらのシナリオでは、SQLAlchemyの魔法からあなた自身を取り除き、あなたが書く必要があるSQLについて考えることが役に立ちます。簡単な方法はEXISTSを使用することです:

SELECT * FROM foo 
WHERE NOT EXISTS (
    SELECT * FROM bar 
    WHERE bar.foo_id = foo.id AND bar.status != 'ready' 
); 

またはJOIN

SELECT * FROM foo 
LEFT JOIN bar ON foo.id = bar.foo_id AND bar.status != 'ready' 
WHERE bar.id IS NULL; 

これで武装し、それが今、あなたのis_readyを書くのは簡単です:

class Foo(Base): 
    @classmethod 
    def is_ready(cls): 
     return ~exists(select([Bar.id]).where(and_(Bar.foo_id == cls.id, Bar.status != "ready"))) 

session.query(Foo).filter(Foo.is_ready()) 

あなたもそれを回すことができます〜にhybrid_property

class Foo(Base): 
    @hybrid_property 
    def is_ready(self): 
     return all(bar.status == "ready" for bar in self.bars) 

    @is_ready.expression 
    def is_ready(cls): 
     bar = Bar.__table__ 
     return ~exists(select([Bar.id]).where(and_(Bar.foo_id == cls.id, Bar.status != "ready"))) 

session.query(Foo).filter(Foo.is_ready) 

JOINclassmethodまたはこのようなhybrid_propertyを使用して表現するのが難しいので、あなたが使用できる1つのトリックは.with_transformationです:

class Foo(Base): 
    @classmethod 
    def is_ready(cls): 
     def _transformer(query): 
      return query.join(Bar, and_(Foo.id == Bar.foo_id, Bar.status != "ready")).filter(Bar.id.is_(None)) 
     return _transformer 

session.query(Foo).with_transformation(Foo.is_ready()) 
+0

それは素晴らしい作品!しかし私は別の質問があります。なぜ 'is_ready(self)'が '@ hybrid_property'ですか?それは通常のプロパティ '@ property'である可能性があり、私が間違っていないとうまくいくでしょう。 –

+0

@ Overflow012 'hybrid_property'はインスタンスとクラスの両方で動作しますので、' Foo.is_ready'と 'foo.is_ready'の両方を(テーブルの列を使って)フィルターとして、クラスのインスタンスで' foo.is_ready' (メモリにロードされた属性と関係を使用して)。 – univerio

0

bar属性はありませんが、barsです。 classmethodの代わりにhybrid_propertyを使用してください。下記のコードですが、私はそれをテストしていません。

from sqlalchemy.ext.hybrid import hybrid_property 

class Foo(Base): 
    id = Column(Integer, primary_key=True) 

    @hybrid_property 
    def is_ready(self): 
     return self.bars.count() == self.bars.filter_by(status="ready").count() 

class Bar(Base): 
    id = Column(Integer, primary_key=True) 
    status = Column(String) 
    foo_id = Column(Integer, ForeignKey("foo.id")) 
    foo = relationship(Foo, back_ref=back_ref("bars", lazy="dynamic")) 
関連する問題