From 0c646f5feef5b1d662d16235098a7c1900487c21 Mon Sep 17 00:00:00 2001 From: Fabian Isensee Date: Sat, 7 Nov 2026 06:56:38 +0100 Subject: [PATCH] Open stores written by python-blosc2 <= 4.0.0 again Up to 4.0.0, the embed.b2e of a DictStore or TreeStore carried only the "b2embed" marker of an EmbedStore; the b2dict and b2tree markers came in 4.1.0 (66b1c58a), together with the meta-first dispatch in blosc2.open(). That dispatch reads the marker from the embed.b2e inside a .b2d or .b2z and, seeing "b2embed", opens the whole store path as an EmbedStore, which fails with "blosc2_schunk_open_offset(...) returned NULL". Old .b2d stores have failed this way since 4.1.0; old .b2z stores since 4.8.1, when 92e02339 made the zip probe work (it used to call the module-level open() and always fell back to the extension). "b2embed" therefore does not identify an EmbedStore, so drop that branch: .b2e files reach the same EmbedStore call through the extension dispatch, and old stores open as they did in 4.0.0 (.b2z as TreeStore, .b2d as DictStore). Stores written by 4.1.0 or newer still dispatch on their b2dict/b2tree marker. The explicit DictStore()/TreeStore() constructors were never affected. The test fixture is a DictStore written by python-blosc2 4.0.0 (2 kB). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01DiPFzu8ziFQsxrJUkHCrzx --- src/blosc2/schunk.py | 9 +++---- tests/data/legacy_store_v4.0.0/dictstore.b2z | Bin 0 -> 1952 bytes tests/test_dict_store.py | 25 +++++++++++++++++++ 3 files changed, 28 insertions(+), 6 deletions(-) create mode 100644 tests/data/legacy_store_v4.0.0/dictstore.b2z diff --git a/src/blosc2/schunk.py b/src/blosc2/schunk.py index 33452acae..97cecb04f 100644 --- a/src/blosc2/schunk.py +++ b/src/blosc2/schunk.py @@ -1874,12 +1874,9 @@ def _open_special_store(urlpath, mode, offset, *, deserialize=DeserializeMode.SA from blosc2.remote_store import RemoteStore return RemoteStore._open_artifact(urlpath, mode=mode, **kwargs) - if "b2embed" in schunk_meta: - if offset != 0: - raise ValueError("Offset must be 0 for EmbedStore") - from blosc2.embed_store import EmbedStore - - return EmbedStore(urlpath, mode=mode, deserialize=deserialize, **kwargs) + # No "b2embed" check: the embed.b2e of a .b2d/.b2z written by python-blosc2 + # <= 4.0.0 carries that marker too (b2dict and b2tree came in 4.1.0), so it + # does not tell the store type. Those stores and .b2e files go by extension. if "b2dict" in schunk_meta: if offset != 0: raise ValueError("Offset must be 0 for DictStore") diff --git a/tests/data/legacy_store_v4.0.0/dictstore.b2z b/tests/data/legacy_store_v4.0.0/dictstore.b2z new file mode 100644 index 0000000000000000000000000000000000000000..d45f14c389b51112cf8141ecc6d0d951d8c408f7 GIT binary patch literal 1952 zcmWIWW@Zs#00GynX|bCH^JZOTVqgGaUZ7ZNMM-K=USf`3l2KmDycJ1CX+?>-sSKBZ zQj^YuG%+wRU0ou?5Xy8J%we=>fG`*se}ELRTw(wUfr*O@GC*|b1{*{nE0_Wilg}~; z-eX`~0X~N3b%F zC$FHWq^zQrLCi@r*B|rWNcz;W^Q3=Wo=_?XYb(X zxMVOsI4<<#|9r;>`|WG&=AVE5`6rx`N9E2xfBrbKG+6crTs1^-Z@&UOVp$p4IXHms z2Vo5$1_N09!}vgY3L`LavE5@}xWmA34#-!&fg}lG2B4(>xc<4tADI~#K$sJ#AT>8B zH3gXXQ&H1?Ei~;je?&_A3{0-j)XFp&l#Ure1SqR0FhJ7&VXSFi1eo@h18o4=1k#ZQ zP5xYs%ga)WiZk=`7=g*0(FEu{5dHu%6a>(c`3ay9I66%jzOphXc`E58q+XUVm~cS) zxtiIMP-X_UVCgd*^%8!?%Rc@1Ds;vmrbVE*k*l@2?}b}g^kmKcj`?4nOux2Z`>f6K zhu19?Iq=a=fMvt!MVl9uohpB&=pcBi?(qB-#hXi38~AmHny>DgwfTp;b!KOR$-CR$ zhZdM`b-O567m>a=Z;_n%zFhs{dx#@I}e_eqk5Xiraffx*s{0oZmFF-t; z{Ja|IA8`B$GVro8JYttoYshY6me_TTm8~r!`H;TD?&4bljgpr;E@6pJ z4T~>F{`xk%WpWI&ht7#b^E&_B;b~_I@(gJe5>ZHCN@28fWb)hj;_J4=hV6}+O+Oh9 zu!?>Y@Z7JdqGVxz+&Y2hB$s1eaa;{2p&STHrr;bX4NT(eQ;SRTi&EorQ%e$45=#<6 zNnH_|)N5E7+Zkhk0w7!i#9)9F;Y};S*&{wTv7l+CKA4@7n$ol^KP{~|wS-|=PHJ9y zNyb@5lhyj^Mfs%#`Z=kIX)tM^FkFG)y5!QLqSU;Sc!<`sOs=4EVJEh7fssjs8F$SH tbtJ=oNz?&6ZH!?9WF%$vSNU(zx0st%@hvNVM literal 0 HcmV?d00001 diff --git a/tests/test_dict_store.py b/tests/test_dict_store.py index b8f14b0a6..98a90f808 100644 --- a/tests/test_dict_store.py +++ b/tests/test_dict_store.py @@ -167,6 +167,31 @@ def test_extensionless_store_is_a_directory(tmp_path): assert np.array_equal(opened["/node1"][:], np.arange(4)) +@pytest.mark.parametrize(("suffix", "store_type"), [("b2z", blosc2.TreeStore), ("b2d", blosc2.DictStore)]) +def test_open_store_written_by_4_0_0(tmp_path, suffix, store_type): + # A DictStore written by 4.0.0 (no b2dict marker yet) with an embedded leaf, + # an external one and a nested key; it opens by its extension, as in 4.0.0 + fixture = pathlib.Path(__file__).parent / "data" / "legacy_store_v4.0.0" / "dictstore.b2z" + path = tmp_path / f"legacy.{suffix}" + if suffix == "b2z": + shutil.copy(fixture, path) + else: # the same members, unzipped + with zipfile.ZipFile(fixture) as zf: + zf.extractall(path) + expected = { + "/embedded": np.arange(12, dtype="