summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2014-04-09 17:49:16 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2014-04-09 17:49:16 -0400
commit9f74861d6d2962cb255887c78d5d0f4cb8891cfa (patch)
tree6941d52bf00434bd452dc7cf81020d7adadb87cc
parente8c08b5d7baf1b04cfcec94977822af291f2fa83 (diff)
downloadsqlalchemy-9f74861d6d2962cb255887c78d5d0f4cb8891cfa.tar.gz
- 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. fix #3017
-rw-r--r--doc/build/changelog/changelog_09.rst10
-rw-r--r--doc/build/orm/session.rst2
-rw-r--r--lib/sqlalchemy/orm/__init__.py3
-rw-r--r--lib/sqlalchemy/orm/session.py36
-rw-r--r--test/orm/test_session.py47
5 files changed, 95 insertions, 3 deletions
diff --git a/doc/build/changelog/changelog_09.rst b/doc/build/changelog/changelog_09.rst
index 7c7f1757e..578edd93f 100644
--- a/doc/build/changelog/changelog_09.rst
+++ b/doc/build/changelog/changelog_09.rst
@@ -15,6 +15,16 @@
: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
Restored the import for :class:`.Function` to the ``sqlalchemy.sql.expression``
diff --git a/doc/build/orm/session.rst b/doc/build/orm/session.rst
index 26c0b3f85..d53dc8630 100644
--- a/doc/build/orm/session.rst
+++ b/doc/build/orm/session.rst
@@ -2475,6 +2475,8 @@ Session Utilites
.. autofunction:: make_transient
+.. autofunction:: make_transient_to_detached
+
.. autofunction:: object_session
.. autofunction:: sqlalchemy.orm.util.was_deleted
diff --git a/lib/sqlalchemy/orm/__init__.py b/lib/sqlalchemy/orm/__init__.py
index 7825a70ac..8dfa68853 100644
--- a/lib/sqlalchemy/orm/__init__.py
+++ b/lib/sqlalchemy/orm/__init__.py
@@ -56,7 +56,8 @@ from .session import (
Session,
object_session,
sessionmaker,
- make_transient
+ make_transient,
+ make_transient_to_detached
)
from .scoping import (
scoped_session
diff --git a/lib/sqlalchemy/orm/session.py b/lib/sqlalchemy/orm/session.py
index 5bd46691e..a040101bf 100644
--- a/lib/sqlalchemy/orm/session.py
+++ b/lib/sqlalchemy/orm/session.py
@@ -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.
diff --git a/test/orm/test_session.py b/test/orm/test_session.py
index 5993c15f3..b68810036 100644
--- a/test/orm/test_session.py
+++ b/test/orm/test_session.py
@@ -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