我使用SQLAlchemy并且我有业务规则:"如果所有的Bar子女孩都准备就绪,Foo就准备好了#34;在一个用例中,我需要准备好所有Foo,以便我尝试执行以下查询:
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'
模型
class Foo(Base):
bars = relationship(Bar, lazy = "dynamic")
@classmethod
def is_ready(self):
return len(self.bar.all()) == len(self.bars.filter(Bar.status == "ready"))
class Bar(Base):
status = Column(String)
foo_id = Column(Integer, ForeignKey("foo.id"))
我做错了什么?我真的需要实现一个方法Foo.is_ready(),因为业务规则将来会更复杂,所以重要的是封装该行为以便以后重新使用
答案 0 :(得分:1)
您的代码无效的原因是因为self
中的classmethod
是类本身,即Foo
。 (这就是为什么传统上它被命名为cls
而不是self
。)当然Foo.bars
没有.all()
,因为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)
JOIN
使用classmethod
或hybrid_property
来表达是很棘手的,所以你可以使用的一个技巧是.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())
答案 1 :(得分:0)
没有bar
属性,但bars
。尝试使用hybrid_property
代替classmethod
。代码如下,但我还没有测试过。
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"))