Skip to content

Commit 8ebb16c

Browse files
jakirkhamalimanfoo
authored andcommitted
Ensure DictStore contains only bytes (#350)
* Ensure `DictStore` gets `bytes` coercible data As the spec requires that the data in a store be a sequence of `bytes`, make sure that non-`DictStore` input meets this requirement when setting values. This effectively ensures that other `DictStore` meet this requirement as well. So we don't need to go through and check their values too. * Drop undefined buffer size case from `DictStore` As everything in `DictStore` must either be another `DictStore` or `bytes`, there shouldn't be any cases where the size is undefined nor cases that this exception should need handling. Given this go ahead and drop the special casing for unknown sizes in `DictStore`. * Drop `test_getsize_ext` While this test case does test a useful subset of the `getsize` API, the contents being added to the store here are non-conforming to our expectations of store contents. Namely the store should only contain values that are an "arbitrary sequence of bytes", which this test case is not. * Test `getsize` with an unknown size case This creates a non-conforming store to make sure that `getsize` handles its contents in the expected way. Namely that it returns `-1`. * Note `DictStore` only contains `bytes` now [ci skip] * Check for `TypeError` for non-buffer objects Add a test to ensure that a non-buffer supporting object when stored into a valid store, will raise a `TypeError` instead of storing it. Disable this checking for generic `MappingStore`s (e.g. `dict`) as they do not perform this sort of checking on the data they accept as values. * Check that `DictStore` coerces all data to `bytes` Provide a simple test for `DictStore` to ensure that non-`bytes` is coerced to `bytes` before storing it and is retrieved as `bytes`. * Disallow mutation of the internal `DictStore` Make sure that users are only able to add data to the `DictStore`. Disallow the storing of a nested `DictStore` though.
1 parent 94f7a8d commit 8ebb16c

File tree

3 files changed

+25
-16
lines changed

3 files changed

+25
-16
lines changed

docs/release.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ Bug fixes
3535
* Failing tests related to pickling/unpickling have been fixed. By :user:`Ryan Williams <ryan-williams>`,
3636
:issue:`273`, :issue:`308`.
3737

38+
* Ensure ``DictStore`` contains only ``bytes`` to facilitate comparisons and protect against writes.
39+
By :user:`John Kirkham <jakirkham>`, :issue:`350`
40+
3841
Maintenance
3942
~~~~~~~~~~~
4043

zarr/storage.py

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,7 @@ def __getitem__(self, item):
544544
def __setitem__(self, item, value):
545545
with self.write_mutex:
546546
parent, key = self._require_parent(item)
547+
value = ensure_bytes(value)
547548
parent[key] = value
548549

549550
def __delitem__(self, item):
@@ -642,17 +643,11 @@ def getsize(self, path=None):
642643
size = 0
643644
for v in value.values():
644645
if not isinstance(v, self.cls):
645-
try:
646-
size += buffer_size(v)
647-
except TypeError:
648-
return -1
646+
size += buffer_size(v)
649647
return size
650648

651649
else:
652-
try:
653-
return buffer_size(value)
654-
except TypeError:
655-
return -1
650+
return buffer_size(value)
656651

657652
def clear(self):
658653
with self.write_mutex:

zarr/tests/test_storage.py

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ def test_get_set_del_contains(self):
6363
# noinspection PyStatementEffect
6464
del store['foo']
6565

66+
def test_set_invalid_content(self):
67+
store = self.create_store()
68+
69+
with pytest.raises(TypeError):
70+
store['baz'] = list(range(5))
71+
6672
def test_clear(self):
6773
store = self.create_store()
6874
store['foo'] = b'bar'
@@ -586,6 +592,10 @@ class TestMappingStore(StoreTests, unittest.TestCase):
586592
def create_store(self):
587593
return dict()
588594

595+
def test_set_invalid_content(self):
596+
# Generic mappings support non-buffer types
597+
pass
598+
589599

590600
def setdel_hierarchy_checks(store):
591601
# these tests are for stores that are aware of hierarchy levels; this
@@ -629,17 +639,14 @@ class TestDictStore(StoreTests, unittest.TestCase):
629639
def create_store(self):
630640
return DictStore()
631641

632-
def test_setdel(self):
642+
def test_store_contains_bytes(self):
633643
store = self.create_store()
634-
setdel_hierarchy_checks(store)
644+
store['foo'] = np.array([97, 98, 99, 100, 101], dtype=np.uint8)
645+
assert store['foo'] == b'abcde'
635646

636-
def test_getsize_ext(self):
647+
def test_setdel(self):
637648
store = self.create_store()
638-
store['a'] = list(range(10))
639-
store['b/c'] = list(range(10))
640-
assert -1 == store.getsize()
641-
assert -1 == store.getsize('a')
642-
assert -1 == store.getsize('b')
649+
setdel_hierarchy_checks(store)
643650

644651

645652
class TestDirectoryStore(StoreTests, unittest.TestCase):
@@ -1096,6 +1103,10 @@ def test_getsize():
10961103
assert 7 == getsize(store)
10971104
assert 5 == getsize(store, 'baz')
10981105

1106+
store = dict()
1107+
store['boo'] = None
1108+
assert -1 == getsize(store)
1109+
10991110

11001111
def test_migrate_1to2():
11011112
from zarr import meta_v1

0 commit comments

Comments
 (0)