Skip to content

Commit cb0bd6a

Browse files
committed
Support for serializing CArray too
1 parent a2aa4f2 commit cb0bd6a

3 files changed

Lines changed: 151 additions & 0 deletions

File tree

src/blosc2/_msgpack_utils.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,48 @@
1616
# reconstructed with ``blosc2.from_cframe()``. Keep this stable for backward
1717
# compatibility with persisted msgpack payloads produced by this package.
1818
_BLOSC2_EXT_CODE = 42
19+
# Reserve code 43 for structured Blosc2 reference objects that are not naturally
20+
# serialized as CFrames. The payload is a msgpack-encoded mapping with a
21+
# stable ``kind`` and ``version`` envelope.
22+
_BLOSC2_STRUCTURED_EXT_CODE = 43
23+
_BLOSC2_STRUCTURED_VERSION = 1
24+
25+
26+
def _encode_structured_reference(obj):
27+
import blosc2
28+
29+
if isinstance(obj, blosc2.C2Array):
30+
payload = {
31+
"kind": "c2array",
32+
"version": _BLOSC2_STRUCTURED_VERSION,
33+
"path": obj.path,
34+
"urlbase": obj.urlbase,
35+
}
36+
return ExtType(_BLOSC2_STRUCTURED_EXT_CODE, packb(payload, use_bin_type=True))
37+
return None
38+
39+
40+
def _decode_structured_reference(data):
41+
import blosc2
42+
43+
payload = unpackb(data)
44+
if not isinstance(payload, dict):
45+
raise TypeError("Structured Blosc2 msgpack payload must decode to a mapping")
46+
47+
version = payload.get("version")
48+
if version != _BLOSC2_STRUCTURED_VERSION:
49+
raise ValueError(f"Unsupported structured Blosc2 msgpack payload version: {version!r}")
50+
51+
kind = payload.get("kind")
52+
if kind == "c2array":
53+
path = payload.get("path")
54+
if not isinstance(path, str):
55+
raise TypeError("Structured C2Array msgpack payload requires a string 'path'")
56+
urlbase = payload.get("urlbase")
57+
if urlbase is not None and not isinstance(urlbase, str):
58+
raise TypeError("Structured C2Array msgpack payload requires 'urlbase' to be a string or None")
59+
return blosc2.C2Array(path, urlbase=urlbase)
60+
raise ValueError(f"Unsupported structured Blosc2 msgpack payload kind: {kind!r}")
1961

2062

2163
def _encode_msgpack_ext(obj):
@@ -32,6 +74,9 @@ def _encode_msgpack_ext(obj):
3274
),
3375
):
3476
return ExtType(_BLOSC2_EXT_CODE, obj.to_cframe())
77+
structured = _encode_structured_reference(obj)
78+
if structured is not None:
79+
return structured
3580
return blosc2_ext.encode_tuple(obj)
3681

3782

@@ -50,6 +95,8 @@ def _decode_msgpack_ext(code, data):
5095

5196
if code == _BLOSC2_EXT_CODE:
5297
return blosc2.from_cframe(data, copy=True)
98+
if code == _BLOSC2_STRUCTURED_EXT_CODE:
99+
return _decode_structured_reference(data)
53100
return ExtType(code, data)
54101

55102

tests/test_batch_store.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import pytest
1010

1111
import blosc2
12+
import blosc2.c2array as blosc2_c2array
1213
from blosc2._msgpack_utils import msgpack_packb, msgpack_unpackb
1314

