Skip to content

Commit f8cd675

Browse files
committed
feat(zarr-metadata): export store-key aliases and type to_key_value with them
The six store-key Literal aliases were private and unused: only reachable via underscore modules, and absent from every signature. Export them from zarr_metadata.model beside their constants (matching the package's name/constant pairing everywhere else), and key each to_key_value return mapping by them so the store keys a model can emit are visible in its signature. from_key_value keeps Mapping[str, bytes] input on purpose — it accepts whole store mappings. A pair test guards export and value drift, and the removal note now states the version-placement and JSON-suffix conventions explicitly. Assisted-by: ClaudeCode:claude-fable-5
1 parent 41e4235 commit f8cd675

6 files changed

Lines changed: 70 additions & 8 deletions

File tree

packages/zarr-metadata/changes/4119.feature.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,13 @@ emits none), while an explicit empty `.zattrs` is `{}` and round-trips as a
7878
file. Previously `to_key_value` always emitted `.zattrs`, silently adding a
7979
file to stores that never had one.
8080

81+
The store-key `Literal` aliases (`ZarrV2ArrayMetadataStoreKey`,
82+
`ZarrV2AttributesStoreKey`, ...) are exported from `zarr_metadata.model`
83+
alongside their constants, and each `to_key_value` return type is keyed by
84+
them, so the set of store keys a model can emit is visible in its signature.
85+
`from_key_value` deliberately keeps `Mapping[str, bytes]` input: it accepts
86+
any string-keyed store mapping and ignores unrelated keys.
87+
8188
`to_json` returns a document that shares no mutable state with the model:
8289
every value that can hold a mutable container (attributes, configurations,
8390
extra fields, v2 codec configurations, fill values, consolidated entries) is

packages/zarr-metadata/changes/4119.removal.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,12 @@ entity names are reserved for the `zarr_metadata.model` dataclasses:
2121
The old names are removed, not aliased. The `zarr_metadata.pydantic` field
2222
types take the bare entity names (`ZarrV3ArrayMetadata`, ...), matching the
2323
model classes they validate into.
24+
25+
The conventions, stated once for future additions: CamelCase type names put
26+
the format version first (`ZarrV2ArrayMetadataJSON`,
27+
`ZarrV3ArrayMetadataStoreKey`), while SCREAMING_SNAKE constants and
28+
snake_case functions put it last (`ARRAY_METADATA_STORE_KEY_V2`,
29+
`validate_array_metadata_v3`). The `JSON` suffix marks a raw-document type
30+
whose bare name is taken by (or reserved for) a `zarr_metadata.model`
31+
dataclass; raw field-level types the models hold verbatim
32+
(`ZarrV2CodecMetadata`, `ZarrV3ExtensionField`) keep their bare names.

