]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- add before_attach event, [ticket:2464]
authorMike Bayer <mike_mp@zzzcomputing.com>
Fri, 8 Jun 2012 01:25:37 +0000 (21:25 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Fri, 8 Jun 2012 01:25:37 +0000 (21:25 -0400)
CHANGES
lib/sqlalchemy/orm/events.py
lib/sqlalchemy/orm/session.py
test/orm/test_events.py

diff --git a/CHANGES b/CHANGES
index 3a0ce4d2c5cf40eee616077d6c745a3cdb58055c..89825af9553afd87c2775efc6acddc14da58487b 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -168,7 +168,10 @@ CHANGES
     upon Session.add(), Session.merge(), 
     etc., so that the object is represented
     in these collections when the event 
-    is called. [ticket:2464]
+    is called.  Added before_attach
+    event to accommodate use cases that 
+    need autoflush w pre-attached object.  
+    [ticket:2464]
 
   - [bug] Fixed bug whereby polymorphic_on
     column that's not otherwise mapped on the 
index d319a3bbb7c69806173794a9a563d4125e87e84d..903b7fadd0b61c1bcc6641fd344c4881db37732a 100644 (file)
@@ -1061,10 +1061,36 @@ class SessionEvents(event.Events):
         
         """
 
+    def before_attach(self, session, instance):
+        """Execute before an instance is attached to a session.
+
+        This is called before an add, delete or merge causes
+        the object to be part of the session.
+        
+        .. versionadded:: 0.8.  Note that :meth:`.after_attach` now 
+           fires off after the item is part of the session.  
+           :meth:`.before_attach` is provided for those cases where
+           the item should not yet be part of the session state.
+           
+        """
+
     def after_attach(self, session, instance):
         """Execute after an instance is attached to a session.
 
-        This is called after an add, delete or merge. """
+        This is called after an add, delete or merge. 
+        
+        .. note::
+           
+           As of 0.8, this event fires off *after* the item
+           has been fully associated with the session, which is
+           different than previous releases.  For event
+           handlers that require the object not yet 
+           be part of session state (such as handlers which
+           may autoflush while the target object is not
+           yet complete) consider the
+           new :meth:`.before_attach` event.
+           
+        """
 
     def after_bulk_update( self, session, query, query_context, result):
         """Execute after a bulk update operation to the session.
index 40e4375ed55411cc11816d8547bd69bad86ab9cc..7f820aa057b76b36faa3d5d7b8d9d67418f232d9 100644 (file)
@@ -1300,7 +1300,7 @@ class Session(object):
         # ensure object is attached to allow the 
         # cascade operation to load deferred attributes
         # and collections
-        self._attach(state)
+        self._attach(state, include_before=True)
 
         # grab the cascades before adding the item to the deleted list
         # so that autoflush does not delete the item
@@ -1472,6 +1472,7 @@ class Session(object):
                 "Object '%s' already has an identity - it can't be registered "
                 "as pending" % mapperutil.state_str(state))
 
+        self._before_attach(state)
         if state not in self._new:
             self._new[state] = state.obj()
             state.insert_order = len(self._new)
@@ -1493,6 +1494,7 @@ class Session(object):
                 "function to send this object back to the transient state." %
                 mapperutil.state_str(state)
             )
+        self._before_attach(state)
         self._deleted.pop(state, None)
         self.identity_map.add(state)
         self._attach(state)
@@ -1510,7 +1512,7 @@ class Session(object):
         if state.key is None:
             return
 
-        self._attach(state)
+        self._attach(state, include_before=True)
         self._deleted[state] = state.obj()
         self.identity_map.add(state)
 
@@ -1555,10 +1557,15 @@ class Session(object):
         
         """
         state = attributes.instance_state(obj)
-        self._attach(state)
+        self._attach(state, include_before=True)
         state._load_pending = True
 
-    def _attach(self, state):
+    def _before_attach(self, state):
+        if state.session_id != self.hash_key and \
+            self.dispatch.before_attach:
+            self.dispatch.before_attach(self, state.obj())
+
+    def _attach(self, state, include_before=False):
         if state.key and \
             state.key in self.identity_map and \
             not self.identity_map.contains_state(state):
@@ -1576,6 +1583,9 @@ class Session(object):
                                     state.session_id, self.hash_key))
 
         if state.session_id != self.hash_key:
+            if include_before and \
+                self.dispatch.before_attach:
+                self.dispatch.before_attach(self, state.obj())
             state.session_id = self.hash_key
             if self.dispatch.after_attach:
                 self.dispatch.after_attach(self, state.obj())
index e5b597cdafcf112225360b235c22cbb224d80621..182005d38d326e2adf789103b5dd1ebd182c8eb8 100644 (file)
@@ -553,6 +553,7 @@ class SessionEventsTest(_RemoveListeners, _fixtures.FixtureTest):
             'after_flush',
             'after_flush_postexec',
             'after_begin',
+            'before_attach',
             'after_attach',
             'after_bulk_update',
             'after_bulk_delete'
@@ -575,7 +576,7 @@ class SessionEventsTest(_RemoveListeners, _fixtures.FixtureTest):
         sess.flush()
         eq_(
             canary, 
-            [ 'after_attach', 'before_flush', 'after_begin',
+            [ 'before_attach', 'after_attach', 'before_flush', 'after_begin',
             'after_flush', 'after_flush_postexec', 
             'before_commit', 'after_commit',]
         )
@@ -596,9 +597,9 @@ class SessionEventsTest(_RemoveListeners, _fixtures.FixtureTest):
             sess.commit
         )
         sess.rollback()
-        eq_(canary, ['after_attach', 'before_commit', 'before_flush', 
+        eq_(canary, ['before_attach', 'after_attach', 'before_commit', 'before_flush', 
         'after_begin', 'after_flush', 'after_flush_postexec', 
-        'after_commit', 'after_attach', 'before_commit', 
+        'after_commit', 'before_attach', 'after_attach', 'before_commit', 
         'before_flush', 'after_begin', 'after_rollback', 
         'after_soft_rollback', 'after_soft_rollback'])
 
@@ -639,7 +640,7 @@ class SessionEventsTest(_RemoveListeners, _fixtures.FixtureTest):
         u = User(name='u1')
         sess.add(u)
         sess.flush()
-        eq_(canary, ['after_attach', 'before_flush', 'after_begin',
+        eq_(canary, ['before_attach', 'after_attach', 'before_flush', 'after_begin',
                        'after_flush', 'after_flush_postexec'])
 
     def test_flush_in_commit_hook(self):
@@ -658,6 +659,25 @@ class SessionEventsTest(_RemoveListeners, _fixtures.FixtureTest):
         eq_(canary, ['before_commit', 'before_flush', 'after_flush',
                        'after_flush_postexec', 'after_commit'])
 
+    def test_state_before_attach(self):
+        User, users = self.classes.User, self.tables.users
+        sess = Session()
+
+        @event.listens_for(sess, "before_attach")
+        def listener(session, inst):
+            state = attributes.instance_state(inst)
+            if state.key:
+                assert state.key not in session.identity_map
+            else:
+                assert inst not in session.new
+
+        mapper(User, users)
+        u= User(name='u1')
+        sess.add(u)
+        sess.flush()
+        sess.expunge(u)
+        sess.add(u)
+
     def test_state_after_attach(self):
         User, users = self.classes.User, self.tables.users
         sess = Session()