Skip to content

Commit 4a837c0

Browse files
authored
Merge branch 'main' into avoid-mem-copy-in-obstore-write
2 parents 40bc156 + 84d3284 commit 4a837c0

File tree

8 files changed

+62
-23
lines changed

8 files changed

+62
-23
lines changed

.github/workflows/test.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ jobs:
6464
run: |
6565
hatch env run --env test.py${{ matrix.python-version }}-${{ matrix.numpy-version }}-${{ matrix.dependency-set }} run-coverage
6666
- name: Upload coverage
67+
if: ${{ matrix.dependency-set == 'optional' && matrix.os == 'ubuntu-latest' }}
6768
uses: codecov/codecov-action@v5
6869
with:
6970
token: ${{ secrets.CODECOV_TOKEN }}

changes/2991.doc.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Updated the 3.0 migration guide to include the removal of "." syntax for getting group members.

changes/2996.bugfix.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fixes `ConsolidatedMetadata` serialization of `nan`, `inf`, and `-inf` to be
2+
consistent with the behavior of `ArrayMetadata`.
3+
4+

docs/user-guide/v3_migration.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ The Group class
117117

118118
- Use :func:`zarr.Group.create_array` in place of :func:`zarr.Group.create_dataset`
119119
- Use :func:`zarr.Group.require_array` in place of :func:`zarr.Group.require_dataset`
120+
3. Disallow "." syntax for getting group members. To get a member of a group named ``foo``,
121+
use ``group["foo"]`` in place of ``group.foo``.
120122

121123
The Store class
122124
~~~~~~~~~~~~~~~

