SQLAlchemy "DetachedInstanceError" lazy-load after commit in CI
SQLAlchemy raises DetachedInstanceError when you access an attribute that needs a database round trip on an object whose session has been closed or whose state was expired (the default after commit). Tests that read relationships after the session ends hit this.
What this error means
A test fails with "sqlalchemy.orm.exc.DetachedInstanceError: Instance <Order at 0x...> is not bound to a Session; attribute refresh operation cannot proceed" when reading a relationship or expired column.
sqlalchemy.orm.exc.DetachedInstanceError: Instance <Order at 0x7f...> is not bound
to a Session; attribute refresh operation cannot proceed (Background on this error at:
https://sqlalche.me/e/20/bhk3)Common causes
Accessing a lazy attribute after the session closed
The object was loaded in a with Session() block; reading a relationship outside that block needs a query, but the session is gone.
Default expire_on_commit refreshes attributes
After commit(), attributes are expired and reloaded lazily on next access; if the session is closed by then, the refresh fails.
How to fix it
Keep the session open or eager-load
- Read needed attributes while the session is still open.
- Eager-load relationships with
selectinload/joinedloadso no later query is needed. - Or set
expire_on_commit=Falseso committed objects stay usable.
from sqlalchemy.orm import selectinload
stmt = select(Order).options(selectinload(Order.items))
orders = session.execute(stmt).scalars().all() # items already loadedDisable expire-on-commit for the test session
Keep attributes populated after commit so assertions outside the session still work.
Session = sessionmaker(engine, expire_on_commit=False)How to prevent it
- Eager-load relationships you will read after the session closes.
- Read attributes inside the session scope in tests.
- Set expire_on_commit=False when objects outlive their session.