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
"""
+ 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.
# 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
"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)
"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)
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)
"""
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):
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())
'after_flush',
'after_flush_postexec',
'after_begin',
+ 'before_attach',
'after_attach',
'after_bulk_update',
'after_bulk_delete'
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',]
)
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'])
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):
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()