diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2014-04-09 17:49:16 -0400 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2014-04-09 17:49:16 -0400 |
commit | 9f74861d6d2962cb255887c78d5d0f4cb8891cfa (patch) | |
tree | 6941d52bf00434bd452dc7cf81020d7adadb87cc | |
parent | e8c08b5d7baf1b04cfcec94977822af291f2fa83 (diff) | |
download | sqlalchemy-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.rst | 10 | ||||
-rw-r--r-- | doc/build/orm/session.rst | 2 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/__init__.py | 3 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/session.py | 36 | ||||
-rw-r--r-- | test/orm/test_session.py | 47 |
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 |