make_transient_to_detached expires deferred attrs making them load on refresh
Issue #4084
new
from sqlalchemy import Column, Integer, String, create_engine
from sqlalchemy.orm import Session, deferred
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm.session import make_transient_to_detached
Base = declarative_base()
class MyTable(Base):
__tablename__ = 'my_table'
id = Column(Integer, primary_key=True)
undeferred = Column(String)
deferred_column = deferred(Column(String))
e = create_engine("sqlite://", echo=True)
Base.metadata.create_all(e)
e.execute(
"insert into my_table (id, undeferred, "
"deferred_column) values (1, 'foo', 'bar')")
s = Session(e)
def expire_via_detached():
item = MyTable(id=1)
make_transient_to_detached(item)
s.add(item)
item.undeferred
assert 'deferred_column' not in item.__dict__
s.close()
def expire_normally():
item = s.query(MyTable).first()
s.expire(item)
item.undeferred
assert 'deferred_column' not in item.__dict__
s.close()
def expire_explicit_attrs():
item = s.query(MyTable).first()
s.expire(item, ['undeferred', 'deferred_column'])
item.undeferred
assert 'deferred_column' in item.__dict__
s.close()
expire_normally()
expire_explicit_attrs()
expire_via_detached()
this is due to state._expire(state, state.unloaded) in make_transient_to_pending(). When a deferred attribute is explicitly expired, it becomes part of the next full load.
Comments (3)
-
reporter -
reporter diff --git a/lib/sqlalchemy/orm/session.py b/lib/sqlalchemy/orm/session.py index 359370ab5..0287f1cfb 100644 --- a/lib/sqlalchemy/orm/session.py +++ b/lib/sqlalchemy/orm/session.py @@ -3037,7 +3037,7 @@ def make_transient_to_detached(instance): if state._deleted: del state._deleted state._commit_all(state.dict) - state._expire_attributes(state.dict, state.unloaded) + state._expire_attributes(state.dict, state.unloaded_expirable) def object_session(instance): diff --git a/lib/sqlalchemy/orm/state.py b/lib/sqlalchemy/orm/state.py index 2e53fe9e3..4964c22e6 100644 --- a/lib/sqlalchemy/orm/state.py +++ b/lib/sqlalchemy/orm/state.py @@ -610,6 +610,7 @@ class InstanceState(interfaces.InspectionAttr): def unmodified_intersection(self, keys): """Return self.unmodified.intersection(keys).""" + return set(keys).intersection(self.manager).\ difference(self.committed_state) @@ -626,6 +627,18 @@ class InstanceState(interfaces.InspectionAttr): difference(self.dict) @property + def unloaded_expirable(self): + """Return the set of keys which do not have a loaded value. + + This includes expired attributes and any other attribute that + was never populated or modified. + + """ + return self.unloaded.intersection( + attr for attr in self.manager + if self.manager[attr].impl.expire_missing) + + @property def _unloaded_non_object(self): return self.unloaded.intersection( attr for attr in self.manager
-
reporter - Log in to comment