Skip to content

Commit 79b51b7

Browse files
d-v-bclaude
andcommitted
Merge loop/4375 into loop/minor-strict
Assisted-by: ClaudeCode:claude-opus-5-5 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 parents 1a05f63 + 8d5785a commit 79b51b7

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
@@ -1609,9 +1609,10 @@ async def get_coordinate_selection(
16091609
return out_array
16101610

16111611
async def _save_metadata(self, metadata: ArrayMetadata, ensure_parents: bool = False) -> None:
1612-
"""Store `metadata` as this array's own documents (creating the array, setting
1613-
its attributes; resizing stores the documents it encoded before deleting chunks,
1614-
then clears the mark the same way)."""
1612+
"""Store `metadata` as this array's own documents, then clear the
1613+
`_stored_document` mark (see `_stored_document_replaced`). `_resize` stores the
1614+
documents it encoded before deleting chunks directly, then clears the mark the
1615+
same way."""
16151616
await save_metadata(self.store_path, metadata, ensure_parents=ensure_parents)
16161617
self._stored_document_replaced()
16171618

‎tests/test_metadata/test_upgrades.py‎

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

750750

751+
def _set_attribute(array: AnyArray) -> None:
752+
array.attrs["x"] = 1
753+
754+
755+
def _grow(array: AnyArray) -> None:
756+
array.resize((9,))
757+
758+
759+
@pytest.mark.parametrize("operation", [_set_attribute, _grow], ids=["attrs", "resize"])
760+
@pytest.mark.parametrize("zarr_format", [2, 3])
761+
def test_failed_metadata_save_keeps_stored_document(
762+
tmp_path: Path,
763+
monkeypatch: pytest.MonkeyPatch,
764+
zarr_format: Literal[2, 3],
765+
operation: Callable[[AnyArray], None],
766+
) -> None:
767+
"""If storing an array's metadata fails, the array still stands for the document
768+
the store holds, which still needs its upgrade."""
769+
path = tmp_path / "legacy.zarr"
770+
_legacy_array(path, zarr_format)
771+
doc_name = ".zarray" if zarr_format == 2 else "zarr.json"
772+
with pytest.warns(ZarrUserWarning, match="is read as"):
773+
arr = zarr.open_array(store=path, mode="r+")
774+
stored = (path / doc_name).read_bytes()
775+
original_set = LocalStore.set
776+
777+
async def failing_set(self: LocalStore, key: str, *args: Any, **kwargs: Any) -> None:
778+
if key == doc_name:
779+
raise OSError(f"cannot store {key}")
780+
await original_set(self, key, *args, **kwargs)
781+
782+
monkeypatch.setattr(LocalStore, "set", failing_set)
783+
784+
with pytest.raises(OSError, match=f"cannot store {re.escape(doc_name)}"):
785+
operation(arr)
786+
787+
assert arr.metadata._stored_document is not None
788+
assert (path / doc_name).read_bytes() == stored
789+
790+
751791
@pytest.mark.parametrize("zarr_format", [2, 3])
752792
def test_write_without_stored_document(zarr_format: Literal[2, 3]) -> None:
753793
"""An array read from an upgraded document that no store holds (as

0 commit comments

Comments
 (0)