Skip to content

Commit aedf2b3

Browse files
committed
Add several list-oriented methods (insert, delete, pop...); docs are here too
1 parent 302987e commit aedf2b3

4 files changed

Lines changed: 157 additions & 6 deletions

File tree

doc/reference/classes.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Main Classes
1616
DictStore
1717
TreeStore
1818
EmbedStore
19+
VLArray
1920
Proxy
2021
ProxySource
2122
ProxyNDSource
@@ -33,6 +34,7 @@ Main Classes
3334
dict_store
3435
tree_store
3536
embed_store
37+
vlarray
3638
proxy
3739
proxysource
3840
proxyndsource

doc/reference/misc.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,8 @@ This page documents the miscellaneous members of the ``blosc2`` module that do n
134134
TreeStore,
135135
DictStore,
136136
EmbedStore,
137+
VLArray,
138+
vlarray_from_cframe,
137139
abs,
138140
acos,
139141
acosh,

src/blosc2/vlarray.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,16 @@ def _maybe_open_existing(self, storage: blosc2.Storage) -> bool:
8989
self._attach_schunk(schunk)
9090
return True
9191

92+
def _make_storage(self) -> blosc2.Storage:
93+
meta = {name: self.meta[name] for name in self.meta}
94+
return blosc2.Storage(
95+
contiguous=self.schunk.contiguous,
96+
urlpath=self.urlpath,
97+
mode=self.mode,
98+
mmap_mode=self.mmap_mode,
99+
meta=meta,
100+
)
101+
92102
def __init__(
93103
self,
94104
chunksize: int | None = None,
@@ -149,6 +159,17 @@ def _normalize_index(self, index: int) -> int:
149159
raise IndexError("VLArray index out of range")
150160
return index
151161

162+
def _normalize_insert_index(self, index: int) -> int:
163+
if not isinstance(index, int):
164+
raise TypeError("VLArray indices must be integers")
165+
if index < 0:
166+
index += len(self)
167+
if index < 0:
168+
return 0
169+
if index > len(self):
170+
return len(self)
171+
return index
172+
152173
def _serialize(self, value: Any) -> bytes:
153174
payload = packb(value, default=blosc2_ext.encode_tuple, strict_types=True, use_bin_type=True)
154175
_check_serialized_size(payload)
@@ -158,10 +179,58 @@ def _compress(self, payload: bytes) -> bytes:
158179
return blosc2.compress2(payload, cparams=self.schunk.cparams)
159180

160181
def append(self, value: Any) -> int:
182+
"""Append one value and return the new number of entries."""
161183
self._check_writable()
162184
chunk = self._compress(self._serialize(value))
163185
return self.schunk.append_chunk(chunk)
164186

187+
def insert(self, index: int, value: Any) -> int:
188+
"""Insert one value at ``index`` and return the new number of entries."""
189+
self._check_writable()
190+
index = self._normalize_insert_index(index)
191+
chunk = self._compress(self._serialize(value))
192+
return self.schunk.insert_chunk(index, chunk)
193+
194+
def delete(self, index: int) -> int:
195+
"""Delete the value at ``index`` and return the new number of entries."""
196+
self._check_writable()
197+
if isinstance(index, slice):
198+
raise NotImplementedError("Slicing is not supported for VLArray")
199+
index = self._normalize_index(index)
200+
return self.schunk.delete_chunk(index)
201+
202+
def pop(self, index: int = -1) -> Any:
203+
"""Remove and return the value at ``index``."""
204+
self._check_writable()
205+
if isinstance(index, slice):
206+
raise NotImplementedError("Slicing is not supported for VLArray")
207+
index = self._normalize_index(index)
208+
value = self[index]
209+
self.schunk.delete_chunk(index)
210+
return value
211+
212+
def extend(self, values: object) -> None:
213+
"""Append all values from an iterable."""
214+
self._check_writable()
215+
for value in values:
216+
chunk = self._compress(self._serialize(value))
217+
self.schunk.append_chunk(chunk)
218+
219+
def clear(self) -> None:
220+
"""Remove all entries from the container."""
221+
self._check_writable()
222+
storage = self._make_storage()
223+
if storage.urlpath is not None:
224+
blosc2.remove_urlpath(storage.urlpath)
225+
schunk = blosc2.SChunk(
226+
chunksize=-1,
227+
data=None,
228+
cparams=copy.deepcopy(self.cparams),
229+
dparams=copy.deepcopy(self.dparams),
230+
storage=storage,
231+
)
232+
self._attach_schunk(schunk)
233+
165234
def __getitem__(self, index: int) -> Any:
166235
if isinstance(index, slice):
167236
raise NotImplementedError("Slicing is not supported for VLArray")
@@ -177,6 +246,9 @@ def __setitem__(self, index: int, value: Any) -> None:
177246
chunk = self._compress(self._serialize(value))
178247
self.schunk.update_chunk(index, chunk)
179248

249+
def __delitem__(self, index: int) -> None:
250+
self.delete(index)
251+
180252
def __len__(self) -> int:
181253
return self.schunk.nchunks
182254

tests/test_vlarray.py

Lines changed: 81 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,16 @@ def test_vlarray_roundtrip(contiguous, urlpath):
5353
expected[-1] = "tiny"
5454
vlarray[1] = expected[1]
5555
vlarray[-1] = expected[-1]
56+
assert vlarray.insert(0, "head") == len(expected) + 1
57+
expected.insert(0, "head")
58+
assert vlarray.insert(-1, {"between": 5}) == len(expected) + 1
59+
expected.insert(-1, {"between": 5})
60+
assert vlarray.insert(999, "tail") == len(expected) + 1
61+
expected.insert(999, "tail")
62+
assert vlarray.delete(2) == len(expected) - 1
63+
del expected[2]
64+
del vlarray[-2]
65+
del expected[-2]
5666
assert list(vlarray) == expected
5767

5868
if urlpath is not None:
@@ -63,6 +73,18 @@ def test_vlarray_roundtrip(contiguous, urlpath):
6373
reopened.append("nope")
6474
with pytest.raises(ValueError):
6575
reopened[0] = "nope"
76+
with pytest.raises(ValueError):
77+
reopened.insert(0, "nope")
78+
with pytest.raises(ValueError):
79+
reopened.delete(0)
80+
with pytest.raises(ValueError):
81+
del reopened[0]
82+
with pytest.raises(ValueError):
83+
reopened.extend(["nope"])
84+
with pytest.raises(ValueError):
85+
reopened.pop()
86+
with pytest.raises(ValueError):
87+
reopened.clear()
6688

6789
reopened_rw = blosc2.open(urlpath, mode="a")
6890
reopened_rw[0] = "changed"
@@ -79,28 +101,32 @@ def test_vlarray_roundtrip(contiguous, urlpath):
79101

80102
def test_vlarray_from_cframe():
81103
vlarray = blosc2.VLArray()
82-
for value in VALUES[:4]:
83-
vlarray.append(value)
104+
vlarray.extend(VALUES)
105+
vlarray.insert(1, {"inserted": True})
106+
del vlarray[3]
107+
expected = list(VALUES)
108+
expected.insert(1, {"inserted": True})
109+
del expected[3]
84110

85111
restored = blosc2.from_cframe(vlarray.to_cframe())
86112
assert isinstance(restored, blosc2.VLArray)
87-
assert list(restored) == VALUES[:4]
113+
assert list(restored) == expected
88114

89115
restored2 = blosc2.vlarray_from_cframe(vlarray.to_cframe())
90116
assert isinstance(restored2, blosc2.VLArray)
91-
assert list(restored2) == VALUES[:4]
117+
assert list(restored2) == expected
92118

93119

94120
def test_vlarray_constructor_kwargs():
95121
urlpath = "test_vlarray_kwargs.b2frame"
96122
blosc2.remove_urlpath(urlpath)
97123

98124
vlarray = blosc2.VLArray(urlpath=urlpath, mode="w", contiguous=True)
99-
for value in VALUES[:3]:
125+
for value in VALUES:
100126
vlarray.append(value)
101127

102128
reopened = blosc2.VLArray(urlpath=urlpath, mode="r", contiguous=True, mmap_mode="r")
103-
assert list(reopened) == VALUES[:3]
129+
assert list(reopened) == VALUES
104130

105131
blosc2.remove_urlpath(urlpath)
106132

@@ -110,3 +136,52 @@ def test_vlarray_size_guard(monkeypatch):
110136
monkeypatch.setattr(blosc2, "MAX_BUFFERSIZE", 4)
111137
with pytest.raises(ValueError, match="Serialized objects cannot be larger"):
112138
vlarray.append("payload")
139+
140+
141+
@pytest.mark.parametrize(
142+
("contiguous", "urlpath"),
143+
[
144+
(False, None),
145+
(True, None),
146+
(True, "test_vlarray_list_ops.b2frame"),
147+
(False, "test_vlarray_list_ops_s.b2frame"),
148+
],
149+
)
150+
def test_vlarray_list_like_ops(contiguous, urlpath):
151+
blosc2.remove_urlpath(urlpath)
152+
153+
vlarray = blosc2.VLArray(storage=_storage(contiguous, urlpath))
154+
vlarray.extend([1, 2, 3])
155+
assert list(vlarray) == [1, 2, 3]
156+
assert vlarray.pop() == 3
157+
assert vlarray.pop(0) == 1
158+
assert list(vlarray) == [2]
159+
160+
vlarray.clear()
161+
assert len(vlarray) == 0
162+
assert list(vlarray) == []
163+
164+
vlarray.extend(["a", "b"])
165+
assert list(vlarray) == ["a", "b"]
166+
167+
if urlpath is not None:
168+
reopened = blosc2.open(urlpath, mode="r")
169+
assert list(reopened) == ["a", "b"]
170+
171+
blosc2.remove_urlpath(urlpath)
172+
173+
174+
def test_vlarray_insert_delete_errors():
175+
vlarray = blosc2.VLArray()
176+
vlarray.append("value")
177+
178+
with pytest.raises(TypeError):
179+
vlarray.insert("0", "bad")
180+
with pytest.raises(IndexError):
181+
vlarray.delete(3)
182+
with pytest.raises(NotImplementedError):
183+
vlarray.delete(slice(0, 1))
184+
with pytest.raises(IndexError):
185+
blosc2.VLArray().pop()
186+
with pytest.raises(NotImplementedError):
187+
vlarray.pop(slice(0, 1))

0 commit comments

Comments
 (0)