Skip to content

Commit 3e31f33

Browse files
committed
storage test coverage; rework DictStore
1 parent b6b9b63 commit 3e31f33

File tree

2 files changed

+130
-87
lines changed

2 files changed

+130
-87
lines changed

zarr/storage.py

Lines changed: 82 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -331,67 +331,64 @@ def __init__(self, cls=dict):
331331
self.root = cls()
332332
self.cls = cls
333333

334-
def __getitem__(self, key):
335-
c = self.root
336-
for k in key.split('/'):
337-
if isinstance(c, self.cls):
338-
c = c[k]
339-
else:
340-
raise KeyError(key)
341-
if isinstance(c, self.cls):
342-
raise KeyError(key)
343-
return c
344-
345-
def __setitem__(self, key, value):
346-
c = self.root
347-
keys = key.split('/')
348-
349-
# ensure intermediate containers
350-
for k in keys[:-1]:
351-
if isinstance(c, self.cls):
352-
try:
353-
c = c[k]
354-
except KeyError:
355-
c[k] = self.cls()
356-
c = c[k]
334+
def _get_parent(self, item):
335+
parent = self.root
336+
# split the item
337+
segments = item.split('/')
338+
# find the parent container
339+
for k in segments[:-1]:
340+
parent = parent[k]
341+
if not isinstance(parent, self.cls):
342+
raise KeyError(item)
343+
return parent, segments[-1]
344+
345+
def _require_parent(self, item):
346+
parent = self.root
347+
# split the item
348+
segments = item.split('/')
349+
# require the parent container
350+
for k in segments[:-1]:
351+
try:
352+
parent = parent[k]
353+
except KeyError:
354+
parent[k] = self.cls()
355+
parent = parent[k]
357356
else:
358-
raise KeyError(key)
359-
360-
if isinstance(c, self.cls):
361-
# set final value
362-
c[keys[-1]] = value
357+
if not isinstance(parent, self.cls):
358+
raise KeyError(item)
359+
return parent, segments[-1]
360+
361+
def __getitem__(self, item):
362+
parent, key = self._get_parent(item)
363+
try:
364+
value = parent[key]
365+
except KeyError:
366+
raise KeyError(item)
363367
else:
364-
raise KeyError(key)
365-
366-
def __delitem__(self, key):
367-
c = self.root
368-
keys = key.split('/')
369-
370-
# obtain final container
371-
for k in keys[:-1]:
372-
if isinstance(c, self.cls):
373-
c = c[k]
368+
if isinstance(value, self.cls):
369+
raise KeyError(item)
374370
else:
375-
raise KeyError(key)
376-
377-
if isinstance(c, self.cls):
378-
# delete item
379-
del c[keys[-1]]
371+
return value
372+
373+
def __setitem__(self, item, value):
374+
parent, key = self._require_parent(item)
375+
parent[key] = value
376+
377+
def __delitem__(self, item):
378+
parent, key = self._get_parent(item)
379+
try:
380+
del parent[key]
381+
except KeyError:
382+
raise KeyError(item)
383+
384+
def __contains__(self, item):
385+
try:
386+
parent, key = self._get_parent(item)
387+
value = parent[key]
388+
except KeyError:
389+
return False
380390
else:
381-
raise KeyError(key)
382-
383-
def __contains__(self, key):
384-
keys = key.split('/')
385-
c = self.root
386-
for k in keys:
387-
if isinstance(c, self.cls):
388-
try:
389-
c = c[k]
390-
except KeyError:
391-
return False
392-
else:
393-
return False
394-
return not isinstance(c, self.cls)
391+
return not isinstance(value, self.cls)
395392

