From 1d3185139a923fa1a1998d4498748d8f49278ac0 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Wed, 11 Feb 2009 20:38:30 +0000 Subject: [PATCH] - a session.expire() on a particular collection attribute will clear any pending backref additions as well, so that the next access correctly returns only what was present in the database. Presents some degree of a workaround for [ticket:1315], although we are considering removing the flush([objects]) feature altogether. --- CHANGES | 7 ++++++ lib/sqlalchemy/orm/attributes.py | 2 ++ test/orm/expire.py | 39 ++++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+) diff --git a/CHANGES b/CHANGES index c2b32672ff..be009b4efe 100644 --- a/CHANGES +++ b/CHANGES @@ -12,6 +12,13 @@ CHANGES union(query1, query2), select([foo]).select_from(query), etc. + - a session.expire() on a particular collection attribute + will clear any pending backref additions as well, so that + the next access correctly returns only what was present + in the database. Presents some degree of a workaround for + [ticket:1315], although we are considering removing the + flush([objects]) feature altogether. + - improvements to the "determine direction" logic of relation() such that the direction of tricky situations like mapper(A.join(B)) -> relation-> mapper(B) can be diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py index 657b961900..557a29098f 100644 --- a/lib/sqlalchemy/orm/attributes.py +++ b/lib/sqlalchemy/orm/attributes.py @@ -758,6 +758,7 @@ class CollectionAttributeImpl(AttributeImpl): state.commit([self.key]) if self.key in state.pending: + # pending items exist. issue a modified event, # add/remove new items. state.modified_event(self, True, user_data) @@ -1027,6 +1028,7 @@ class InstanceState(object): if impl.accepts_scalar_loader: self.callables[key] = self self.dict.pop(key, None) + self.pending.pop(key, None) self.committed_state.pop(key, None) def reset(self, key): diff --git a/test/orm/expire.py b/test/orm/expire.py index 4e8771347e..56384f3c23 100644 --- a/test/orm/expire.py +++ b/test/orm/expire.py @@ -293,6 +293,45 @@ class ExpireTest(_fixtures.FixtureTest): print attributes.instance_state(u).dict assert u.addresses[0].email_address == 'ed@wood.com' + @testing.resolve_artifact_names + def test_expired_pending(self): + mapper(User, users, properties={ + 'addresses':relation(Address, backref='user'), + }) + mapper(Address, addresses) + + sess = create_session() + a1 = Address(email_address='a1') + sess.add(a1) + sess.flush() + + u1 = User(name='u1') + a1.user = u1 + sess.flush() + + # expire 'addresses'. backrefs + # which attach to u1 will expect to be "pending" + sess.expire(u1, ['addresses']) + + # attach an Address. now its "pending" + # in user.addresses + a2 = Address(email_address='a2') + a2.user = u1 + + # expire u1.addresses again. this expires + # "pending" as well. + sess.expire(u1, ['addresses']) + + # insert a new row + sess.execute(addresses.insert(), dict(email_address='a3', user_id=u1.id)) + + # only two addresses pulled from the DB, no "pending" + assert len(u1.addresses) == 2 + + sess.flush() + sess.expire_all() + assert len(u1.addresses) == 3 + @testing.resolve_artifact_names def test_expired_lazy(self): mapper(User, users, properties={ -- 2.47.3