Skip to content

make_transient_to_detached expires deferred attrs making them load on refresh #4084

Closed
@sqlalchemy-bot

Description

@sqlalchemy-bot
Collaborator

Migrated issue, originally created by Michael Bayer (@zzzeek)

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.

Activity

sqlalchemy-bot

sqlalchemy-bot commented on Sep 19, 2017

@sqlalchemy-bot
CollaboratorAuthor

Changes by Michael Bayer (@zzzeek):

  • changed title from "make_transient_to_pending expires deferred attrs m" to "make_transient_to_detached expires deferred attrs "
sqlalchemy-bot

sqlalchemy-bot commented on Sep 19, 2017

@sqlalchemy-bot
CollaboratorAuthor

Michael Bayer (@zzzeek) wrote:

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

sqlalchemy-bot

sqlalchemy-bot commented on Sep 26, 2017

@sqlalchemy-bot
CollaboratorAuthor
sqlalchemy-bot

sqlalchemy-bot commented on Sep 27, 2017

@sqlalchemy-bot
CollaboratorAuthor

Michael Bayer (@zzzeek) wrote:

Don't expire "deferred" attributes in make_transient_to_detached

Fixed issue where the :func:.make_transient_to_detached function
would expire all attributes on the target object, including "deferred"
attributes, which has the effect of the attribute being undeferred
for the next refesh, causing an unexpected load of the attribute.

Change-Id: I82a385e3033e3f3c31569b1e908efb5f258d0f27
Fixes: #4084

5abb036

sqlalchemy-bot

sqlalchemy-bot commented on Sep 27, 2017

@sqlalchemy-bot
CollaboratorAuthor

Changes by Michael Bayer (@zzzeek):

  • changed status to closed
added this to the 1.2 milestone on Nov 27, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingorm

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @sqlalchemy-bot

        Issue actions

          make_transient_to_detached expires deferred attrs making them load on refresh · Issue #4084 · sqlalchemy/sqlalchemy