[server/sources] make sure entity._cw is a Connection before calling Storages
authorJulien Cristau <julien.cristau@logilab.fr>
Mon, 27 Jul 2015 10:51:29 +0200
changeset 10550 d4bd28d5fca8
parent 10549 5fc21bf2684f
child 10551 1182f5f16a3d
[server/sources] make sure entity._cw is a Connection before calling Storages The storage callbacks don't get an explicit Connection objects, so they go through entity._cw to access the repo and schedule operations. Since that entity object comes out of the entity cache, its _cw may be a web request instead of a Connection. So fix it up around the storage callbacks. Closes #5753543
server/sources/native.py
server/test/unittest_storage.py
--- a/server/sources/native.py	Mon Jul 06 14:34:41 2015 +0200
+++ b/server/sources/native.py	Mon Jul 27 10:51:29 2015 +0200
@@ -563,7 +563,16 @@
         return results
 
     @contextmanager
-    def _storage_handler(self, entity, event):
+    def _fixup_cw(self, cnx, entity):
+        _cw = entity._cw
+        entity._cw = cnx
+        try:
+            yield
+        finally:
+            entity._cw = _cw
+
+    @contextmanager
+    def _storage_handler(self, cnx, entity, event):
         # 1/ memorize values as they are before the storage is called.
         #    For instance, the BFSStorage will replace the `data`
         #    binary value with a Binary containing the destination path
@@ -578,14 +587,15 @@
         etype = entities[0].__regid__
         for attr, storage in self._storages.get(etype, {}).items():
             for entity in entities:
-                if event == 'deleted':
-                    storage.entity_deleted(entity, attr)
-                else:
-                    edited = entity.cw_edited
-                    if attr in edited:
-                        handler = getattr(storage, 'entity_%s' % event)
-                        to_restore = handler(entity, attr)
-                        restore_values.append((entity, attr, to_restore))
+                with self._fixup_cw(cnx, entity):
+                    if event == 'deleted':
+                        storage.entity_deleted(entity, attr)
+                    else:
+                        edited = entity.cw_edited
+                        if attr in edited:
+                            handler = getattr(storage, 'entity_%s' % event)
+                            to_restore = handler(entity, attr)
+                            restore_values.append((entity, attr, to_restore))
         try:
             yield # 2/ execute the source's instructions
         finally:
@@ -595,7 +605,7 @@
 
     def add_entity(self, cnx, entity):
         """add a new entity to the source"""
-        with self._storage_handler(entity, 'added'):
+        with self._storage_handler(cnx, entity, 'added'):
             attrs = self.preprocess_entity(entity)
             sql = self.sqlgen.insert(SQL_PREFIX + entity.cw_etype, attrs)
             self.doexec(cnx, sql, attrs)
@@ -605,7 +615,7 @@
 
     def update_entity(self, cnx, entity):
         """replace an entity in the source"""
-        with self._storage_handler(entity, 'updated'):
+        with self._storage_handler(cnx, entity, 'updated'):
             attrs = self.preprocess_entity(entity)
             if cnx.ertype_supports_undo(entity.cw_etype):
                 changes = self._save_attrs(cnx, entity, attrs)
@@ -618,7 +628,7 @@
 
     def delete_entity(self, cnx, entity):
         """delete an entity from the source"""
-        with self._storage_handler(entity, 'deleted'):
+        with self._storage_handler(cnx, entity, 'deleted'):
             if cnx.ertype_supports_undo(entity.cw_etype):
                 attrs = [SQL_PREFIX + r.type
                          for r in entity.e_schema.subject_relations()
--- a/server/test/unittest_storage.py	Mon Jul 06 14:34:41 2015 +0200
+++ b/server/test/unittest_storage.py	Mon Jul 27 10:51:29 2015 +0200
@@ -87,27 +87,31 @@
                              'managed attribute. Is FSPATH() argument BFSS managed?')
 
     def test_bfss_storage(self):
-        with self.admin_access.repo_cnx() as cnx:
-            f1 = self.create_file(cnx)
+        with self.admin_access.web_request() as req:
+            cnx = req.cnx
+            f1 = self.create_file(req)
             filepaths = glob(osp.join(self.tempdir, '%s_data_*' % f1.eid))
             self.assertEqual(len(filepaths), 1, filepaths)
             expected_filepath = filepaths[0]
             # file should be read only
             self.assertFalse(os.access(expected_filepath, os.W_OK))
-            self.assertEqual(file(expected_filepath).read(), 'the-data')
+            self.assertEqual(open(expected_filepath).read(), 'the-data')
             cnx.rollback()
             self.assertFalse(osp.isfile(expected_filepath))
             filepaths = glob(osp.join(self.tempdir, '%s_data_*' % f1.eid))
             self.assertEqual(len(filepaths), 0, filepaths)
-            f1 = self.create_file(cnx)
+            f1 = self.create_file(req)
             cnx.commit()
             filepaths = glob(osp.join(self.tempdir, '%s_data_*' % f1.eid))
             self.assertEqual(len(filepaths), 1, filepaths)
             expected_filepath = filepaths[0]
-            self.assertEqual(file(expected_filepath).read(), 'the-data')
+            self.assertEqual(open(expected_filepath).read(), 'the-data')
+
+            # add f1 back to the entity cache with req as _cw
+            f1 = req.entity_from_eid(f1.eid)
             f1.cw_set(data=Binary('the new data'))
             cnx.rollback()
-            self.assertEqual(file(expected_filepath).read(), 'the-data')
+            self.assertEqual(open(expected_filepath).read(), 'the-data')
             f1.cw_delete()
             self.assertTrue(osp.isfile(expected_filepath))
             cnx.rollback()