Skip to content

Commit 8d5785a

Browse files
d-v-bclaude
andcommitted
Merge loop/4334 into loop/4375
Assisted-by: ClaudeCode:claude-opus-5-5 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 parents 8ce9a91 + 30d7ca6 commit 8d5785a

2 files changed

Lines changed: 44 additions & 3 deletions

File tree

‎src/zarr/core/array.py‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1627,9 +1627,10 @@ async def get_coordinate_selection(
16271627
return out_array
16281628

16291629
async def _save_metadata(self, metadata: ArrayMetadata, ensure_parents: bool = False) -> None:
1630-
"""Store `metadata` as this array's own documents (creating the array, setting
1631-
its attributes; resizing stores the documents it encoded before deleting chunks,
1632-
then clears the mark the same way)."""
1630+
"""Store `metadata` as this array's own documents, then clear the
1631+
`_stored_document` mark (see `_stored_document_replaced`). `_resize` stores the
1632+
documents it encoded before deleting chunks directly, then clears the mark the
1633+
same way."""
16331634
await save_metadata(self.store_path, metadata, ensure_parents=ensure_parents)
16341635
self._stored_document_replaced()
16351636

‎tests/test_metadata/test_upgrades.py‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -781,6 +781,46 @@ def test_stale_handle_write_after_chunk_grid_change_raises(
781781
assert {p.name: p.read_bytes() for p in path.iterdir()} == documents
782782

783783

784+
def _set_attribute(array: AnyArray) -> None:
785+
array.attrs["x"] = 1
786+
787+
788+
def _grow(array: AnyArray) -> None:
789+
array.resize((9,))
790+
791+
792+
@pytest.mark.parametrize("operation", [_set_attribute, _grow], ids=["attrs", "resize"])
793+
@pytest.mark.parametrize("zarr_format", [2, 3])
794+
def test_failed_metadata_save_keeps_stored_document(
795+
tmp_path: Path,
796+
monkeypatch: pytest.MonkeyPatch,
797+
zarr_format: Literal[2, 3],
798+
operation: Callable[[AnyArray], None],
799+
) -> None:
800+
"""If storing an array's metadata fails, the array still stands for the document
801+
the store holds, which still needs its upgrade."""
802+
path = tmp_path / "legacy.zarr"
803+
_legacy_array(path, zarr_format)
804+
doc_name = ".zarray" if zarr_format == 2 else "zarr.json"
805+
with pytest.warns(ZarrUserWarning, match="is read as"):
806+
arr = zarr.open_array(store=path, mode="r+")
807+
stored = (path / doc_name).read_bytes()
808+
original_set = LocalStore.set
809+
810+
async def failing_set(self: LocalStore, key: str, *args: Any, **kwargs: Any) -> None:
811+
if key == doc_name:
812+
raise OSError(f"cannot store {key}")
813+
await original_set(self, key, *args, **kwargs)
814+
815+
monkeypatch.setattr(LocalStore, "set", failing_set)
816+
817+
with pytest.raises(OSError, match=f"cannot store {re.escape(doc_name)}"):
818+
operation(arr)
819+
820+
assert arr.metadata._stored_document is not None
821+
assert (path / doc_name).read_bytes() == stored
822+
823+
784824
@pytest.mark.parametrize("zarr_format", [2, 3])
785825
def test_write_without_stored_document(zarr_format: Literal[2, 3]) -> None:
786826
"""An array read from an upgraded document that no store holds (as

0 commit comments

Comments
 (0)