mirror of
https://github.com/sqlalchemy/sqlalchemy.git
synced 2026-05-29 20:14:55 -04:00
- 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:
@@ -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
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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"""
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user