Skip to content

Commit 5aa9b1e

Browse files
committed
Ignore FileNotFound Exception when deleting, optimize external table delete method, and re write test_removal_fail
1 parent 84e4aa4 commit 5aa9b1e

File tree

4 files changed

+48
-98
lines changed

4 files changed

+48
-98
lines changed

datajoint/external.py

Lines changed: 15 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
"""
@@ -337,18 +340,19 @@ def delete(self, *, delete_external_files=None, limit=None, display_progress=Tru
337340
# delete items one by one, close to transaction-safe
338341
error_list = []
339342
for uuid, external_path in items:
340-
try:
341-
count = len(self & {'hash': uuid}) # optimize
342-
except Exception:
343-
pass # if delete failed, do not remove the external file
344-
else:
345-
assert count in (0, 1)
343+
row = (self & {'hash': uuid}).fetch()
344+
if row.size:
346345
try:
347-
self._remove_external_file(external_path)
348-
except Exception as error:
349-
error_list.append((uuid, external_path, str(error)))
346+
(self & {'hash': uuid}).delete_quick()
347+
except Exception:
348+
pass # if delete failed, do not remove the external file
350349
else:
351-
(self & {'hash': uuid}).delete_quick(get_count=True)
350+
try:
351+
self._remove_external_file(external_path)
352+
except Exception as error:
353+
# adding row back into table after failed delete
354+
self.insert1(row[0])
355+
error_list.append((uuid, external_path, str(error)))
352356
return error_list
353357

354358

-49 Bytes
Binary file not shown.
Binary file not shown.

tests/test_external.py

Lines changed: 33 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +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-
86
import datajoint as dj
97
from .schema_external import stores_config, SimpleRemote, Simple, schema
8+
import os
109

11-
import json
12-
import os
13-
from os import stat
14-
import pwd
15-
from pwd import getpwuid
1610
current_location_s3 = dj.config['stores']['share']['location']
1711
current_location_local = dj.config['stores']['local']['location']
1812

@@ -26,6 +20,27 @@ def tearDown(self):
2620
dj.config['stores']['local']['location'] = current_location_local
2721

2822

23+
def test_external_put():
24+
"""
25+
external storage put and get and remove
26+
"""
27+
ext = ExternalTable(schema.connection, store='raw', database=schema.database)
28+
initial_length = len(ext)
29+
input_ = np.random.randn(3, 7, 8)
30+
count = 7
31+
extra = 3
32+
for i in range(count):
33+
hash1 = ext.put(pack(input_))
34+
for i in range(extra):
35+
hash2 = ext.put(pack(np.random.randn(4, 3, 2)))
36+
37+
fetched_hashes = ext.fetch('hash')
38+
assert_true(all(hash in fetched_hashes for hash in (hash1, hash2)))
39+
assert_equal(len(ext), initial_length + 1 + extra)
40+
41+
output_ = unpack(ext.get(hash1))
42+
assert_array_equal(input_, output_)
43+
2944

3045
def test_s3_leading_slash(index=100, store='share'):
3146
"""
@@ -86,95 +101,26 @@ def test_s3_leading_slash(index=100, store='share'):
86101

87102
dj.config['stores'][store]['location'] = oldConfig
88103

104+
89105
def test_file_leading_slash():
90106
"""
91107
file external storage configured with leading slash
92108
"""
93109
test_s3_leading_slash(index=200, store='local')
94110

95-
def test_remove_fail():
96-
#https://github.com/datajoint/datajoint-python/issues/953
97-
98-
#print(json.dumps(dj.config['stores'], indent=4))
99-
#print(dj.config['stores']['local']['location'])
100111

101-
# oldConfig = dj.config['stores']
102-
# dj.config['stores'] = stores_config
103-
104-
dirName = dj.config['stores']['local']['location']
105-
106-
#print('directory: ' + dirName)
107-
108-
109-
110-
data = dict(simple = 2, item = [1, 2, 3])
112+
def test_remove_fail():
113+
# https://github.com/datajoint/datajoint-python/issues/953
114+
data = dict(simple=2, item=[1, 2, 3])
111115
Simple.insert1(data)
112-
113-
#print('location')
114-
# print('\n IN TEST: BEFORE DELETE: list of dir stores, local, location')
115-
print('stores location -----------\n')
116-
print(dj.config['stores']['local']['location'])
117-
print('local location -----------\n')
118-
print(schema.external['local'])
119-
print('----------------------------')
120-
121116
path1 = dj.config['stores']['local']['location'] + '/djtest_extern/4/c/'
122-
123-
argDir = dj.config['stores']['local']['location'] + '/djtest_extern/4/c/'
124-
125-
print(f'argDir----------\n{argDir}\n')
126-
127-
path2 = os.listdir(argDir)
128-
129-
# print(path1 + path2[0])
130-
131-
old_name = path1 + path2[0]
132-
133-
new_name = "/tmp/newfile"
134-
135-
os.rename(old_name, new_name)
136-
137-
# print(f'\n IN TEST: is the new file name a file? {os.path.isfile(new_name)}')
138-
# print(f'\n IN TEST: is the old file name a file? {os.path.isfile(old_name)}')
139-
140-
# print(os.listdir(dj.config['stores']['local']['location'] + '/djtest_extern/4/c/'))
141-
142-
# st = stat(path1 + path2[0])
143-
# print(bool(st.st_mode & stat.S_IXUSR))
144-
145-
#print(getpwuid(stat(path3).st_uid).pw_name)
146-
147-
# print(f' IN TEST: simple table before delete {Simple()}')
117+
currentMode = int(oct(os.stat(path1).st_mode), 8)
118+
os.chmod(path1, 0o40555)
148119
(Simple & 'simple=2').delete()
149-
# print(f' IN TEST: simple table after delete {Simple()}')
150-
# print(' IN TEST: -------------showing external store before delete with flag---------')
151-
# print(schema.external['local'])
152120
listOfErrors = schema.external['local'].delete(delete_external_files=True)
153-
# print(f' IN TEST: list of errors: {listOfErrors}')
154-
# print(' IN TEST: list of dir stores, local, location')
155-
# print(os.listdir(dj.config['stores']['local']['location'] + '/djtest_extern/4/c'))
156-
# print(' IN TEST: -------------showing external store after delete with flag---------')
157-
# print(schema.external['local'])
158-
159-
# print(f'\n IN TEST: is this the UID or HASH? {listOfErrors[0][0]}')
160-
161-
# LENGTH_OF_QUERY = len(schema.external['local'] & dict(hash = listOfErrors[0][0]))
162-
163-
# print(f'\n IN TEST: WHAT IS THE LENGTH OF THIS? {LENGTH_OF_QUERY}')
164-
165121
assert len(listOfErrors) == 1, 'unexpected number of errors'
166-
assert len(schema.external['local'] & dict(hash = listOfErrors[0][0])) == 1, 'unexpected number of rows in external table'
167-
168-
#---------------------CLEAN UP--------------------
169-
os.rename(new_name, old_name) #switching from the new name back to the old name
170-
171-
# print(f'this is the old_name after the asserts {old_name}')
172-
173-
# print(f'\n IN TEST: is the new file name a file? {os.path.isfile(new_name)}')
174-
# print(f'\n IN TEST: is the old file name a file? {os.path.isfile(old_name)}')
175-
176-
listOfErrors = schema.external['local'].delete(delete_external_files=True)
177-
178-
print(len(listOfErrors))
179-
180-
# dj.config['stores'] = oldConfig
122+
assert len(schema.external['local'] & dict(hash=listOfErrors[0][0])) == 1, 'unexpec' + \
123+
'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)