Skip to content

Commit d778da3

Browse files
committed
Merge commit '0992e948b' into HEAD
2 parents fbb0bbf + 0992e94 commit d778da3

11 files changed

Lines changed: 142 additions & 142 deletions

File tree

‎changes/4334.bugfix.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ A chunk edge length is now always at least 1, while an array extent may be 0. Ev
44

55
Stored metadata with a regular chunk size of 0 or JSON `false` (Zarr format 2 `chunks`, Zarr format 3 `regular` `chunk_shape`) is now read as chunk size 1 (the inner chunk size for sharded arrays, which previously failed to open), however long the axis is: that is the chunk size `chunks=-1` gives on a zero-length axis, and it does not depend on how far the axis has grown since. zarr-python wrote such sizes for arrays created with a zero-length axis until 3.4, and, from 2.18.7 to 3.2.1, for an explicit chunk size of 0 or `False` on an axis of any length, as in `chunks=(0,)` (those arrays could store no data). On a zero-length axis this opens silently, as before, and appending to it stores chunks of size 1. On an axis of positive length it opens with a `ZarrUserWarning`, which says that the array holds only its fill value, so that recreating it with the wanted chunk shape loses nothing (`zarr.from_array(array.store, name=array.path, data=array, chunks=..., overwrite=True, write_data=False)` keeps its data type, fill value, attributes and codecs), and how to keep it by storing valid metadata instead: `array.update_attributes({})`, then `zarr.consolidate_metadata` if the metadata is consolidated. JSON `true`, as zarr-python 3.0 and 3.2 wrote for a chunk size of `True`, is read as 1, silently, in the chunk grid (regular, and the explicit edges and run-length encoded sizes of a rectilinear one) and in the inner chunk shape of every sharding codec, nested or not. A stored rectilinear chunk grid whose edge lengths are integral JSON floats (`[[4.0, 2]]`), as zarr-python 3.2 wrote for float edges, is read with those edges as integers, silently; a float anywhere no release wrote one (a regular chunk shape, the inner chunk shape of a sharding codec, a run-length repeat count, an edge below 1) is rejected.
66

7-
Writing data to an array whose stored metadata holds a regular chunk size of 0 or `false` (other than an empty selection) first stores the upgrade of the metadata the store then holds, unless another writer has stored valid metadata since, so that other readers find the chunks written (a `true` chunk size or an integral float edge is read as the value it equals, so a write stores no metadata for it); in a `ZipStore` this adds a second entry for the metadata document, as every metadata update does. If the metadata the store then holds lays out chunks differently (another writer stored a different chunk size since, say), the write raises a `ValueError` asking to reopen the array, and stores nothing. Apart from the array's own metadata writes (`update_attributes`, `resize`), which store the upgrade as before, this is the only operation that stores it: a group's consolidated metadata keeps such an array's metadata as it was stored (`zarr.consolidate_metadata` copies it as the array's own document holds it) until the array stores its upgrade through the group's handle, and changing a group reads and writes no metadata of its members.
7+
Writing data to an array whose stored metadata holds a regular chunk size of 0 or `false` (other than an empty selection) first stores the repair of the metadata the store then holds, unless another writer has stored valid metadata since, so that other readers find the chunks written (a `true` chunk size or an integral float edge is read as the value it equals, so a write stores no metadata for it); in a `ZipStore` this adds a second entry for the metadata document, as every metadata update does. If the metadata the store then holds lays out chunks differently (another writer stored a different chunk size since, say), the write raises a `ValueError` asking to reopen the array, and stores nothing. Apart from the array's own metadata writes (`update_attributes`, `resize`), which store the repair as before, this is the only operation that stores it: a group's consolidated metadata keeps such an array's metadata as it was stored (`zarr.consolidate_metadata` copies it as the array's own document holds it) until the array stores its repair through the group's handle, and changing a group reads and writes no metadata of its members.

