Skip to content

Commit

Permalink
Delete items from group (#84)
Browse files Browse the repository at this point in the history
* Implement delete item for group, dataset and raw

* Matching error messages in tests

* __str__ in Dataset returns __str__ from Dataset.data (memmap object)
  • Loading branch information
lepmik authored May 8, 2019
1 parent 976c0ce commit 5a9a41f
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 38 deletions.
3 changes: 3 additions & 0 deletions exdir/core/dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,9 @@ def __iter__(self):
for i in range(self.shape[0]):
yield self[i]

def __str__(self):
return self.data.__str__()

@property
def _data(self):
if self._data_memmap is None:
Expand Down
11 changes: 11 additions & 0 deletions exdir/core/exdir_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from enum import Enum
import os
import warnings
import shutil
try:
import pathlib
except ImportError as e:
Expand Down Expand Up @@ -71,6 +72,16 @@ def _create_object_directory(directory, metadata):
meta_file.write(metadata_string.decode('utf8'))


def _remove_object_directory(directory):
"""
Remove object directory and meta file if directory exist.
"""
if not directory.exists():
raise IOError("The directory '" + str(directory) + "' does not exist")
assert is_inside_exdir(directory)
shutil.rmtree(directory)


def _default_metadata(typename):
return {
EXDIR_METANAME: {
Expand Down
13 changes: 13 additions & 0 deletions exdir/core/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,19 @@ def __setitem__(self, name, value):

self[name].value = value

def __delitem__(self, name):
"""
Delete a child (an object contained in group).
Parameters
----------
name: str
name of the existing child
"""
if self.io_mode == self.OpenMode.READ_ONLY:
raise IOError("Cannot change data on file in read only 'r' mode")
exob._remove_object_directory(self[name].directory)

def keys(self):
"""
Returns
Expand Down
124 changes: 86 additions & 38 deletions tests/test_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
except:
from collections import KeysView, ValuesView, ItemsView

from exdir.core import Group, File
from exdir.core import Group, File, Dataset
from exdir import validation as fv
from conftest import remove

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


# TODO uncomment when deletion is implemented
# Feature: Objects can be unlinked via "del" operator
# def test_delete(setup_teardown_file):
# """Object deletion via "del"."""
#
# f = setup_teardown_file[3]
# grp = f.create_group("test")
# grp.create_group("foo")
#
# assert "foo" in grp
# del grp["foo"]
# assert "foo" not in grp
#
# def test_nonexisting(setup_teardown_file):
# """Deleting non-existent object raises KeyError."""
# f = setup_teardown_file[3]
# grp = f.create_group("test")
#
# with pytest.raises(KeyError):
# del grp["foo"]
#
# def test_readonly_delete_exception(setup_teardown_file):
# """Deleting object in readonly file raises KeyError."""
# f = setup_teardown_file[3]
# f.close()
#
# f = File(setup_teardown_folder[1], "r")
#
# with pytest.raises(KeyError):
# del f["foo"]
def test_delete_group(setup_teardown_file):
"""Object deletion via "del"."""

f = setup_teardown_file[3]
grp = f.create_group("test")
grp.create_group("foo")

assert "foo" in grp
del grp["foo"]
assert "foo" not in grp


def test_delete_group_from_file(setup_teardown_file):
"""Object deletion via "del"."""

f = setup_teardown_file[3]
grp = f.create_group("test")

assert "test" in f
del f["test"]
assert "test" not in f


def test_delete_raw(setup_teardown_file):
"""Object deletion via "del"."""

f = setup_teardown_file[3]
grp = f.create_group("test")
grp.create_raw("foo")

assert "foo" in grp
del grp["foo"]
assert "foo" not in grp


def test_nonexisting(setup_teardown_file):
"""Deleting non-existent object raises KeyError."""
f = setup_teardown_file[3]
grp = f.create_group("test")
match = "No such object: 'foo' in path *"
with pytest.raises(KeyError, match=match):
del grp["foo"]


def test_readonly_delete_exception(setup_teardown_file):
"""Deleting object in readonly file raises KeyError."""
f = setup_teardown_file[3]
f.close()

f = File(setup_teardown_file[1], "r")
match = "Cannot change data on file in read only 'r' mode"
with pytest.raises(IOError, match=match):
del f["foo"]


def test_delete_dataset(setup_teardown_file):
"""Create new dataset with no conflicts."""
f = setup_teardown_file[3]
grp = f.create_group("test")

foo = grp.create_dataset('foo', (10, 3), 'f')
assert isinstance(grp['foo'], Dataset)
assert foo.shape == (10, 3)
bar = grp.require_dataset('bar', data=(3, 10))
del foo
assert 'foo' in grp
del grp['foo']
match = "No such object: 'foo' in path *"
with pytest.raises(KeyError, match=match):
grp['foo']
# the "bar" dataset is intact
assert isinstance(grp['bar'], Dataset)
assert np.all(bar[:] == (3, 10))
# even though the dataset is deleted on file, the memmap stays open until
# garbage collected
del grp['bar']
assert bar.shape == (2,)
assert np.all(bar[:] == (3, 10))
with pytest.raises(KeyError):
grp['bar']

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

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


def test_open_deep(setup_teardown_file):
"""Simple obj[name] opening."""
f = setup_teardown_file[3]
Expand All @@ -238,12 +289,11 @@ def test_open_deep(setup_teardown_file):
assert grp3 == grp4



def test_nonexistent(setup_teardown_file):
"""Opening missing objects raises KeyError."""
f = setup_teardown_file[3]

with pytest.raises(KeyError):
match = "No such object: 'foo' in path *"
with pytest.raises(KeyError, match=match):
f["foo"]


Expand Down Expand Up @@ -277,12 +327,13 @@ def test_contains_deep(setup_teardown_file):
# def test_exc(setup_teardown_file):
# """'in' on closed group returns False."""
# f = setup_teardown_file[3]

#
# f.create_group("a")
# f.close()

#
# assert not "a" in f


def test_empty(setup_teardown_file):
"""Empty strings work properly and aren"t contained."""
f = setup_teardown_file[3]
Expand Down Expand Up @@ -314,9 +365,6 @@ def test_trailing_slash(setup_teardown_file):
assert "a//" in grp
assert "a////" in grp




# Feature: Standard Python 3 .keys, .values, etc. methods are available
def test_keys(setup_teardown_file):
""".keys provides a key view."""
Expand Down

0 comments on commit 5a9a41f

Please sign in to comment.