Commits

Anonymous committed e1b1b1b Merge

merge of default

Comments (0)

Files changed (5)

.hgpatchinfo/per-update-mapperext.dep

+ee51da9cb6f5a070b76c0809a7d7b1fb36ca2475

.hgpatchinfo/per-update-mapperext.desc

+Adds 'before/after_row_insert/update/delete' mapper extension hooks.

lib/sqlalchemy/orm/interfaces.py

 
         return EXT_CONTINUE
 
+    def before_row_insert(self, mapper, connection, instance,
+                          statement, value_params, params):
+        return EXT_CONTINUE
+
+    def after_row_insert(self, mapper, connection, instance,
+                         statement, value_params, params):
+        return EXT_CONTINUE
+
     def after_insert(self, mapper, connection, instance):
         """Receive an object instance after that instance is inserted.
 
 
         return EXT_CONTINUE
 
+    def before_row_update(self, mapper, connection, instance,
+                          statement, value_params, params):
+        return EXT_CONTINUE
+
+    def after_row_update(self, mapper, connection, instance,
+                         statement, value_params, params):
+        return EXT_CONTINUE
+
     def after_update(self, mapper, connection, instance):
         """Receive an object instance after that instance is updated.
 
 
         return EXT_CONTINUE
 
+    def before_row_delete(self, mapper, connection, instance):
+        # FIXME
+        return EXT_CONTINUE
+
+    def after_row_delete(self, mapper, connection, instance):
+        return EXT_CONTINUE
+
     def after_delete(self, mapper, connection, instance):
         """Receive an object instance after that instance is deleted.
 
 
     def dict_getter(self, class_):
         return lambda inst: self.get_instance_dict(class_, inst)
-        
+        

lib/sqlalchemy/orm/mapper.py

                 for state, state_dict, params, mapper, \
                             connection, value_params in update:
                     
+                    if 'before_row_update' in mapper.extension:
+                        mapper.extension.before_row_update(
+                            mapper, connection, state.obj(),
+                            statement, value_params, params)
+
                     if value_params:
                         c = connection.execute(
                                             statement.values(value_params),
                                         c.last_updated_params(), value_params)
 
                     rows += c.rowcount
+                    if 'after_row_update' in mapper.extension:
+                        mapper.extension.after_row_update(
+                            mapper, connection, state.obj(),
+                            statement, value_params, params)
 
                 if connection.dialect.supports_sane_rowcount:
                     if rows != len(update):
                 for state, state_dict, params, mapper, \
                             connection, value_params in insert:
 
+                    if 'before_row_insert' in mapper.extension:
+                        mapper.extension.before_row_insert(
+                            mapper, connection, state.obj(),
+                            statement, value_params, params)
+
                     if value_params:
                         c = connection.execute(
                                             statement.values(value_params),
                                         state, state_dict, c,
                                         c.last_inserted_params(),
                                         value_params)
+                    if 'after_row_insert' in mapper.extension:
+                        mapper.extension.after_row_insert(
+                            mapper, connection, state.obj(),
+                            statement, value_params, params)
 
         for state, state_dict, mapper, connection, has_identity, \
                         instance_key, row_switch in tups:
         
         for table in reversed(table_to_mapper.keys()):
             delete = util.defaultdict(list)
+            deleted_states = util.defaultdict(list)
             for state, state_dict, mapper, has_identity, connection in tups:
                 if not has_identity or table not in mapper._pks_by_table:
                     continue
 
                 params = {}
                 delete[connection].append(params)
+                deleted_states[connection].append(state)
                 for col in mapper._pks_by_table[table]:
                     params[col.key] = \
                             mapper._get_state_attr_by_column(
                 statement = self._memo(('delete', table), delete_stmt)
                 rows = -1
 
+                non_cached_connection = connection
                 connection = cached_connections[connection]
 
+                if 'before_row_delete' in mapper.extension:
+                    extension_event = mapper.extension.before_row_delete
+                    for deleted_state in deleted_states[non_cached_connection]:
+                        extension_event(mapper, connection, deleted_state)
+
                 if need_version_id and \
                         not connection.dialect.supports_sane_multi_rowcount:
                     # TODO: need test coverage for this [ticket:1761]
                         (table.description, len(del_objects), c.rowcount)
                     )
 
+                if 'after_row_delete' in mapper.extension:
+                    extension_event = mapper.extension.after_row_delete
+                    for deleted_state in deleted_states[non_cached_connection]:
+                        extension_event(mapper, connection, deleted_state)
+
         for state, state_dict, mapper, has_identity, connection in tups:
             if 'after_delete' in mapper.extension:
                 mapper.extension.after_delete(mapper, connection, state.obj())

test/orm/test_mapper.py

                 methods.append('before_insert')
                 return sa.orm.EXT_CONTINUE
 
+            def before_row_insert(self, mapper, connection, instance,
+                                  statement, value_params, params):
+                methods.append('before_row_insert')
+                return sa.orm.EXT_CONTINUE
+
+            def after_row_insert(self, mapper, connection, instance,
+                                  statement, value_params, params):
+                methods.append('after_row_insert')
+                return sa.orm.EXT_CONTINUE
+
             def after_insert(self, mapper, connection, instance):
                 methods.append('after_insert')
                 return sa.orm.EXT_CONTINUE
                 methods.append('before_update')
                 return sa.orm.EXT_CONTINUE
 
+            def before_row_update(self, mapper, connection, instance,
+                                  statement, value_params, params):
+                methods.append('before_row_update')
+                return sa.orm.EXT_CONTINUE
+
+            def after_row_update(self, mapper, connection, instance,
+                                  statement, value_params, params):
+                methods.append('after_row_update')
+                return sa.orm.EXT_CONTINUE
+
             def after_update(self, mapper, connection, instance):
                 methods.append('after_update')
                 return sa.orm.EXT_CONTINUE
                 methods.append('before_delete')
                 return sa.orm.EXT_CONTINUE
 
+            def before_row_delete(self, mapper, connection, instance):
+                methods.append('before_row_delete')
+                return sa.orm.EXT_CONTINUE
+
+            def after_row_delete(self, mapper, connection, instance):
+                methods.append('after_row_delete')
+                return sa.orm.EXT_CONTINUE
+
             def after_delete(self, mapper, connection, instance):
                 methods.append('after_delete')
                 return sa.orm.EXT_CONTINUE
         sess.flush()
         eq_(methods,
             ['instrument_class', 'init_instance', 'before_insert',
+             'before_row_insert', 'after_row_insert',
              'after_insert', 'translate_row', 'populate_instance',
              'append_result', 'translate_row', 'create_instance',
              'populate_instance', 'reconstruct_instance', 'append_result',
-             'before_update', 'after_update', 'before_delete', 'after_delete'])
+             'before_update', 'before_row_update', 'after_row_update',
+             'after_update', 'before_delete', 'before_row_delete',
+             'after_row_delete', 'after_delete'])
+
+    @testing.resolve_artifact_names
+    def test_related(self):
+        # test that logical events are distinct from low-level events
+        Ext, methods = self.extension()
+        mapper(User, users, extension=Ext(), properties={
+            'addresses': relationship(Address, backref='user')
+        })
+        mapper(Address, addresses, extension=Ext())
+
+        sess = create_session()
+        u = User(name='u1')
+        sess.add(u)
+        sess.flush()
+        sess.expunge_all()
+
+        del methods[:]
+        u = sess.query(User).first()
+        u.addresses.append(Address(email_address='x'))
+        sess.flush()
+
+        eq_(methods,
+            ['translate_row', 'create_instance', 'populate_instance',
+             'reconstruct_instance', 'append_result', 'init_instance',
+             'before_update', 'after_update', 'before_insert',
+             'before_row_insert', 'after_row_insert', 'after_insert'])
 
     @testing.resolve_artifact_names
     def test_inheritance(self):
         sess.flush()
         eq_(methods,
             ['instrument_class', 'instrument_class', 'init_instance',
-             'before_insert', 'after_insert', 'translate_row',
+             'before_insert', 'before_row_insert', 'after_row_insert',
+             'before_row_insert', 'after_row_insert',
+             'after_insert', 'translate_row',
              'populate_instance', 'append_result', 'translate_row',
              'create_instance', 'populate_instance', 'reconstruct_instance',
-             'append_result', 'before_update', 'after_update', 'before_delete',
-             'after_delete'])
+             'append_result', 'before_update', 'before_row_update',
+             'after_row_update', 'after_update', 'before_delete',
+             'before_row_delete', 'after_row_delete', 'before_row_delete',
+             'after_row_delete', 'after_delete'])
 
     @testing.resolve_artifact_names
     def test_before_after_only_collection(self):
         sess.add(i1)
         sess.add(k1)
         sess.flush()
+
         eq_(methods1,
             ['instrument_class', 'init_instance', 
-            'before_insert', 'after_insert'])
+             'before_insert', 'before_row_insert',
+             'after_row_insert', 'after_insert'])
         eq_(methods2,
             ['instrument_class', 'init_instance', 
-            'before_insert', 'after_insert'])
+             'before_insert', 'before_row_insert',
+             'after_row_insert', 'after_insert'])
 
         del methods1[:]
         del methods2[:]
+
         i1.keywords.append(k1)
         sess.flush()
         eq_(methods1, ['before_update', 'after_update'])
         sess.flush()
         eq_(methods,
             ['instrument_class', 'instrument_class', 'init_instance',
-             'before_insert', 'after_insert', 'translate_row',
+             'before_insert', 'before_row_insert', 'after_row_insert',
+             'before_row_insert', 'after_row_insert',
+             'after_insert', 'translate_row',
              'populate_instance', 'append_result', 'translate_row',
              'create_instance', 'populate_instance', 'reconstruct_instance',
-             'append_result', 'before_update', 'after_update', 'before_delete',
-             'after_delete'])
-        
+             'append_result', 'before_update', 'before_row_update',
+             'after_row_update', 'after_update', 'before_delete',
+             'before_row_delete', 'after_row_delete',
+             'before_row_delete', 'after_row_delete','after_delete'])
+
     @testing.resolve_artifact_names
     def test_create_instance(self):
         class CreateUserExt(sa.orm.MapperExtension):