‎src/zarr/core/array.py‎

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ def parse_array_metadata(data: Any, path: str | None = None) -> ArrayMetadata:
214214
215215
`ArrayV2Metadata` accepts a chunk size of 0, as it always has, though only an
216216
invalid document holds one: such metadata is read as the documents it would store
217-
are (see `zarr.core.metadata.upgrades`), so an array can be built from it. No data
217+
are (see `zarr.core.metadata.repair`), so an array can be built from it. No data
218218
was read or written under that chunk size, so the reading is silent."""
219219
if isinstance(data, ArrayV2Metadata) and 0 in data.chunks:
220220
return parse_stored_array(data.to_buffer_dict(default_buffer_prototype()), 2)
@@ -1613,28 +1613,28 @@ async def _save_metadata(self, metadata: ArrayMetadata, ensure_parents: bool = F
16131613

16141614
def _stored_document_replaced(self) -> None:
16151615
"""Record that the store no longer holds a document of this array that needs an
1616-
upgrade: it holds the upgrade, a valid document, or none. The metadata this handle
1616+
repair: it holds the repair, a valid document, or none. The metadata this handle
16171617
holds, which a consolidated group handle may share, then stops standing for the
1618-
document it was read from (see `mark_upgraded`), so no later write through either
1618+
document it was read from (see `mark_repaired`), so no later write through either
16191619
handle stores that document again."""
16201620
object.__setattr__(self.metadata, "_stored_document", None)
16211621

1622-
async def _store_upgraded_document(self) -> None:
1623-
"""Store the upgrade of this array's current stored document, if it needs one,
1622+
async def _store_repaired_document(self) -> None:
1623+
"""Store the repair of this array's current stored document, if it needs one,
16241624
before chunks are written under this handle's metadata.
16251625
1626-
Only for metadata read from a document whose upgrade moves chunks (see
1627-
`zarr.core.metadata.upgrades`). The document is read again, because the store may
1626+
Only for metadata read from a document whose repair moves chunks (see
1627+
`zarr.core.metadata.repair`). The document is read again, because the store may
16281628
hold a newer one than this handle's metadata. If that one lays out chunks
16291629
differently (another writer stored a different chunk size since), this handle
16301630
would write chunks no reader finds, so it raises and stores nothing. If it needs
1631-
no upgrade (the array was re-saved since, possibly by another implementation), it
1632-
is left as written; if there is none, there is nothing to upgrade.
1631+
no repair (the array was re-saved since, possibly by another implementation), it
1632+
is left as written; if there is none, there is nothing to repair.
16331633
1634-
Storing the same upgrade twice is harmless, so handles that write chunks
1634+
Storing the same repair twice is harmless, so handles that write chunks
16351635
concurrently need no coordination. The read and the store are not one atomic
16361636
step, though: a metadata write by another handle between them (a resize, an
1637-
attribute update) is replaced by the upgrade of the document read before it, as
1637+
attribute update) is replaced by the repair of the document read before it, as
16381638
with any two metadata writes that race.
16391639
"""
16401640
if self.metadata._stored_document is None:
@@ -1664,9 +1664,9 @@ async def _set_selection(
16641664
fields: Fields | None = None,
16651665
) -> None:
16661666
if product(indexer.shape) > 0:
1667-
# Chunks are about to be stored under the upgraded metadata, so store it
1667+
# Chunks are about to be stored under the repaired metadata, so store it
16681668
# first: every reader of the store then agrees with them.
1669-
await self._store_upgraded_document()
1669+
await self._store_repaired_document()
16701670
return await _set_selection(
16711671
self.store_path,
16721672
self.metadata,

‎src/zarr/core/common.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,7 @@ def _subject(name: str, axis: int | None) -> str:
290290
def _parse_positive_int(value: object, name: str, axis: int | None) -> int:
291291
"""`value` as an `int` of at least 1. A `bool` is read as the `int` it equals; any
292292
other type, a NumPy integer or a float (even an integral one: stored documents with
293-
integral floats are read by `zarr.core.metadata.upgrades`), is rejected."""
293+
integral floats are read by `zarr.core.metadata.repair`), is rejected."""
294294
subject = _subject(name, axis)
295295
if not isinstance(value, int):
296296
raise TypeError(f"{subject} must be an int, got {value!r}")

‎src/zarr/core/group.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,8 @@ class ConsolidatedMetadata:
150150

151151
def to_dict(self) -> dict[str, JSON]:
152152
"""The consolidated metadata document. An array read from a stored document that
153-
had to be upgraded is written as that document was stored, so every reader of
154-
the consolidated metadata reads it as upgraded again (see `mark_upgraded`)."""
153+
had to be repaired is written as that document was stored, so every reader of
154+
the consolidated metadata reads it as repaired again (see `mark_repaired`)."""
155155
return {
156156
"kind": self.kind,
157157
"must_understand": self.must_understand,

‎src/zarr/core/metadata/io.py‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from zarr.core.buffer.core import default_buffer_prototype
1111
from zarr.core.buffer.cpu import buffer_prototype as cpu_buffer_prototype
1212
from zarr.core.common import ZARR_JSON, ZARRAY_JSON, ZATTRS_JSON
13-
from zarr.core.metadata.upgrades import mark_upgraded, upgrade_array_document
13+
from zarr.core.metadata.repair import mark_repaired, repair_array_document
1414
from zarr.errors import ArrayNotFoundError, ContainsArrayError
1515
from zarr.storage._common import StorePath, ensure_no_existing_node
1616

@@ -116,9 +116,9 @@ async def upsert_metadata(
116116

117117
def parse_stored_array(documents: Mapping[str, Buffer], zarr_format: ZarrFormat) -> ArrayMetadata:
118118
"""The metadata of an array from its documents (by store key, see `ARRAY_DOCUMENTS`),
119-
read with the upgrades but without their warnings (whoever asks has warned, or reads
120-
metadata built in code), and marked (see `mark_upgraded`) if they had to be
121-
upgraded. Raises `ArrayNotFoundError` if there is no array document among them."""
119+
read with the repairs but without their warnings (whoever asks has warned, or reads
120+
metadata built in code), and marked (see `mark_repaired`) if they had to be
121+
repaired. Raises `ArrayNotFoundError` if there is no array document among them."""
122122
from zarr.core.array import (
123123
_array_metadata_dict_v2,
124124
_array_metadata_dict_v3,
@@ -131,8 +131,8 @@ def parse_stored_array(documents: Mapping[str, Buffer], zarr_format: ZarrFormat)
131131
stored = _array_metadata_dict_v3(documents[ZARR_JSON])
132132
else:
133133
raise ArrayNotFoundError(f"No Zarr format {zarr_format} array metadata document.")
134-
upgraded, readings = upgrade_array_document(stored, zarr_format)
135-
return mark_upgraded(parse_array_metadata(dict(upgraded)), stored, readings, None, warn=False)
134+
repaired, readings = repair_array_document(stored, zarr_format)
135+
return mark_repaired(parse_array_metadata(dict(repaired)), stored, readings, None, warn=False)
136136

137137

138138
def _build_parents(store_path: StorePath, zarr_format: ZarrFormat) -> dict[str, GroupMetadata]:
Lines changed: 38 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
1-
"""Upgrades that read invalid stored array metadata documents written by older software.
1+
"""Repairs that read invalid stored array metadata documents written by older software.
22
3-
This is the only place invalid metadata is read leniently. An upgrade maps a stored
3+
This is the only place invalid metadata is read leniently. A repair maps a stored
44
array metadata document (parsed JSON) to a valid one. `ArrayV2Metadata.from_dict` and
5-
`ArrayV3Metadata.from_dict` apply the upgrades for their Zarr format, so every path
5+
`ArrayV3Metadata.from_dict` apply the repairs for their Zarr format, so every path
66
that parses a stored document, including consolidated metadata, goes through them.
77
88
A reading warns only where the user must act on it; a reading that gives what zarr
9-
read from the same document before these upgrades existed is silent, so a document
9+
read from the same document before these repairs existed is silent, so a document
1010
that opened without a warning still does. The warnings are given once per document,
11-
after the upgraded document has passed the metadata constructor, so an invalid
11+
after the repaired document has passed the metadata constructor, so an invalid
1212
document raises its own error, not a warning about how it was read. Metadata read
13-
from a document whose upgrade moves chunks (a chunk size read as another size) is
14-
marked (see `mark_upgraded`), silent or not, so the array stores the upgrade before it
15-
writes chunks under it. An upgrade that only respells a value zarr already read the
13+
from a document whose repair moves chunks (a chunk size read as another size) is
14+
marked (see `mark_repaired`), silent or not, so the array stores the repair before it
15+
writes chunks under it. A repair that only respells a value zarr already read the
1616
same way (`true` as 1, `4.0` as 4) moves no chunks, so it is not marked.
1717
18-
To read another kind of invalid document, add an upgrade to `ARRAY_UPGRADES`.
18+
To read another kind of invalid document, add a repair to `ARRAY_REPAIRS`.
1919
"""
2020

2121
from __future__ import annotations
@@ -36,18 +36,18 @@
3636

3737

3838
class Reading(NamedTuple):
39-
"""How an upgrade read a stored document."""
39+
"""How a repair read a stored document."""
4040

4141
moves_chunks: bool
42-
"""Whether chunks written under the upgraded document are not where a reader of the
43-
stored document looks for them, so the array must store the upgrade before it
42+
"""Whether chunks written under the repaired document are not where a reader of the
43+
stored document looks for them, so the array must store the repair before it
4444
writes chunks."""
4545
warning: str | None
4646
"""What the user must act on, if anything."""
4747

4848

49-
type Upgrade = Callable[[ArrayDocument], tuple[ArrayDocument, Reading] | None]
50-
"""Returns `None` if the document needs no upgrade, else the upgraded document and how
49+
type Repair = Callable[[ArrayDocument], tuple[ArrayDocument, Reading] | None]
50+
"""Returns `None` if the document needs no repair, else the repaired document and how
5151
it was read."""
5252

5353
RECREATE_HINT: Final = (
@@ -64,7 +64,7 @@ class Reading(NamedTuple):
6464
and a re-save would keep it."""
6565

6666

67-
def mark_upgraded[M](
67+
def mark_repaired[M](
6868
metadata: M,
6969
stored: ArrayDocument,
7070
readings: Sequence[Reading],
@@ -73,9 +73,9 @@ def mark_upgraded[M](
7373
warn: bool = True,
7474
) -> M:
7575
"""Record that `metadata` was read from the document `stored`, which needed the
76-
upgrades whose `readings` `upgrade_array_document` returned, if any. If a reading
76+
repairs whose `readings` `repair_array_document` returned, if any. If a reading
7777
moves chunks, keep a copy of `stored` as `_stored_document` on `metadata`, so the
78-
array stores the upgrade before it writes chunks under it and consolidated metadata
78+
array stores the repair before it writes chunks under it and consolidated metadata
7979
stores it as it was stored. If `warn`, warn once with the readings' warnings (each
8080
says what the user must act on and how), naming the array at `path` when the caller
8181
knows it."""
@@ -142,7 +142,7 @@ def _read_chunk_shape(
142142
Returns the chunk shape and how it was read, if any entry was read as another value
143143
(the warning says how the chunk shape was read and, as the array then holds only its
144144
fill value, recommends recreating it, see `RECREATE_HINT`); `None` if it cannot be
145-
read or needs no upgrade.
145+
read or needs no repair.
146146
"""
147147
if not (isinstance(stored, list) and len(stored) == len(spans)):
148148
return None
@@ -190,22 +190,22 @@ def _read_codec(codec: JSON) -> JSON | None:
190190
no chunks."""
191191
match codec:
192192
case {"name": "sharding_indexed", "configuration": Mapping() as configuration}:
193-
upgraded: dict[str, JSON] = {}
193+
repaired: dict[str, JSON] = {}
194194
stored = configuration.get("chunk_shape")
195195
if isinstance(stored, list) and (
196196
read := _read_chunk_shape(stored, [None] * len(stored))
197197
):
198-
upgraded["chunk_shape"] = read[0]
198+
repaired["chunk_shape"] = read[0]
199199
if isinstance(codecs := configuration.get("codecs"), list) and (
200200
inner := _read_codecs(codecs)
201201
):
202-
upgraded["codecs"] = inner
203-
if not upgraded:
202+
repaired["codecs"] = inner
203+
if not repaired:
204204
return None
205205
# The mapping pattern does not narrow `codec` for mypy.
206206
return {
207207
**cast("Mapping[str, JSON]", codec),
208-
"configuration": {**configuration, **upgraded},
208+
"configuration": {**configuration, **repaired},
209209
}
210210
return None
211211

@@ -251,8 +251,8 @@ def _invalid_chunk_sizes_v3(doc: ArrayDocument) -> tuple[ArrayDocument, Reading]
251251
return None
252252
match _read_chunk_shape(configuration.get("chunk_shape"), shape, units):
253253
case chunk_shape, reading:
254-
upgraded = {**configuration, "chunk_shape": chunk_shape}
255-
return {**doc, "chunk_grid": {**grid, "configuration": upgraded}}, reading
254+
repaired = {**configuration, "chunk_shape": chunk_shape}
255+
return {**doc, "chunk_grid": {**grid, "configuration": repaired}}, reading
256256
return None
257257

258258

@@ -308,33 +308,33 @@ def _invalid_edge_lengths_v3(doc: ArrayDocument) -> tuple[ArrayDocument, Reading
308308
read = [_read_rectilinear_axis(axis) for axis in stored]
309309
if not _respelled(read, stored):
310310
return None
311-
upgraded = {**configuration, "chunk_shapes": read}
311+
repaired = {**configuration, "chunk_shapes": read}
312312
# A stored edge is read as the value it equals, so the reading moves no chunks.
313-
return {**doc, "chunk_grid": {**grid, "configuration": upgraded}}, Reading(False, None)
313+
return {**doc, "chunk_grid": {**grid, "configuration": repaired}}, Reading(False, None)
314314

315315

316-
ARRAY_UPGRADES: Final[Mapping[ZarrFormat, tuple[Upgrade, ...]]] = {
316+
ARRAY_REPAIRS: Final[Mapping[ZarrFormat, tuple[Repair, ...]]] = {
317317
2: (_invalid_chunk_sizes_v2,),
318318
# The inner chunk shape is read first: it gives the unit of the outer chunk shape.
319-
# The rectilinear edge lengths are read last, after any upgrade that yields a
319+
# The rectilinear edge lengths are read last, after any repair that yields a
320320
# rectilinear chunk grid.
321321
3: (_invalid_inner_chunk_sizes_v3, _invalid_chunk_sizes_v3, _invalid_edge_lengths_v3),
322322
}
323-
"""The upgrades of an array document of each Zarr format, applied in order."""
323+
"""The repairs of an array document of each Zarr format, applied in order."""
324324

325325

326-
def upgrade_array_document(
326+
def repair_array_document(
327327
doc: ArrayDocument, zarr_format: ZarrFormat
328328
) -> tuple[ArrayDocument, list[Reading]]:
329-
"""Apply the upgrades for `zarr_format` to a stored array metadata document.
329+
"""Apply the repairs for `zarr_format` to a stored array metadata document.
330330
331-
Returns the upgraded document and the reading of each upgrade that changed it, for
332-
`mark_upgraded` once the document has been validated.
331+
Returns the repaired document and the reading of each repair that changed it, for
332+
`mark_repaired` once the document has been validated.
333333
"""
334334
readings: list[Reading] = []
335-
for upgrade in ARRAY_UPGRADES[zarr_format]:
336-
upgraded = upgrade(doc)
337-
if upgraded is not None:
338-
doc, reading = upgraded
335+
for repair in ARRAY_REPAIRS[zarr_format]:
336+
repaired = repair(doc)
337+
if repaired is not None:
338+
doc, reading = repaired
339339
readings.append(reading)
340340
return doc, readings

0 commit comments

Comments
 (0)