【问题标题】:How can I query a sqlalchemy relationship with an int instead of an instance?如何使用 int 而不是实例查询 sqlalchemy 关系?
【发布时间】:2017-04-02 02:03:06
【问题描述】:

我有两个模型,Foo 和 Bar。 Bar 与 Foo 具有一对多的关系,具有“foo_id”列和“foo”关系。我想查询 foo_id=1 的 Bar 行。

据我所知,有两种工作方法可以做到这一点:

  1. 访问底层外键列对象:query.filter(Bar.foo_id == 1)
  2. 获取 Foo 的实例,然后执行 query.filter(Bar.foo == instance)

我想要这两种方法的替代方法,它更像query.filter(Bar.foo == 1) - 即使用关系列和纯整数而不是实例。这目前因AttributeError: 'int' object has no attribute '_sa_instance_state' 而失败

我想避免上述两种方法的原因:

  1. 第一种方法在实际应用程序中不可行,因为我不知道这个foo_id 列的名称 - 所有表元数据都是通过反射生成的,列名是我的实现细节'd 宁愿不依赖(它们目前非常不一致,并且正在通过 alembic 迁移重命名,因此不希望依赖这些名称)

  2. 第二种方法添加了一个额外的 SQL 查询往返,以在我能够执行实际查询之前获取 Foo 的实例,该查询不使用除我已经拥有的整数 ID 之外的任何内容(并且身份映射没有以我使用它的方式缓存)。我承认这部分很危险地接近于过早的优化,但仍然觉得很浪费,应该有更好的方法。

表达这个问题的另一种方式是如何从关系属性开始到达外键列,可能使用自省

我也可以通过某种方式获得某种延迟加载的 Foo 实例,该实例足以通过 ID 进行查询,但实际上并不发送 SQL 查询

这里有一些说明问题的独立测试代码:

from sqlalchemy import create_engine, Column, Integer, ForeignKey
from sqlalchemy.orm import relationship, Session
from sqlalchemy.ext.declarative import declarative_base

def method_1(session, some_foo_id):
    """Works, but i don't actually know foo_id"""

    session.query(Bar).filter(Bar.foo_id == some_foo_id).first()

def method_2(session, some_foo_id):
    """Works, but adds a pointless roundtrip"""

    some_foo = session.query(Foo).get(some_foo_id)
    session.query(Bar).filter(Bar.foo == some_foo).first()

def method_3(session, some_foo_id):
    """Throws an exception, passing int instead of instance"""

    session.query(Bar).filter(Bar.foo == some_foo_id).first()


# database setup follows

Base = declarative_base()

class Foo(Base):
    __tablename__ = 'foo'
    id = Column(Integer, primary_key=True)
    value = Column(Integer)

class Bar(Base):
    __tablename__ = 'bar'
    id = Column(Integer, primary_key=True)
    foo_id = Column(Integer, ForeignKey('foo.id'))
    foo = relationship(Foo)

if __name__ == '__main__':
    engine = create_engine('sqlite://')
    Base.metadata.create_all(engine)
    session = Session(bind=engine)

    foo1, foo2 = Foo(value=1), Foo(value=2)
    bar1, bar2 = Bar(foo=foo1), Bar(foo=foo2)
    session.add_all([foo1, foo2, bar1, bar2])
    session.commit()
    engine.echo = True

    for fun in [method_1, method_2, method_3]:
        print("\n---> %s (%s)\n" % (fun.__name__, fun.__doc__))

        fun(session, 1)
        session.rollback()

输出:

---> method_1 (Works, but i don't actually know foo_id

BEGIN (implicit)
SELECT bar.id AS bar_id, bar.foo_id AS bar_foo_id
    FROM bar
    WHERE bar.foo_id = ?
     LIMIT ? OFFSET ?
(1, 1, 0)
ROLLBACK

---> method_2 (Works, but adds a pointless roundtrip)

BEGIN (implicit)
SELECT foo.id AS foo_id, foo.value AS foo_value
    FROM foo
    WHERE foo.id = ?
(1,)
SELECT bar.id AS bar_id, bar.foo_id AS bar_foo_id
    FROM bar
    WHERE ? = bar.foo_id
     LIMIT ? OFFSET ?
(1, 1, 0)
ROLLBACK

---> method_3 (Throws an exception, passing int instead of instance)

Traceback (most recent call last):
  File "asd.py", line 51, in <module>
    fun(session, 1)
  File "asd.py", line 19, in method_3
    session.query(Bar).filter(Bar.foo == some_foo_id).first()
  File "/usr/lib/python2.7/site-packages/sqlalchemy/sql/operators.py", line 304, in __eq__
    return self.operate(eq, other)
  File "/usr/lib/python2.7/site-packages/sqlalchemy/orm/attributes.py", line 175, in operate
    return op(self.comparator, *other, **kwargs)
  File "/usr/lib/python2.7/site-packages/sqlalchemy/orm/relationships.py", line 1042, in __eq__
    other, adapt_source=self.adapter))
  File "/usr/lib/python2.7/site-packages/sqlalchemy/orm/relationships.py", line 1369, in _optimized_compare
    state = attributes.instance_state(state)
AttributeError: 'int' object has no attribute '_sa_instance_state'

【问题讨论】:

    标签: python orm sqlalchemy


    【解决方案1】:

    找到了一种方法,Bar.foo.prop.local_columns 是RelationshipProperty 的一个属性,它返回一个通常包含一个项目的集合(对于像这样的简单关系)。集合是无序的,你不能只得到第一个项目,所以list(...)[0]。完整代码:

    def method_4(session, some_foo_id):
        foo_col = list(Bar.foo.prop.local_columns)[0]
        session.query(Bar).filter(foo_col == some_foo_id).first()
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-09-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-08-26
      • 2011-02-18
      • 1970-01-01
      相关资源
      最近更新 更多