Skip to content

Commit 5a9a41f

Browse files
authored
Delete items from group (#84)
* Implement delete item for group, dataset and raw * Matching error messages in tests * __str__ in Dataset returns __str__ from Dataset.data (memmap object)
1 parent 976c0ce commit 5a9a41f

File tree

4 files changed

+113
-38
lines changed

4 files changed

+113
-38
lines changed

exdir/core/dataset.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,9 @@ def __iter__(self):
262262
for i in range(self.shape[0]):
263263
yield self[i]
264264

265+
def __str__(self):
266+
return self.data.__str__()
267+
265268
@property
266269
def _data(self):
267270
if self._data_memmap is None:

exdir/core/exdir_object.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from enum import Enum
66
import os
77
import warnings
8+
import shutil
89
try:
910
import pathlib
1011
except ImportError as e:
@@ -71,6 +72,16 @@ def _create_object_directory(directory, metadata):
7172
meta_file.write(metadata_string.decode('utf8'))
7273

7374

75+
def _remove_object_directory(directory):
76+
"""
77+
Remove object directory and meta file if directory exist.
78+
"""
79+
if not directory.exists():
80+
raise IOError("The directory '" + str(directory) + "' does not exist")
81+
assert is_inside_exdir(directory)
82+
shutil.rmtree(directory)
83+
84+
7485
def _default_metadata(typename):
7586
return {
7687
EXDIR_METANAME: {

exdir/core/group.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,19 @@ def __setitem__(self, name, value):
457457

458458
self[name].value = value
459459

460+
def __delitem__(self, name):
461+
"""
462+
Delete a child (an object contained in group).
463+
464+
Parameters
465+
----------
466+
name: str
467+
name of the existing child
468+
"""
469+
if self.io_mode == self.OpenMode.READ_ONLY:
470+
raise IOError("Cannot change data on file in read only 'r' mode")
471+
exob._remove_object_directory(self[name].directory)
472+
460473
def keys(self):
461474
"""
462475
Returns

tests/test_group.py

Lines changed: 86 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
except:
2828
from collections import KeysView, ValuesView, ItemsView
2929

30-
from exdir.core import Group, File
30+
from exdir.core import Group, File, Dataset
3131
from exdir import validation as fv
3232
from conftest import remove
3333

@@ -176,37 +176,87 @@ def test_set_item_intermediate(exdir_tmpfile):
176176
assert np.array_equal(exdir_tmpfile["group1/group2/group3/dataset"].data, np.array([1, 2, 3]))
177177

178178

179-
# TODO uncomment when deletion is implemented
180179
# Feature: Objects can be unlinked via "del" operator
181-
# def test_delete(setup_teardown_file):
182-
# """Object deletion via "del"."""
183-
#
184-
# f = setup_teardown_file[3]
185-
# grp = f.create_group("test")
186-
# grp.create_group("foo")
187-
#
188-
# assert "foo" in grp
189-
# del grp["foo"]
190-
# assert "foo" not in grp
191-
#
192-
# def test_nonexisting(setup_teardown_file):
193-
# """Deleting non-existent object raises KeyError."""
194-
# f = setup_teardown_file[3]
195-
# grp = f.create_group("test")
196-
#
197-
# with pytest.raises(KeyError):
198-
# del grp["foo"]
199-
#
200-
# def test_readonly_delete_exception(setup_teardown_file):
201-
# """Deleting object in readonly file raises KeyError."""
202-
# f = setup_teardown_file[3]
203-
# f.close()
204-
#
205-
# f = File(setup_teardown_folder[1], "r")
206-
#
207-
# with pytest.raises(KeyError):
208-
# del f["foo"]
180+
def test_delete_group(setup_teardown_file):
181+
"""Object deletion via "del"."""
182+
183+
f = setup_teardown_file[3]
184+
grp = f.create_group("test")
185+
grp.create_group("foo")
186+
187+
assert "foo" in grp
188+
del grp["foo"]
189+
assert "foo" not in grp
190+
191+
192+
def test_delete_group_from_file(setup_teardown_file):
193+
"""Object deletion via "del"."""
194+
195+
f = setup_teardown_file[3]
196+
grp = f.create_group("test")
197+
198+
assert "test" in f
199+
del f["test"]
200+
assert "test" not in f
201+
202+
203+
def test_delete_raw(setup_teardown_file):
204+
"""Object deletion via "del"."""
205+
206+
f = setup_teardown_file[3]
207+
grp = f.create_group("test")
208+
grp.create_raw("foo")
209209

210+
assert "foo" in grp
211+
del grp["foo"]
212+
assert "foo" not in grp
213+
214+
215+
def test_nonexisting(setup_teardown_file):
216+
"""Deleting non-existent object raises KeyError."""
217+
f = setup_teardown_file[3]
218+
grp = f.create_group("test")
219+
match = "No such object: 'foo' in path *"
220+
with pytest.raises(KeyError, match=match):
221+
del grp["foo"]
222+
223+
224+
def test_readonly_delete_exception(setup_teardown_file):
225+
"""Deleting object in readonly file raises KeyError."""
226+
f = setup_teardown_file[3]
227+
f.close()
228+
229+
f = File(setup_teardown_file[1], "r")
230+
match = "Cannot change data on file in read only 'r' mode"
231+
with pytest.raises(IOError, match=match):
232+
del f["foo"]
233+
234+
235+
def test_delete_dataset(setup_teardown_file):
236+
"""Create new dataset with no conflicts."""
237+
f = setup_teardown_file[3]
238+
grp = f.create_group("test")
239+
240+
foo = grp.create_dataset('foo', (10, 3), 'f')
241+
assert isinstance(grp['foo'], Dataset)
242+
assert foo.shape == (10, 3)
243+
bar = grp.require_dataset('bar', data=(3, 10))
244+
del foo
245+
assert 'foo' in grp
246+
del grp['foo']
247+
match = "No such object: 'foo' in path *"
248+
with pytest.raises(KeyError, match=match):
249+
grp['foo']
250+
# the "bar" dataset is intact
251+
assert isinstance(grp['bar'], Dataset)
252+
assert np.all(bar[:] == (3, 10))
253+
# even though the dataset is deleted on file, the memmap stays open until
254+
# garbage collected
255+
del grp['bar']
256+
assert bar.shape == (2,)
257+
assert np.all(bar[:] == (3, 10))
258+
with pytest.raises(KeyError):
259+
grp['bar']
210260

211261
# Feature: Objects can be opened via indexing syntax obj[name]
212262

@@ -226,6 +276,7 @@ def test_open(setup_teardown_file):
226276
with pytest.raises(NotImplementedError):
227277
grp["/test"]
228278

279+
229280
def test_open_deep(setup_teardown_file):
230281
"""Simple obj[name] opening."""
231282
f = setup_teardown_file[3]
@@ -238,12 +289,11 @@ def test_open_deep(setup_teardown_file):
238289
assert grp3 == grp4
239290

240291

241-
242292
def test_nonexistent(setup_teardown_file):
243293
"""Opening missing objects raises KeyError."""
244294
f = setup_teardown_file[3]
245-
246-
with pytest.raises(KeyError):
295+
match = "No such object: 'foo' in path *"
296+
with pytest.raises(KeyError, match=match):
247297
f["foo"]
248298

249299

@@ -277,12 +327,13 @@ def test_contains_deep(setup_teardown_file):
277327
# def test_exc(setup_teardown_file):
278328
# """'in' on closed group returns False."""
279329
# f = setup_teardown_file[3]
280-
330+
#
281331
# f.create_group("a")
282332
# f.close()
283-
333+
#
284334
# assert not "a" in f
285335

336+
286337
def test_empty(setup_teardown_file):
287338
"""Empty strings work properly and aren"t contained."""
288339
f = setup_teardown_file[3]
@@ -314,9 +365,6 @@ def test_trailing_slash(setup_teardown_file):
314365
assert "a//" in grp
315366
assert "a////" in grp
316367

317-
318-
319-
320368
# Feature: Standard Python 3 .keys, .values, etc. methods are available
321369
def test_keys(setup_teardown_file):
322370
""".keys provides a key view."""

0 commit comments

Comments
 (0)