Skip to content

Commit c8c1632

Browse files
committed
Allow use a filename in .b2z as a single argument
1 parent 20a958c commit c8c1632

2 files changed

Lines changed: 78 additions & 5 deletions

File tree

src/blosc2/dict_store.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ class DictStore:
8585
>>> schunk.append_data(b"abcd")
8686
4
8787
>>> dstore["/dir1/schunk1"] = schunk # externalized as .b2f if above threshold
88-
>>> dstore.to_b2z() # persist to the zip file; external files are copied in
88+
>>> dstore.to_b2z(filename="my_dstore.b2z") # persist to the zip file; external files are copied in
8989
>>> print(sorted(dstore.keys()))
9090
['/dir1/node3', '/dir1/schunk1', '/node1', '/node2']
9191
>>> print(dstore["/node1"][:]))
@@ -555,14 +555,19 @@ def to_b2z(self, overwrite=False, filename=None) -> os.PathLike[Any] | str:
555555
If True, overwrite the existing b2z file if it exists. Default is False.
556556
filename : str, optional
557557
If provided, use this filename instead of the default b2z file path.
558+
Keyword use is recommended for clarity.
558559
559560
Returns
560561
-------
561562
filename : str
562563
The absolute path to the created b2z file.
563564
"""
564-
if self.mode == "r":
565-
raise ValueError("Cannot call to_b2z() on a DictStore opened in read mode.")
565+
if isinstance(overwrite, str | os.PathLike) and filename is None:
566+
filename = overwrite
567+
overwrite = False
568+
569+
if self.mode == "r" and self.is_zip_store:
570+
raise ValueError("Cannot call to_b2z() on a .b2z DictStore opened in read mode.")
566571

567572
b2z_path = self.b2z_path if filename is None else filename
568573
if not b2z_path.endswith(".b2z"):
@@ -582,7 +587,7 @@ def to_b2z(self, overwrite=False, filename=None) -> os.PathLike[Any] | str:
582587
# Sort filepaths by file size from largest to smallest
583588
filepaths.sort(key=os.path.getsize, reverse=True)
584589

585-
with zipfile.ZipFile(self.b2z_path, "w", zipfile.ZIP_STORED) as zf:
590+
with zipfile.ZipFile(b2z_path, "w", zipfile.ZIP_STORED) as zf:
586591
# Write all files (except estore_path) first (sorted by size)
587592
for filepath in filepaths:
588593
arcname = os.path.relpath(filepath, self.working_dir)
@@ -591,7 +596,7 @@ def to_b2z(self, overwrite=False, filename=None) -> os.PathLike[Any] | str:
591596
if os.path.exists(self.estore_path):
592597
arcname = os.path.relpath(self.estore_path, self.working_dir)
593598
zf.write(self.estore_path, arcname)
594-
return os.path.abspath(self.b2z_path)
599+
return os.path.abspath(b2z_path)
595600

596601
def _get_zip_offsets(self) -> dict[str, dict[str, int]]:
597602
"""Get offset and length of all files in the zip archive."""

tests/test_dict_store.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,74 @@ def test_to_b2z_and_reopen(populated_dict_store):
114114
assert np.all(dstore_read["/nodeB"][:] == np.arange(6))
115115

116116

117+
def test_to_b2z_from_readonly_b2d():
118+
b2d_path = "test_to_b2z_from_readonly.b2d"
119+
b2z_path = "test_to_b2z_from_readonly.b2z"
120+
121+
if os.path.exists(b2d_path):
122+
shutil.rmtree(b2d_path)
123+
if os.path.exists(b2z_path):
124+
os.remove(b2z_path)
125+
126+
with DictStore(b2d_path, mode="w") as dstore:
127+
dstore["/nodeA"] = np.arange(5)
128+
dstore["/nodeB"] = np.arange(6)
129+
130+
with DictStore(b2d_path, mode="r") as dstore:
131+
packed = dstore.to_b2z(filename=b2z_path)
132+
assert packed.endswith(b2z_path)
133+
134+
with DictStore(b2z_path, mode="r") as dstore:
135+
assert np.all(dstore["/nodeA"][:] == np.arange(5))
136+
assert np.all(dstore["/nodeB"][:] == np.arange(6))
137+
138+
shutil.rmtree(b2d_path)
139+
os.remove(b2z_path)
140+
141+
142+
def test_to_b2z_accepts_positional_filename():
143+
b2d_path = "test_to_b2z_positional_filename.b2d"
144+
b2z_path = "test_to_b2z_positional_filename.b2z"
145+
146+
if os.path.exists(b2d_path):
147+
shutil.rmtree(b2d_path)
148+
if os.path.exists(b2z_path):
149+
os.remove(b2z_path)
150+
151+
with DictStore(b2d_path, mode="w") as dstore:
152+
dstore["/nodeA"] = np.arange(5)
153+
154+
with DictStore(b2d_path, mode="r") as dstore:
155+
packed = dstore.to_b2z(b2z_path)
156+
assert packed.endswith(b2z_path)
157+
158+
with DictStore(b2z_path, mode="r") as dstore:
159+
assert np.all(dstore["/nodeA"][:] == np.arange(5))
160+
161+
shutil.rmtree(b2d_path)
162+
os.remove(b2z_path)
163+
164+
165+
def test_to_b2z_from_readonly_b2z_raises():
166+
b2z_path = "test_to_b2z_readonly_zip.b2z"
167+
out_path = "test_to_b2z_readonly_zip_out.b2z"
168+
169+
for path in (b2z_path, out_path):
170+
if os.path.exists(path):
171+
os.remove(path)
172+
173+
with DictStore(b2z_path, mode="w") as dstore:
174+
dstore["/nodeA"] = np.arange(5)
175+
176+
with (
177+
DictStore(b2z_path, mode="r") as dstore,
178+
pytest.raises(ValueError, match=r"\.b2z DictStore opened in read mode"),
179+
):
180+
dstore.to_b2z(filename=out_path)
181+
182+
os.remove(b2z_path)
183+
184+
117185
def test_map_tree_precedence(populated_dict_store):
118186
dstore, path = populated_dict_store
119187
# Create external file and add to dstore

0 commit comments

Comments
 (0)