[Sqlalchemy-tickets] Issue #4359: very intricate expiration sequence can lead to a detached object
Brought to you by:
zzzeek
From: Michael B. <iss...@bi...> - 2018-11-06 15:04:55
|
New issue 4359: very intricate expiration sequence can lead to a detached object buried in a bound parameter https://bitbucket.org/zzzeek/sqlalchemy/issues/4359/very-intricate-expiration-sequence-can Michael Bayer: I've not managed to reduce this further yet, simple attempts are still failing to identify the full issue ``` #!python from sqlalchemy import Column, ForeignKey, Integer, MetaData, create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import backref, relationship, Session Base = declarative_base() class Person(Base): __tablename__ = "persons" id = Column(Integer, primary_key=True) class Car(Base): __tablename__ = "cars" id = Column(Integer, primary_key=True) owner_id = Column(Integer, ForeignKey("persons.id")) owner = relationship("Person") class House(Base): __tablename__ = "houses" id = Column(Integer, primary_key=True) owner_id = Column(Integer, ForeignKey("persons.id")) owner = relationship("Person") class PersonPersonRelationship(Base): __tablename__ = "person_person_relationships" from_person_id = Column(Integer, ForeignKey("persons.id"), primary_key=True) from_person = relationship( "Person", foreign_keys=[from_person_id], backref=backref("relationships"), ) to_person_id = Column(Integer, ForeignKey("persons.id"), primary_key=True) to_person = relationship("Person", foreign_keys=[to_person_id]) engine = create_engine("sqlite://") Base.metadata.create_all(engine) # Set up our data session = Session(bind=engine) person1 = Person(id=1) session.add(person1) person2 = Person(id=2) session.add(person2) PersonPersonRelationship(from_person=person2, to_person=person1) car = Car(owner=person2) session.add(car) session.commit() session.close() print("------------------------------------------------------------") # Start with a fresh session to reproduce the issue new_session = Session(engine) def make_foo_query(sess): owner = sess.query(Person).get(2) return House.owner == owner # TODO: need to figure out how to reduce this mapping # Pull the car, its owner, and the owner's related people into memory car = new_session.query(Car).one() relationships = car.owner.relationships crit = make_foo_query(new_session) for s in sorted( new_session.identity_map.all_states(), key=lambda x: id(x.obj()), reverse=True ): print("object: %s" % s.obj()) new_session.expire(s.obj()) print(crit.left.callable()) ``` ``` #!python ------------------------------------------------------------ object: <__main__.Person object at 0x7fd62fd87d30> object: <__main__.Car object at 0x7fd62fd87898> object: <__main__.PersonPersonRelationship object at 0x7fd62fd214e0> Traceback (most recent call last): File "test.py", line 84, in <module> print(crit.left.callable()) File "/home/classic/dev/sqlalchemy/lib/sqlalchemy/orm/relationships.py", line 1429, in _go value = fn(*arg, **kw) File "/home/classic/dev/sqlalchemy/lib/sqlalchemy/orm/mapper.py", line 2625, in _get_state_attr_by_column return state.manager[prop.key].impl.get(state, dict_, passive=passive) File "/home/classic/dev/sqlalchemy/lib/sqlalchemy/orm/attributes.py", line 597, in get value = state._load_expired(state, passive) File "/home/classic/dev/sqlalchemy/lib/sqlalchemy/orm/state.py", line 617, in _load_expired self.manager.deferred_scalar_loader(self, toload) File "/home/classic/dev/sqlalchemy/lib/sqlalchemy/orm/loading.py", line 813, in load_scalar_attributes (state_str(state))) sqlalchemy.orm.exc.DetachedInstanceError: Instance <Person at 0x87b640> is not bound to a Session; attribute refresh operation cannot proceed (Background on this error at: http://sqlalche.me/e/bhk3) ``` for some reason if I just do a simple A/B test and have "B" expired first, the issue does not reproduce. so not sure what's going on yet. |