]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- Added new utility function :func:`.make_transient_to_detached` which can
authorMike Bayer <mike_mp@zzzcomputing.com>
Wed, 9 Apr 2014 21:49:16 +0000 (17:49 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 9 Apr 2014 21:49:16 +0000 (17:49 -0400)
be used to manufacture objects that behave as though they were loaded
from a session, then detached.   Attributes that aren't present
are marked as expired, and the object can be added to a Session
where it will act like a persistent one. fix #3017

doc/build/changelog/changelog_09.rst
doc/build/orm/session.rst
lib/sqlalchemy/orm/__init__.py
lib/sqlalchemy/orm/session.py
test/orm/test_session.py

index 7c7f1757e6d991c273b8c47380294b4803530b87..578edd93f87888f686334bbadef3902c976b9874 100644 (file)
 .. changelog::
     :version: 0.9.5
 
+    .. change::
+        :tags: feature, orm
+        :tickets: 3017
+
+        Added new utility function :func:`.make_transient_to_detached` which can
+        be used to manufacture objects that behave as though they were loaded
+        from a session, then detached.   Attributes that aren't present
+        are marked as expired, and the object can be added to a Session
+        where it will act like a persistent one.
+
     .. change::
         :tags: bug, sql
 
index 26c0b3f85add069af0c813a9f7fb53509cae3dec..d53dc863010faa760dfd0963387394d429346396 100644 (file)
@@ -2475,6 +2475,8 @@ Session Utilites
 
 .. autofunction:: make_transient
 
+.. autofunction:: make_transient_to_detached
+
 .. autofunction:: object_session
 
 .. autofunction:: sqlalchemy.orm.util.was_deleted
index 7825a70acf542578f6255db9a5045402fad4c80f..8dfa68853c581b6d1a9205cc917befd796f34c59 100644 (file)
@@ -56,7 +56,8 @@ from .session import (
     Session,
     object_session,
     sessionmaker,
-    make_transient
+    make_transient,
+    make_transient_to_detached
 )
 from .scoping import (
     scoped_session
index 5bd46691e93fb3a9d2a4825802b9361deb27ce28..a040101bf93300aabb86a74deaebda1a41bca864 100644 (file)
@@ -2361,7 +2361,6 @@ class sessionmaker(_SessionClassMethods):
                 )
 
 
-
 def make_transient(instance):
     """Make the given instance 'transient'.
 
@@ -2390,6 +2389,41 @@ def make_transient(instance):
     if state.deleted:
         del state.deleted
 
+def make_transient_to_detached(instance):
+    """Make the given transient instance 'detached'.
+
+    All attribute history on the given instance
+    will be reset as though the instance were freshly loaded
+    from a query.  Missing attributes will be marked as expired.
+    The primary key attributes of the object, which are required, will be made
+    into the "key" of the instance.
+
+    The object can then be added to a session, or merged
+    possibly with the load=False flag, at which point it will look
+    as if it were loaded that way, without emitting SQL.
+
+    This is a special use case function that differs from a normal
+    call to :meth:`.Session.merge` in that a given persistent state
+    can be manufactured without any SQL calls.
+
+    .. versionadded:: 0.9.5
+
+    .. seealso::
+
+        :func:`.make_transient`
+
+    """
+    state = attributes.instance_state(instance)
+    if state.session_id or state.key:
+        raise sa_exc.InvalidRequestError(
+                    "Given object must be transient")
+    state.key = state.mapper._identity_key_from_state(state)
+    if state.deleted:
+        del state.deleted
+    state._commit_all(state.dict)
+    state._expire_attributes(state.dict, state.unloaded)
+
+
 
 def object_session(instance):
     """Return the ``Session`` to which instance belongs.
index 5993c15f34718310f1cb21a5eb1cc9c48ecbc600..b68810036e72fdf509699b7fbb7e5f26ef757eb3 100644 (file)
@@ -5,7 +5,7 @@ from sqlalchemy.testing import pickleable
 from sqlalchemy.util import pickle
 import inspect
 from sqlalchemy.orm import create_session, sessionmaker, attributes, \
-    make_transient, Session
+    make_transient, make_transient_to_detached, Session
 import sqlalchemy as sa
 from sqlalchemy.testing import engines, config
 from sqlalchemy import testing
@@ -393,6 +393,51 @@ class SessionUtilTest(_fixtures.FixtureTest):
         make_transient(u1)
         sess.rollback()
 
+    def test_make_transient_to_detached(self):
+        users, User = self.tables.users, self.classes.User
+
+        mapper(User, users)
+        sess = Session()
+        u1 = User(id=1, name='test')
+        sess.add(u1)
+        sess.commit()
+        sess.close()
+
+        u2 = User(id=1)
+        make_transient_to_detached(u2)
+        assert 'id' in u2.__dict__
+        sess.add(u2)
+        eq_(u2.name, "test")
+
+    def test_make_transient_to_detached_no_session_allowed(self):
+        users, User = self.tables.users, self.classes.User
+
+        mapper(User, users)
+        sess = Session()
+        u1 = User(id=1, name='test')
+        sess.add(u1)
+        assert_raises_message(
+            sa.exc.InvalidRequestError,
+            "Given object must be transient",
+            make_transient_to_detached, u1
+        )
+
+    def test_make_transient_to_detached_no_key_allowed(self):
+        users, User = self.tables.users, self.classes.User
+
+        mapper(User, users)
+        sess = Session()
+        u1 = User(id=1, name='test')
+        sess.add(u1)
+        sess.commit()
+        sess.expunge(u1)
+        assert_raises_message(
+            sa.exc.InvalidRequestError,
+            "Given object must be transient",
+            make_transient_to_detached, u1
+        )
+
+
 class SessionStateTest(_fixtures.FixtureTest):
     run_inserts = None