Reputation: 2077
I don't understand why SQLAlchemy gives me the following warning:
SAWarning: This collection has been invalidated.
util.warn("This collection has been invalidated.")
Here is a minimal example:
import sqlalchemy
print(sqlalchemy.__version__)
from sqlalchemy import create_engine
from sqlalchemy import Column, Integer, ForeignKey
from sqlalchemy.exc import IntegrityError
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship, sessionmaker
Base = declarative_base()
class ModelBase:
@classmethod
def create(cls, session, **kwargs):
obj = cls(**kwargs)
session.add(obj)
session.commit()
return obj
class User(ModelBase, Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
addresses = relationship("Address", order_by="Address.id", back_populates="user")
class Address(ModelBase, Base):
__tablename__ = 'addresses'
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey('users.id'))
user = relationship("User", back_populates="addresses")
engine = create_engine('sqlite:///:memory:', echo=False)
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
jack = User.create(session)
jack.addresses.append(Address.create(session))
The warning is thrown in the last line. When I rewrite the code a little the warning disappears.
For instance:
jack = User.create(session)
a = Address.create(session)
jack.addresses.append(a)
or
jack = User.create(session)
jack.addresses = [Address.create(session)]
I can reproduce this warning with SQLAlchemy 1.3.18 and 1.4.0b1.
Upvotes: 1
Views: 524
Reputation: 55600
The warning was added to SQLAlchemy in this commit. The commit message is
A warning is emitted when a reference to an instrumented collection is no longer associated with the parent class due to expiration/attribute refresh/collection replacement, but an append or remove operation is received on the now-detached collection.
I think what happens here is jack.addresses
is valid when initially accessed, but the commit inside creates
expires all the objects in the session including jack.addresses
, and this causes the warning.
If the code is split
a = Address.create(session)
jack.addresses.append(a)
jack.addresses
is accessed after the commit in create
and so the session is able to refresh it and no warning is emitted.
It's worth noting that committing in create-type methods like in the question goes against the grain of SQLAlchemy's unit of work model, in which changes are generally accumulated in the session and finally sent to the database in a single commit. Removing obj.commit()
from the create
method and committing once both objects are created will work just as well, is more efficient and avoids partial updates, for example the user instance is created and committed but there is an error creating the address.
Upvotes: 2