pyproject.toml

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -73,18 +73,19 @@ gpu = [
7373
test = [
7474
"coverage",
7575
"pytest",
76+
"pytest-asyncio",
7677
"pytest-cov",
78+
"pytest-accept",
79+
"rich",
80+
"mypy",
81+
"hypothesis",
82+
]
83+
remote_tests = [
7784
'zarr[remote]',
7885
"botocore",
7986
"s3fs",
8087
"moto[s3,server]",
81-
"pytest-asyncio",
82-
"pytest-accept",
8388
"requests",
84-
"rich",
85-
"mypy",
86-
"hypothesis",
87-
"universal-pathlib",
8889
]
8990
optional = ["rich", "universal-pathlib"]
9091
docs = [
@@ -143,28 +144,21 @@ hooks.vcs.version-file = "src/zarr/_version.py"
143144
[tool.hatch.envs.test]
144145
dependencies = [
145146
"numpy~={matrix:numpy}",
146-
"universal_pathlib",
147147
]
148148
features = ["test"]
149149

150150
[[tool.hatch.envs.test.matrix]]
151151
python = ["3.11", "3.12", "3.13"]
152152
numpy = ["1.25", "2.1"]
153-
version = ["minimal"]
154-
155-
[[tool.hatch.envs.test.matrix]]
156-
python = ["3.11", "3.12", "3.13"]
157-
numpy = ["1.25", "2.1"]
158-
features = ["optional"]
153+
deps = ["minimal", "optional"]
159154

160-
[[tool.hatch.envs.test.matrix]]
161-
python = ["3.11", "3.12", "3.13"]
162-
numpy = ["1.25", "2.1"]
163-
features = ["gpu"]
155+
[tool.hatch.envs.test.overrides]
156+
matrix.deps.dependencies = [
157+
{value = "zarr[remote, remote_tests, test, optional]", if = ["optional"]}
158+
]
164159

165160
[tool.hatch.envs.test.scripts]
166161
run-coverage = "pytest --cov-config=pyproject.toml --cov=pkg --cov-report xml --cov=src --junitxml=junit.xml -o junit_family=legacy"
167-
run-coverage-gpu = "pip install cupy-cuda12x && pytest -m gpu --cov-config=pyproject.toml --cov=pkg --cov-report xml --cov=src --junitxml=junit.xml -o junit_family=legacy"
168162
run-coverage-html = "pytest --cov-config=pyproject.toml --cov=pkg --cov-report html --cov=src"
169163
run = "run-coverage --no-cov"
170164
run-pytest = "run"
@@ -174,7 +168,7 @@ run-hypothesis = "run-coverage --hypothesis-profile ci --run-slow-hypothesis tes
174168
list-env = "pip list"
175169

176170
[tool.hatch.envs.doctest]
177-
features = ["test", "optional", "remote"]
171+
features = ["test", "optional", "remote", "remote_tests"]
178172
description = "Test environment for doctests"
179173

180174
[tool.hatch.envs.doctest.scripts]
@@ -255,6 +249,7 @@ dependencies = [
255249
'universal_pathlib==0.0.22',
256250
'typing_extensions==4.9.*',
257251
'donfig==0.8.*',
252+
'obstore==0.5.*',
258253
# test deps
259254
'zarr[test]',
260255
]

src/zarr/core/group.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
)
5050
from zarr.core.config import config
5151
from zarr.core.metadata import ArrayV2Metadata, ArrayV3Metadata
52-
from zarr.core.metadata.v3 import V3JsonEncoder
52+
from zarr.core.metadata.v3 import V3JsonEncoder, _replace_special_floats
5353
from zarr.core.sync import SyncMixin, sync
5454
from zarr.errors import ContainsArrayError, ContainsGroupError, MetadataValidationError
5555
from zarr.storage import StoreLike, StorePath
@@ -334,7 +334,7 @@ def to_buffer_dict(self, prototype: BufferPrototype) -> dict[str, Buffer]:
334334
if self.zarr_format == 3:
335335
return {
336336
ZARR_JSON: prototype.buffer.from_bytes(
337-
json.dumps(self.to_dict(), cls=V3JsonEncoder).encode()
337+
json.dumps(_replace_special_floats(self.to_dict()), cls=V3JsonEncoder).encode()
338338
)
339339
}
340340
else:
@@ -355,10 +355,10 @@ def to_buffer_dict(self, prototype: BufferPrototype) -> dict[str, Buffer]:
355355
assert isinstance(consolidated_metadata, dict)
356356
for k, v in consolidated_metadata.items():
357357
attrs = v.pop("attributes", None)
358-
d[f"{k}/{ZATTRS_JSON}"] = attrs
358+
d[f"{k}/{ZATTRS_JSON}"] = _replace_special_floats(attrs)
359359
if "shape" in v:
360360
# it's an array
361-
d[f"{k}/{ZARRAY_JSON}"] = v
361+
d[f"{k}/{ZARRAY_JSON}"] = _replace_special_floats(v)
362362
else:
363363
d[f"{k}/{ZGROUP_JSON}"] = {
364364
"zarr_format": self.zarr_format,

tests/test_metadata/test_consolidated.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -573,3 +573,37 @@ async def test_use_consolidated_false(
573573
assert len([x async for x in good.members()]) == 2
574574
assert good.metadata.consolidated_metadata
575575
assert sorted(good.metadata.consolidated_metadata.metadata) == ["a", "b"]
576+
577+
578+
@pytest.mark.parametrize("fill_value", [np.nan, np.inf, -np.inf])
579+
async def test_consolidated_metadata_encodes_special_chars(
580+
memory_store: Store, zarr_format: ZarrFormat, fill_value: float
581+
):
582+
root = await group(store=memory_store, zarr_format=zarr_format)
583+
_child = await root.create_group("child", attributes={"test": fill_value})
584+
_time = await root.create_array("time", shape=(12,), dtype=np.float64, fill_value=fill_value)
585+
await zarr.api.asynchronous.consolidate_metadata(memory_store)
586+
587+
root = await group(store=memory_store, zarr_format=zarr_format)
588+
root_buffer = root.metadata.to_buffer_dict(default_buffer_prototype())
589+
590+
if zarr_format == 2:
591+
root_metadata = json.loads(root_buffer[".zmetadata"].to_bytes().decode("utf-8"))["metadata"]
592+
elif zarr_format == 3:
593+
root_metadata = json.loads(root_buffer["zarr.json"].to_bytes().decode("utf-8"))[
594+
"consolidated_metadata"
595+
]["metadata"]
596+
597+
if np.isnan(fill_value):
598+
expected_fill_value = "NaN"
599+
elif np.isneginf(fill_value):
600+
expected_fill_value = "-Infinity"
601+
elif np.isinf(fill_value):
602+
expected_fill_value = "Infinity"
603+
604+
if zarr_format == 2:
605+
assert root_metadata["child/.zattrs"]["test"] == expected_fill_value
606+
assert root_metadata["time/.zarray"]["fill_value"] == expected_fill_value
607+
elif zarr_format == 3:
608+
assert root_metadata["child"]["attributes"]["test"] == expected_fill_value
609+
assert root_metadata["time"]["fill_value"] == expected_fill_value

tests/test_store/test_core.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,8 @@ async def test_make_store_path_invalid() -> None:
121121

122122
async def test_make_store_path_fsspec(monkeypatch) -> None:
123123
pytest.importorskip("fsspec")
124+
pytest.importorskip("requests")
125+
pytest.importorskip("aiohttp")
124126
store_path = await make_store_path("http://foo.com/bar")
125127
assert isinstance(store_path.store, FsspecStore)
126128

0 commit comments

Comments
 (0)