Skip to content

Commit 75928ae

Browse files
committed
refactor(zarr-metadata): build each drawn configuration as its TypedDict
Each configuration strategy now returns a strategy of its definition's configuration TypedDict, built with the TypedDict's constructor, so pyright checks every key and value it draws. Optional keys are set on the built TypedDict rather than drawn through `fixed_dictionaries`. Assisted-by: ClaudeCode:claude-opus-5-5
1 parent 855815b commit 75928ae

4 files changed

Lines changed: 194 additions & 106 deletions

File tree

‎packages/zarr-metadata/src/zarr_metadata/testing/_chunk_grid.py‎

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,16 @@
1414
from hypothesis import strategies as st
1515

1616
from zarr_metadata.testing._strategy import ChunkGridStrategy
17-
from zarr_metadata.v3.chunk_grid.rectilinear import RECTILINEAR_CHUNK_GRID
18-
from zarr_metadata.v3.chunk_grid.regular import REGULAR_CHUNK_GRID
17+
from zarr_metadata.v3.chunk_grid.rectilinear import (
18+
RECTILINEAR_CHUNK_GRID,
19+
RectilinearChunkGridConfiguration,
20+
RectilinearDimSpec,
21+
)
22+
from zarr_metadata.v3.chunk_grid.regular import REGULAR_CHUNK_GRID, RegularChunkGridConfiguration
1923

2024
if TYPE_CHECKING:
2125
from collections.abc import Mapping
2226

23-
from zarr_metadata._common import JSONValue
2427
from zarr_metadata.testing._strategy import Strategies
2528
from zarr_metadata.v3._definition import ChunkGridDefinition
2629

@@ -46,24 +49,24 @@ def _length(extent: int | None) -> st.SearchStrategy[int]:
4649

4750
def _regular(
4851
shape: tuple[int, ...] | None, _strategies: Strategies
49-
) -> st.SearchStrategy[Mapping[str, JSONValue]]:
52+
) -> st.SearchStrategy[RegularChunkGridConfiguration]:
5053
"""A chunk length for each dimension."""
5154
if shape is None:
5255
lengths = st.lists(_length(None), max_size=_MAX_RANK).map(tuple)
5356
else:
5457
lengths = st.tuples(*(_length(extent) for extent in shape))
55-
return lengths.map(lambda chunk_shape: {"chunk_shape": chunk_shape})
58+
return lengths.map(lambda chunk_shape: RegularChunkGridConfiguration(chunk_shape=chunk_shape))
5659

5760

58-
def _spelled_run(draw: st.DrawFn, size: int, count: int) -> list[JSONValue]:
61+
def _spelled_run(draw: st.DrawFn, size: int, count: int) -> list[int | tuple[int, int]]:
5962
"""`count` chunks of `size`: one `[size, count]` pair, or the size written once for each."""
6063
if draw(st.booleans()):
6164
return [(size, count)]
6265
return [size] * count
6366

6467

