Reputation: 4168
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
.
Upvotes: 1
Views: 406
Reputation: 123779
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", …)
Upvotes: 1