"Collection was loaded during event handling." #7605
-
|
Hello! I've been tasked with migrating a moderately sized codebase (https://github.com/ThePalaceProject/circulation) from SQLAlchemy 1.3.25 to 1.4.30. I'm not very familiar with SQLAlchemy although I have spent the last week going through the tutorial and documentation and fixing other test issues. The I've, unfortunately, run into this exception: https://github.com/sqlalchemy/sqlalchemy/blob/main/lib/sqlalchemy/orm/attributes.py#L1416 I don't really understand what the exception is trying to tell me, or what the code that's triggering it is doing wrong. Is there anything in particular that I should be looking out for? Methods I should be searching for in the codebase to investigate? I've managed to find this one issue talking about it: ... but I don't really understand what this means. |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 5 replies
-
|
this means you have either an append event listener or a @validates listener on a collection, and the code within that handler is causing the collection to be loaded, when it is not expected to be loaded. the main purpose of append /validates events are to work upon the value that is being appended, not the collection itself. so best strategy is, inside that event handler, don't touch the collection itself, if possible. otherwise, the assertion here likely means the event is being triggered by a backref. the example below shows how to reproduce this issue and also how to work around it by only accessing the collection if this is the primary event and not the backref event. from sqlalchemy import Column
from sqlalchemy import create_engine
from sqlalchemy import event
from sqlalchemy import ForeignKey
from sqlalchemy import Integer
from sqlalchemy import String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import declarative_base
from sqlalchemy.orm import relationship
from sqlalchemy.orm import Session
Base = declarative_base()
class A(Base):
__tablename__ = 'a'
id = Column(Integer, primary_key=True)
data = Column(String)
bs = relationship("B", backref="a")
class B(Base):
__tablename__ = 'b'
id = Column(Integer, primary_key=True)
a_id = Column(ForeignKey("a.id"))
data = Column(String)
# this listen could also be a mapper @validates function, same thing
@event.listens_for(A.bs, "append")
def _append_a_b(target, value, initiator):
target.bs
return value
# one way to work around issue: check that the initiator is actually
# "bs" and not a backref
# @event.listens_for(A.bs, "append")
# def _append_a_b(target, value, initiator):
# if initiator.key == 'bs':
# target.bs
# return value
e = create_engine("sqlite://", echo=True)
Base.metadata.create_all(e)
s = Session(e)
s.add(A(bs=[B()]))
s.commit()
a1 = s.query(A).first()
# here, a1 is expired. the "bs" attribute is not loaded in
# a1.__dict__
b1 = B()
# now assign B().a = a1. this fires a backref that also appends b1
# to a1.bs. however, a1.bs does not load here, it places b1 in a "pending"
# collection that will be fulfilled if and when a1.bs is actually loaded.
b1.a = a1
# but the event handler does "target.bs" and populates a1.__dict__. we dont
# want that to happen hence the assertion |
Beta Was this translation helpful? Give feedback.
this means you have either an append event listener or a @validates listener on a collection, and the code within that handler is causing the collection to be loaded, when it is not expected to be loaded.
the main purpose of append /validates events are to work upon the value that is being appended, not the collection itself. so best strategy is, inside that event handler, don't touch the collection itself, if possible.
otherwise, the assertion here likely means the event is being triggered by a backref. the example below shows how to reproduce this issue and also how to work around it by only accessing the collection if this is the primary event and not the backref event.