在父关系中由 backref= 创建时无法识别子属性

发布于 2025-01-10 21:41:52 字数 2051 浏览 0 评论 0原文

我有几个具有以下模式的sqlalchemy模型,

class CM(Base):
    __tablename__ = "cm"

    id = Column("id", Integer(), primary_key=True)
    status = Column("status", String(32), nullable=False)
    hostname = Column("hostname", String(128), nullable=False)
    faults = relationship(
        "Fault", backref="cm", lazy="selectin", cascade="all, delete-orphan"
    )

class Fault(Base):
    __tablename__ = "fault"

    id = Column("id", Integer, primary_key=True)
    cm_id = Column(
        Integer,
        ForeignKey("cm.id", ondelete="CASCADE"),
        index=True,
        nullable=False,
    )
    component = Column("component", Text(255))

当我选择父表(即一对多关系的一侧)时, 在我的例子中是CM 一切正常

select(CM).filter(CM.faults.any(component="fake"))
<sqlalchemy.sql.selectable.Select object at 0x105187910>

如果我尝试选择其他方式,它似乎会失败

select(Fault).filter(Fault.cm.has(hostname="fake"))
Traceback (most recent call last):
  File "<input>", line 1, in <module>
AttributeError: type object 'Fault' has no attribute 'cm'

事实证明,如果相关列 cm 以某种方式加载,则上面的 select 开始工作,所以如果我做这样的事情

session.query(Fault).filter(Fault.cm.has(hostname="fake"))
<sqlalchemy.orm.query.Query object at 0x10da57fd0>
select(Fault).filter(Fault.cm.has(hostname="fake"))
<sqlalchemy.sql.selectable.Select object at 0x10da669d0>

或者甚至这样

select(CM).filter(CM.faults.any(component="fake"))
<sqlalchemy.sql.selectable.Select object at 0x1077a39d0>
select(Fault).filter(Fault.cm.has(hostname="fake"))
<sqlalchemy.sql.selectable.Select object at 0x1077bb510>

我的猜测是 select 无法在关系的许多方面加载相关字段,但我不确定如何修复。

事实上,我还尝试将 backref 列重命名为 cm 中的其他内容,这样它就不会与表名冲突,甚至这也不起作用。

我可以使用 sqlalchemy 1.x 风格的 query API 使用 session.query 来解决此问题,但由于 query API 将从 2.x 中删除有没有办法让它与select一起工作。

I have couple of sqlalchemy models with the following schemas

class CM(Base):
    __tablename__ = "cm"

    id = Column("id", Integer(), primary_key=True)
    status = Column("status", String(32), nullable=False)
    hostname = Column("hostname", String(128), nullable=False)
    faults = relationship(
        "Fault", backref="cm", lazy="selectin", cascade="all, delete-orphan"
    )

class Fault(Base):
    __tablename__ = "fault"

    id = Column("id", Integer, primary_key=True)
    cm_id = Column(
        Integer,
        ForeignKey("cm.id", ondelete="CASCADE"),
        index=True,
        nullable=False,
    )
    component = Column("component", Text(255))

When I select the parent table(i.e one side of one-to-many relation) which in my case is CM everything works fine

select(CM).filter(CM.faults.any(component="fake"))
<sqlalchemy.sql.selectable.Select object at 0x105187910>

If I try selecting the other way around it seems to fail

select(Fault).filter(Fault.cm.has(hostname="fake"))
Traceback (most recent call last):
  File "<input>", line 1, in <module>
AttributeError: type object 'Fault' has no attribute 'cm'

It turns out that if the related column cm is loaded in some way then the above select starts to work, so if I do something like this

session.query(Fault).filter(Fault.cm.has(hostname="fake"))
<sqlalchemy.orm.query.Query object at 0x10da57fd0>
select(Fault).filter(Fault.cm.has(hostname="fake"))
<sqlalchemy.sql.selectable.Select object at 0x10da669d0>

Or even this

select(CM).filter(CM.faults.any(component="fake"))
<sqlalchemy.sql.selectable.Select object at 0x1077a39d0>
select(Fault).filter(Fault.cm.has(hostname="fake"))
<sqlalchemy.sql.selectable.Select object at 0x1077bb510>

My guess is select is not able to load the related field on many side of the relationship, but I am not sure how do I fix.

In fact I also tried renaming the backref column to something else from cm so that it doesn't conflict with table name and even that did not work.

I can use the sqlalchemy 1.x style query API using session.query to workaround this but since query API will be removed from 2.x is there a way to make it work with select.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

辞慾 2025-01-17 21:41:52

CM 类中指定 relationship("Fault", backref="cm", …) 将“自动”在Fault 类,但该属性不会在声明(或导入)模型时立即创建。如果我们做的第一件事是

q = select(Fault).filter(Fault.cm.has(hostname="fake"))

然后我们得到错误

AttributeError:类型对象“Fault”没有属性“cm”

子类中的魔法通常会在第一次实例化父类时发生

dummy_cm = CM(status="foo", hostname="bar")

q = select(Fault).filter(Fault.cm.has(hostname="fake"))
# no error

另一种选择是在执行任何实际工作之前调用 configure_mappers()

configure_mappers()

q = select(Fault).filter(Fault.cm.has(hostname="fake"))
# no error

然而,backref 现在被视为遗留,因此首选方法是使用 back_populates= 而不是 backref= 并显式声明属性两个班级:

class CM(Base):
    # …
    faults = relationship("Fault", back_populates="cm", …)


class Fault(Base):
    # …
    cm = relationship("CM", back_populates="faults", …)

Specifying relationship("Fault", backref="cm", …) in the CM class will "automagically" create a cm attribute in the Fault class, but that attribute does not get created immediately on declaring (or importing) the models. If the first thing we do is

q = select(Fault).filter(Fault.cm.has(hostname="fake"))

then we get the error

AttributeError: type object 'Fault' has no attribute 'cm'

The magic in the child class normally happens the first time that the parent class is instantiated

dummy_cm = CM(status="foo", hostname="bar")

q = select(Fault).filter(Fault.cm.has(hostname="fake"))
# no error

Another option is to call configure_mappers() before doing any real work

configure_mappers()

q = select(Fault).filter(Fault.cm.has(hostname="fake"))
# no error

However, backref is now considered legacy so the preferred approach is to use back_populates= instead of backref= and explicitly declare the attributes in both classes:

class CM(Base):
    # …
    faults = relationship("Fault", back_populates="cm", …)


class Fault(Base):
    # …
    cm = relationship("CM", back_populates="faults", …)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文