backref behavior with lazy=dynamic
I know this has been worked out, so I'm not understanding this behavior, seems very basic:
from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
assoc = Table("assoc", Base.metadata,
Column('aid', Integer, ForeignKey('a.id')),
Column('bid', Integer, ForeignKey('b.id'))
)
class A(Base):
__tablename__ = "a"
id = Column(Integer, primary_key=True)
bs = relationship("B", backref="as_", secondary=assoc, lazy="dynamic")
class B(Base):
__tablename__ = "b"
id = Column(Integer, primary_key=True)
e = create_engine("sqlite://", echo=True)
Base.metadata.create_all(e)
s = Session(e)
a1 = A()
b1 = B()
s.add_all([b1](a1,))
a1.bs = [b1](b1)
assert len(b1.as_) == 1
without a flush, b1.as_ is empty. this causes history issues later on particularly when using association proxy and stuff like that. shouldn't dynamic be firing off all the exact same events ?
Comments (9)
-
reporter -
reporter the flip around case also:
from sqlalchemy import * from sqlalchemy.orm import * from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import attributes Base = declarative_base() assoc = Table("assoc", Base.metadata, Column('aid', Integer, ForeignKey('a.id')), Column('bid', Integer, ForeignKey('b.id')) ) class A(Base): __tablename__ = "a" id = Column(Integer, primary_key=True) bs = relationship("B", backref=backref("as_", lazy="dynamic"), secondary=assoc, ) class B(Base): __tablename__ = "b" id = Column(Integer, primary_key=True) e = create_engine("sqlite://", echo=True) Base.metadata.create_all(e) s = Session(e, autoflush=False) a1 = A() b1 = B() s.add_all([b1](a1,)) a1.bs = [b1](b1) a1.bs = [= [b1](] a1.bs) hist = attributes.get_history(b1, 'as_') assert hist.added == [a1](a1) assert hist.unchanged == [hist.deleted == [](] assert)
basically CollectionHistory is going to need to reconcile its added/removed lists at all times.
-
reporter here's the user's original case for when we do this:
from sqlalchemy import * from sqlalchemy.orm import * from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.associationproxy import association_proxy from sqlalchemy.orm.exc import NoResultFound Base = declarative_base() classifiers = Table("version_classifiers", Base.metadata, Column("id", Integer, primary_key=True), Column("classifier_id", Integer, ForeignKey("classifiers.id", ondelete="CASCADE"), nullable=False), Column("version_id", Integer, ForeignKey("versions.id", ondelete="CASCADE"), nullable=False), UniqueConstraint("classifier_id", "version_id") ) class Classifier(Base): __tablename__ = "classifiers" id = Column(Integer, primary_key=True) trove = Column(UnicodeText, unique=True, nullable=False) def __init__(self, trove): self.trove = trove def __repr__(self): return "<Classifier: {trove}>".format(trove=self.trove) @classmethod def get_or_create(cls, trove): try: obj = Session.query(cls).filter_by(trove=trove).one() except NoResultFound: obj = cls(trove) return obj class Version(Base): __tablename__ = "versions" id = Column(Integer, primary_key=True) _classifiers = relationship("Classifier", secondary=classifiers, backref=backref("versions", lazy='dynamic')) classifiers = association_proxy("_classifiers", "trove", creator=Classifier.get_or_create) e = create_engine('sqlite://', echo=True) Base.metadata.create_all(e) Session = scoped_session(sessionmaker(e, autoflush=False)) v = Version() Session.add(v) v.classifiers = [u"Foo"](u"Foo") Session.commit() # A Classifier with trove=u"Foo" is either retrieved or created v.classifiers = [u"Bar"](u"Foo",) Session.commit()
-
reporter - changed status to resolved
-
reporter - removed status
- changed status to open
i want to add some tests that get an understanding of if/when the history operation loads the entire collection. this behavior basically should not be occurring, else it defeats the purpose of the dynamic loader. Only if a collection reassignment is done does that imply a full collection load. want to check if it's been doing this in 0.7 before, or if it's doing it now, and if we can make it not do that without too bad of a compatibility change.
-
reporter reverting back to the old style, while keeping lots of the new stuff too, is efbbe88705eb19d68b587aae6dfb60cfe4356edb. I'd like to add more tests.
-
reporter #2642 refers the original issue here to be dealt with by the association proxy. but also, why doesn't calling clear() + set() on a dynamic fully load the collection for history ? a "clear()" should mean, "load everything and mark all as deleted".
-
reporter - changed status to resolved
found the real cause of the original issue, the "pop()" method was missing from DynamicAttributeImpl. This as well as the tests for SELECT are in 5b23a6808041ff90e2097e2cb37188d4845828f4.
-
reporter - removed milestone
Removing milestone: 0.8.0final (automated comment)
- Log in to comment
patch
#1:but issues remain:
"A" is added twice to "bs":