1

In my application I have an entity that is part of a many to many relationship. However I want to define the == method on that entity. But when I reference the children I get the error

Traceback (most recent call last):
  File "C:/Users/user/workspace/app/tmp.py", line 61, in <module>
    print(p == p1)
  File "C:/Users/user/workspace/app/tmp.py", line 18, in __eq__
    return self.id == o.id and self.children == o.children
  File "C:\Users\user\.virtualenvs\websocketserver-QkEQJFtA\lib\site-packages\sqlalchemy\orm\attributes.py", line 282, in __get__
    return self.impl.get(instance_state(instance), dict_)
  File "C:\Users\user\.virtualenvs\websocketserver-QkEQJFtA\lib\site-packages\sqlalchemy\orm\attributes.py", line 710, in get
    value = self.callable_(state, passive)
  File "C:\Users\user\.virtualenvs\websocketserver-QkEQJFtA\lib\site-packages\sqlalchemy\orm\strategies.py", line 707, in _load_for_state
    raise orm_exc.DetachedInstanceError(
sqlalchemy.orm.exc.DetachedInstanceError: Parent instance <Parent at 0x189b95446a0> is not bound to a Session; lazy load operation of attribute 'children' cannot proceed (Background on this error at: http://sqlalche.me/e/bhk3)

The App code looks like this.

from sqlalchemy import Column, Integer, ForeignKey, create_engine, Boolean
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship, sessionmaker

Base = declarative_base()


class Parent(Base):
    __tablename__ = 'parent'
    id = Column(Integer, primary_key=True)
    children = relationship("Association", back_populates="parent", lazy='select')

    def __repr__(self):
        return f"<Parent(id='{self.id}',  children='{self.children}')>"

    def __eq__(self, o: object) -> bool:
        self.children   # <--- crashes when referencing self.children
        if isinstance(o, Parent):
            return self.id == o.id and self.children == o.children
        return False


class Child(Base):
    __tablename__ = 'child'
    id = Column(Integer, primary_key=True)
    parents = relationship("Association", back_populates="child", cascade="all, delete", lazy='select')

    def __repr__(self):
        return f"<Widget(id='{self.id}', parents='{self.parents}')>"


class Association(Base):
    __tablename__ = 'association'
    active = Column(Boolean, nullable=False)
    child_id = Column(Integer, ForeignKey('child.id'), primary_key=True)
    parent_id = Column(Integer, ForeignKey('parent.id'), primary_key=True)
    parent = relationship("Parent", back_populates="children", lazy='select')
    child = relationship("Child", back_populates="parents", lazy='select')

    def __repr__(self):
        return f"<Association(child_id='{self.child_id}', parent_id='{self.parent_id}',)> "


engine = create_engine("sqlite://")
SessionLocal = sessionmaker(bind=engine, autocommit=False, expire_on_commit=False)

Base.metadata.create_all(engine)

session = SessionLocal()

p = Parent()
session.add(p)
session.commit()
session.close()

session = SessionLocal()

p1 = session.query(Parent).filter(Parent.id == 1).first()

print(p1)
print(p1.children)
print(p == p1)

Why can I access the children using p1.children or inside the __repr__ but not inside __eq__?

hadamard
  • 401
  • 4
  • 16

1 Answers1

2

If it's still relevant, the answer was given here

"detached" means you're dealing with an ORM object that is not associated with a Session. The Session is the gateway to the relational database, so anytime you refer to attributes on the mapped object, the ORM will sometimes need to go back to the database to get the current value of that attribute. In general, you should only work with "attached" objects - "detached" is a temporary state used for caching and for moving objects between sessions.

In your case p object is not bound to the same session as p1, since you closed it earlier. So, basically, it fails not when you're trying to access p1's children, but when p1's method is trying to access p's children (which is o there), as p object is not in the same session as p. So if you want to compare 2 objects, you should either not close the session, or get p once more by p = session.query(Parent).filter(Parent.id == 1).first() which doesn't make much sense.

Hope someone's got the point.

Temax
  • 87
  • 7