如何实现自定义方法并将其与SQLAlchemy中的查询一起使用

时间:2017-06-27 21:14:04

标签: python sqlalchemy

我使用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(),因为业务规则将来会更复杂,所以重要的是封装该行为以便以后重新使用

2 个答案:

答案 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使用classmethodhybrid_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"))