packages/zarr-metadata/src/zarr_metadata/model/__init__.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,11 @@
1717
ATTRIBUTES_STORE_KEY_V2,
1818
ZarrV2ArrayMetadata,
1919
ZarrV2ArrayMetadataPartial,
20+
ZarrV2ArrayMetadataStoreKey,
21+
ZarrV2AttributesStoreKey,
2022
ZarrV3ArrayMetadata,
2123
ZarrV3ArrayMetadataPartial,
24+
ZarrV3ArrayMetadataStoreKey,
2225
ZarrV3MetadataField,
2326
ZarrV3NamedConfig,
2427
)
@@ -28,11 +31,14 @@
2831
GROUP_METADATA_STORE_KEY_V2,
2932
GROUP_METADATA_STORE_KEY_V3,
3033
ZarrV2ConsolidatedMetadata,
34+
ZarrV2ConsolidatedMetadataStoreKey,
3135
ZarrV2GroupMetadata,
3236
ZarrV2GroupMetadataPartial,
37+
ZarrV2GroupMetadataStoreKey,
3338
ZarrV3ConsolidatedMetadata,
3439
ZarrV3GroupMetadata,
3540
ZarrV3GroupMetadataPartial,
41+
ZarrV3GroupMetadataStoreKey,
3642
)
3743
from zarr_metadata.model._sentinel import UNSET
3844
from zarr_metadata.model._validation import (
@@ -89,14 +95,20 @@
8995
"ValidationProblem",
9096
"ZarrV2ArrayMetadata",
9197
"ZarrV2ArrayMetadataPartial",
98+
"ZarrV2ArrayMetadataStoreKey",
99+
"ZarrV2AttributesStoreKey",
92100
"ZarrV2ConsolidatedMetadata",
101+
"ZarrV2ConsolidatedMetadataStoreKey",
93102
"ZarrV2GroupMetadata",
94103
"ZarrV2GroupMetadataPartial",
104+
"ZarrV2GroupMetadataStoreKey",
95105
"ZarrV3ArrayMetadata",
96106
"ZarrV3ArrayMetadataPartial",
107+
"ZarrV3ArrayMetadataStoreKey",
97108
"ZarrV3ConsolidatedMetadata",
98109
"ZarrV3GroupMetadata",
99110
"ZarrV3GroupMetadataPartial",
111+
"ZarrV3GroupMetadataStoreKey",
100112
"ZarrV3MetadataField",
101113
"ZarrV3NamedConfig",
102114
"is_array_metadata_v2",

packages/zarr-metadata/src/zarr_metadata/model/_array.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,9 @@ def must_understand_fields(self) -> dict[str, ZarrV3ExtensionField]:
321321
def from_key_value(cls, mapping: Mapping[str, bytes]) -> ZarrV3ArrayMetadata:
322322
return cls.from_json(load_store_json(mapping, ARRAY_METADATA_STORE_KEY_V3))
323323

324-
def to_key_value(self, *, indent: int | str | None = None) -> Mapping[str, bytes]:
324+
def to_key_value(
325+
self, *, indent: int | str | None = None
326+
) -> Mapping[ZarrV3ArrayMetadataStoreKey, bytes]:
325327
return {ARRAY_METADATA_STORE_KEY_V3: dump_store_json(self.to_json(), indent=indent)}
326328

327329

@@ -481,12 +483,16 @@ def from_key_value(cls, mapping: Mapping[str, bytes]) -> ZarrV2ArrayMetadata:
481483
return cls.from_json({**zarray, "attributes": zattrs})
482484
return cls.from_json(zarray)
483485

484-
def to_key_value(self, *, indent: int | str | None = None) -> Mapping[str, bytes]:
486+
def to_key_value(
487+
self, *, indent: int | str | None = None
488+
) -> Mapping[ZarrV2ArrayMetadataStoreKey | ZarrV2AttributesStoreKey, bytes]:
485489
# Attributes live only in the sibling `.zattrs` file; the `.zarray`
486490
# document must exclude them. The `.zattrs` key is present exactly
487491
# when attributes are set (even empty) — UNSET emits no file.
488492
zarray = {k: v for k, v in self.to_json().items() if k != "attributes"}
489-
out = {ARRAY_METADATA_STORE_KEY_V2: dump_store_json(zarray, indent=indent)}
493+
out: dict[ZarrV2ArrayMetadataStoreKey | ZarrV2AttributesStoreKey, bytes] = {
494+
ARRAY_METADATA_STORE_KEY_V2: dump_store_json(zarray, indent=indent)
495+
}
490496
if self.attributes is not UNSET:
491497
out[ATTRIBUTES_STORE_KEY_V2] = dump_store_json(self.attributes, indent=indent)
492498
return out

packages/zarr-metadata/src/zarr_metadata/model/_group.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131

3232
if TYPE_CHECKING:
3333
from zarr_metadata._common import JSONValue
34+
from zarr_metadata.model._array import ZarrV2AttributesStoreKey
3435
from zarr_metadata.v2.group import ZarrV2GroupMetadataJSON
3536
from zarr_metadata.v3.array import ZarrV3ExtensionField
3637
from zarr_metadata.v3.consolidated import ZarrV3ConsolidatedMetadataJSON
@@ -186,7 +187,9 @@ def must_understand_fields(self) -> dict[str, ZarrV3ExtensionField]:
186187
def from_key_value(cls, mapping: Mapping[str, bytes]) -> ZarrV3GroupMetadata:
187188
return cls.from_json(load_store_json(mapping, GROUP_METADATA_STORE_KEY_V3))
188189

189-
def to_key_value(self, *, indent: int | str | None = None) -> Mapping[str, bytes]:
190+
def to_key_value(
191+
self, *, indent: int | str | None = None
192+
) -> Mapping[ZarrV3GroupMetadataStoreKey, bytes]:
190193
return {GROUP_METADATA_STORE_KEY_V3: dump_store_json(self.to_json(), indent=indent)}
191194

192195

@@ -338,12 +341,16 @@ def from_key_value(cls, mapping: Mapping[str, bytes]) -> ZarrV2GroupMetadata:
338341
return cls.from_json({**zgroup, "attributes": zattrs})
339342
return cls.from_json(zgroup)
340343

341-
def to_key_value(self, *, indent: int | str | None = None) -> Mapping[str, bytes]:
344+
def to_key_value(
345+
self, *, indent: int | str | None = None
346+
) -> Mapping[ZarrV2GroupMetadataStoreKey | ZarrV2AttributesStoreKey, bytes]:
342347
# Attributes live only in the sibling `.zattrs` file; the `.zgroup`
343348
# document must exclude them. The `.zattrs` key is present exactly
344349
# when attributes are set (even empty) — UNSET emits no file.
345350
zgroup = {k: v for k, v in self.to_json().items() if k != "attributes"}
346-
out = {GROUP_METADATA_STORE_KEY_V2: dump_store_json(zgroup, indent=indent)}
351+
out: dict[ZarrV2GroupMetadataStoreKey | ZarrV2AttributesStoreKey, bytes] = {
352+
GROUP_METADATA_STORE_KEY_V2: dump_store_json(zgroup, indent=indent)
353+
}
347354
if self.attributes is not UNSET:
348355
out[ATTRIBUTES_STORE_KEY_V2] = dump_store_json(self.attributes, indent=indent)
349356
return out
@@ -429,5 +436,7 @@ def from_json(cls, data: object) -> ZarrV2ConsolidatedMetadata:
429436
def from_key_value(cls, mapping: Mapping[str, bytes]) -> ZarrV2ConsolidatedMetadata:
430437
return cls.from_json(load_store_json(mapping, CONSOLIDATED_METADATA_STORE_KEY_V2))
431438

432-
def to_key_value(self, *, indent: int | str | None = None) -> Mapping[str, bytes]:
439+
def to_key_value(
440+
self, *, indent: int | str | None = None
441+
) -> Mapping[ZarrV2ConsolidatedMetadataStoreKey, bytes]:
433442
return {CONSOLIDATED_METADATA_STORE_KEY_V2: dump_store_json(self.to_json(), indent=indent)}

packages/zarr-metadata/tests/model/test_array.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import json
66
from collections import UserDict
77
from collections.abc import Callable
8-
from typing import TYPE_CHECKING
8+
from typing import TYPE_CHECKING, get_args
99

1010
import pytest
1111
from typing_extensions import Unpack
@@ -64,6 +64,25 @@ def test_guards_exported_from_package() -> None:
6464
assert hasattr(zarr_metadata.model, name)
6565

6666

67+
def test_store_key_pairs_exported_from_package() -> None:
68+
"""Each store-key constant is exported together with its Literal type
69+
alias, and the pair cannot drift apart."""
70+
import zarr_metadata.model as m
71+
72+
pairs = [
73+
("ARRAY_METADATA_STORE_KEY_V2", "ZarrV2ArrayMetadataStoreKey"),
74+
("ARRAY_METADATA_STORE_KEY_V3", "ZarrV3ArrayMetadataStoreKey"),
75+
("ATTRIBUTES_STORE_KEY_V2", "ZarrV2AttributesStoreKey"),
76+
("GROUP_METADATA_STORE_KEY_V2", "ZarrV2GroupMetadataStoreKey"),
77+
("GROUP_METADATA_STORE_KEY_V3", "ZarrV3GroupMetadataStoreKey"),
78+
("CONSOLIDATED_METADATA_STORE_KEY_V2", "ZarrV2ConsolidatedMetadataStoreKey"),
79+
]
80+
for const_name, alias_name in pairs:
81+
assert const_name in m.__all__
82+
assert alias_name in m.__all__
83+
assert (getattr(m, const_name),) == get_args(getattr(m, alias_name))
84+
85+
6786
def test_validation_diagnostics_exported_from_package() -> None:
6887
"""The validation-diagnostic types and validators are exported from the package."""
6988
import zarr_metadata.model

0 commit comments

Comments
 (0)