396393
def __eq__(self, other):
397394
return (
@@ -412,46 +409,52 @@ def __len__(self):
412409

413410
def listdir(self, path=None):
414411
path = normalize_storage_path(path)
415-
c = self.root
416412
if path:
417-
# split path and find container
418-
for k in path.split('/'):
419-
c = c[k]
420-
return sorted(c.keys())
413+
try:
414+
parent, key = self._get_parent(path)
415+
value = parent[key]
416+
except KeyError:
417+
return []
418+
else:
419+
value = self.root
420+
if isinstance(value, self.cls):
421+
return sorted(value.keys())
422+
else:
423+
return []
421424

422425
def rmdir(self, path=None):
423426
path = normalize_storage_path(path)
424-
c = self.root
425427
if path:
426-
# split path and find container
427-
segments = path.split('/')
428-
for k in segments[:-1]:
429-
c = c[k]
430-
# remove final key
431428
try:
432-
del c[segments[-1]]
429+
parent, key = self._get_parent(path)
430+
value = parent[key]
433431
except KeyError:
434-
# does not exist
435-
pass
432+
return
433+
else:
434+
if isinstance(value, self.cls):
435+
del parent[key]
436436
else:
437437
# clear out root
438438
self.root = self.cls()
439439

440440
def getsize(self, path=None):
441441
path = normalize_storage_path(path)
442-
c = self.root
442+
443+
# obtain value to return size of
443444
if path:
444-
# split path and find value
445-
segments = path.split('/')
446445
try:
447-
for k in segments:
448-
c = c[k]
446+
parent, key = self._get_parent(path)
447+
value = parent[key]
449448
except KeyError:
450449
raise ValueError('path not found: %r' % path)
451-
if isinstance(c, self.cls):
450+
else:
451+
value = self.root
452+
453+
# obtain size of value
454+
if isinstance(value, self.cls):
452455
# total size for directory
453456
size = 0
454-
for v in c.values():
457+
for v in value.values():
455458
if not isinstance(v, self.cls):
456459
try:
457460
size += buffersize(v)
@@ -460,7 +463,7 @@ def getsize(self, path=None):
460463
return size
461464
else:
462465
try:
463-
return buffersize(c)
466+
return buffersize(value)
464467
except TypeError:
465468
return -1
466469

zarr/tests/test_storage.py

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -156,13 +156,6 @@ def test_hierarchy(self):
156156
with assert_raises(KeyError):
157157
store['c/d/x']
158158

159-
# test listdir (optional)
160-
if hasattr(store, 'listdir'):
161-
eq({'a', 'b', 'c'}, set(store.listdir()))
162-
eq({'d', 'e'}, set(store.listdir('c')))
163-
eq({'f', 'g'}, set(store.listdir('c/e')))
164-
# TODO further listdir tests
165-
166159
# test getsize (optional)
167160
if hasattr(store, 'getsize'):
168161
eq(6, store.getsize())
@@ -173,7 +166,32 @@ def test_hierarchy(self):
173166
eq(6, store.getsize('c/e'))
174167
eq(3, store.getsize('c/e/f'))
175168
eq(3, store.getsize('c/e/g'))
176-
# TODO further getsize tests
169+
with assert_raises(ValueError):
170+
store.getsize('x')
171+
with assert_raises(ValueError):
172+
store.getsize('a/x')
173+
with assert_raises(ValueError):
174+
store.getsize('c/x')
175+
with assert_raises(ValueError):
176+
store.getsize('c/x/y')
177+
with assert_raises(ValueError):
178+
store.getsize('c/d/y')
179+
with assert_raises(ValueError):
180+
store.getsize('c/d/y/z')
181+
182+
# test listdir (optional)
183+
if hasattr(store, 'listdir'):
184+
eq({'a', 'b', 'c'}, set(store.listdir()))
185+
eq({'d', 'e'}, set(store.listdir('c')))
186+
eq({'f', 'g'}, set(store.listdir('c/e')))
187+
# no exception raised if path does not exist or is leaf
188+
eq([], store.listdir('x'))
189+
eq([], store.listdir('a/x'))
190+
eq([], store.listdir('c/x'))
191+
eq([], store.listdir('c/x/y'))
192+
eq([], store.listdir('c/d/y'))
193+
eq([], store.listdir('c/d/y/z'))
194+
eq([], store.listdir('c/e/f'))
177195

178196
# test rmdir (optional)
179197
if hasattr(store, 'rmdir'):
@@ -186,6 +204,20 @@ def test_hierarchy(self):
186204
store.rmdir()
187205
assert 'a' not in store
188206
assert 'b' not in store
207+
store['a'] = b'aaa'
208+
store['c/d'] = b'ddd'
209+
store['c/e/f'] = b'fff'
210+
# no exceptions raised if path does not exist or is leaf
211+
store.rmdir('x')
212+
store.rmdir('a/x')
213+
store.rmdir('c/x')
214+
store.rmdir('c/x/y')
215+
store.rmdir('c/d/y')
216+
store.rmdir('c/d/y/z')
217+
store.rmdir('c/e/f')
218+
assert 'a' in store
219+
assert 'c/d' in store
220+
assert 'c/e/f' in store
189221

190222
def test_init_array(self):
191223
store = self.create_store()
@@ -459,6 +491,14 @@ def test_setdel(self):
459491
store = self.create_store()
460492
setdel_hierarchy_checks(store)
461493

494+
def test_getsize_ext(self):
495+
store = self.create_store()
496+
store['a'] = list(range(10))
497+
store['b/c'] = list(range(10))
498+
eq(-1, store.getsize())
499+
eq(-1, store.getsize('a'))
500+
eq(-1, store.getsize('b'))
501+
462502

463503
def rmtree_if_exists(path, rmtree=shutil.rmtree, isdir=os.path.isdir):
464504
if isdir(path):

0 commit comments

Comments
 (0)