Skip to content

Commit fb938ba

Browse files
authored
Merge branch 'v3' into docs_v3
2 parents 1b1fab4 + a24e194 commit fb938ba

File tree

13 files changed

+116
-117
lines changed

13 files changed

+116
-117
lines changed

.github/workflows/releases.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ jobs:
5555
with:
5656
name: releases
5757
path: dist
58-
- uses: pypa/[email protected].2
58+
- uses: pypa/[email protected].3
5959
with:
6060
user: __token__
6161
password: ${{ secrets.pypi_password }}

docs/_static/custom.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
"highlights": "getting_started.html#highlights",
77
"contributing": "contributing.html",
88
"projects-using-zarr": "getting_started.html#projects-using-zarr",
9-
"acknowledgments": "acknowledgments.html",
109
"contents": "getting_started.html#contents",
1110
"indices-and-tables": "api.html#indices-and-tables"
1211
}

docs/acknowledgments.rst

Lines changed: 0 additions & 76 deletions
This file was deleted.

docs/conf.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,8 @@ def setup(app: sphinx.application.Sphinx) -> None:
255255
# Output file base name for HTML help builder.
256256
htmlhelp_basename = "zarrdoc"
257257

258+
maximum_signature_line_length = 80
259+
258260
# -- Options for LaTeX output ---------------------------------------------
259261