1415
BATCHES = [
@@ -46,6 +47,14 @@ def _make_nested_blosc2_objects():
4647
return ndarray, schunk, nested_vlarray, nested_batchstore, estore
4748

4849

50+
def _make_c2array(monkeypatch, path="@public/examples/ds-1d.b2nd", urlbase="https://cat2.cloud/demo/"):
51+
def fake_info(path_, urlbase_, params=None, headers=None, model=None, auth_token=None):
52+
return {"schunk": {"cparams": dict(blosc2.cparams_dflts)}}
53+
54+
monkeypatch.setattr(blosc2_c2array, "info", fake_info)
55+
return blosc2.C2Array(path, urlbase=urlbase)
56+
57+
4958
@pytest.mark.parametrize(
5059
("contiguous", "urlpath"),
5160
[
@@ -225,6 +234,62 @@ def test_batchstore_msgpack_supports_blosc2_objects():
225234
assert np.array_equal(restored[4]["/node"][:], estore["/node"][:])
226235

227236

237+
def test_msgpack_supports_c2array(monkeypatch):
238+
c2array = _make_c2array(monkeypatch)
239+
240+
payload = msgpack_packb({"remote": c2array})
241+
restored = msgpack_unpackb(payload)
242+
243+
assert isinstance(restored["remote"], blosc2.C2Array)
244+
assert restored["remote"].path == c2array.path
245+
assert restored["remote"].urlbase == c2array.urlbase
246+
assert restored["remote"].auth_token is None
247+
248+
249+
def test_batchstore_msgpack_supports_c2array(monkeypatch):
250+
c2array = _make_c2array(monkeypatch)
251+
252+
barray = blosc2.BatchStore(items_per_block=2)
253+
barray.append([c2array])
254+
255+
restored = barray[0][0]
256+
257+
assert isinstance(restored, blosc2.C2Array)
258+
assert restored.path == c2array.path
259+
assert restored.urlbase == c2array.urlbase
260+
assert restored.auth_token is None
261+
262+
263+
@pytest.mark.network
264+
def test_msgpack_roundtrip_c2array_network(cat2_context):
265+
path = "@public/expr/ds-1-2-linspace-float64-b2-(5,)d.b2nd"
266+
original = blosc2.C2Array(path)
267+
268+
payload = msgpack_packb({"remote": original})
269+
restored = msgpack_unpackb(payload)["remote"]
270+
271+
assert isinstance(restored, blosc2.C2Array)
272+
assert restored.path == original.path
273+
assert restored.urlbase == original.urlbase
274+
np.testing.assert_allclose(restored[:], original[:])
275+
276+
277+
@pytest.mark.network
278+
def test_batchstore_msgpack_roundtrip_c2array_network(cat2_context):
279+
path = "@public/expr/ds-1-2-linspace-float64-b2-(5,)d.b2nd"
280+
original = blosc2.C2Array(path)
281+
282+
barray = blosc2.BatchStore(items_per_block=2)
283+
barray.append([original])
284+
285+
restored = barray[0][0]
286+
287+
assert isinstance(restored, blosc2.C2Array)
288+
assert restored.path == original.path
289+
assert restored.urlbase == original.urlbase
290+
np.testing.assert_allclose(restored[:], original[:])
291+
292+
228293
def test_batchstore_info():
229294
barray = blosc2.BatchStore()
230295
barray.extend(BATCHES)

tests/test_vlarray.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import pytest
1010

1111
import blosc2
12+
import blosc2.c2array as blosc2_c2array
1213

1314
VALUES = [
1415
b"bytes\x00payload",
@@ -45,6 +46,14 @@ def _make_nested_blosc2_objects():
4546
return ndarray, schunk, nested_vlarray, nested_batchstore, estore
4647

4748

49+
def _make_c2array(monkeypatch, path="@public/examples/ds-1d.b2nd", urlbase="https://cat2.cloud/demo/"):
50+
def fake_info(path_, urlbase_, params=None, headers=None, model=None, auth_token=None):
51+
return {"schunk": {"cparams": dict(blosc2.cparams_dflts)}}
52+
53+
monkeypatch.setattr(blosc2_c2array, "info", fake_info)
54+
return blosc2.C2Array(path, urlbase=urlbase)
55+
56+
4857
@pytest.mark.parametrize(
4958
("contiguous", "urlpath"),
5059
[
@@ -169,6 +178,36 @@ def test_vlarray_msgpack_supports_blosc2_objects():
169178
assert np.array_equal(restored["estore"]["/node"][:], estore["/node"][:])
170179

171180

181+
def test_vlarray_msgpack_supports_c2array(monkeypatch):
182+
c2array = _make_c2array(monkeypatch)
183+
184+
vlarray = blosc2.VLArray()
185+
vlarray.append(c2array)
186+
187+
restored = vlarray[0]
188+
189+
assert isinstance(restored, blosc2.C2Array)
190+
assert restored.path == c2array.path
191+
assert restored.urlbase == c2array.urlbase
192+
assert restored.auth_token is None
193+
194+
195+
@pytest.mark.network
196+
def test_vlarray_msgpack_roundtrip_c2array_network(cat2_context):
197+
path = "@public/expr/ds-1-2-linspace-float64-b2-(5,)d.b2nd"
198+
original = blosc2.C2Array(path)
199+
200+
vlarray = blosc2.VLArray()
201+
vlarray.append(original)
202+
203+
restored = vlarray[0]
204+
205+
assert isinstance(restored, blosc2.C2Array)
206+
assert restored.path == original.path
207+
assert restored.urlbase == original.urlbase
208+
np.testing.assert_allclose(restored[:], original[:])
209+
210+
172211
def test_vlarray_info():
173212
vlarray = blosc2.VLArray()
174213
vlarray.extend(VALUES)

0 commit comments

Comments
 (0)