Skip to content

Commit 77fefd4

Browse files
Merge pull request #956 from zitrosolrac/external_removal_fail
Fix logic bug in external file clean up
2 parents da108b5 + 707953b commit 77fefd4

File tree

4 files changed

+44
-15
lines changed

4 files changed

+44
-15
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
### 0.13.3 -- TBD
44
* Bugfix - Dependencies not properly loaded on populate. (#902) PR #919
55
* Bugfix - Replace use of numpy aliases of built-in types with built-in type. (#938) PR #939
6+
* Bugfix - `ExternalTable.delete` should not remove row on error (#953) PR #956
67
* Bugfix - Fix error handling of remove_object function in `s3.py` (#952) PR #955
78

89
### 0.13.2 -- May 7, 2021

datajoint/external.py

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,10 @@ def _remove_external_file(self, external_path):
127127
if self.spec['protocol'] == 's3':
128128
self.s3.remove_object(external_path)
129129
elif self.spec['protocol'] == 'file':
130-
Path(external_path).unlink()
130+
try:
131+
Path(external_path).unlink()
132+
except FileNotFoundError:
133+
pass
131134

132135
def exists(self, external_filepath):
133136
"""
@@ -338,17 +341,20 @@ def delete(self, *, delete_external_files=None, limit=None, display_progress=Tru
338341
# delete items one by one, close to transaction-safe
339342
error_list = []
340343
for uuid, external_path in items:
341-
try:
342-
count = (self & {'hash': uuid}).delete_quick(get_count=True) # optimize
343-
except Exception:
344-
pass # if delete failed, do not remove the external file
345-
else:
346-
assert count in (0, 1)
344+
row = (self & {'hash': uuid}).fetch()
345+
if row.size:
347346
try:
348-
self._remove_external_file(external_path)
349-
except Exception as error:
350-
error_list.append((uuid, external_path,
351-
str(error) if errors_as_string else error))
347+
(self & {'hash': uuid}).delete_quick()
348+
except Exception:
349+
pass # if delete failed, do not remove the external file
350+
else:
351+
try:
352+
self._remove_external_file(external_path)
353+
except Exception as error:
354+
# adding row back into table after failed delete
355+
self.insert1(row[0], skip_duplicates=True)
356+
error_list.append((uuid, external_path,
357+
str(error) if errors_as_string else error))
352358
return error_list
353359

354360

docs-parts/intro/Releases_lang1.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
----------------------
33
* Bugfix - Dependencies not properly loaded on populate. (#902) PR #919
44
* Bugfix - Replace use of numpy aliases of built-in types with built-in type. (#938) PR #939
5+
* Bugfix - `ExternalTable.delete` should not remove row on error (#953) PR #956
56
* Bugfix - Fix error handling of remove_object function in `s3.py` (#952) PR #955
67

78
0.13.2 -- May 7, 2021

tests/test_external.py

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,10 @@
33
from nose.tools import assert_true, assert_equal
44
from datajoint.external import ExternalTable
55
from datajoint.blob import pack, unpack
6-
7-
from .schema_external import schema
86
import datajoint as dj
9-
from .schema_external import stores_config
10-
from .schema_external import SimpleRemote
7+
from .schema_external import stores_config, SimpleRemote, Simple, schema
8+
import os
9+
1110
current_location_s3 = dj.config['stores']['share']['location']
1211
current_location_local = dj.config['stores']['local']['location']
1312

@@ -47,6 +46,9 @@ def test_s3_leading_slash(index=100, store='share'):
4746
"""
4847
s3 external storage configured with leading slash
4948
"""
49+
50+
oldConfig = dj.config['stores'][store]['location']
51+
5052
value = np.array([1, 2, 3])
5153

5254
id = index
@@ -97,9 +99,28 @@ def test_s3_leading_slash(index=100, store='share'):
9799
assert_true(np.array_equal(
98100
value, (SimpleRemote & 'simple={}'.format(id)).fetch1('item')))
99101

102+
dj.config['stores'][store]['location'] = oldConfig
103+
100104

101105
def test_file_leading_slash():
102106
"""
103107
file external storage configured with leading slash
104108
"""
105109
test_s3_leading_slash(index=200, store='local')
110+
111+
112+
def test_remove_fail():
113+
# https://github.com/datajoint/datajoint-python/issues/953
114+
data = dict(simple=2, item=[1, 2, 3])
115+
Simple.insert1(data)
116+
path1 = dj.config['stores']['local']['location'] + '/djtest_extern/4/c/'
117+
currentMode = int(oct(os.stat(path1).st_mode), 8)
118+
os.chmod(path1, 0o40555)
119+
(Simple & 'simple=2').delete()
120+
listOfErrors = schema.external['local'].delete(delete_external_files=True)
121+
assert len(listOfErrors) == 1, 'unexpected number of errors'
122+
assert len(schema.external['local'] & dict(hash=listOfErrors[0][0])) == 1, \
123+
'unexpected number of rows in external table'
124+
# ---------------------CLEAN UP--------------------
125+
os.chmod(path1, currentMode)
126+
listOfErrors = schema.external['local'].delete(delete_external_files=True)

0 commit comments

Comments
 (0)