260262
latex_elements = {

docs/index.rst

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ Zarr-Python
1414
spec
1515
release
1616
license
17-
acknowledgments
1817
contributing
1918

2019
**Version**: |version|

src/zarr/abc/codec.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from zarr.core.indexing import SelectorTuple
2121

2222
__all__ = [
23+
"BaseCodec",
2324
"ArrayArrayCodec",
2425
"ArrayBytesCodec",
2526
"ArrayBytesCodecPartialDecodeMixin",
@@ -34,11 +35,15 @@
3435
CodecOutput = TypeVar("CodecOutput", bound=NDBuffer | Buffer)
3536

3637

37-
class _Codec(Metadata, Generic[CodecInput, CodecOutput]):
38+
class BaseCodec(Metadata, Generic[CodecInput, CodecOutput]):
3839
"""Generic base class for codecs.
39-
Please use ArrayArrayCodec, ArrayBytesCodec or BytesBytesCodec for subclassing.
4040
4141
Codecs can be registered via zarr.codecs.registry.
42+
43+
Warnings
44+
--------
45+
This class is not intended to be directly, please use
46+
ArrayArrayCodec, ArrayBytesCodec or BytesBytesCodec for subclassing.
4247
"""
4348

4449
is_fixed_size: bool
@@ -148,19 +153,19 @@ async def encode(
148153
return await _batching_helper(self._encode_single, chunks_and_specs)
149154

150155

151-
class ArrayArrayCodec(_Codec[NDBuffer, NDBuffer]):
156+
class ArrayArrayCodec(BaseCodec[NDBuffer, NDBuffer]):
152157
"""Base class for array-to-array codecs."""
153158

154159
...
155160

156161

157-
class ArrayBytesCodec(_Codec[NDBuffer, Buffer]):
162+
class ArrayBytesCodec(BaseCodec[NDBuffer, Buffer]):
158163
"""Base class for array-to-bytes codecs."""
159164

160165
...
161166

162167

163-
class BytesBytesCodec(_Codec[Buffer, Buffer]):
168+
class BytesBytesCodec(BaseCodec[Buffer, Buffer]):
164169
"""Base class for bytes-to-bytes codecs."""
165170

166171
...

src/zarr/core/group.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import asyncio
44
import json
55
import logging
6-
from dataclasses import asdict, dataclass, field, replace
6+
from dataclasses import asdict, dataclass, field, fields, replace
77
from typing import TYPE_CHECKING, Literal, cast, overload
88

99
import numpy as np
@@ -116,6 +116,15 @@ def __init__(
116116
@classmethod
117117
def from_dict(cls, data: dict[str, Any]) -> GroupMetadata:
118118
assert data.pop("node_type", None) in ("group", None)
119+
120+
zarr_format = data.get("zarr_format")
121+
if zarr_format == 2 or zarr_format is None:
122+
# zarr v2 allowed arbitrary keys here.
123+
# We don't want the GroupMetadata constructor to fail just because someone put an
124+
# extra key in the metadata.
125+
expected = {x.name for x in fields(cls)}
126+
data = {k: v for k, v in data.items() if k in expected}
127+
119128
return cls(**data)
120129

121130
def to_dict(self) -> dict[str, Any]:

src/zarr/core/metadata/v2.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from zarr.core.common import JSON, ChunkCoords
1414

1515
import json
16-
from dataclasses import dataclass, field, replace
16+
from dataclasses import dataclass, field, fields, replace
1717

1818
import numcodecs
1919
import numpy as np
@@ -140,6 +140,17 @@ def from_dict(cls, data: dict[str, Any]) -> ArrayV2Metadata:
140140
_data = data.copy()
141141
# check that the zarr_format attribute is correct
142142
_ = parse_zarr_format(_data.pop("zarr_format"))
143+
144+
# zarr v2 allowed arbitrary keys here.
145+
# We don't want the ArrayV2Metadata constructor to fail just because someone put an
146+
# extra key in the metadata.
147+
expected = {x.name for x in fields(cls)}
148+
# https://github.com/zarr-developers/zarr-python/issues/2269
149+
# handle the renames
150+
expected |= {"dtype", "chunks"}
151+
152+
_data = {k: v for k, v in _data.items() if k in expected}
153+
143154
return cls(**_data)
144155

145156
def to_dict(self) -> dict[str, JSON]:

src/zarr/core/metadata/v3.py

Lines changed: 37 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66
if TYPE_CHECKING:
77
from typing import Self
88

9-
import numpy.typing as npt
10-
119
from zarr.core.buffer import Buffer, BufferPrototype
1210
from zarr.core.chunk_grids import ChunkGrid
1311
from zarr.core.common import JSON, ChunkCoords
@@ -20,6 +18,7 @@
2018

2119
import numcodecs.abc
2220
import numpy as np
21+
import numpy.typing as npt
2322

2423
from zarr.abc.codec import ArrayArrayCodec, ArrayBytesCodec, BytesBytesCodec, Codec
2524
from zarr.core.array_spec import ArraySpec
@@ -31,6 +30,8 @@
3130
from zarr.core.metadata.common import ArrayMetadata, parse_attributes
3231
from zarr.registry import get_codec_class
3332

33+
DEFAULT_DTYPE = "float64"
34+
3435

3536
def parse_zarr_format(data: object) -> Literal[3]:
3637
if data == 3:
@@ -152,7 +153,7 @@ def _replace_special_floats(obj: object) -> Any:
152153
@dataclass(frozen=True, kw_only=True)
153154
class ArrayV3Metadata(ArrayMetadata):
154155
shape: ChunkCoords
155-
data_type: np.dtype[Any]
156+
data_type: DataType
156157
chunk_grid: ChunkGrid
157158
chunk_key_encoding: ChunkKeyEncoding
158159
fill_value: Any
@@ -167,7 +168,7 @@ def __init__(
167168
self,
168169
*,
169170
shape: Iterable[int],
170-
data_type: npt.DTypeLike,
171+
data_type: npt.DTypeLike | DataType,
171172
chunk_grid: dict[str, JSON] | ChunkGrid,
172173
chunk_key_encoding: dict[str, JSON] | ChunkKeyEncoding,
173174
fill_value: Any,
@@ -180,18 +181,18 @@ def __init__(
180181
Because the class is a frozen dataclass, we set attributes using object.__setattr__
181182
"""
182183
shape_parsed = parse_shapelike(shape)
183-
data_type_parsed = parse_dtype(data_type)
184+
data_type_parsed = DataType.parse(data_type)
184185
chunk_grid_parsed = ChunkGrid.from_dict(chunk_grid)
185186
chunk_key_encoding_parsed = ChunkKeyEncoding.from_dict(chunk_key_encoding)
186187
dimension_names_parsed = parse_dimension_names(dimension_names)
187-
fill_value_parsed = parse_fill_value(fill_value, dtype=data_type_parsed)
188+
fill_value_parsed = parse_fill_value(fill_value, dtype=data_type_parsed.to_numpy())
188189
attributes_parsed = parse_attributes(attributes)
189190
codecs_parsed_partial = parse_codecs(codecs)
190191
storage_transformers_parsed = parse_storage_transformers(storage_transformers)
191192

192193
array_spec = ArraySpec(
193194
shape=shape_parsed,
194-
dtype=data_type_parsed,
195+
dtype=data_type_parsed.to_numpy(),
195196
fill_value=fill_value_parsed,
196197
order="C", # TODO: order is not needed here.
197198
prototype=default_buffer_prototype(), # TODO: prototype is not needed here.
@@ -224,11 +225,14 @@ def _validate_metadata(self) -> None:
224225
if self.fill_value is None:
225226
raise ValueError("`fill_value` is required.")
226227
for codec in self.codecs:
227-
codec.validate(shape=self.shape, dtype=self.data_type, chunk_grid=self.chunk_grid)
228+
codec.validate(
229+
shape=self.shape, dtype=self.data_type.to_numpy(), chunk_grid=self.chunk_grid
230+
)
228231

229232
@property
230233
def dtype(self) -> np.dtype[Any]:
231-
return self.data_type
234+
"""Interpret Zarr dtype as NumPy dtype"""
235+
return self.data_type.to_numpy()
232236

233237
@property
234238
def ndim(self) -> int:
@@ -266,13 +270,13 @@ def from_dict(cls, data: dict[str, JSON]) -> Self:
266270
_ = parse_node_type_array(_data.pop("node_type"))
267271

268272
# check that the data_type attribute is valid
269-
_ = DataType(_data["data_type"])
273+
data_type = DataType.parse(_data.pop("data_type"))
270274

271275
# dimension_names key is optional, normalize missing to `None`
272276
_data["dimension_names"] = _data.pop("dimension_names", None)
273277
# attributes key is optional, normalize missing to `None`
274278
_data["attributes"] = _data.pop("attributes", None)
275-
return cls(**_data) # type: ignore[arg-type]
279+
return cls(**_data, data_type=data_type) # type: ignore[arg-type]
276280

277281
def to_dict(self) -> dict[str, JSON]:
278282
out_dict = super().to_dict()
@@ -490,8 +494,11 @@ def to_numpy_shortname(self) -> str:
490494
}
491495
return data_type_to_numpy[self]
492496

497+
def to_numpy(self) -> np.dtype[Any]:
498+
return np.dtype(self.to_numpy_shortname())
499+
493500
@classmethod
494-
def from_dtype(cls, dtype: np.dtype[Any]) -> DataType:
501+
def from_numpy(cls, dtype: np.dtype[Any]) -> DataType:
495502
dtype_to_data_type = {
496503
"|b1": "bool",
497504
"bool": "bool",
@@ -511,16 +518,21 @@ def from_dtype(cls, dtype: np.dtype[Any]) -> DataType:
511518
}
512519
return DataType[dtype_to_data_type[dtype.str]]
513520

514-
515-
def parse_dtype(data: npt.DTypeLike) -> np.dtype[Any]:
516-
try:
517-
dtype = np.dtype(data)
518-
except (ValueError, TypeError) as e:
519-
raise ValueError(f"Invalid V3 data_type: {data}") from e
520-
# check that this is a valid v3 data_type
521-
try:
522-
_ = DataType.from_dtype(dtype)
523-
except KeyError as e:
524-
raise ValueError(f"Invalid V3 data_type: {dtype}") from e
525-
526-
return dtype
521+
@classmethod
522+
def parse(cls, dtype: None | DataType | Any) -> DataType:
523+
if dtype is None:
524+
# the default dtype
525+
return DataType[DEFAULT_DTYPE]
526+
if isinstance(dtype, DataType):
527+
return dtype
528+
else:
529+
try:
530+
dtype = np.dtype(dtype)
531+
except (ValueError, TypeError) as e:
532+
raise ValueError(f"Invalid V3 data_type: {dtype}") from e
533+
# check that this is a valid v3 data_type
534+
try:
535+
data_type = DataType.from_numpy(dtype)
536+
except KeyError as e:
537+
raise ValueError(f"Invalid V3 data_type: {dtype}") from e
538+
return data_type

tests/v3/test_array.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import pytest
77

88
import zarr.api.asynchronous
9+
import zarr.storage
910
from zarr import Array, AsyncArray, Group
1011
from zarr.codecs.bytes import BytesCodec
1112
from zarr.core.array import chunks_initialized

0 commit comments

Comments
 (0)