[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
--- 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()