- 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.
This commit is contained in:
Mike Bayer
2010-06-30 16:12:32 -04:00
parent 5a58907c99
commit 005d045350
5 changed files with 55 additions and 0 deletions
+14
View File
@@ -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
+8
View File
@@ -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 \
+8
View File
@@ -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
+16
View File
@@ -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"""
+9
View File
@@ -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