6568
@st.composite
66-
def _edges(draw: st.DrawFn, extent: int | None) -> JSONValue:
69+
def _edges(draw: st.DrawFn, extent: int | None) -> tuple[int | tuple[int, int], ...]:
6770
"""Edge lengths along a dimension, covering `extent`, in runs spelled as pairs or written out.
6871
6972
Runs are drawn apart, so two of one size can sit side by side and a
@@ -81,26 +84,30 @@ def _edges(draw: st.DrawFn, extent: int | None) -> JSONValue:
8184
covered = sum(size * count for size, count in runs)
8285
if extent is not None and covered < extent:
8386
runs.append((extent - covered, 1))
84-
spelled: list[JSONValue] = []
87+
spelled: list[int | tuple[int, int]] = []
8588
for size, count in runs:
8689
spelled.extend(_spelled_run(draw, size, count))
8790
return tuple(spelled)
8891

8992

90-
def _rectilinear_dimension(extent: int | None) -> st.SearchStrategy[JSONValue]:
93+
def _rectilinear_dimension(extent: int | None) -> st.SearchStrategy[RectilinearDimSpec]:
9194
"""A bare length, which repeats until it covers the dimension, or edges that cover it."""
9295
return _length(extent) | _edges(extent)
9396

9497

9598
def _rectilinear(
9699
shape: tuple[int, ...] | None, _strategies: Strategies
97-
) -> st.SearchStrategy[Mapping[str, JSONValue]]:
100+
) -> st.SearchStrategy[RectilinearChunkGridConfiguration]:
98101
"""Inline chunk shapes, one entry for each dimension."""
99102
if shape is None:
100103
dimensions = st.lists(_rectilinear_dimension(None), max_size=_MAX_RANK).map(tuple)
101104
else:
102105
dimensions = st.tuples(*(_rectilinear_dimension(extent) for extent in shape))
103-
return dimensions.map(lambda chunk_shapes: {"kind": "inline", "chunk_shapes": chunk_shapes})
106+
return dimensions.map(
107+
lambda chunk_shapes: RectilinearChunkGridConfiguration(
108+
kind="inline", chunk_shapes=chunk_shapes
109+
)
110+
)
104111

105112

106113
CHUNK_GRID_STRATEGIES: Final[Mapping[ChunkGridDefinition[Any], ChunkGridStrategy]] = {

‎packages/zarr-metadata/src/zarr_metadata/testing/_chunk_key_encoding.py‎

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,29 +10,35 @@
1010
from zarr_metadata.v3.chunk_key_encoding.default import (
1111
DEFAULT_CHUNK_KEY_ENCODING,
1212
DEFAULT_CHUNK_KEY_ENCODING_SEPARATOR,
13+
DefaultChunkKeyEncodingConfiguration,
1314
)
1415
from zarr_metadata.v3.chunk_key_encoding.v2 import (
1516
V2_CHUNK_KEY_ENCODING,
1617
V2_CHUNK_KEY_ENCODING_SEPARATOR,
18+
V2ChunkKeyEncodingConfiguration,
1719
)
1820

1921
if TYPE_CHECKING:
2022
from collections.abc import Mapping
2123

2224
from zarr_metadata.v3._definition import ChunkKeyEncodingDefinition
2325

26+
_DEFAULT_SEPARATORS: Final = st.just(DefaultChunkKeyEncodingConfiguration()) | st.sampled_from(
27+
DEFAULT_CHUNK_KEY_ENCODING_SEPARATOR
28+
).map(lambda separator: DefaultChunkKeyEncodingConfiguration(separator=separator))
29+
30+
_V2_SEPARATORS: Final = st.just(V2ChunkKeyEncodingConfiguration()) | st.sampled_from(
31+
V2_CHUNK_KEY_ENCODING_SEPARATOR
32+
).map(lambda separator: V2ChunkKeyEncodingConfiguration(separator=separator))
33+
2434
CHUNK_KEY_ENCODING_STRATEGIES: Final[
2535
Mapping[ChunkKeyEncodingDefinition[Any], ChunkKeyEncodingStrategy]
2636
] = {
2737
DEFAULT_CHUNK_KEY_ENCODING: ChunkKeyEncodingStrategy(
28-
configurations=lambda _strategies: st.fixed_dictionaries(
29-
{}, optional={"separator": st.sampled_from(DEFAULT_CHUNK_KEY_ENCODING_SEPARATOR)}
30-
)
38+
configurations=lambda _strategies: _DEFAULT_SEPARATORS
3139
),
3240
V2_CHUNK_KEY_ENCODING: ChunkKeyEncodingStrategy(
33-
configurations=lambda _strategies: st.fixed_dictionaries(
34-
{}, optional={"separator": st.sampled_from(V2_CHUNK_KEY_ENCODING_SEPARATOR)}
35-
)
41+
configurations=lambda _strategies: _V2_SEPARATORS
3642
),
3743
}
3844
"""A strategy for each chunk key encoding: a `separator`, or none, which means the encoding's default."""

0 commit comments

Comments
 (0)