Rohit
Rohit

Reputation: 4168

Child attribute not recognized when created by backref= in parent relationship

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

Answers (1)

Gord Thompson
Gord Thompson

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

Related Questions