From: Mike Bayer Date: Wed, 30 Jun 2010 20:12:32 +0000 (-0400) Subject: - An instance which is moved to "transient", has X-Git-Tag: rel_0_6_2~16 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=005d0453500c85fc8a6f15ce709251e2c5dae0f7;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - An instance which is moved to "transient", has an incomplete or missing set of primary key attributes, and contains expired attributes, will raise an InvalidRequestError if an expired attribute is accessed, instead of getting a recursion overflow. - make_transient() removes all "loader" callables from the state being made transient, removing any "expired" state - all unloaded attributes reset back to undefined, None/empty on access. --- diff --git a/CHANGES b/CHANGES index aba313a7ce..721cc4d732 100644 --- a/CHANGES +++ b/CHANGES @@ -50,6 +50,20 @@ CHANGES those configured on the mapper. False will make it as though order_by() was never called, while None is an active setting. + + - An instance which is moved to "transient", has + an incomplete or missing set of primary key + attributes, and contains expired attributes, will + raise an InvalidRequestError if an expired attribute + is accessed, instead of getting a recursion overflow. + + - The make_transient() function is now in the generated + documentation. + + - make_transient() removes all "loader" callables from + the state being made transient, removing any + "expired" state - all unloaded attributes reset back + to undefined, None/empty on access. - sql - The warning emitted by the Unicode and String types diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py index 935cbc35d7..b122b49510 100644 --- a/lib/sqlalchemy/orm/mapper.py +++ b/lib/sqlalchemy/orm/mapper.py @@ -2407,6 +2407,14 @@ def _load_scalar_attributes(state, attribute_names): if has_key: identity_key = state.key else: + # this codepath is rare - only valid when inside a flush, and the + # object is becoming persistent but hasn't yet been assigned an identity_key. + # check here to ensure we have the attrs we need. + pk_attrs = [mapper._get_col_to_prop(col).key for col in mapper.primary_key] + if state.expired_attributes.intersection(pk_attrs): + raise sa_exc.InvalidRequestError("Instance %s cannot be refreshed - it's not " + " persistent and does not " + "contain a full primary key." % state_str(state)) identity_key = mapper._identity_key_from_state(state) if (_none_set.issubset(identity_key) and \ diff --git a/lib/sqlalchemy/orm/session.py b/lib/sqlalchemy/orm/session.py index af646aa5f5..ead860ebd3 100644 --- a/lib/sqlalchemy/orm/session.py +++ b/lib/sqlalchemy/orm/session.py @@ -1592,11 +1592,19 @@ def make_transient(instance): such that it's as though the object were newly constructed, except retaining its values. + Attributes which were "expired" or deferred at the + instance level are reverted to undefined, and + will not trigger any loads. + """ state = attributes.instance_state(instance) s = _state_session(state) if s: s._expunge_state(state) + + # remove expired state and + # deferred callables + state.callables.clear() del state.key diff --git a/test/orm/test_expire.py b/test/orm/test_expire.py index 926ae06299..c0c96f8730 100644 --- a/test/orm/test_expire.py +++ b/test/orm/test_expire.py @@ -221,7 +221,23 @@ class ExpireTest(_fixtures.FixtureTest): assert 'name' not in u.__dict__ sess.add(u) assert u.name == 'jack' + + @testing.resolve_artifact_names + def test_no_instance_key_no_pk(self): + # same as test_no_instance_key, but the PK columns + # are absent. ensure an error is raised. + mapper(User, users) + sess = create_session() + u = sess.query(User).get(7) + sess.expire(u, attribute_names=['name', 'id']) + sess.expunge(u) + attributes.instance_state(u).key = None + assert 'name' not in u.__dict__ + sess.add(u) + assert_raises(sa_exc.InvalidRequestError, getattr, u, 'name') + + @testing.resolve_artifact_names def test_expire_preserves_changes(self): """test that the expire load operation doesn't revert post-expire changes""" diff --git a/test/orm/test_session.py b/test/orm/test_session.py index fca3bf757e..36709dbcb3 100644 --- a/test/orm/test_session.py +++ b/test/orm/test_session.py @@ -250,6 +250,15 @@ class SessionTest(_fixtures.FixtureTest): sess.add(u1) assert u1 in sess.new + # test expired attributes + # get unexpired + u1 = sess.query(User).first() + sess.expire(u1) + make_transient(u1) + assert u1.id is None + assert u1.name is None + + @testing.resolve_artifact_names def test_autoflush_expressions(self): """test that an expression which is dependent on object state is