.. 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
.. autofunction:: make_transient
+.. autofunction:: make_transient_to_detached
+
.. autofunction:: object_session
.. autofunction:: sqlalchemy.orm.util.was_deleted
Session,
object_session,
sessionmaker,
- make_transient
+ make_transient,
+ make_transient_to_detached
)
from .scoping import (
scoped_session
)
-
def make_transient(instance):
"""Make the given instance 'transient'.
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.
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
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